Repository navigation
Conversation
An unmatched run of '{' in a word made scanBraceExpansion scan to the end of
the window once per opener. Record the result for every opener visited by a
scan, including valid expansions nested in an unmatched outer brace, so each
opener is scanned at most once per source window.
readDollar treats a $[ without a matching ] as a literal and the word reader retries at the next $[, scanning to the end of the window each time. Record failed searches only, for every still-open bracket of the failed scan, so a successful inner search keeps its existing error and position handling.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #27
Two commits, each with tests.
Brace expansion.
scanBraceExpansionnow keeps one cache per source window (keyed on source, window end and nesting depth). Every brace opener visited by a scan gets an entry, including a valid expansion nested inside an unmatched outer brace, son{{a,b}still yields the inner{a,b}expansion. Cached results are returned before any scanning. The scanner position, error list and unbalanced state are restored exactly as before.Legacy
$[.findClosingArithmeticBracketkeeps a per-window set of opener positions whose search is known to have failed.findClosingShellDelimitertakes an optional set and, when a search fails, adds every[that was still open at that point. Only failures are recorded. Successful inner searches are not cached because they must keep running to retain their existing error and position handling, andreadDollardiscards the errors of a failed lookahead. The window is re-assigned after the search because the scan can re-enter through nested lexers.Why failures only for
$[: a failed search at an outer opener implies the same text to the same end fails for every opener still open inside it, while a success depends on how errors and positions are handled inside the scan. Caching only failures leaves all successful paths byte-for-byte as before.Constraints kept: no input length cap, no timeout, no grammar change, a single active cache window per construct, and at most one key per input position. A change of source, window end or nesting depth discards the previous cache.
findClosingBracketand subscript recovery are untouched.Tests (test/failed-lookahead-bounds.test.ts) count
String.prototype.charCodeAtreads for"n" + "{".repeat(size - 1),"echo " + "$[".repeat(k)and"echo " + "$[".repeat(k) + "]"at 1, 4, 16 and 64 KiB and assert reads <= 64 * length. Correctness tests covern{{a,b}(inner expansion kept),n{{{a,b},echo $[$[1]],echo $[1+$[2]]xandecho $[$[1](literal$[followed by$[1]).Validation measured on current main (61b82c2) and this branch, Node v24.22.2:
n{{{...3,075 / 12,291 / 49,155 / 196,611 at 1 / 4 / 16 / 64 KiB (3.0 * N);$[runs 6,120 / 24,552 / 98,280 / 393,192 (about 6 * N);$[runs with a trailing]6,121 / 24,553 / 98,281 / 393,193.npm test): 2075 tests, 2075 pass, 0 fail (2059 on main plus the 16 new).npm run test:typespasses,oxfmt --checkis clean for src and test,oxlintreports the same pre-existing warnings as main.$[controls, 4,096 seeded legacy$[fuzz cases, 9 extra scaling cases up to 16 KiB). Result: 9,379 identical, 0 differences, 0 parser exceptions. For one 16 KiB deeply nested arithmetic input nativeJSON.stringifyoverflows the stack on both builds, so an iterative serializer was used there; it matched nativeJSON.stringifyon all other comparisons.Not tested: complexity of the rest of the grammar (this bounds the two input families above, it does not prove linear parsing overall; the
$(path was only observed, not shown to be uncapped quadratic); inputs above 64 KiB in the committed tests; other runtimes than Node 24 for this branch; wall-clock timing (the tests count reads on purpose); the playground and bench builds.Overlap: #20 ("keep unclosed subscript recovery linear") also edits
findClosingShellDelimiter, adding a trailing optionalbracketMatchesparameter and bookkeeping next to the samedelimiters.pushanddelimiters.popsites that the second commit extends with a different trailing optional parameter. The two changes are independent in behaviour (that PR caches insidefindClosingBracket, this one insidefindClosingArithmeticBracket, which calls the scanner withbraces = false) but will conflict textually; whichever lands second needs a small rebase. Neither PR touchesscanBraceExpansion.