Skip to content

Commit 405cb21

Browse files
committed
Fix StringScanner and patterns starting with ^
1 parent d77a849 commit 405cb21

File tree

5 files changed

+16
-33
lines changed

5 files changed

+16
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Bug fixes:
88
using XML conversion options and a new destination encoding (#1545).
99
* Fixed a bug where a raised cloned exception would be caught as the
1010
original exception (#1542).
11+
* Fixed a bug with `StringScanner` and patterns starting with `^` (#1544).
1112

1213
Compatibility:
1314

lib/truffle/strscan.rb

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -312,18 +312,19 @@ def scan_internal(pattern, advance_pos, getstr, headonly)
312312
end
313313
raise ArgumentError, 'uninitialized StringScanner object' unless @string
314314

315-
@match = nil
316-
317-
if headonly
318-
# NOTE - match_start is an Oniguruma feature that Rubinius exposes.
319-
# We use it here to avoid creating a new Regexp with '^' prepended.
320-
@match = pattern.match_start @string, @pos
321-
else
322-
# NOTE - search_from is an Oniguruma feature that Rubinius exposes.
323-
# We use it so we can begin the search in the middle of the string
324-
@match = pattern.search_from @string, @pos
315+
# If the pattern already starts with a ^, and we're not at the start of
316+
# the string, then we can't match as normal because match_from still tries
317+
# to match the ^ at position 0 even though it's looking from point pos
318+
# onwards, even if headonly is set. Instead, remove the ^. This could
319+
# possibly be fixed in Joni instead, or maybe there is already some option
320+
# we're not using.
321+
322+
if pattern.source[0] == '^' && pos > 0
323+
pattern = Regexp.new(pattern.source[1..-1])
324+
headonly = true
325325
end
326326

327+
@match = pattern.match_onwards @string, pos, headonly
327328
return nil unless @match
328329

329330
fin = @match.byte_end(0)

spec/tags/library/stringscanner/scan_tags.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
fails:StringScanner#scan treats ^ as matching from the beginning of the current position
21
graalvm:StringScanner#scan returns the matched string
32
graalvm:StringScanner#scan returns nil if there's no match
43
graalvm:StringScanner#scan returns nil when there is no more to scan
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
fails:StringScanner#scan_until can match anchors properly
21
graalvm:StringScanner#scan_until returns the substring up to and including the end of the match
32
graalvm:StringScanner#scan_until returns nil if there's no match

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

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -168,18 +168,18 @@ public int hash(DynamicObject regexp) {
168168
}
169169

170170
@NonStandard
171-
@CoreMethod(names = "match_start", required = 2, lowerFixnum = 2)
172-
public abstract static class MatchStartNode extends CoreMethodArrayArgumentsNode {
171+
@CoreMethod(names = "match_onwards", required = 3, lowerFixnum = 2)
172+
public abstract static class MatchOnwardsNode extends CoreMethodArrayArgumentsNode {
173173

174174
@Child private TruffleRegexpNodes.MatchNode matchNode = TruffleRegexpNodes.MatchNode.create();
175175
@Child private RopeNodes.BytesNode bytesNode = RopeNodes.BytesNode.create();
176176

177177
@Specialization(guards = "isRubyString(string)")
178-
public Object matchStart(DynamicObject regexp, DynamicObject string, int startPos) {
178+
public Object matchOnwards(DynamicObject regexp, DynamicObject string, int startPos, boolean atStart) {
179179
final Rope rope = StringOperations.rope(string);
180180
final Matcher matcher = createMatcher(getContext(), regexp, rope, bytesNode.execute(rope), true);
181181
int range = rope.byteLength();
182-
return matchNode.execute(regexp, string, matcher, startPos, range, true);
182+
return matchNode.execute(regexp, string, matcher, startPos, range, atStart);
183183
}
184184
}
185185

@@ -224,23 +224,6 @@ private StringNodes.MakeStringNode getMakeStringNode() {
224224
}
225225
}
226226

227-
@NonStandard
228-
@CoreMethod(names = "search_from", required = 2, lowerFixnum = 2)
229-
public abstract static class SearchFromNode extends CoreMethodArrayArgumentsNode {
230-
231-
@Child private TruffleRegexpNodes.MatchNode matchNode = TruffleRegexpNodes.MatchNode.create();
232-
@Child private RopeNodes.BytesNode bytesNode = RopeNodes.BytesNode.create();
233-
234-
@Specialization(guards = "isRubyString(string)")
235-
public Object searchFrom(DynamicObject regexp, DynamicObject string, int startPos) {
236-
final Rope rope = StringOperations.rope(string);
237-
final Matcher matcher = createMatcher(getContext(), regexp, rope, bytesNode.execute(rope), true);
238-
int range = StringOperations.rope(string).byteLength();
239-
240-
return matchNode.execute(regexp, string, matcher, startPos, range, false);
241-
}
242-
}
243-
244227
@Primitive(name = "regexp_search_from_binary", lowerFixnum = 2)
245228
public abstract static class SearchFromBinaryNode extends CoreMethodArrayArgumentsNode {
246229

0 commit comments

Comments
 (0)