Skip to content

Commit eedda85

Browse files
committed
[GR-19220] Implement new MatchData methods for 3.2 (#3393)
PullRequest: truffleruby/4124
2 parents 96da5db + be7a3e3 commit eedda85

File tree

9 files changed

+87
-43
lines changed

9 files changed

+87
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Compatibility:
3131
* Implement the `Data` class from Ruby 3.2 (#3039, @moste00, @eregon).
3232
* Make `Coverage.start` and `Coverage.result` accept parameters (#3149, @mtortonesi, @andrykonchin).
3333
* Implement `rb_check_funcall()` (@eregon).
34+
* Implement `MatchData#{byteoffset,deconstruct,deconstruct_keys}` from Ruby 3.2 (#3039, @rwstauner).
3435

3536
Performance:
3637

spec/ruby/core/matchdata/begin_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@
3636
match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
3737
match_data.begin(obj).should == 2
3838
end
39+
40+
it "raises IndexError if index is out of bounds" do
41+
match_data = /(?<f>foo)(?<b>bar)/.match("foobar")
42+
43+
-> {
44+
match_data.begin(-1)
45+
}.should raise_error(IndexError, "index -1 out of matches")
46+
47+
-> {
48+
match_data.begin(3)
49+
}.should raise_error(IndexError, "index 3 out of matches")
50+
end
3951
end
4052

4153
context "when passed a String argument" do
@@ -68,6 +80,14 @@
6880
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
6981
match_data.begin("æ").should == 1
7082
end
83+
84+
it "raises IndexError if there is no group with the provided name" do
85+
match_data = /(?<f>foo)(?<b>bar)/.match("foobar")
86+
87+
-> {
88+
match_data.begin("y")
89+
}.should raise_error(IndexError, "undefined group name reference: y")
90+
end
7191
end
7292

7393
context "when passed a Symbol argument" do
@@ -100,5 +120,13 @@
100120
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
101121
match_data.begin().should == 1
102122
end
123+
124+
it "raises IndexError if there is no group with the provided name" do
125+
match_data = /(?<f>foo)(?<b>bar)/.match("foobar")
126+
127+
-> {
128+
match_data.begin(:y)
129+
}.should raise_error(IndexError, "undefined group name reference: y")
130+
end
103131
end
104132
end

spec/ruby/core/matchdata/byteoffset_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def obj.to_int; 2; end
6060
m.byteoffset(obj).should == [3, 6]
6161
end
6262

63-
it "raises IndexError if there is no group with provided name" do
63+
it "raises IndexError if there is no group with the provided name" do
6464
m = /(?<f>foo)(?<b>bar)/.match("foobar")
6565

6666
-> {
@@ -72,7 +72,7 @@ def obj.to_int; 2; end
7272
}.should raise_error(IndexError, "undefined group name reference: y")
7373
end
7474

75-
it "raises IndexError if index is out of matches" do
75+
it "raises IndexError if index is out of bounds" do
7676
m = /(?<f>foo)(?<b>bar)/.match("foobar")
7777

7878
-> {

spec/tags/core/matchdata/byteoffset_tags.txt

Lines changed: 0 additions & 11 deletions
This file was deleted.

spec/tags/core/matchdata/deconstruct_keys_tags.txt

Lines changed: 0 additions & 9 deletions
This file was deleted.

spec/tags/core/matchdata/deconstruct_tags.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,13 @@ Object byteBegin(RubyMatchData matchData, int index,
627627
}
628628
}
629629

630+
@Specialization(guards = "!inBounds(matchData, index)")
631+
Object byteBeginError(RubyMatchData matchData, int index) {
632+
throw new RaiseException(
633+
getContext(),
634+
coreExceptions().indexError(StringUtils.format("index %d out of matches", index), this));
635+
}
636+
630637
protected boolean inBounds(RubyMatchData matchData, int index) {
631638
return index >= 0 && index < matchData.region.numRegs;
632639
}
@@ -649,6 +656,13 @@ Object byteEnd(RubyMatchData matchData, int index,
649656
}
650657
}
651658

659+
@Specialization(guards = "!inBounds(matchData, index)")
660+
Object byteEndError(RubyMatchData matchData, int index) {
661+
throw new RaiseException(
662+
getContext(),
663+
coreExceptions().indexError(StringUtils.format("index %d out of matches", index), this));
664+
}
665+
652666
protected boolean inBounds(RubyMatchData matchData, int index) {
653667
return index >= 0 && index < matchData.region.numRegs;
654668
}

src/main/ruby/truffleruby/core/match_data.rb

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ class << self
4040
undef_method :allocate
4141
end
4242

43+
def byteoffset(idx)
44+
backref = backref_from_arg(idx)
45+
[Primitive.match_data_byte_begin(self, backref), Primitive.match_data_byte_end(self, backref)]
46+
end
47+
4348
def offset(idx)
4449
[self.begin(idx), self.end(idx)]
4550
end
@@ -61,6 +66,26 @@ def string
6166
def captures
6267
to_a[1..-1]
6368
end
69+
alias_method :deconstruct, :captures
70+
71+
def deconstruct_keys(array_of_names)
72+
Truffle::Type.rb_check_type(array_of_names, Array) unless Primitive.nil?(array_of_names)
73+
74+
hash = named_captures.transform_keys(&:to_sym)
75+
return hash if Primitive.nil?(array_of_names)
76+
77+
ret = {}
78+
return ret if array_of_names.size > hash.size
79+
80+
array_of_names.each do |key|
81+
Truffle::Type.rb_check_type(key, Symbol)
82+
value = Primitive.hash_get_or_undefined(hash, key)
83+
break if Primitive.undefined?(value)
84+
ret[key] = value
85+
end
86+
87+
ret
88+
end
6489

6590
def names
6691
regexp.names
@@ -71,26 +96,12 @@ def named_captures
7196
end
7297

7398
def begin(index)
74-
backref = if Primitive.is_a?(index, String) || Primitive.is_a?(index, Symbol)
75-
names_to_backref = Hash[Primitive.regexp_names(self.regexp)]
76-
names_to_backref[index.to_sym].last
77-
else
78-
Truffle::Type.coerce_to(index, Integer, :to_int)
79-
end
80-
81-
99+
backref = backref_from_arg(index)
82100
Primitive.match_data_begin(self, backref)
83101
end
84102

85103
def end(index)
86-
backref = if Primitive.is_a?(index, String) || Primitive.is_a?(index, Symbol)
87-
names_to_backref = Hash[Primitive.regexp_names(self.regexp)]
88-
names_to_backref[index.to_sym].last
89-
else
90-
Truffle::Type.coerce_to(index, Integer, :to_int)
91-
end
92-
93-
104+
backref = backref_from_arg(index)
94105
Primitive.match_data_end(self, backref)
95106
end
96107

@@ -153,6 +164,21 @@ def match_length(n)
153164
def to_s
154165
self[0]
155166
end
167+
168+
private
169+
170+
def backref_from_arg(index)
171+
if Primitive.is_a?(index, String) || Primitive.is_a?(index, Symbol)
172+
names_to_backref = Hash[Primitive.regexp_names(self.regexp)]
173+
array = names_to_backref[index.to_sym]
174+
175+
raise IndexError, "undefined group name reference: #{index}" unless array
176+
177+
return array.last
178+
end
179+
180+
Primitive.rb_to_int(index)
181+
end
156182
end
157183

158184
Truffle::KernelOperations.define_hooked_variable(

test/mri/excludes/TestRegexp.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@
3939
exclude :test_match_control_meta_escape, "<0> expected but was"
4040
exclude :test_initialize_option, "<//m> expected but was"
4141
exclude :test_initialize_bool_warning, "expected: /expected true or false as ignorecase/"
42-
exclude :test_match_byteoffset_begin_end, "NoMethodError: undefined method `byteoffset' for #<MatchData \"bar\" x:\"bar\">"
43-
exclude :test_match_data_deconstruct, "NoMethodError: undefined method `deconstruct' for #<MatchData \"foobarbaz\">"
4442
exclude :test_linear_time_p, "NoMethodError: undefined method `linear_time?' for Regexp:Class"
45-
exclude :test_match_data_deconstruct_keys, "NoMethodError: undefined method `deconstruct_keys' for #<MatchData \"foobarbaz\">"
4643
exclude :test_extended_comment_invalid_escape_bug_18294, "assert_separately failed with error message"
4744
exclude :test_timeout_nil, "NoMethodError: undefined method `timeout=' for Regexp:Class"
4845
exclude :test_timeout_shorter_than_global, "NoMethodError: undefined method `timeout=' for Regexp:Class"

0 commit comments

Comments
 (0)