Make String.prototype.matchAll return a lazy iterator#206
Conversation
Replace the eager array-backed iterator with a lazy TGocciaRegExpMatchAllIteratorValue that computes one match per .next() call, matching the ES2026 spec behavior. The new iterator stores a cloned RegExp and advances FSearchIndex on demand. Both RegExp.prototype[Symbol.matchAll] and the String.prototype.matchAll fallback path now return this lazy iterator. Closes #175 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 5 minutes and 10 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe PR converts Changes
Sequence DiagramsequenceDiagram
participant Consumer as Iterator Consumer
participant Iterator as RegExpMatchAllIterator
participant Regex as RegExp Object
participant Engine as Match Engine
Consumer->>Iterator: Create iterator<br/>(regex, input, global)
activate Iterator
Iterator->>Regex: Clone regex
note over Iterator: Store state:<br/>SearchIndex = 0<br/>Global flag
loop For Each next() call
Consumer->>Iterator: next()
Iterator->>Engine: MatchRegExpObject<br/>(input, SearchIndex)
activate Engine
Engine-->>Iterator: Match found<br/>at [start, end]
deactivate Engine
Iterator->>Iterator: Update SearchIndex<br/>to end position
Iterator-->>Consumer: {value: match, done: false}
end
Note over Iterator: No more matches<br/>or done condition met
Iterator-->>Consumer: {value: undefined, done: true}
deactivate Iterator
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Benchmark Results274 benchmarks Interpreted: 🟢 15 improved · 🔴 133 regressed · 126 unchanged · avg -1.8% arraybuffer.js — Interp: 🟢 2, 🔴 5, 7 unch. · avg -0.5% · Bytecode: 🟢 12, 🔴 1, 1 unch. · avg +8.7%
arrays.js — Interp: 🟢 3, 🔴 6, 10 unch. · avg -0.3% · Bytecode: 🟢 19 · avg +8.7%
async-await.js — Interp: 🔴 6 · avg -2.3% · Bytecode: 🟢 6 · avg +11.1%
classes.js — Interp: 🔴 12, 19 unch. · avg -1.3% · Bytecode: 🟢 11, 🔴 4, 16 unch. · avg +2.2%
closures.js — Interp: 🔴 7, 4 unch. · avg -2.0% · Bytecode: 🟢 7, 4 unch. · avg +3.7%
collections.js — Interp: 🟢 1, 🔴 7, 4 unch. · avg -2.1% · Bytecode: 🟢 10, 🔴 2 · avg +11.6%
destructuring.js — Interp: 🔴 19, 3 unch. · avg -3.4% · Bytecode: 🟢 17, 5 unch. · avg +6.4%
fibonacci.js — Interp: 🟢 1, 🔴 5, 2 unch. · avg -2.2% · Bytecode: 🟢 8 · avg +5.9%
for-of.js — Interp: 🔴 3, 4 unch. · avg -1.4% · Bytecode: 🟢 5, 2 unch. · avg +4.2%
helpers/bench-module.js — Interp: 0 · Bytecode: 0
iterators.js — Interp: 🔴 11, 9 unch. · avg -2.6% · Bytecode: 🟢 16, 🔴 1, 3 unch. · avg +6.1%
json.js — Interp: 🔴 5, 15 unch. · avg -1.2% · Bytecode: 🟢 20 · avg +10.2%
jsx.jsx — Interp: 🟢 5, 🔴 1, 15 unch. · avg +0.5% · Bytecode: 🟢 19, 2 unch. · avg +5.8%
modules.js — Interp: 🔴 6, 3 unch. · avg -2.4% · Bytecode: 🟢 2, 🔴 3, 4 unch. · avg +0.7%
numbers.js — Interp: 🟢 1, 🔴 6, 4 unch. · avg -2.6% · Bytecode: 🟢 10, 🔴 1 · avg +8.8%
objects.js — Interp: 🟢 1, 🔴 1, 5 unch. · avg -0.7% · Bytecode: 🟢 6, 1 unch. · avg +4.5%
promises.js — Interp: 🔴 9, 3 unch. · avg -3.2% · Bytecode: 🟢 11, 1 unch. · avg +6.3%
regexp.js — Interp: 🟢 1, 🔴 3, 7 unch. · avg -1.3% · Bytecode: 🟢 8, 🔴 1, 2 unch. · avg +3.5%
strings.js — Interp: 🔴 8, 3 unch. · avg -3.6% · Bytecode: 🟢 10, 1 unch. · avg +9.5%
typed-arrays.js — Interp: 🔴 13, 9 unch. · avg -2.3% · Bytecode: 🟢 11, 🔴 6, 5 unch. · avg +1.6%
Measured on ubuntu-latest x64. Benchmark ranges compare cached main-branch min/max ops/sec with the PR run; overlapping ranges are treated as unchanged noise. Percentage deltas are secondary context. |
Suite Timing
Measured on ubuntu-latest x64. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@units/Goccia.Builtins.GlobalRegExp.pas`:
- Around line 444-447: The code treats sticky (PROP_STICKY) as enabling repeated
matchAll iteration, but per spec only global (PROP_GLOBAL) should; change the
determination of IsGlobal used when constructing the iterator (see RegexClone,
GetRegExpBooleanProperty, PROP_GLOBAL, PROP_STICKY and
TGocciaRegExpMatchAllIteratorValue.Create) so it only checks PROP_GLOBAL (remove
or ignore PROP_STICKY here) and leave sticky handling to the regex matching
logic elsewhere.
In `@units/Goccia.Values.Iterator.RegExp.pas`:
- Around line 40-49: The constructor TGocciaRegExpMatchAllIteratorValue
currently resets FSearchIndex := 0 and thus discards the cloned regex's
preserved lastIndex; instead, when the iterator was created from a cloned RegExp
for global or sticky patterns (FGlobal or sticky flag on the cloned object),
initialize FSearchIndex from the cloned RegExp's lastIndex property (the value
preserved by CloneRegExpObject / the ARegExp passed into the constructor) rather
than unconditionally zero—leave the zero initialization only for
non-global/non-sticky regexes; update TGocciaRegExpMatchAllIteratorValue.Create
to read the cloned object's lastIndex and assign it to FSearchIndex when
appropriate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 396e3dfe-0824-438f-82d5-c73eca83887c
📒 Files selected for processing (7)
docs/built-ins.mddocs/language-restrictions.mdtests/built-ins/RegExp/prototype/symbol-matchAll.jstests/built-ins/String/prototype/matchAll.jsunits/Goccia.Builtins.GlobalRegExp.pasunits/Goccia.Values.Iterator.RegExp.pasunits/Goccia.Values.StringObjectValue.pas
💤 Files with no reviewable changes (1)
- docs/language-restrictions.md
…onour cloned lastIndex Per ES2026 §22.2.6.9, the internal `global` boolean in CreateRegExpStringIterator is derived from flags containing "g" — sticky (y) affects match positioning but does not trigger repeated iteration. Also initialise FSearchIndex from the cloned regex's lastIndex so that /a/g with lastIndex=2 starts matching at index 2 as the spec requires. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
TGocciaRegExpMatchAllIteratorValuethat computes one match per.next()call, aligning with ES2026 §22.2.6.9 spec behavior.RegExp.prototype[Symbol.matchAll]and theString.prototype.matchAllfallback path now return the lazy iterator.Goccia.Values.Iterator.RegExp.pasfollows the established lazy iterator pattern (same asTGocciaLazyMapIteratorValueetc.), with proper GCMarkReferences,ToStringTag, and support for both global and non-global modes.Closes #175
Testing
🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
String.prototype.matchAll()returns a lazy iterator computing matches on demand per ES2026 specification.Tests
for..ofloops.