Found by the randomized differential run for #27. The runs are executed through Regex.compileWithFlags(..., .{ .ecmascript = true }), the flags zig-js uses, against node v24.4.1 on the same pattern, input and lastIndex.
After #27 (bc14170), the Thompson engine agrees with node on every run, across 35,980 runs from two seeds over 8,493 generated patterns. What still disagrees is all on the backtracker: 521 runs over 218 distinct patterns, mostly quantified alternations with captures, which requiresBacktracking routes there. These disagreements predate #27; the committed engine shows them too.
Representative rows (byte offsets; captures listed in order, with undefined for a group that did not participate):
| pattern | input (lastIndex) | node | zig-regex backtracker |
|---|---|---|---|
(((.|b*)){1,2}){2}. | "BB" | match 0..2 | no match |
((b*){2}[ab]|$)\b | " dBBAdb " | match 6..7 | no match |
((ab|a)(ab){0,3}){0,2} | "BaabBa" (1) | 1..4, groups aab, a, ab | 1..4, all undefined |
(?:((a+){2,3}|(.){3})){0,3} | "aABBb" | 0..3, groups aAB, undefined, B | 0..3, all undefined |
((.)+|($)?) | "" | group 3 undefined | group 3 "" |
(?:((a?|$)){1,})? | "" | groups undefined | groups "" |
((?:(b?|ab)){3}){1,2} | "B" | groups "" | groups undefined |
(?:((\b){3}|\ba)){2,}(?:b?){3} /i | "adAaAa" | 0..1, group 1 a | 0..0 |
The families that stand out:
- match / no-match disagreements;
- captures lost on a quantified alternation's later iterations;
undefinedversus"": the backtracker records an empty capture where RepeatMatcher's empty-iteration rule fails the iteration, and the reverse.
The backtracker already knows canMatchEmpty(.backref) returns false incorrectly (noted on #29), and matchStarGreedy keeps only the longest iteration end (backtrack.zig:1019-1048). Both look related.
Reproduce with the generator and harness from #27: gen.js <seed> <patterns> writes node's answers as JSON, and a zig test root runs them through findFrom. I'll attach both if useful.