@@ -1329,6 +1329,12 @@ let tkT = new ${T_ARR}(4096);
13291329let tkOff = new Int32Array(4096);
13301330let tkEnd = new Int32Array(4096);
13311331let tkFl = new Uint8Array(4096);
1332+ // lexer-state depth records per token (windowed relex restart/resync safety):
1333+ // tkDp = template-interp stack depth, tkPd = paren-head stack depth, both AS RECORDED
1334+ // at the token's push (the convention per token kind is fixed by the lexer's code
1335+ // path; determinism is what the predicates rely on, depth-0 is the safe state).
1336+ let tkDp = new Uint8Array(4096);
1337+ let tkPd = new Uint16Array(4096);
13321338let tkCap = 4096;
13331339let tokN = 0;
13341340let src = '';
@@ -1340,6 +1346,8 @@ function growTok() {
13401346 const o = new Int32Array(tkCap); o.set(tkOff); tkOff = o;
13411347 const e2 = new Int32Array(tkCap); e2.set(tkEnd); tkEnd = e2;
13421348 const f = new Uint8Array(tkCap); f.set(tkFl); tkFl = f;
1349+ const d = new Uint8Array(tkCap); d.set(tkDp); tkDp = d;
1350+ const q = new Uint16Array(tkCap); q.set(tkPd); tkPd = q;
13431351}
13441352
13451353// ── CST arena: nodes are rows in parallel columns; leaves are TOKEN REFERENCES ──
@@ -1489,6 +1497,8 @@ function matchPuLitGT(pu) {
14891497 tkT.copyWithin(pos + 1, pos, tokN);
14901498 tkOff.copyWithin(pos + 1, pos, tokN);
14911499 tkEnd.copyWithin(pos + 1, pos, tokN);
1500+ tkDp.copyWithin(pos + 1, pos, tokN);
1501+ tkPd.copyWithin(pos + 1, pos, tokN);
14921502 tkFl.copyWithin(pos + 1, pos, tokN);
14931503 ${ e . soa ? '' : "tkText.splice(pos, 1, '>', restText);" }
14941504 tkT[pos] = pu; tkEnd[pos] = off + 1; tkFl[pos] = 0;
@@ -2099,6 +2109,7 @@ ${e.soa ? ` tokenize(source);` : String.raw` src = source;
20992109 const _t = _toks[_i];
21002110 tkK[_i] = _t.k; tkT[_i] = _t.t; tkOff[_i] = _t.offset; tkEnd[_i] = _t.offset + _t.text.length;
21012111 tkFl[_i] = (_t.newlineBefore ? 1 : 0) | (_t.commentBefore ? 2 : 0) | (_t.multilineFlowBefore ? 4 : 0);
2112+ tkDp[_i] = 0; tkPd[_i] = 0;
21022113 tkText[_i] = _t.text;
21032114 }
21042115 tokN = _n;` }
@@ -2141,7 +2152,20 @@ function runParse(entryRule) {
21412152let lastSrc = null;
21422153// The spare token-column buffer set (parseEdited ping-pongs between the live set and
21432154// this one, so steady-state edits never allocate columns).
2144- let altK = null, altT = null, altOff = null, altEnd = null, altFl = null;
2155+ let altK = null, altT = null, altOff = null, altEnd = null, altFl = null, altDp = null, altPd = null;
2156+ let altCap = 0;
2157+ let altN = 0; // old-stream token count while a window lex runs (lexCore's resync bound)
2158+ function swapBuffers() {
2159+ let x;
2160+ x = tkK; tkK = altK; altK = x;
2161+ x = tkT; tkT = altT; altT = x;
2162+ x = tkOff; tkOff = altOff; altOff = x;
2163+ x = tkEnd; tkEnd = altEnd; altEnd = x;
2164+ x = tkFl; tkFl = altFl; altFl = x;
2165+ x = tkDp; tkDp = altDp; altDp = x;
2166+ x = tkPd; tkPd = altPd; altPd = x;
2167+ x = tkCap; tkCap = altCap; altCap = x;
2168+ }
21452169${ e . soa ? '' : 'let altText = [];' }
21462170
21472171export function parse(source, entryRule) {
@@ -2176,46 +2200,106 @@ export function parseEdited(source, entryRule) {
21762200 if (lastSrc === null) return parse(source, entryRule);
21772201 const oSrc = lastSrc;
21782202 lastSrc = null;
2179- // Stash the old columns BY REFERENCE and lex into the spare buffer set (ping-pong
2180- // double buffer — steady-state edits allocate nothing and keep the pages warm).
2203+ ${ e . soa ? String . raw ` // ── M1: WINDOWED re-lex ──
2204+ // Char-level envelope (cheapest possible without an edit protocol).
2205+ const oldLen = oSrc.length, newLen = source.length;
2206+ const minL = oldLen < newLen ? oldLen : newLen;
2207+ let cs = 0;
2208+ while (cs < minL && oSrc.charCodeAt(cs) === source.charCodeAt(cs)) cs++;
2209+ let ce = 0;
2210+ while (ce < minL - cs && oSrc.charCodeAt(oldLen - 1 - ce) === source.charCodeAt(newLen - 1 - ce)) ce++;
2211+ const ceOld = oldLen - ce, ceNew = newLen - ce;
2212+ const charDelta = newLen - oldLen;
2213+ // Restart anchor: the last token B ending at/before the damage whose recorded
2214+ // depths are zero and whose shape carries no cross-token lexer flag (')' control-
2215+ // head, postfix-ambiguous op). B = -1 restarts at the file head — always sound.
2216+ const B = findRestart(cs);
2217+ const oN = tokN;
2218+ // first old token at/after the damage end — the resync search floor
2219+ let r0 = oN;
2220+ { let lo = 0, hi = oN;
2221+ while (lo < hi) { const mid = (lo + hi) >> 1; if (tkOff[mid] < ceOld) lo = mid + 1; else hi = mid; }
2222+ r0 = lo; }
2223+ // Lex the window into the spare buffers (the old stream stays live for resync).
2224+ if (altK === null || altCap < tkCap) {
2225+ altK = new tkK.constructor(tkCap); altT = new tkT.constructor(tkCap);
2226+ altOff = new Int32Array(tkCap); altEnd = new Int32Array(tkCap); altFl = new Uint8Array(tkCap);
2227+ altDp = new Uint8Array(tkCap); altPd = new Uint16Array(tkCap);
2228+ altCap = tkCap;
2229+ }
2230+ altN = oN;
2231+ swapBuffers(); // live = scratch, alt = OLD stream
2232+ src = source;
2233+ tokN = 0;
2234+ const startOff = B >= 0 ? altEnd[B] : 0;
2235+ const R0 = lexCore(source, startOff, B >= 0 ? altK[B] : -1, B >= 0 ? altT[B] : 0, r0, ceNew, charDelta, cs);
2236+ const W = tokN;
2237+ const R = R0 >= 0 ? R0 : oN;
2238+ swapBuffers(); // live = OLD stream again; window sits in the alt buffers
2239+ tokN = oN;
2240+ // TRUE token prefix p: the window re-derives [B+1 .. p) byte-identically; only past
2241+ // p is real damage (compared BEFORE the splice clobbers the old slots).
2242+ let p = B + 1;
2243+ { let i = 0;
2244+ while (i < W && p < R && altK[i] === tkK[p] && altT[i] === tkT[p] && altOff[i] === tkOff[p]
2245+ && altEnd[i] === tkEnd[p] && altFl[i] === tkFl[p]) { i++; p++; }
2246+ }
2247+ const dOldEnd = R;
2248+ const tokenDelta = (B + 1 + W) - R;
2249+ const charThresh = R < oN ? tkOff[R] : 0x7fffffff;
2250+ // ── splice: old[0..B] + window[0..W) + old[R..oN), then shift the suffix spans ──
2251+ const nN = B + 1 + W + (oN - R);
2252+ while (tkCap < nN + 1) growTok();
2253+ tkK.copyWithin(B + 1 + W, R, oN); tkT.copyWithin(B + 1 + W, R, oN);
2254+ tkOff.copyWithin(B + 1 + W, R, oN); tkEnd.copyWithin(B + 1 + W, R, oN);
2255+ tkFl.copyWithin(B + 1 + W, R, oN); tkDp.copyWithin(B + 1 + W, R, oN); tkPd.copyWithin(B + 1 + W, R, oN);
2256+ if (W > 0) {
2257+ tkK.set(altK.subarray(0, W), B + 1); tkT.set(altT.subarray(0, W), B + 1);
2258+ tkOff.set(altOff.subarray(0, W), B + 1); tkEnd.set(altEnd.subarray(0, W), B + 1);
2259+ tkFl.set(altFl.subarray(0, W), B + 1); tkDp.set(altDp.subarray(0, W), B + 1); tkPd.set(altPd.subarray(0, W), B + 1);
2260+ }
2261+ if (charDelta !== 0) {
2262+ for (let i = B + 1 + W; i < nN; i++) { tkOff[i] += charDelta; tkEnd[i] += charDelta; }
2263+ }
2264+ tokN = nN;
2265+ const nN2 = nN;
2266+ const oN2 = oN;` : String . raw ` // (fallback-lexer grammars keep the full-relex + token-diff path)
21812267 const oK = tkK, oT = tkT, oOff = tkOff, oEnd = tkEnd, oFl = tkFl, oN = tokN;
2182- ${ e . soa ? '' : ' const oText = tkText;' }
2268+ const oText = tkText;
21832269 if (altK === null || altK.length !== tkCap) {
21842270 altK = new tkK.constructor(tkCap); altT = new tkT.constructor(tkCap);
21852271 altOff = new Int32Array(tkCap); altEnd = new Int32Array(tkCap); altFl = new Uint8Array(tkCap);
2272+ altDp = new Uint8Array(tkCap); altPd = new Uint16Array(tkCap);
21862273 }
21872274 tkK = altK; tkT = altT; tkOff = altOff; tkEnd = altEnd; tkFl = altFl;
2188- ${ e . soa ? '' : ' tkText = altText; tkText.length = 0;' }
2275+ { const _d = tkDp; tkDp = altDp; altDp = _d; const _q = tkPd; tkPd = altPd; altPd = _q; }
2276+ tkText = altText; tkText.length = 0;
21892277 altK = oK; altT = oT; altOff = oOff; altEnd = oEnd; altFl = oFl;
2190- ${ e . soa ? '' : ' altText = oText;' }
2278+ altText = oText;
21912279 lexInto(source);
2192- if (tkCap !== oK.length) {
2193- // the new lex outgrew the buffer (growTok reallocated): drop the stale spare
2194- altK = null;
2195- }
21962280 const nN = tokN;
21972281 const charDelta = source.length - oSrc.length;
21982282 const minN = oN < nN ? oN : nN;
2199- // Longest identical prefix (positions included — the prefix is unshifted).
22002283 let p = 0;
22012284 while (p < minN && oK[p] === tkK[p] && oT[p] === tkT[p] && oFl[p] === tkFl[p]
2202- && oOff[p] === tkOff[p] && oEnd[p] === tkEnd[p]${ e . soa ? '' : ' && oText[p] === tkText[p]' } ) p++;
2203- // Longest identical suffix modulo charDelta (disjoint from the prefix).
2285+ && oOff[p] === tkOff[p] && oEnd[p] === tkEnd[p] && oText[p] === tkText[p]) p++;
22042286 let s = 0;
22052287 while (s < minN - p) {
22062288 const i = oN - 1 - s, j = nN - 1 - s;
22072289 if (oK[i] === tkK[j] && oT[i] === tkT[j] && oFl[i] === tkFl[j]
2208- && oOff[i] + charDelta === tkOff[j] && oEnd[i] + charDelta === tkEnd[j]${ e . soa ? '' : ' && oText[i] === tkText[j]' } ) s++;
2290+ && oOff[i] + charDelta === tkOff[j] && oEnd[i] + charDelta === tkEnd[j] && oText[i] === tkText[j]) s++;
22092291 else break;
22102292 }
2211- const dOldEnd = oN - s; // damaged OLD tokens: [p, dOldEnd)
2293+ const dOldEnd = oN - s;
22122294 const tokenDelta = nN - oN;
2213- // Re-base the old arena in place: rows starting at/after the first suffix token's OLD
2214- // offset shift by charDelta; reused leaf entries past the damage shift by tokenDelta.
2215- // (A reusable subtree lies entirely on one side of the damage, so the start-threshold
2216- // classifies it correctly; damage-spanning rows are garbage either way.)
2217- if (s > 0 && (charDelta !== 0 || tokenDelta !== 0)) {
2218- const charThresh = oOff[dOldEnd];
2295+ const charThresh = s > 0 ? oOff[dOldEnd] : 0x7fffffff;
2296+ const nN2 = nN;
2297+ const oN2 = oN;` }
2298+ // Re-base the old arena in place: rows starting at/after the first kept-suffix
2299+ // token's OLD offset shift by charDelta; reused leaf entries past the damage shift
2300+ // by tokenDelta. (A reusable subtree lies entirely on one side of the damage; rows
2301+ // spanning it are unreachable garbage either way.)
2302+ if (dOldEnd < oN2 && (charDelta !== 0 || tokenDelta !== 0)) {
22192303 if (charDelta !== 0) {
22202304 for (let i = 0; i < nodeN; i++) if (rowOff[i] >= charThresh) rowOff[i] += charDelta;
22212305 }
@@ -2227,18 +2311,12 @@ ${e.soa ? '' : ' altText = oText;'}
22272311 }
22282312 }
22292313 }
2230- // Carry the memo across: prefix entries whose lookahead never reached the damage stay
2231- // at their index; suffix entries move by tokenDelta (ids reference the re-based rows).
2232- // tokenDelta === 0 (the common keystroke: editing within a token) mutates IN PLACE —
2233- // no per-rule array allocation; only the damage window and the prefix entries whose
2234- // extent crossed into it are cleared.
2314+ // Carry the memo across: prefix entries whose lookahead never reached the damage
2315+ // stay; suffix entries shift by tokenDelta; the damage window drops.
22352316 for (let r = 0; r < MEMO_RULES; r++) {
22362317 const me = memoEnd[r];
22372318 if (me === undefined) continue;
22382319 const mn = memoNode[r], mx = memoExt[r];
2239- // prefix entries whose lookahead may have crossed into the damage die in place
2240- // (mx is the advance watermark; reads run up to two tokens past it: the stop
2241- // token and the SECOND-token dispatch probe)
22422320 for (let i = 0; i < p; i++) {
22432321 if (me[i] !== undefined && mx[i] + 2 > p) { me[i] = undefined; mn[i] = undefined; mx[i] = undefined; }
22442322 }
@@ -2248,15 +2326,12 @@ ${e.soa ? '' : ' altText = oText;'}
22482326 }
22492327 continue;
22502328 }
2251- // token count changed: rebuild the rule's arrays sparsely (measured FASTER than an
2252- // in-place direction-aware shift — writing undefined through the holes materializes
2253- // them; fresh holey arrays skip that entirely).
2254- const nme = new Array(nN + 1), nmn = new Array(nN + 1), nmx = new Array(nN + 1);
2255- const pCap = p < nN + 1 ? p : nN + 1;
2329+ const nme = new Array(nN2 + 1), nmn = new Array(nN2 + 1), nmx = new Array(nN2 + 1);
2330+ const pCap = p < nN2 + 1 ? p : nN2 + 1;
22562331 for (let i = 0; i < pCap; i++) {
22572332 if (me[i] !== undefined) { nme[i] = me[i]; nmn[i] = mn[i]; nmx[i] = mx[i]; }
22582333 }
2259- for (let i = dOldEnd; i <= oN ; i++) {
2334+ for (let i = dOldEnd; i <= oN2 ; i++) {
22602335 if (me[i] !== undefined) {
22612336 const j = i + tokenDelta;
22622337 nme[j] = me[i] + tokenDelta; nmn[j] = mn[i]; nmx[j] = mx[i] + tokenDelta;
0 commit comments