Skip to content

Commit

Permalink
[GR-19220] Implement new MatchData methods for 3.2 (#3393)
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/4124
  • Loading branch information
eregon committed Jan 24, 2024
2 parents 96da5db + be7a3e3 commit eedda85
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Compatibility:
* Implement the `Data` class from Ruby 3.2 (#3039, @moste00, @eregon).
* Make `Coverage.start` and `Coverage.result` accept parameters (#3149, @mtortonesi, @andrykonchin).
* Implement `rb_check_funcall()` (@eregon).
* Implement `MatchData#{byteoffset,deconstruct,deconstruct_keys}` from Ruby 3.2 (#3039, @rwstauner).

Performance:

Expand Down
28 changes: 28 additions & 0 deletions spec/ruby/core/matchdata/begin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@
match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
match_data.begin(obj).should == 2
end

it "raises IndexError if index is out of bounds" do
match_data = /(?<f>foo)(?<b>bar)/.match("foobar")

-> {
match_data.begin(-1)
}.should raise_error(IndexError, "index -1 out of matches")

-> {
match_data.begin(3)
}.should raise_error(IndexError, "index 3 out of matches")
end
end

context "when passed a String argument" do
Expand Down Expand Up @@ -68,6 +80,14 @@
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.begin("æ").should == 1
end

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

-> {
match_data.begin("y")
}.should raise_error(IndexError, "undefined group name reference: y")
end
end

context "when passed a Symbol argument" do
Expand Down Expand Up @@ -100,5 +120,13 @@
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.begin().should == 1
end

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

-> {
match_data.begin(:y)
}.should raise_error(IndexError, "undefined group name reference: y")
end
end
end
4 changes: 2 additions & 2 deletions spec/ruby/core/matchdata/byteoffset_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def obj.to_int; 2; end
m.byteoffset(obj).should == [3, 6]
end

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

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

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

-> {
Expand Down
11 changes: 0 additions & 11 deletions spec/tags/core/matchdata/byteoffset_tags.txt

This file was deleted.

9 changes: 0 additions & 9 deletions spec/tags/core/matchdata/deconstruct_keys_tags.txt

This file was deleted.

2 changes: 0 additions & 2 deletions spec/tags/core/matchdata/deconstruct_tags.txt

This file was deleted.

14 changes: 14 additions & 0 deletions src/main/java/org/truffleruby/core/regexp/MatchDataNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,13 @@ Object byteBegin(RubyMatchData matchData, int index,
}
}

@Specialization(guards = "!inBounds(matchData, index)")
Object byteBeginError(RubyMatchData matchData, int index) {
throw new RaiseException(
getContext(),
coreExceptions().indexError(StringUtils.format("index %d out of matches", index), this));
}

protected boolean inBounds(RubyMatchData matchData, int index) {
return index >= 0 && index < matchData.region.numRegs;
}
Expand All @@ -649,6 +656,13 @@ Object byteEnd(RubyMatchData matchData, int index,
}
}

@Specialization(guards = "!inBounds(matchData, index)")
Object byteEndError(RubyMatchData matchData, int index) {
throw new RaiseException(
getContext(),
coreExceptions().indexError(StringUtils.format("index %d out of matches", index), this));
}

protected boolean inBounds(RubyMatchData matchData, int index) {
return index >= 0 && index < matchData.region.numRegs;
}
Expand Down
58 changes: 42 additions & 16 deletions src/main/ruby/truffleruby/core/match_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ class << self
undef_method :allocate
end

def byteoffset(idx)
backref = backref_from_arg(idx)
[Primitive.match_data_byte_begin(self, backref), Primitive.match_data_byte_end(self, backref)]
end

def offset(idx)
[self.begin(idx), self.end(idx)]
end
Expand All @@ -61,6 +66,26 @@ def string
def captures
to_a[1..-1]
end
alias_method :deconstruct, :captures

def deconstruct_keys(array_of_names)
Truffle::Type.rb_check_type(array_of_names, Array) unless Primitive.nil?(array_of_names)

hash = named_captures.transform_keys(&:to_sym)
return hash if Primitive.nil?(array_of_names)

ret = {}
return ret if array_of_names.size > hash.size

array_of_names.each do |key|
Truffle::Type.rb_check_type(key, Symbol)
value = Primitive.hash_get_or_undefined(hash, key)
break if Primitive.undefined?(value)
ret[key] = value
end

ret
end

def names
regexp.names
Expand All @@ -71,26 +96,12 @@ def named_captures
end

def begin(index)
backref = if Primitive.is_a?(index, String) || Primitive.is_a?(index, Symbol)
names_to_backref = Hash[Primitive.regexp_names(self.regexp)]
names_to_backref[index.to_sym].last
else
Truffle::Type.coerce_to(index, Integer, :to_int)
end


backref = backref_from_arg(index)
Primitive.match_data_begin(self, backref)
end

def end(index)
backref = if Primitive.is_a?(index, String) || Primitive.is_a?(index, Symbol)
names_to_backref = Hash[Primitive.regexp_names(self.regexp)]
names_to_backref[index.to_sym].last
else
Truffle::Type.coerce_to(index, Integer, :to_int)
end


backref = backref_from_arg(index)
Primitive.match_data_end(self, backref)
end

Expand Down Expand Up @@ -153,6 +164,21 @@ def match_length(n)
def to_s
self[0]
end

private

def backref_from_arg(index)
if Primitive.is_a?(index, String) || Primitive.is_a?(index, Symbol)
names_to_backref = Hash[Primitive.regexp_names(self.regexp)]
array = names_to_backref[index.to_sym]

raise IndexError, "undefined group name reference: #{index}" unless array

return array.last
end

Primitive.rb_to_int(index)
end
end

Truffle::KernelOperations.define_hooked_variable(
Expand Down
3 changes: 0 additions & 3 deletions test/mri/excludes/TestRegexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@
exclude :test_match_control_meta_escape, "<0> expected but was"
exclude :test_initialize_option, "<//m> expected but was"
exclude :test_initialize_bool_warning, "expected: /expected true or false as ignorecase/"
exclude :test_match_byteoffset_begin_end, "NoMethodError: undefined method `byteoffset' for #<MatchData \"bar\" x:\"bar\">"
exclude :test_match_data_deconstruct, "NoMethodError: undefined method `deconstruct' for #<MatchData \"foobarbaz\">"
exclude :test_linear_time_p, "NoMethodError: undefined method `linear_time?' for Regexp:Class"
exclude :test_match_data_deconstruct_keys, "NoMethodError: undefined method `deconstruct_keys' for #<MatchData \"foobarbaz\">"
exclude :test_extended_comment_invalid_escape_bug_18294, "assert_separately failed with error message"
exclude :test_timeout_nil, "NoMethodError: undefined method `timeout=' for Regexp:Class"
exclude :test_timeout_shorter_than_global, "NoMethodError: undefined method `timeout=' for Regexp:Class"
Expand Down

0 comments on commit eedda85

Please sign in to comment.