Skip to content

emit-portable: windowed relexing for stateless lexers (S3, #57)#64

Merged
johnsoncodehk merged 1 commit into
masterfrom
portable-relex-s3
Jul 11, 2026
Merged

emit-portable: windowed relexing for stateless lexers (S3, #57)#64
johnsoncodehk merged 1 commit into
masterfrom
portable-relex-s3

Conversation

@johnsoncodehk

Copy link
Copy Markdown
Owner

Summary

Slice 3 of #57 (portable incremental edit). Builds on the S2 alignment window (#63) — this slice makes Doc.edit() actually reuse tokens instead of relexing the whole document:

  • For grammars with a stateless lexer (no regex-context, no templates, no newline mode — e.g. calc), edit() now performs windowed relexing: keep the old token prefix up to a restart anchor (last token ending strictly before the edit, minus one token of maximal-munch guard), scan fresh tokens from that boundary, and resync once a scanned token past the edit region matches an old token shifted by the byte-length delta — the remaining old suffix is adopted with offsets shifted, no rescan.
  • Stateful lexers (javascript: regex-context + template stack; envspec: newline mode) keep the S2 full-relex path — restarting a stateful lexer mid-stream requires state reconstruction (anchors/checkpoints), deferred to a later slice.
  • The generated lexer for stateless grammars is refactored into lexFrom(src, pos, pendingNl, acc, limit) with a byte-identical loop body; lex() delegates to it from (0, false). Stateful lexer codegen is untouched.
  • Self-check scaffolding: after every edit(), the doc compares its token stream against a full fresh relex (streamEq). The edit-session stderr JSON grows to {oldN, newN, prefix, suffix, relexed, streamEq}. The self-check deliberately cancels the perf win for now — this slice ships the correctness mechanism; the perf switch comes with later slices.
  • Gate: 14 scenarios (calc 8 incl. token-merge / large append / large head, javascript 5, envspec 1) × 3 targets assert streamEq === true everywhere, relexed ≤ 8/5/8 bounds on calc large-doc scenarios (measured: 2–4 tokens relexed on ~1.2k-char docs), and relexed === newN on all fallback scenarios.

Parse is still a full reparse — tree reuse is future work.

Validation

  • node test/portable-targets.ts — calc 8/8 + javascript 5/5 + envspec 1/1 edit-sessions and token-alignments, × 3 targets
  • node test/check.ts emit-parity — 5/5 gates
  • node test/newline-mode.ts — 29/29; npx tsc --noEmit — clean
  • Mutation test: corrupting the suffix shift delta makes the gate fail with streamEq:false + alignment mismatches (self-check has teeth), reverted before commit
  • Independent adversarial probe (7 attacks × 3 targets): trivia-only insert, EOF token extension, cross-edit token merge, multi-edit batch, whole-text replace, newline deletion flipping nl flags, insert-before-first — all parity with oracle-derived expectations, windowed path staying at relexed 2–4 on large docs

Extend doc edit resync with windowed relex step and S3 suffix handling across TS, Go, and Rust targets; update portable-targets coverage.
@johnsoncodehk johnsoncodehk merged commit a813515 into master Jul 11, 2026
3 checks passed
@johnsoncodehk johnsoncodehk deleted the portable-relex-s3 branch July 11, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant