Skip to content

Commit 72eb59d

Browse files
committed
[Feature #21932] Add MatchData#get_int
1 parent 8da57d5 commit 72eb59d

4 files changed

Lines changed: 149 additions & 3 deletions

File tree

depend

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14354,6 +14354,7 @@ re.$(OBJEXT): $(hdrdir)/ruby.h
1435414354
re.$(OBJEXT): $(hdrdir)/ruby/ruby.h
1435514355
re.$(OBJEXT): $(top_srcdir)/internal/array.h
1435614356
re.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
14357+
re.$(OBJEXT): $(top_srcdir)/internal/bignum.h
1435714358
re.$(OBJEXT): $(top_srcdir)/internal/bits.h
1435814359
re.$(OBJEXT): $(top_srcdir)/internal/box.h
1435914360
re.$(OBJEXT): $(top_srcdir)/internal/class.h

re.c

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "encindex.h"
1717
#include "hrtime.h"
1818
#include "internal.h"
19+
#include "internal/bignum.h"
1920
#include "internal/encoding.h"
2021
#include "internal/error.h"
2122
#include "internal/hash.h"
@@ -1187,7 +1188,7 @@ match_size(VALUE match)
11871188
return INT2FIX(RMATCH_REGS(match)->num_regs);
11881189
}
11891190

1190-
static int name_to_backref_number(struct re_registers *, VALUE, const char*, const char*);
1191+
static int name_to_backref_number(const struct re_registers *, VALUE, const char*, const char*);
11911192
NORETURN(static void name_to_backref_error(VALUE name));
11921193

11931194
static void
@@ -2147,7 +2148,7 @@ match_captures(VALUE match)
21472148
}
21482149

21492150
static int
2150-
name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name, const char* name_end)
2151+
name_to_backref_number(const struct re_registers *regs, VALUE regexp, const char* name, const char* name_end)
21512152
{
21522153
if (NIL_P(regexp)) return -1;
21532154
return onig_name_to_backref_number(RREGEXP_PTR(regexp),
@@ -2160,7 +2161,7 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name
21602161
name_to_backref_number((regs), (re), (name_ptr), (name_end)))
21612162

21622163
static int
2163-
namev_to_backref_number(struct re_registers *regs, VALUE re, VALUE name)
2164+
namev_to_backref_number(const struct re_registers *regs, VALUE re, VALUE name)
21642165
{
21652166
int num;
21662167

@@ -3629,6 +3630,76 @@ match_equal(VALUE match1, VALUE match2)
36293630
return Qtrue;
36303631
}
36313632

3633+
/*
3634+
* call-seq:
3635+
* integer_at(index, base = 10) -> integer or nil
3636+
* integer_at(name, base = 10) -> integer or nil
3637+
*
3638+
* Converts the matched substring to integer and return the result.
3639+
* +$~.integer_at(N)+ is equivalent to +$N&.to_i+.
3640+
*
3641+
* m = /(\d+{4})(\d+{2})(\d+{2})/.match("20260308")
3642+
* # => #<MatchData "20260308" 1:"2026" 2:"03" 3:"08">
3643+
* m.integer_at(0) # => 20260308
3644+
* m.integer_at(1) # => 2026
3645+
* m.integer_at(2) # => 3
3646+
* m.integer_at(3) # => 8
3647+
*
3648+
* m = /(?<y>\d+{4})(?<m>\d+{2})(?<d>\d+{2})/.match("20260308")
3649+
* m.integer_at("y") # => 2026
3650+
* m.integer_at("m") # => 3
3651+
* m.integer_at("d") # => 8
3652+
*
3653+
* If the substring does not match, returns +nil+.
3654+
*
3655+
* re = /(\d+)?/
3656+
* re.match("123").integer_at(1) #=> 123
3657+
* re.match("abc").integer_at(1) #=> nil
3658+
*
3659+
* The string is converted in decimal by default.
3660+
*
3661+
* /\d+/.match("011").integer_at(0) #=> 10
3662+
* /\d+/.match("011").integer_at(0, 12) #=> 13
3663+
* /\d+/.match("011").integer_at(0, 0) #=> 9
3664+
*
3665+
* See also MatchData#[], String#to_i.
3666+
*/
3667+
static VALUE
3668+
match_integer_at(int argc, VALUE *argv, VALUE match)
3669+
{
3670+
const struct re_registers *regs = RMATCH_REGS(match_check(match));
3671+
3672+
int base = 10;
3673+
VALUE idx;
3674+
long nth;
3675+
3676+
argc = rb_check_arity(argc, 1, 2);
3677+
if (FIXNUM_P(idx = argv[0])) {
3678+
nth = NUM2INT(idx);
3679+
}
3680+
else if ((nth = namev_to_backref_number(regs, RMATCH(match)->regexp, idx)) < 0) {
3681+
name_to_backref_error(idx);
3682+
}
3683+
3684+
if (argc > 1 && (base = NUM2INT(argv[1])) < 0) {
3685+
rb_raise(rb_eArgError, "invalid radix %d", base);
3686+
}
3687+
3688+
if (nth >= regs->num_regs) return Qnil;
3689+
if (nth < 0 && (nth += regs->num_regs) <= 0) return Qnil;
3690+
3691+
long start = BEG(nth), end = END(nth);
3692+
if (start < 0) return Qnil;
3693+
RUBY_ASSERT(start <= end, "%ld > %ld", start, end);
3694+
3695+
VALUE str = RMATCH(match)->str;
3696+
RUBY_ASSERT(end <= RSTRING_LEN(str), "%ld > %ld", end, RSTRING_LEN(str));
3697+
3698+
char *endp;
3699+
return rb_int_parse_cstr(RSTRING_PTR(str) + start, end - start, &endp, NULL,
3700+
base, RB_INT_PARSE_DEFAULT);
3701+
}
3702+
36323703
static VALUE
36333704
reg_operand(VALUE s, int check)
36343705
{
@@ -4908,4 +4979,5 @@ Init_Regexp(void)
49084979
rb_define_method(rb_cMatch, "hash", match_hash, 0);
49094980
rb_define_method(rb_cMatch, "eql?", match_equal, 1);
49104981
rb_define_method(rb_cMatch, "==", match_equal, 1);
4982+
rb_define_method(rb_cMatch, "integer_at", match_integer_at, -1);
49114983
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- encoding: utf-8 -*-
2+
3+
require_relative '../../spec_helper'
4+
5+
ruby_version_is "4.1" do
6+
describe "MatchData#integer_at" do
7+
it "converts the corresponding match to an Integer and returns it when given an Integer" do
8+
md = /(\d{4})(\d{2})(\d{2})/.match("20260308")
9+
md.integer_at(0).should == 20260308
10+
md.integer_at(1).should == 2026
11+
md.integer_at(2).should == 3
12+
end
13+
14+
it "returns nil on non-matching index matches" do
15+
md = /\b(\d)?\b/.match("THX1138.")
16+
md.integer_at(1).should == nil
17+
end
18+
19+
it "returns nil on non-integer matches" do
20+
md = /(\w)?/.match("THX1138.")
21+
md.integer_at(1).should == nil
22+
end
23+
24+
it "converts the match to an Integer in the given base" do
25+
md = /\w+/.match("0c")
26+
md.integer_at(0).should == 0
27+
md.integer_at(0, 16).should == 12
28+
end
29+
30+
it "converts the match to an Integer in the prefix when given base is zero" do
31+
/\w+/.match("010").integer_at(0, 0).should == 010
32+
/\w+/.match("0x10").integer_at(0, 0).should == 0x10
33+
/\w+/.match("0d10").integer_at(0, 0).should == 0d10
34+
/\w+/.match("0o10").integer_at(0, 0).should == 0o10
35+
/\w+/.match("0b10").integer_at(0, 0).should == 0b10
36+
end
37+
end
38+
end

test/ruby/test_regexp.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,41 @@ def test_matchdata_large_capture_groups_stack
16931693
RUBY
16941694
end
16951695

1696+
def test_match_integer_at
1697+
m = /(\d+{4})(\d+{2})(\d+{2})/.match("20260308")
1698+
assert_equal(20260308, m.integer_at(0))
1699+
assert_equal(2026, m.integer_at(1))
1700+
assert_equal(3, m.integer_at(2))
1701+
assert_equal(8, m.integer_at(3))
1702+
assert_equal(nil, m.integer_at(4))
1703+
assert_equal(8, m.integer_at(-1))
1704+
assert_equal(3, m.integer_at(-2))
1705+
assert_equal(2026, m.integer_at(-3))
1706+
assert_equal(nil, m.integer_at(-4))
1707+
1708+
re = /[a-z]+|(\d+)/
1709+
assert_equal(123, re.match("123").integer_at(1))
1710+
assert_equal(nil, re.match("abc").integer_at(1))
1711+
end
1712+
1713+
def test_match_integer_at_name
1714+
m = /(?<y>\d+{4})(?<m>\d+{2})(?<d>\d+{2})/.match("20260308")
1715+
assert_equal(2026, m.integer_at("y"))
1716+
assert_equal(3, m.integer_at("m"))
1717+
assert_equal(8, m.integer_at("d"))
1718+
end
1719+
1720+
def test_match_integer_at_base
1721+
assert_equal(91, /\w+/.match("111").integer_at(0, 9))
1722+
assert_equal(10_0000, /\w+/.match("10_0000").integer_at(0))
1723+
assert_equal(0d1_0000, /\w+/.match("01_0000").integer_at(0))
1724+
assert_equal(0o1_0000, /\w+/.match("01_0000").integer_at(0, 0))
1725+
assert_equal(0b1_0000, /\w+/.match("0b1_0000").integer_at(0, 0))
1726+
assert_equal(0o1_0000, /\w+/.match("0o1_0000").integer_at(0, 0))
1727+
assert_equal(0d1_0000, /\w+/.match("0d1_0000").integer_at(0, 0))
1728+
assert_equal(0x1_0000, /\w+/.match("0x1_0000").integer_at(0, 0))
1729+
end
1730+
16961731
def test_regexp_popped
16971732
EnvUtil.suppress_warning do
16981733
assert_nothing_raised { eval("a = 1; /\#{ a }/; a") }

0 commit comments

Comments
 (0)