From fad6bf0cc7cc2cb579d9ad03147a7014906f6b92 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 00:36:25 -0300 Subject: A repeat inside a regexp group gives back to what follows the group On aab, /a+ab/ matched but /(a+)ab/ and /(a*)ab/ missed, and so did (.+)_area and ([a-z_]+)_area, in addr, Edit and look alike. mvzr's hasAlt counts a group's own ) as an alternative, so every group with pattern after it takes matchGroup's alternatives branch, which never tries a repeat's shorter matches. No newer mvzr fixes it (trunk is the pinned commit), so the build patches the fetched source as it does the step budget, anchor-checked: when the rest fails after a group, the group's shorter matches are tried, longest first. docs/mvzr-group-backtrack.md is a report for upstream. Co-Authored-By: Claude Opus 5.5 --- src/regexp.zig | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src') diff --git a/src/regexp.zig b/src/regexp.zig index 249763c2..97e3dc91 100644 --- a/src/regexp.zig +++ b/src/regexp.zig @@ -235,3 +235,26 @@ test "a \\x without two hex digits is refused, not handed to mvzr to panic on" { var rx = try Regex.compile("\\x41"); try std.testing.expectEqual(@as(usize, 1), (try rx.find("xA", 0, 1, 2)).?.start); } + +test "a repeat inside a group gives back to what follows the group" { + const Case = struct { pat: []const u8, text: []const u8, start: usize, end: usize }; + for ([_]Case{ + .{ .pat = "a+ab", .text = "aab", .start = 0, .end = 3 }, + .{ .pat = "(a+)ab", .text = "aab", .start = 0, .end = 3 }, + .{ .pat = "(a*)ab", .text = "aab", .start = 0, .end = 3 }, + .{ .pat = "(.+)_area", .text = "the_total_area x", .start = 0, .end = 14 }, + .{ .pat = "([a-z_]+)_area", .text = "the_total_area x", .start = 0, .end = 14 }, + .{ .pat = "\\w+_area", .text = "the_total_area x", .start = 0, .end = 14 }, + .{ .pat = "(\\w)+_area", .text = "the_total_area x", .start = 0, .end = 14 }, + .{ .pat = "([a-z]+)_area", .text = "the_total_area x", .start = 4, .end = 14 }, + .{ .pat = "x(ab|a)bc", .text = "xabc", .start = 0, .end = 4 }, + }) |c| { + var rx = try Regex.compile(c.pat); + const m = (try rx.find(c.text, 0, c.text.len, c.text.len)) orelse { + std.debug.print("no match for {s}\n", .{c.pat}); + return error.NoMatch; + }; + try std.testing.expectEqual(c.start, m.start); + try std.testing.expectEqual(c.end, m.end); + } +} -- cgit v1.3