parseman 0.49.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/analysis/commitment.d.ts +2 -1
  2. package/dist/analysis/commitment.d.ts.map +1 -1
  3. package/dist/analysis/diagnostics.js +1931 -861
  4. package/dist/analysis/diagnostics.js.map +3 -3
  5. package/dist/analysis/rebuild.d.ts.map +1 -1
  6. package/dist/cli/index.js +1235 -164
  7. package/dist/cli/index.js.map +3 -3
  8. package/dist/combinators/trivia-skip.d.ts +1 -1
  9. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  10. package/dist/compiler/build-arity.d.ts +16 -0
  11. package/dist/compiler/build-arity.d.ts.map +1 -1
  12. package/dist/compiler/degradation.d.ts +0 -2
  13. package/dist/compiler/degradation.d.ts.map +1 -1
  14. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  15. package/dist/compiler/token-alphabet.d.ts +11 -0
  16. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  17. package/dist/compiler/token-capability.js +185 -20
  18. package/dist/compiler/token-capability.js.map +1 -1
  19. package/dist/cst/capture-buffer.d.ts +5 -1
  20. package/dist/cst/capture-buffer.d.ts.map +1 -1
  21. package/dist/index.js +1348 -279
  22. package/dist/index.js.map +3 -3
  23. package/dist/language-service/index.js +2 -1
  24. package/dist/language-service/index.js.map +1 -1
  25. package/dist/plugin/entry-supercompiler.d.ts +48 -0
  26. package/dist/plugin/entry-supercompiler.d.ts.map +1 -0
  27. package/dist/plugin/evaluator.d.ts.map +1 -1
  28. package/dist/plugin/index.d.ts.map +1 -1
  29. package/dist/plugin/index.js +1940 -385
  30. package/dist/plugin/index.js.map +3 -3
  31. package/dist/run/index.js +2 -1
  32. package/dist/run/index.js.map +1 -1
  33. package/dist/spec/index.js +28 -7
  34. package/dist/spec/index.js.map +1 -1
  35. package/dist/table/assemble.d.ts.map +1 -1
  36. package/dist/table/compile-rule-map.d.ts +6 -5
  37. package/dist/table/compile-rule-map.d.ts.map +1 -1
  38. package/dist/table/emit-assembly.d.ts +6 -1
  39. package/dist/table/emit-assembly.d.ts.map +1 -1
  40. package/dist/table/encode.d.ts.map +1 -1
  41. package/dist/table/index.js +1298 -231
  42. package/dist/table/index.js.map +3 -3
  43. package/dist/table/ops.d.ts +3 -2
  44. package/dist/table/ops.d.ts.map +1 -1
  45. package/dist/table/program.d.ts +15 -1
  46. package/dist/table/program.d.ts.map +1 -1
  47. package/dist/table/scalar-terminal.d.ts +30 -0
  48. package/dist/table/scalar-terminal.d.ts.map +1 -1
  49. package/dist/table/site-labels.d.ts +8 -0
  50. package/dist/table/site-labels.d.ts.map +1 -1
  51. package/dist/types.d.ts +2 -1
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/package.json +1 -1
  55. package/src/analysis/commitment.ts +19 -8
  56. package/src/analysis/rebuild.ts +1 -0
  57. package/src/combinators/trivia-skip.ts +2 -2
  58. package/src/compiler/build-arity.ts +50 -0
  59. package/src/compiler/degradation.ts +0 -6
  60. package/src/compiler/ir-serialize.ts +6 -4
  61. package/src/compiler/token-alphabet.ts +322 -28
  62. package/src/cst/capture-buffer.ts +34 -11
  63. package/src/plugin/entry-supercompiler.ts +567 -0
  64. package/src/plugin/evaluator.ts +10 -0
  65. package/src/plugin/index.ts +7 -3
  66. package/src/table/assemble.ts +125 -42
  67. package/src/table/compile-rule-map.ts +8 -7
  68. package/src/table/compile.ts +7 -8
  69. package/src/table/emit-assembly.ts +646 -126
  70. package/src/table/emit.ts +1 -1
  71. package/src/table/encode.ts +132 -7
  72. package/src/table/ops.ts +3 -2
  73. package/src/table/program.ts +44 -3
  74. package/src/table/scalar-terminal.ts +213 -1
  75. package/src/table/site-labels.ts +22 -9
  76. package/src/types.ts +1 -1
  77. package/src/version.ts +1 -1
@@ -45,6 +45,7 @@
45
45
  * what `encode.ts:1208-1213` refuses to allow for `OP_LIVE`.
46
46
  */
47
47
  import type { ParseContext } from '../types.ts'
48
+ import { regexCanMatchEmpty } from '../regex/first-set.ts'
48
49
  import {
49
50
  OP_ADJ, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
50
51
  OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
@@ -52,12 +53,20 @@ import {
52
53
  OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
53
54
  OP_LEX_BODY, OP_LEX_PROGRAM,
54
55
  } from './ops.ts'
55
- import { validateDispatchSpec, type ResolvedClass, type ResolvedTable, type TableProgram } from './program.ts'
56
+ import {
57
+ choiceCannotCommit, choiceRollbackMask, failureRollbackClean, validateDispatchSpec,
58
+ type ResolvedClass, type ResolvedTable, type TableProgram,
59
+ } from './program.ts'
56
60
  import { emitShapeMatch, scanShapeFromRegex } from './scan-shapes.ts'
57
61
  import {
58
- CAP_OFF, CAP_ON, TRI_NONE, TRI_UNKNOWN, computeSiteLabels, reachableSites, type SiteLabel,
62
+ CAP_OFF, CAP_ON, RAW_CAPTURE, RAW_OMIT, TRI_NONE, TRI_UNKNOWN,
63
+ computeSiteLabels, reachableSites, type SiteLabel,
59
64
  } from './site-labels.ts'
60
- import { scalarTerminalNodeChild, scalarTerminalNotChild } from './scalar-terminal.ts'
65
+ import {
66
+ leadingLiteralFamily, leadingScalarSequence, leadingScalarTerminal,
67
+ scalarTerminalNodeChild, scalarTerminalNotChild, type LeadingScalarSequence,
68
+ } from './scalar-terminal.ts'
69
+ import { childSlots } from './child-slots.ts'
61
70
 
62
71
  /** What the compiled factory hands back — the emitted twin of `Assembly`. */
63
72
  export type EmittedPiece = (input: string, pos: number, ctx: ParseContext) => unknown
@@ -134,6 +143,16 @@ let _pfScan=null
134
143
  let _pfHost
135
144
  let _pfDepth=0
136
145
  const _pfFrames=[]
146
+ let _pfTokInput
147
+ let _pfTokPos=-1
148
+ let _pfTokBody=-1
149
+ let _pfTokPacked=-1
150
+ let _pfTokValue
151
+ let _pfTokDispatch=-1
152
+ let _pfTokArm=-1
153
+ let _pfTokEnd=-1
154
+ let _pfSeqEnd=-1
155
+ function _asciiFoldCode(c){return c>=65&&c<=90?c+32:c}
137
156
  function _skipTrivia(input,cur,ctx){
138
157
  const s=_pfScan
139
158
  if(s!==null&&ctx._triviaLog===undefined&&!(ctx.captureTrivia===true&&(ctx._cstBuf!==undefined||ctx._cstTriviaLog!==undefined)))return s(input,cur)
@@ -200,6 +219,42 @@ if(ctx._rootTriviaLog!==undefined&&ctx._rootTriviaLog.length!==rt)ctx._rootTrivi
200
219
  }
201
220
  `
202
221
 
222
+ /** Printed only into assemblies that open a count-only raw buffer. */
223
+ const NO_RAW_RUNTIME_PRELUDE = `
224
+ function _pushLeafNoRawBuf(ctx,value,s,e){
225
+ const l={_tag:'leaf',value,span:{start:s,end:e}}
226
+ const b=ctx._cstBuf
227
+ if(b.ch!==undefined)b.ch.push(l)
228
+ else if(b.single!==undefined){b.ch=[b.single,l];b.single=undefined}
229
+ else b.single=l
230
+ b.rawLen++
231
+ }
232
+ function _pushNodeNoRawBuf(ctx,value){
233
+ const b=ctx._cstBuf
234
+ if(b.ch!==undefined)b.ch.push(value)
235
+ else if(b.single!==undefined){b.ch=[b.single,value];b.single=undefined}
236
+ else b.single=value
237
+ b.rawLen++
238
+ }
239
+ function _rbNoRawBuf(ctx,raw,tl,lv,lg,rt){
240
+ const b=ctx._cstBuf
241
+ if(b.rawLen!==raw)b.rawLen=raw
242
+ const ch=b.ch
243
+ if(ch!==undefined){
244
+ if(lv===0)b.ch=undefined
245
+ else if(lv===1){b.single=ch[0];b.ch=undefined}
246
+ else if(ch.length!==lv)ch.length=lv
247
+ }else if(lv===0)b.single=undefined
248
+ const bt=b.tl
249
+ if(bt!==undefined){
250
+ if(tl===0)b.tl=undefined
251
+ else if(bt.length!==tl)bt.length=tl
252
+ }
253
+ if(ctx._triviaLog!==undefined&&ctx._triviaLog.length!==lg)ctx._triviaLog.length=lg
254
+ if(ctx._rootTriviaLog!==undefined&&ctx._rootTriviaLog.length!==rt)ctx._rootTriviaLog.length=rt
255
+ }
256
+ `
257
+
203
258
  function q(s: string): string {
204
259
  return JSON.stringify(s)
205
260
  }
@@ -240,7 +295,7 @@ function sinkReads(t: string, s: Sinks): string {
240
295
  return `${s.fd ? `\n${t}fd=ctx._fields!==undefined?ctx._fields.length:0` : ''}${s.er ? `\n${t}er=ctx._errors!==undefined?ctx._errors.length:0` : ''}`
241
296
  }
242
297
 
243
- function emitMark(t: string, buf: boolean, s: Sinks = NO_SINKS, decl = true): string {
298
+ function emitMark(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SINKS, decl = true): string {
244
299
  // THE SITE IS INSIDE A NODE. `OP_NODE` opens `ctx._cstBuf` unconditionally and
245
300
  // closes it on the way out, so the whole discriminating chain below — which
246
301
  // sink is live, and whether a mark is needed at all — has ONE answer here, and
@@ -249,8 +304,13 @@ function emitMark(t: string, buf: boolean, s: Sinks = NO_SINKS, decl = true): st
249
304
  // `decl === false` RE-TAKES a mark into locals that already exist —
250
305
  // `OP_DISPATCH` marks, rolls back to the selector's mark, and marks again.
251
306
  if (buf) {
307
+ const raw = rawMode === RAW_OMIT
308
+ ? `${t}raw=b.rawLen`
309
+ : rawMode === RAW_CAPTURE
310
+ ? `const r=b.raw;${t}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0`
311
+ : `const r=b.raw;${t}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)`
252
312
  return `${decl ? `let ${t}raw=0,${t}tl=0,${t}lv=0,${t}lg=0,${t}rt=0${sinkSlots(t, s)}\n` : ''}{const b=ctx._cstBuf
253
- const r=b.raw;${t}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0
313
+ ${raw}
254
314
  const h=b.ch;${t}lv=h!==undefined?h.length:b.single!==undefined?1:0
255
315
  const l=b.tl;${t}tl=l!==undefined?l.length:0
256
316
  ${t}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
@@ -258,7 +318,7 @@ ${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${sinkReads(t,
258
318
  }
259
319
  return `${decl ? `let ${t}n=false,${t}raw=0,${t}tl=0,${t}lv=0,${t}lg=0,${t}rt=0${sinkSlots(t, s)}\n` : `${t}n=false\n`}{const b=ctx._cstBuf
260
320
  if(b!==undefined){
261
- const r=b.raw;${t}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0
321
+ const r=b.raw;${t}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)
262
322
  const h=b.ch;${t}lv=h!==undefined?h.length:b.single!==undefined?1:0
263
323
  const l=b.tl;${t}tl=l!==undefined?l.length:0
264
324
  ${t}n=true
@@ -286,14 +346,12 @@ ${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${sinkReads(t,
286
346
  * than a per-parse guess, and it is why `assemble.ts`'s `nextTerm` still takes
287
347
  * all seven: it is the engine those constructs actually run in.
288
348
  *
289
- * The two sinks are passed differently because `rollbackCstCaptureAt` guards
290
- * them differently. `_errors` is guarded on `errors !== undefined`, so
291
- * `undefined` is its established "no mark taken" sentinel and is correct even
292
- * for a caller-supplied context that arrived with errors already on it.
293
- * `_fields` has no such guard — `length = undefined` would throw — so it takes
294
- * the literal `0` that a per-node `_fields` no `OP_FIELD` can push to always has.
349
+ * Both sinks use `undefined` as "no mark taken". A literal zero is not a safe
350
+ * substitute: a sink-free speculative subtree may run inside a node whose
351
+ * enclosing `_fields` already contains entries, and rolling that array back to
352
+ * zero would erase state the subtree did not create.
295
353
  */
296
- function emitRollback(t: string, buf: boolean, s: Sinks = NO_SINKS): string {
354
+ function emitRollback(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SINKS): string {
297
355
  // `_rbBuf` is the `_cstBuf` arm of `rollbackCstCaptureAt` plus the two trivia
298
356
  // truncations, with the sink discrimination the label already answered removed.
299
357
  // It takes no piece, so the header's sharing rule admits it.
@@ -301,14 +359,15 @@ function emitRollback(t: string, buf: boolean, s: Sinks = NO_SINKS): string {
301
359
  // The two side sinks stay OUT of `_rbBuf` rather than growing its parameter
302
360
  // list: they are absent from most tables, and a fixed seven-argument helper
303
361
  // would make every grammar pay the two extra pushes to serve the ones that
304
- // carry a field. Guarded exactly as `rollbackCstCaptureAt:242,233` guards them
305
- // — `_errors` on a defined mark, because `undefined` is its "no mark taken"
306
- // sentinel, and `_fields` on the array alone.
362
+ // carry a field. Both are emitted only from a defined mark; the generic
363
+ // helper applies the same `undefined` sentinel when a site cannot write one.
307
364
  const fd = s.fd ? `\nif(ctx._fields!==undefined&&ctx._fields.length!==${t}fd)ctx._fields.length=${t}fd` : ''
308
365
  const er = s.er ? `\nif(ctx._errors!==undefined&&ctx._errors.length!==${t}er)ctx._errors.length=${t}er` : ''
309
- if (buf) return `_rbBuf(ctx,${t}raw,${t}tl,${t}lv,${t}lg,${t}rt)${fd}${er}`
310
- if (!s.fd && !s.er) return `if(${t}n)rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,0,undefined,${t}lg,${t}rt)`
311
- return `if(${t}n){rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,${s.fd ? `${t}fd` : '0'},${s.er ? `${t}er` : 'undefined'},${t}lg,${t}rt)}`
366
+ if (buf && rawMode === RAW_OMIT) return `_rbNoRawBuf(ctx,${t}raw,${t}tl,${t}lv,${t}lg,${t}rt)${fd}${er}`
367
+ if (buf && rawMode === RAW_CAPTURE) return `_rbBuf(ctx,${t}raw,${t}tl,${t}lv,${t}lg,${t}rt)${fd}${er}`
368
+ if (buf) return `rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,${s.fd ? `${t}fd` : 'undefined'},${s.er ? `${t}er` : 'undefined'},${t}lg,${t}rt)`
369
+ if (!s.fd && !s.er) return `if(${t}n)rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,undefined,undefined,${t}lg,${t}rt)`
370
+ return `if(${t}n){rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,${s.fd ? `${t}fd` : 'undefined'},${s.er ? `${t}er` : 'undefined'},${t}lg,${t}rt)}`
312
371
  }
313
372
 
314
373
  /**
@@ -447,6 +506,11 @@ export function rebuildPools(
447
506
  * Throws `Unemittable` for any construct not lowered. It does NOT compile the
448
507
  * text — `assemble.ts` does — so a refusal and a compile failure stay two
449
508
  * distinguishable outcomes at the call site.
509
+ *
510
+ * `staticBuild` is true only when `emit.ts` embeds the returned source as an
511
+ * ordinary factory literal in a macro artifact. It permits macro-only code
512
+ * shaping without perturbing the runtime `compile()` emitter, whose generated
513
+ * parser must remain byte-identical when the optimization cannot affect it.
450
514
  */
451
515
  export function emitAssemblySource(
452
516
  t: ResolvedTable,
@@ -461,8 +525,9 @@ export function emitAssemblySource(
461
525
  probe: boolean
462
526
  },
463
527
  extraIps: readonly number[] = [],
528
+ staticBuild = false,
464
529
  ): EmitResult {
465
- const { code, k, disp, dsp, triviaLabelled } = t
530
+ const { code, k, fx, disp, dsp, triviaLabelled } = t
466
531
  const swapLegal = !cfg.trackLines
467
532
  const hostCst = cfg.hostCst
468
533
 
@@ -498,23 +563,108 @@ export function emitAssemblySource(
498
563
  // pool's `extraIps` — and each starts at `TOP`, because a caller outside the
499
564
  // emitted scope supplies a context this pass cannot see.
500
565
  const roots = [...Object.values(prog.rules), ...extraIps]
566
+ const reachable = [...reachableSites(code, roots)]
501
567
  const labels = computeSiteLabels(code, roots, hostCst)
502
568
  // Eligibility is pooled by the terminal's existing constant operand, matching
503
569
  // the closure recognizer pool: distinct terminal rows sharing one spec share
504
570
  // both the recognizer and the ordinary-terminal lowering.
505
571
  const scalarSpecs = new Set<number>()
572
+ // Large choices are different: admitting every wrapped scalar would add a
573
+ // recognizer call to many arms whose one-character gate already did all the
574
+ // useful work. A multi-character literal can still refine an overlapping
575
+ // first-character mask (`@@` beside `@name`) without changing the ordinary
576
+ // terminal's inline lowering, so keep that narrower inventory separate.
577
+ const largeChoiceScalarSpecs = new Set<number>()
578
+ const largeChoiceLiteralFamilies = new Map<number, readonly number[]>()
579
+ const largeChoiceScalarSequences = new Map<number, LeadingScalarSequence>()
580
+ type TokenChoiceCandidate = { readonly arm: number; readonly dispatchIp: number }
581
+ const tokenChoiceCandidates = new Map<number, TokenChoiceCandidate>()
582
+ const tokenChoiceDispatches = new Set<number>()
583
+ const tokenChoiceBodies = new Set<number>()
506
584
  if (!hostCst && !cfg.tolerant && !cfg.probe && !cfg.coverage && !cfg.trackLines) {
507
- for (const ip of reachableSites(code, roots)) {
508
- if (code[ip] !== OP_NODE) continue
509
- const child = scalarTerminalNodeChild(code, ip)
510
- if (child >= 0) scalarSpecs.add(code[child + 1]!)
585
+ for (const ip of reachable) {
586
+ if (code[ip] === OP_NODE) {
587
+ const child = scalarTerminalNodeChild(code, ip)
588
+ if (child >= 0) scalarSpecs.add(code[child + 1]!)
589
+ continue
590
+ }
591
+ if (code[ip] !== OP_CHOICE || disp[code[ip + 1]!]!.exclusive) continue
592
+ const n = code[ip + 2]!
593
+ for (let i = 0; i < n; i++) {
594
+ const armIp = code[ip + 4 + i]!
595
+ const child = leadingScalarTerminal(code, armIp, 2, true, true)
596
+ if (n === 2 || n === 3) {
597
+ if (child >= 0) scalarSpecs.add(code[child + 1]!)
598
+ continue
599
+ }
600
+ if (child >= 0) {
601
+ const spec = k[code[child + 1]!]
602
+ if (code[child] === OP_LIT && typeof spec === 'string' && spec.length >= 2) {
603
+ largeChoiceScalarSpecs.add(code[child + 1]!)
604
+ continue
605
+ }
606
+ }
607
+ const family = leadingLiteralFamily(code, k, armIp)
608
+ if (family !== undefined && family.length >= 2 && family.every(terminal => {
609
+ const value = k[code[terminal + 1]!]
610
+ return typeof value === 'string' && value.length >= 2
611
+ })) {
612
+ largeChoiceLiteralFamilies.set(armIp, family)
613
+ }
614
+ const sequence = leadingScalarSequence(code, armIp)
615
+ // A labelled trivia scanner is still recognition authority here. This
616
+ // pretest is pure: on a miss the ordinary arm's trivia log would have
617
+ // been rolled back, while on a hit the arm runs and records it normally.
618
+ if (sequence !== undefined && (sequence.trivia < 0
619
+ || (swapLegal && t.triviaScan[sequence.trivia] != null))) {
620
+ largeChoiceScalarSequences.set(armIp, sequence)
621
+ }
622
+ }
623
+
624
+ // A direct value-only transform around a dispatch cannot consume, branch,
625
+ // publish, or call author code before the dispatch selector. When that
626
+ // selector is one compiler-selected lexical body, the emitted choice may
627
+ // recognize and classify it before entering the arm, then hand the exact
628
+ // packed range and route to the ordinary LEX_BODY/DISPATCH readers. One
629
+ // candidate per ordered choice keeps source order authoritative: a second
630
+ // eligible arm leaves the whole site on the established PEG path.
631
+ let candidate: TokenChoiceCandidate | undefined
632
+ let ambiguous = false
633
+ for (let i = 0; i < n; i++) {
634
+ const armIp = code[ip + 4 + i]!
635
+ if (code[armIp] !== OP_XFORM) continue
636
+ const dispatchIp = code[armIp + 2]!
637
+ if (code[dispatchIp] !== OP_DISPATCH) continue
638
+ const selectorIp = code[dispatchIp + 1]!
639
+ if (code[selectorIp] !== OP_LEX_BODY) continue
640
+ if (candidate !== undefined) { ambiguous = true; break }
641
+ candidate = { arm: i, dispatchIp }
642
+ }
643
+ if (!ambiguous && candidate !== undefined) {
644
+ tokenChoiceCandidates.set(ip, candidate)
645
+ tokenChoiceDispatches.add(candidate.dispatchIp)
646
+ tokenChoiceBodies.add(code[code[candidate.dispatchIp + 1]! + 1]!)
647
+ }
511
648
  }
512
649
  }
513
650
 
514
- // THE SIDE-SINK CENSUS, over the same reachable set the labels are computed
515
- // from. `OP_FIELD` is the only ROW that writes `_fields` and `OP_EXPECT` the
516
- // only emittable row that writes `_errors` — `recoverScan` is the other, and a
517
- // recovery assembly is refused above. A table with neither pays for neither.
651
+ // Helpers are referenced by DESCENDANT publication/rollback sites, not only
652
+ // by the node row that opened the count-only buffer. A specialized no-raw node
653
+ // can contain a shared child whose merged label is unknown even though this
654
+ // occurrence emits `_pushNodeNoRawBuf`; the node flag is the conservative
655
+ // factory-level authority for including the tiny prelude.
656
+ const needsNoRawPrelude = !hostCst && reachable.some(ip => {
657
+ const op = code[ip]
658
+ return (op === OP_NODE || op === OP_NODE_TRACK)
659
+ && code[ip + 1]! >= 0 && code[ip + 4]! < 0 && (code[ip + 3]! & 2) !== 0
660
+ })
661
+
662
+ // THE SIDE-SINK FIXPOINT, over the same graph the labels walk. `OP_FIELD` is
663
+ // the only direct `_fields` writer and `OP_EXPECT` the only direct `_errors`
664
+ // writer. A tolerant repetition can also write `_errors` through recoverScan.
665
+ // Propagating those two bits through the child graph gives every speculative
666
+ // site the exact side sinks its subtree can mutate, including through cyclic
667
+ // OP_RULE edges.
518
668
  //
519
669
  // `OP_SCAN` RAISES BOTH, and that is not caution — it is a measured defect.
520
670
  // The row runs an INTERPRETED combinator (`scanTo`/`balanced`, rebuilt by
@@ -528,26 +678,34 @@ export function emitAssemblySource(
528
678
  //
529
679
  // Any future row whose child is not an offset in `code` belongs here for the
530
680
  // same reason. `OP_LIVE` is the other such row, and it is unemittable.
531
- const sinks: Sinks = (() => {
532
- let fd = false
533
- // A RECOVERY ASSEMBLY IS AN `_errors` WRITER EVERYWHERE. `recoverScan` pushes
534
- // through `captureError` from inside any `OP_REP` that recovers, and the
535
- // recovering row is the repetition itself rather than a distinguishable
536
- // opcode — so there is no site to census and the answer is the assembly's.
537
- //
538
- // This is the `OP_SCAN` lesson applied before it costs anything rather than
539
- // after: that row raised both sinks because its interior is not in `code`,
540
- // and the defect it was raised for was precisely an `_errors` rollback the
541
- // two engines disagreed on while matching byte for byte on the tree.
542
- let er = REC
543
- for (const ip of reachableSites(code, roots)) {
544
- const op = code[ip]
545
- if (op === OP_SCAN) { fd = true; er = true; break }
546
- if (op === OP_FIELD) fd = true
547
- else if (op === OP_EXPECT) er = true
681
+ const sinkSites = reachable
682
+ const sinkBits = new Map<number, number>()
683
+ for (const ip of sinkSites) {
684
+ const op = code[ip]
685
+ sinkBits.set(ip,
686
+ op === OP_SCAN ? 3
687
+ : (op === OP_FIELD ? 1 : 0)
688
+ | (op === OP_EXPECT || (REC && (op === OP_REP || op === OP_REPV)) ? 2 : 0))
689
+ }
690
+ const sinkKids: number[] = []
691
+ let sinkChanged = true
692
+ while (sinkChanged) {
693
+ sinkChanged = false
694
+ for (const ip of sinkSites) {
695
+ let bits = sinkBits.get(ip) ?? 0
696
+ sinkKids.length = 0
697
+ childSlots(code, ip, sinkKids)
698
+ for (let i = 0; i < sinkKids.length; i++) bits |= sinkBits.get(sinkKids[i]!) ?? 0
699
+ if (bits !== sinkBits.get(ip)) {
700
+ sinkBits.set(ip, bits)
701
+ sinkChanged = true
702
+ }
548
703
  }
549
- return { fd, er }
550
- })()
704
+ }
705
+ const sinksAt = (ip: number): Sinks => {
706
+ const bits = sinkBits.get(ip) ?? 0
707
+ return bits === 0 ? NO_SINKS : { fd: (bits & 1) !== 0, er: (bits & 2) !== 0 }
708
+ }
551
709
 
552
710
  const bodies: string[] = []
553
711
  const byIp = new Map<number, string>()
@@ -584,6 +742,27 @@ export function emitAssemblySource(
584
742
  const fxRef = (i: number): string => hoist('fx', `FX[${i}]`)
585
743
  const fnRef = (i: number): string => hoist('fn', `FNS[${i}]`)
586
744
  const recognizerRef = (i: number): string => hoist('rec', `RECOG[${i}]`)
745
+ type ScalarSequenceRef = { readonly name: string; readonly expected: readonly string[] }
746
+ const scalarSequenceRefs = new Map<string, ScalarSequenceRef>()
747
+ const scalarSequenceRef = (sequence: LeadingScalarSequence): ScalarSequenceRef => {
748
+ const key = `${sequence.trivia}:${sequence.terminals.map(terminal => code[terminal + 1]!).join(',')}`
749
+ const prior = scalarSequenceRefs.get(key)
750
+ if (prior !== undefined) return prior
751
+ const recognizers = sequence.terminals.map(terminal => recognizerRef(code[terminal + 1]!))
752
+ const expected = sequence.terminals.map(terminal => fxRef(code[terminal + 2]!))
753
+ const scan = sequence.trivia < 0 ? undefined : hoist('ts', `TRIVIASCAN[${sequence.trivia}]`)
754
+ const name = `_sd${scalarSequenceRefs.size}_`
755
+ const steps = recognizers.map((recognize, i) => `${i === 0 ? 'let ' : ''}at=${i === 0 ? 'pos' : scan === undefined ? 'cur' : `${scan}(input,cur)`}
756
+ ${i === 0 ? 'let ' : ''}cur=${recognize}(input,at)
757
+ if(cur<0){_pfSeqEnd=at;return ${~i}}`).join('\n')
758
+ choiceDefs.push(`function ${name}(input,pos){
759
+ ${steps}
760
+ return cur
761
+ }`)
762
+ const made = { name, expected }
763
+ scalarSequenceRefs.set(key, made)
764
+ return made
765
+ }
587
766
  /**
588
767
  * The sync sentinel for a char-class index, hoisted once per class.
589
768
  *
@@ -597,6 +776,105 @@ export function emitAssemblySource(
597
776
  let uid = 0
598
777
  const tmp = (): string => `_t${uid++}_`
599
778
 
779
+ /** One dispatch matcher as source, shared by the ordinary and token-first paths. */
780
+ const dispatchClaim = (m: readonly [number, string, string, number]): string => {
781
+ switch (m[0]) {
782
+ case 0: return `key.startsWith(${q(m[1])})`
783
+ case 1: return `key.endsWith(${q(m[1])})`
784
+ case 3: return `asciiFoldKey(key).startsWith(${q(m[1])})`
785
+ case 4: return `asciiFoldKey(key).endsWith(${q(m[1])})`
786
+ default: {
787
+ if (!m[2].includes('g') && !m[2].includes('y')) {
788
+ try {
789
+ // Validate without changing malformed-row error timing.
790
+ new RegExp(m[1], m[2])
791
+ return `${hoist('dm', `new RegExp(${q(m[1])},${q(m[2])})`)}.test(key)`
792
+ } catch {}
793
+ }
794
+ return `new RegExp(${q(m[1])},${q(m[2])}).test(key)`
795
+ }
796
+ }
797
+ }
798
+
799
+ type TokenDecisionRef = { readonly name: string; readonly expected: string }
800
+ const tokenDecisionRefs = new Map<number, TokenDecisionRef>()
801
+ function tokenDecisionFor(dispatchIp: number): TokenDecisionRef {
802
+ const prior = tokenDecisionRefs.get(dispatchIp)
803
+ if (prior !== undefined) return prior
804
+ const selectorIp = code[dispatchIp + 1]!
805
+ const body = code[selectorIp + 1]!
806
+ const lineFlags = code[selectorIp + 4]!
807
+ const hasSuffix = (lineFlags & 4) !== 0
808
+ const di = code[dispatchIp + 2]!
809
+ const spec = dsp[di]!
810
+ const n = code[dispatchIp + 5]!
811
+ validateDispatchSpec(spec, n, code[dispatchIp + 4]!)
812
+ const recognize = hoist('lex', `LEX[${body}]`)
813
+ const bk = hoist('bk', `DSP[${di}].byKey`)
814
+ const expected = hoist('dx', `DSP[${di}].expected`)
815
+ const fold = spec.byFold.size > 0
816
+ ? `if(arm===undefined)arm=${hoist('bf', `DSP[${di}].byFold`)}.get(asciiFoldKey(key))\n`
817
+ : ''
818
+ const chain = spec.match.length === 0
819
+ ? ''
820
+ : `if(arm===undefined){\n${spec.match.map((m, i) => `${i === 0 ? '' : 'else '}if(${dispatchClaim(m)})arm=${m[3]}`).join('\n')}\n}\n`
821
+ const foldedEntries = [...spec.byFold.entries()]
822
+ // Exact, fail-closed shape of Jess Less's function-token dispatch. Keep the
823
+ // source+flags check explicit: changing that grammar matcher must disable
824
+ // this hand-lowered classifier until its url(/calc( exclusions are reviewed.
825
+ const fixedFunctionChoice = spec.byKey.size === 0
826
+ && foldedEntries.length === 1
827
+ && spec.match.length === 1
828
+ && spec.match[0]![0] === 2
829
+ && spec.match[0]![1] === '^(?!(?:url|calc)\\($).+\\($'
830
+ && spec.match[0]![2] === 'i'
831
+ && code[dispatchIp + 3]! < 0
832
+ const foldedRangeEquals = (value: string): string => {
833
+ const folded = value.replace(/[A-Z]/g, c => c.toLowerCase())
834
+ return `e-pos===${folded.length}&&${[...folded].map((c, i) => {
835
+ const cc = c.charCodeAt(0)
836
+ return `_asciiFoldCode(input.charCodeAt(pos+${i}))===${cc}`
837
+ }).join('&&')}`
838
+ }
839
+ const classify = fixedFunctionChoice
840
+ ? (() => {
841
+ const [exact, exactArm] = foldedEntries[0]!
842
+ const genericArm = spec.match[0]![3]
843
+ return `let arm
844
+ if(${foldedRangeEquals(exact)})arm=${exactArm}
845
+ else if(sm&&e>pos+1){
846
+ let clean=true
847
+ for(let i=pos;i<e;i++){const c=input.charCodeAt(i);if(c===10||c===13||c===0x2028||c===0x2029){clean=false;break}}
848
+ if(clean&&!(${foldedRangeEquals('url(')})&&!(${foldedRangeEquals('calc(')}))arm=${genericArm}
849
+ }`
850
+ })()
851
+ : `const key=input.slice(pos,e)
852
+ let arm=${bk}.get(key)
853
+ ${fold}${chain}`
854
+ const name = `_td${tokenDecisionRefs.size}_`
855
+ choiceDefs.push(`function ${name}(input,pos){
856
+ const r=${recognize}(input,pos)
857
+ if(r<0)return -1
858
+ const sm=${hasSuffix ? 'r%2===1' : 'false'},e=(r-(sm?1:0))/2
859
+ ${classify}
860
+ if(arm===undefined){
861
+ ${code[dispatchIp + 3]! < 0 ? '_pfTokEnd=e;return 0' : `arm=${n}`}
862
+ }
863
+ ${fixedFunctionChoice ? 'const key=input.slice(pos,e)' : ''}
864
+ _pfTokInput=input
865
+ _pfTokPos=pos
866
+ _pfTokBody=${body}
867
+ _pfTokPacked=r
868
+ _pfTokValue=key
869
+ _pfTokDispatch=${dispatchIp}
870
+ _pfTokArm=arm
871
+ return 1
872
+ }`)
873
+ const made = { name, expected }
874
+ tokenDecisionRefs.set(dispatchIp, made)
875
+ return made
876
+ }
877
+
600
878
  /**
601
879
  * THE TRIVIA SCAN FOR ONE SITE LABEL.
602
880
  *
@@ -796,6 +1074,7 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
796
1074
  const op = code[ip]
797
1075
  const head = `function ${fname}(input,pos,ctx){`
798
1076
  const L = labels.at(ip)
1077
+ const sinks = sinksAt(ip)
799
1078
  /**
800
1079
  * THE LEAF CAPTURE TEST, and only the test.
801
1080
  *
@@ -813,7 +1092,9 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
813
1092
  * `trackLines` is `RunCfg`, and an open `_cstBuf` is the label. `_pushLeafBuf`
814
1093
  * is that pair with both branches taken, and it takes no piece.
815
1094
  */
816
- const pushLeaf = L.buf && !cfg.trackLines ? '_pushLeafBuf' : '_pushLeaf'
1095
+ const pushLeaf = L.buf && !cfg.trackLines
1096
+ ? L.raw === RAW_OMIT ? '_pushLeafNoRawBuf' : L.raw === RAW_CAPTURE ? '_pushLeafBuf' : '_pushLeaf'
1097
+ : '_pushLeaf'
817
1098
  const captureLeaf = (value: string): string => {
818
1099
  const call = `${pushLeaf}(ctx,${value},pos,e)`
819
1100
  return L.buf ? call : `if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)${call}`
@@ -1173,20 +1454,24 @@ return out
1173
1454
  }
1174
1455
 
1175
1456
  case OP_LEX_BODY: {
1176
- const recognize = hoist('lex', `LEX[${code[ip + 1]!}]`)
1457
+ const body = code[ip + 1]!
1458
+ const recognize = hoist('lex', `LEX[${body}]`)
1177
1459
  const expected = fxRef(code[ip + 2]!)
1178
1460
  const suffixExpected = fxRef(code[ip + 3]!)
1179
1461
  const lineFlags = code[ip + 4]!
1180
1462
  const hasSuffix = (lineFlags & 4) !== 0
1463
+ const pending = tokenChoiceBodies.has(body)
1181
1464
  return `${head}
1182
- const r=${recognize}(input,pos)
1465
+ ${pending ? `const tp=_pfTokBody===${body}&&_pfTokInput===input&&_pfTokPos===pos
1466
+ const r=tp?_pfTokPacked:${recognize}(input,pos)` : `const r=${recognize}(input,pos)`}
1183
1467
  if(r<0){ctx._fe=pos;ctx._fx=${expected};if(ctx._probe!==undefined)failAt(ctx,${expected},pos);return FAIL}
1184
1468
  const sm=${hasSuffix ? 'r%2===1' : 'false'},e=(r-(sm?1:0))/2
1185
1469
  ${(lineFlags & 1) !== 0 ? '_trackLines(ctx,input,sm?e-1:e)' : ''}
1186
1470
  ${hasSuffix ? 'ctx._fc=false' : ''}
1187
1471
  ${hasSuffix && (lineFlags & 2) !== 0 ? 'if(sm)_trackLines(ctx,input,e)' : ''}
1188
1472
  ${hasSuffix ? `if(!sm){ctx._fe=e;ctx._fx=${suffixExpected};if(ctx._probe!==undefined)failAt(ctx,${suffixExpected},e)}` : ''}
1189
- const v=input.slice(pos,e)
1473
+ const v=${pending ? 'tp?_pfTokValue:' : ''}input.slice(pos,e)
1474
+ ${pending ? 'if(tp){_pfTokBody=-1;_pfTokValue=undefined}' : ''}
1190
1475
  if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
1191
1476
  EC.e=e
1192
1477
  return v
@@ -1245,12 +1530,13 @@ return v
1245
1530
 
1246
1531
  case OP_ATTEMPT: {
1247
1532
  const child = link(code[ip + 1]!)
1533
+ const clean = !REC && failureRollbackClean(prog, ip)
1248
1534
  const p = tmp()
1249
1535
  return `${head}
1250
- ${emitMark(p, L.buf, sinks)}
1536
+ ${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
1251
1537
  const v=${child}(input,pos,ctx)
1252
1538
  if(v!==FAIL)return v
1253
- ${emitRollback(p, L.buf, sinks)}
1539
+ ${clean ? '' : emitRollback(p, L.buf, L.raw, sinks)}
1254
1540
  if(ctx._fc===true)return FAIL
1255
1541
  ctx._fe=pos
1256
1542
  return FAIL
@@ -1274,9 +1560,9 @@ return FAIL
1274
1560
  const xf = fxRef(code[ip + 2]!)
1275
1561
  const p = tmp()
1276
1562
  return `${head}
1277
- ${emitMark(p, L.buf, sinks)}
1563
+ ${emitMark(p, L.buf, L.raw, sinks)}
1278
1564
  const v=${child}(input,pos,ctx)
1279
- ${emitRollback(p, L.buf, sinks)}
1565
+ ${emitRollback(p, L.buf, L.raw, sinks)}
1280
1566
  if(v===FAIL){EC.e=pos;return null}
1281
1567
  ctx._fe=pos
1282
1568
  ctx._fx=${xf}
@@ -1289,9 +1575,9 @@ return FAIL
1289
1575
  const xf = fxRef(code[ip + 2]!)
1290
1576
  const p = tmp()
1291
1577
  return `${head}
1292
- ${emitMark(p, L.buf, sinks)}
1578
+ ${emitMark(p, L.buf, L.raw, sinks)}
1293
1579
  const v=${child}(input,pos,ctx)
1294
- ${emitRollback(p, L.buf, sinks)}
1580
+ ${emitRollback(p, L.buf, L.raw, sinks)}
1295
1581
  if(v===FAIL){ctx._fe=pos;ctx._fx=${xf};return FAIL}
1296
1582
  EC.e=pos
1297
1583
  return null
@@ -1300,14 +1586,15 @@ return null
1300
1586
 
1301
1587
  case OP_OPT: {
1302
1588
  const child = link(code[ip + 1]!)
1589
+ const clean = !REC && failureRollbackClean(prog, ip)
1303
1590
  const p = tmp()
1304
1591
  return `${head}
1305
- ${emitMark(p, L.buf, sinks)}
1592
+ ${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
1306
1593
  ctx._fc=false
1307
1594
  const v=${child}(input,pos,ctx)
1308
1595
  if(v===FAIL){
1309
1596
  if(ctx._fc===true)return FAIL
1310
- ${emitRollback(p, L.buf, sinks)}
1597
+ ${clean ? '' : emitRollback(p, L.buf, L.raw, sinks)}
1311
1598
  EC.e=pos
1312
1599
  return null
1313
1600
  }
@@ -1443,6 +1730,30 @@ return FAIL
1443
1730
  const expected = Array.from({ length: n }, (_, i) => fxRef(code[base + n + i]!))
1444
1731
  const gates = Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)
1445
1732
  const gateRefs = Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
1733
+ type ChoicePretest = {
1734
+ readonly scalar?: readonly string[]
1735
+ readonly sequence?: ScalarSequenceRef
1736
+ readonly token?: TokenDecisionRef
1737
+ }
1738
+ const tokenCandidate = tokenChoiceCandidates.get(ip)
1739
+ const pretests = Array.from({ length: n }, (_, i): ChoicePretest | undefined => {
1740
+ if (tokenCandidate?.arm === i) {
1741
+ return { token: tokenDecisionFor(tokenCandidate.dispatchIp) }
1742
+ }
1743
+ const terminal = leadingScalarTerminal(code, code[base + i]!, 2, true, true)
1744
+ if (terminal >= 0) {
1745
+ const spec = code[terminal + 1]!
1746
+ if ((n === 2 || n === 3 ? scalarSpecs : largeChoiceScalarSpecs).has(spec)) {
1747
+ return { scalar: [recognizerRef(spec)] }
1748
+ }
1749
+ }
1750
+ const family = largeChoiceLiteralFamilies.get(code[base + i]!)
1751
+ if (family !== undefined) {
1752
+ return { scalar: family.map(member => recognizerRef(code[member + 1]!)) }
1753
+ }
1754
+ const sequence = largeChoiceScalarSequences.get(code[base + i]!)
1755
+ return sequence === undefined ? undefined : { sequence: scalarSequenceRef(sequence) }
1756
+ })
1446
1757
  const maskable = n <= 32
1447
1758
  const maskName = maskable
1448
1759
  ? hoist('mk', `MASK[${masks.push(maskForClassRow(gates)) - 1}]`)
@@ -1452,53 +1763,222 @@ return FAIL
1452
1763
  // cannot carry a second mutable answer for the arms' classes.
1453
1764
  if (maskable) maskPlan.push(~di)
1454
1765
  const p = tmp()
1455
- const catchName = maskable ? `_cx${uid++}_` : ''
1456
- if (maskable) {
1766
+ // This proof lives only on the compiler-created program while the
1767
+ // precompiled assembly is printed. A hand-built or deserialized table
1768
+ // has no authority and therefore keeps the established all-arm path.
1769
+ const encodedRollbackMask = choiceRollbackMask(prog, ip) ?? -1
1770
+ const rollbackMask = REC ? -1 : encodedRollbackMask
1771
+ const hasRollback = rollbackMask !== 0
1772
+ // An always-consuming leading scalar makes a failure at `pos`
1773
+ // statically exact: it is the arm's derived opener set, and the
1774
+ // choice's own `choiceFx` already concatenates every such set in
1775
+ // source order. Defer array merging until an arm reaches deeper input
1776
+ // so a later successful arm pays no diagnostic-allocation tax.
1777
+ //
1778
+ // attempt() is a hard boundary: it can fail deeper and deliberately
1779
+ // re-anchor `_fe` at `pos` while keeping the inner dynamic expected
1780
+ // set. A zero-width regex has the same ambiguity, so neither qualifies.
1781
+ const startFailureExact = arms.every((_arm, i) => {
1782
+ const terminal = leadingScalarTerminal(code, code[base + i]!, 0, false)
1783
+ if (terminal < 0) return false
1784
+ const op = code[terminal]
1785
+ const spec = k[code[terminal + 1]!]
1786
+ // Recognition shape is not diagnostic authority. word() is one
1787
+ // important counterexample: its terminal reports `keyword`, while
1788
+ // deriveExpected at an enclosing choice names the concrete word.
1789
+ // Substituting choiceFx for the dynamic terminal set is sound only
1790
+ // when both encoded authorities agree byte-for-byte, including
1791
+ // duplicates and source order.
1792
+ const terminalFx = fx[code[terminal + 2]!]!
1793
+ const armFx = fx[code[base + n + i]!]!
1794
+ if (terminalFx.length !== armFx.length
1795
+ || terminalFx.some((expected, at) => expected !== armFx[at])) return false
1796
+ if (op === OP_LIT) return typeof spec === 'string' && spec.length > 0
1797
+ return op === OP_RX && spec instanceof RegExp && !regexCanMatchEmpty(spec.source)
1798
+ })
1799
+ const rollbackFor = (i: number): string =>
1800
+ rollbackMask === -1 || (rollbackMask & (1 << i)) !== 0
1801
+ ? emitRollback(p, L.buf, L.raw, sinks)
1802
+ : ''
1803
+ if (choiceCannotCommit(prog, ip) && !startFailureExact) {
1804
+ // A successful ordered choice does not expose any losing arm's
1805
+ // diagnostic. Snapshot each entered arm's failure in scalar locals;
1806
+ // choose the deepest set and concatenate ties on the cold TOTAL-
1807
+ // failure exit. This is the direct-emitter topology from 0.45,
1808
+ // restored without changing the table wire format.
1809
+ const deepAt = Array.from({ length: n }, () => tmp())
1810
+ const deepFx = Array.from({ length: n }, () => tmp())
1811
+ const remember = (i: number, at: string, expectedSet: string): string =>
1812
+ `${deepAt[i]}=${at};${deepFx[i]}=${expectedSet}`
1813
+ const lazyArms = (ascii: boolean): string => arms.map((arm, i) => {
1814
+ const pretest = pretests[i]
1815
+ const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
1816
+ const scalarCondition = pretest?.scalar === undefined ? ''
1817
+ : pretest.scalar.length === 1
1818
+ ? `&&${pretest.scalar[0]}(input,pos)>=0`
1819
+ : `&&(${pretest.scalar.map(recognize => `${recognize}(input,pos)>=0`).join('||')})`
1820
+ const sequenceCondition = pretest?.sequence === undefined ? ''
1821
+ : `&&(${decision}=${pretest.sequence.name}(input,pos))>=0`
1822
+ const condition = pretest?.token !== undefined
1823
+ ? `&&(${decision}=${pretest.token.name}(input,pos))>0`
1824
+ : scalarCondition || sequenceCondition
1825
+ const gate = ascii
1826
+ ? `(bits&${1 << i})!==0${condition}`
1827
+ : `(${gateRefs[i]}===null||classHas(${gateRefs[i]},c))${condition}`
1828
+ const routeMiss = pretest?.token === undefined ? '' : `
1829
+ if(${decision}===0){
1830
+ ctx._fc=false
1831
+ {const at=_pfTokEnd
1832
+ ${remember(i, 'at', pretest.token.expected)}}
1833
+ }`
1834
+ const sequenceMiss = pretest?.sequence === undefined ? '' : `
1835
+ else if(${ascii ? `(bits&${1 << i})!==0&&` : ''}${decision}<0){
1836
+ ctx._fc=false
1837
+ {const at=_pfSeqEnd
1838
+ ${remember(i, 'at', pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('') + pretest.sequence.expected.at(-1))}}
1839
+ }`
1840
+ return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if(${gate}){
1841
+ ctx._fc=false
1842
+ {const v=${arm}(input,pos,ctx)
1843
+ if(v!==FAIL)return v}
1844
+ {const at=ctx._fe??pos
1845
+ ${remember(i, 'at', 'ctx._fx')}}
1846
+ ${rollbackFor(i)}
1847
+ }${routeMiss}${sequenceMiss}`
1848
+ }).join('\n')
1849
+ const merge = deepAt.map((at, i) => `{const at=${at}??pos
1850
+ const ex=${at}===undefined?${expected[i]}:${deepFx[i]}
1851
+ if(at>best){best=at;acc=_accSet(ex,undefined)}
1852
+ else if(at===best)acc=_accSet(ex,acc)
1853
+ }`).join('\n')
1854
+ const finish = `let best=pos,acc
1855
+ ${merge}
1856
+ ctx._fe=pos;ctx._fx=acc??${choiceFx}
1857
+ return FAIL`
1858
+ return `${head}
1859
+ const c=lead(input,pos)
1860
+ ${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
1861
+ ${deepAt.map((at, i) => `let ${at},${deepFx[i]}`).join('\n')}
1862
+ ${maskable ? `if(c<128){
1863
+ const bits=${maskName}[c<0?128:c]
1864
+ if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
1865
+ ${lazyArms(true)}
1866
+ }else{
1867
+ ${lazyArms(false)}
1868
+ }
1869
+ ` : lazyArms(false)}
1870
+ ${finish}
1871
+ }`
1872
+ }
1873
+ const needsCatch = maskable || startFailureExact
1874
+ const catchName = needsCatch ? `_cx${uid++}_` : ''
1875
+ if (needsCatch) {
1457
1876
  const catchCases = expected.map((e, i) =>
1458
1877
  `case ${i}:if(target<=${i})return acc;acc=_accSet(${e},acc)`).join('\n')
1459
1878
  choiceDefs.push(`function ${catchName}(target,prev,acc){switch(prev){\n${catchCases}\n}return acc}`)
1460
1879
  }
1461
1880
  const maskArms = maskable ? arms.map((arm, i) => {
1462
- return `if((bits&${1 << i})!==0){
1881
+ const pretest = pretests[i]
1882
+ const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
1883
+ const scalarCondition = pretest?.scalar === undefined ? ''
1884
+ : pretest.scalar.length === 1
1885
+ ? `&&${pretest.scalar[0]}(input,pos)>=0`
1886
+ : `&&(${pretest.scalar.map(recognize => `${recognize}(input,pos)>=0`).join('||')})`
1887
+ const sequenceCondition = pretest?.sequence === undefined ? ''
1888
+ : `&&(${decision}=${pretest.sequence.name}(input,pos))>=0`
1889
+ const condition = pretest?.token !== undefined
1890
+ ? `&&(${decision}=${pretest.token.name}(input,pos))>0`
1891
+ : scalarCondition || sequenceCondition
1892
+ const routeMiss = pretest?.token === undefined ? '' : `
1893
+ if(${decision}===0){
1894
+ ctx._fc=false
1895
+ ${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${i},prev,acc)
1896
+ prev=${i + 1}`}
1897
+ {const at=_pfTokEnd
1898
+ if(at>best){best=at;acc=undefined}
1899
+ if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.token.expected},acc)}
1900
+ }`
1901
+ const sequenceMiss = pretest?.sequence === undefined ? '' : `
1902
+ else if((bits&${1 << i})!==0&&${decision}<0){
1903
+ ctx._fc=false
1904
+ ${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${i},prev,acc)
1905
+ prev=${i + 1}`}
1906
+ {const at=_pfSeqEnd
1907
+ if(at>best){best=at;acc=undefined}
1908
+ if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('')}${pretest.sequence.expected.at(-1)},acc)}
1909
+ }`
1910
+ return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if((bits&${1 << i})!==0${condition}){
1463
1911
  ctx._fc=false
1464
1912
  {const v=${arm}(input,pos,ctx)
1465
1913
  if(v!==FAIL)return v}
1466
- if(best===pos)acc=${catchName}(${i},prev,acc)
1467
- prev=${i + 1}
1914
+ ${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${i},prev,acc)
1915
+ prev=${i + 1}`}
1468
1916
  {const at=ctx._fe??pos
1469
1917
  if(at>best){best=at;acc=undefined}
1470
- if(at===best)acc=_accSet(ctx._fx,acc)}
1471
- if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
1472
- ${emitRollback(p, L.buf, sinks)}
1473
- }`
1918
+ if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(ctx._fx,acc)}
1919
+ if(ctx._fc===true){${startFailureExact ? `if(best===pos){acc=${catchName}(${i},0,undefined);acc=_accSet(ctx._fx,acc)}` : ''}if(acc!==undefined)ctx._fx=acc;return FAIL}
1920
+ ${rollbackFor(i)}
1921
+ }${routeMiss}${sequenceMiss}`
1474
1922
  }).join('\n') : ''
1475
- const generalArms = arms.map((arm, i) => `if(${gateRefs[i]}===null||classHas(${gateRefs[i]},c)){
1923
+ const generalArms = arms.map((arm, i) => {
1924
+ const pretest = pretests[i]
1925
+ const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
1926
+ const scalarCondition = pretest?.scalar === undefined ? ''
1927
+ : pretest.scalar.length === 1
1928
+ ? `&&${pretest.scalar[0]}(input,pos)>=0`
1929
+ : `&&(${pretest.scalar.map(recognize => `${recognize}(input,pos)>=0`).join('||')})`
1930
+ const sequenceCondition = pretest?.sequence === undefined ? ''
1931
+ : `&&(${decision}=${pretest.sequence.name}(input,pos))>=0`
1932
+ const condition = pretest?.token !== undefined
1933
+ ? `&&(${decision}=${pretest.token.name}(input,pos))>0`
1934
+ : scalarCondition || sequenceCondition
1935
+ const miss = pretest?.sequence !== undefined
1936
+ ? `else if(${decision}<0){
1937
+ ctx._fc=false
1938
+ const at=_pfSeqEnd
1939
+ if(at>best){best=at;acc=undefined}
1940
+ if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('')}${pretest.sequence.expected.at(-1)},acc)
1941
+ }${startFailureExact ? '' : `else if(best===pos)acc=_accSet(${expected[i]},acc)`}`
1942
+ : pretest?.token === undefined
1943
+ ? startFailureExact ? '' : `else if(best===pos)acc=_accSet(${expected[i]},acc)`
1944
+ : `else if(${decision}===0){
1945
+ ctx._fc=false
1946
+ const at=_pfTokEnd
1947
+ if(at>best){best=at;acc=undefined}
1948
+ if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.token.expected},acc)
1949
+ }${startFailureExact ? '' : `else if(best===pos)acc=_accSet(${expected[i]},acc)`}`
1950
+ return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if((${gateRefs[i]}===null||classHas(${gateRefs[i]},c))${condition}){
1476
1951
  ctx._fc=false
1477
1952
  {const v=${arm}(input,pos,ctx)
1478
1953
  if(v!==FAIL)return v}
1479
1954
  {const at=ctx._fe??pos
1480
1955
  if(at>best){best=at;acc=undefined}
1481
- if(at===best)acc=_accSet(ctx._fx,acc)}
1482
- if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
1483
- ${emitRollback(p, L.buf, sinks)}
1484
- }else if(best===pos)acc=_accSet(${expected[i]},acc)`).join('\n')
1956
+ if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(ctx._fx,acc)}
1957
+ if(ctx._fc===true){${startFailureExact ? `if(best===pos){acc=${catchName}(${i},0,undefined);acc=_accSet(ctx._fx,acc)}` : ''}if(acc!==undefined)ctx._fx=acc;return FAIL}
1958
+ ${rollbackFor(i)}
1959
+ }${miss}`
1960
+ }).join('\n')
1485
1961
 
1962
+ // A zero compatible-arm mask is stronger than a speculative miss: no
1963
+ // arm can run, so the choice's encoded flat expected set is already
1964
+ // the exact result. Do not walk the arm ladder merely to rebuild it.
1486
1965
  return `${head}
1487
1966
  const c=lead(input,pos)
1488
- ${emitMark(p, L.buf, sinks)}
1967
+ ${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
1489
1968
  let acc
1490
1969
  let best=pos
1491
1970
  ${maskable ? `if(c<128){
1492
1971
  const bits=${maskName}[c<0?128:c]
1493
- let prev=0
1972
+ if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
1973
+ ${startFailureExact ? '' : 'let prev=0'}
1494
1974
  ${maskArms}
1495
- if(best===pos)acc=${catchName}(${n},prev,acc)
1496
- ctx._fe=pos;ctx._fx=acc??${choiceFx}
1975
+ ${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${n},prev,acc)`}
1976
+ ctx._fe=pos;ctx._fx=${startFailureExact ? `best===pos?${choiceFx}:acc??${choiceFx}` : `acc??${choiceFx}`}
1497
1977
  return FAIL
1498
1978
  }
1499
1979
  ` : ''}
1500
1980
  ${generalArms}
1501
- ctx._fe=pos;ctx._fx=acc??${choiceFx}
1981
+ ctx._fe=pos;ctx._fx=${startFailureExact ? `best===pos?${choiceFx}:acc??${choiceFx}` : `acc??${choiceFx}`}
1502
1982
  return FAIL
1503
1983
  }`
1504
1984
  }
@@ -1519,44 +1999,17 @@ return FAIL
1519
1999
  for (let i = 0; i < n; i++) arms.push(link(code[armBase + i]!))
1520
2000
  const bk = hoist('bk', `DSP[${di}].byKey`)
1521
2001
  const dx = hoist('dx', `DSP[${di}].expected`)
1522
- // THE MATCHER ARMS, AS SOURCE. `exec.ts`'s `linkMatcher` mints one
1523
- // closure per arm from four literals it already has in hand; the four
1524
- // are TABLE DATA, so here they are the test itself and no closure, no
1525
- // pool and no indexed call exist. Kinds are `matcherClaims`'s exactly
1526
- // — 0/1 raw prefix/suffix, 3/4 the pre-folded pair, anything else a
1527
- // regex. Public `matches()` refuses global/sticky patterns, so its
1528
- // RegExp can be compiled once into the assembly prelude. Hand-built
1529
- // low-level rows with g/y or invalid flags retain the old per-test
1530
- // construction and therefore the old `lastIndex`/throw behavior.
1531
- const claims = (m: readonly [number, string, string, number]): string => {
1532
- switch (m[0]) {
1533
- case 0: return `key.startsWith(${q(m[1])})`
1534
- case 1: return `key.endsWith(${q(m[1])})`
1535
- case 3: return `asciiFoldKey(key).startsWith(${q(m[1])})`
1536
- case 4: return `asciiFoldKey(key).endsWith(${q(m[1])})`
1537
- default: {
1538
- if (!m[2].includes('g') && !m[2].includes('y')) {
1539
- try {
1540
- // Validate without changing malformed-row error timing.
1541
- new RegExp(m[1], m[2])
1542
- return `${hoist('dm', `new RegExp(${q(m[1])},${q(m[2])})`)}.test(key)`
1543
- } catch {}
1544
- }
1545
- return `new RegExp(${q(m[1])},${q(m[2])}).test(key)`
1546
- }
1547
- }
1548
- }
1549
2002
  const chain = spec.match.length === 0
1550
2003
  ? ''
1551
- : `if(arm===undefined){\n${spec.match.map((m, i) => `${i === 0 ? '' : 'else '}if(${claims(m)})arm=${m[3]}`).join('\n')}\n}\n`
2004
+ : `if(arm===undefined){\n${spec.match.map((m, i) => `${i === 0 ? '' : 'else '}if(${dispatchClaim(m)})arm=${m[3]}`).join('\n')}\n}\n`
1552
2005
  const fold = spec.byFold.size > 0
1553
2006
  ? `if(arm===undefined)arm=${hoist('bf', `DSP[${di}].byFold`)}.get(asciiFoldKey(key))\n`
1554
2007
  : ''
1555
2008
  const m1 = tmp()
1556
2009
  const m2 = tmp()
1557
2010
  const routedCall = (target: string): string => `{const savedRouted=ctx._routed
1558
- ${emitRollback(m1, L.buf, sinks)}
1559
- ${emitMark(m2, L.buf, sinks, false)}
2011
+ ${emitRollback(m1, L.buf, L.raw, sinks)}
2012
+ ${emitMark(m2, L.buf, L.raw, sinks, false)}
1560
2013
  ctx._routed={value:key,span:{start:pos,end:selEnd}}
1561
2014
  try{v=${target}(input,pos,ctx)}finally{ctx._routed=savedRouted}
1562
2015
  break}`
@@ -1567,29 +2020,41 @@ break}`
1567
2020
  const fallbackCase = other === undefined ? '' : `default:${otherRouted
1568
2021
  ? routedCall(other)
1569
2022
  : plainCall(other)}`
2023
+ const armSelection = tokenChoiceDispatches.has(ip)
2024
+ ? `const tp=_pfTokDispatch===${ip}&&_pfTokInput===input&&_pfTokPos===pos
2025
+ let arm
2026
+ if(tp){
2027
+ arm=_pfTokArm
2028
+ _pfTokDispatch=-1
2029
+ _pfTokInput=undefined
2030
+ if(arm===${n})arm=undefined
2031
+ }else{
2032
+ arm=${bk}.get(key)
2033
+ ${fold}${chain}}`
2034
+ : `let arm=${bk}.get(key)
2035
+ ${fold}${chain}`
1570
2036
  // THE SELECTOR RUNS ONCE and the key it returns picks the arm — that is
1571
2037
  // what `dispatch()` buys over a choice of arms that each re-parse the
1572
2038
  // opener. A routed arm rewinds the selector's trivia capture and gets
1573
2039
  // the token handed to it (`OP_ROUTED`) instead of re-matching it.
1574
2040
  return `${head}
1575
- ${emitMark(m1, L.buf, sinks)}
2041
+ ${emitMark(m1, L.buf, L.raw, sinks)}
1576
2042
  const sv=${selector}(input,pos,ctx)
1577
2043
  if(sv===FAIL)return FAIL
1578
2044
  const selEnd=EC.e
1579
2045
  const key=sv
1580
- let arm=${bk}.get(key)
1581
- ${fold}${chain}
2046
+ ${armSelection}
1582
2047
  if(arm===undefined){
1583
2048
  ${other === undefined ? `ctx._fe=selEnd;ctx._fx=${dx};return FAIL` : ''}
1584
2049
  }
1585
- ${emitMark(m2, L.buf, sinks)}
2050
+ ${emitMark(m2, L.buf, L.raw, sinks)}
1586
2051
  let v
1587
2052
  switch(arm){
1588
2053
  ${armCases}
1589
2054
  ${fallbackCase}
1590
2055
  }
1591
2056
  if(v===FAIL){
1592
- ${emitRollback(m2, L.buf, sinks)}
2057
+ ${emitRollback(m2, L.buf, L.raw, sinks)}
1593
2058
  ctx._fc=true
1594
2059
  return FAIL
1595
2060
  }
@@ -1643,15 +2108,23 @@ return [key,v]
1643
2108
  // non-loop marks. Before `OP_FIELD` and `OP_EXPECT` were emittable the
1644
2109
  // answer was "never"; it is now "per table", and a grammar with no field
1645
2110
  // still pays neither the two loads nor the two stores per item.
1646
- const pfd = sinks.fd ? `${p}fd` : '0'
2111
+ const pfd = sinks.fd ? `${p}fd` : 'undefined'
1647
2112
  const per = sinks.er ? `${p}er` : 'undefined'
1648
- const rb = L.buf
1649
- ? `_rbBuf(ctx,${p}raw,${p}tl,${p}lv,${p}lg,${p}rt)${sinks.fd ? `\nif(ctx._fields!==undefined&&ctx._fields.length!==${p}fd)ctx._fields.length=${p}fd` : ''}${sinks.er ? `\nif(ctx._errors!==undefined&&ctx._errors.length!==${p}er)ctx._errors.length=${p}er` : ''}`
2113
+ const rbHelper = L.raw === RAW_OMIT ? '_rbNoRawBuf' : '_rbBuf'
2114
+ const rb = L.buf && L.raw !== 0
2115
+ ? `${rbHelper}(ctx,${p}raw,${p}tl,${p}lv,${p}lg,${p}rt)${sinks.fd ? `\nif(ctx._fields!==undefined&&ctx._fields.length!==${p}fd)ctx._fields.length=${p}fd` : ''}${sinks.er ? `\nif(ctx._errors!==undefined&&ctx._errors.length!==${p}er)ctx._errors.length=${p}er` : ''}`
2116
+ : L.buf
2117
+ ? `rollbackTriviaAt(ctx,${p}raw,${p}tl,${p}lv,${pfd},${per},${p}lg,${p}rt)`
1650
2118
  : `if(needMark)rollbackTriviaAt(ctx,${p}raw,${p}tl,${p}lv,${pfd},${per},${p}lg,${p}rt)`
1651
2119
  const markSinks = `${sinks.fd ? `\n${p}fd=ctx._fields!==undefined?ctx._fields.length:0` : ''}${sinks.er ? `\n${p}er=ctx._errors!==undefined?ctx._errors.length:0` : ''}`
2120
+ const markRaw = L.raw === RAW_OMIT
2121
+ ? `${p}raw=b.rawLen`
2122
+ : L.raw === RAW_CAPTURE
2123
+ ? `const r=b.raw;${p}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0`
2124
+ : `const r=b.raw;${p}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)`
1652
2125
  const markBody = L.buf
1653
2126
  ? `const b=ctx._cstBuf
1654
- const r=b.raw;${p}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0
2127
+ ${markRaw}
1655
2128
  const h=b.ch;${p}lv=h!==undefined?h.length:b.single!==undefined?1:0
1656
2129
  const l=b.tl;${p}tl=l!==undefined?l.length:0
1657
2130
  ${p}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
@@ -1659,7 +2132,7 @@ ${p}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${markSinks}`
1659
2132
  : `if(needMark){
1660
2133
  const b=ctx._cstBuf
1661
2134
  if(b!==undefined){
1662
- const r=b.raw;${p}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0
2135
+ const r=b.raw;${p}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)
1663
2136
  const h=b.ch;${p}lv=h!==undefined?h.length:b.single!==undefined?1:0
1664
2137
  const l=b.tl;${p}tl=l!==undefined?l.length:0
1665
2138
  }else{
@@ -1804,7 +2277,13 @@ const leaf={_tag:'leaf',value,span:{start:pos,end}}
1804
2277
  const kids=[leaf],rawKids=[leaf],span={start:pos,end}
1805
2278
  EC.e=end
1806
2279
  const nd=${build}(kids,undefined,span,rawKids,EMPTY_TL,undefined)
1807
- ${L.buf ? 'pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))' : 'if(ctx._cstBuf!==undefined||ctx._cstChildren!==undefined)pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'}
2280
+ ${L.buf && L.raw === RAW_OMIT
2281
+ ? '_pushNodeNoRawBuf(ctx,nd)'
2282
+ : L.buf && L.raw === RAW_CAPTURE
2283
+ ? 'pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'
2284
+ : staticBuild
2285
+ ? 'if(ctx._cstBuf!==undefined||ctx._cstChildren!==undefined)pushCstChild(ctx,nd,ctx._cstBuf!==undefined&&ctx._cstBuf.noRaw===true?undefined:ctx._cstBuf!==undefined||ctx._cstRawChildren!==undefined?rawEntry(nd,input,pos,end):undefined)'
2286
+ : 'if(ctx._cstBuf!==undefined||ctx._cstChildren!==undefined)pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'}
1808
2287
  EC.e=end
1809
2288
  return nd
1810
2289
  }`
@@ -1834,7 +2313,9 @@ return nd
1834
2313
  && (flags === 2 || flags === 18 || flags === 34)) {
1835
2314
  const fields = flags === 18
1836
2315
  const collapseChildren = flags === 34
1837
- const publish = `if(sBuf!==undefined){
2316
+ const publish = L.buf && L.raw === RAW_OMIT
2317
+ ? '_pushNodeNoRawBuf(ctx,nd)'
2318
+ : L.buf && L.raw === RAW_CAPTURE ? `if(sBuf!==undefined){
1838
2319
  if(sBuf.rawOnly!==true){
1839
2320
  if(sBuf.ch!==undefined)sBuf.ch.push(nd)
1840
2321
  else if(sBuf.single!==undefined){sBuf.ch=[sBuf.single,nd];sBuf.single=undefined}
@@ -1847,6 +2328,22 @@ else sBuf.rawSingle=rawNd
1847
2328
  }else if(sCh!==undefined){
1848
2329
  sCh.push(nd)
1849
2330
  if(sRaw!==undefined)sRaw.push(rawEntry(nd,input,pos,end))
2331
+ }` : `if(sBuf!==undefined){
2332
+ if(sBuf.noRaw===true)_pushNodeNoRawBuf(ctx,nd)
2333
+ else{
2334
+ if(sBuf.rawOnly!==true){
2335
+ if(sBuf.ch!==undefined)sBuf.ch.push(nd)
2336
+ else if(sBuf.single!==undefined){sBuf.ch=[sBuf.single,nd];sBuf.single=undefined}
2337
+ else sBuf.single=nd
2338
+ }
2339
+ const rawNd=rawEntry(nd,input,pos,end)
2340
+ if(sBuf.raw!==undefined)sBuf.raw.push(rawNd)
2341
+ else if(sBuf.rawSingle!==undefined){sBuf.raw=[sBuf.rawSingle,rawNd];sBuf.rawSingle=undefined}
2342
+ else sBuf.rawSingle=rawNd
2343
+ }
2344
+ }else if(sCh!==undefined){
2345
+ sCh.push(nd)
2346
+ if(sRaw!==undefined)sRaw.push(rawEntry(nd,input,pos,end))
1850
2347
  }`
1851
2348
  return `${head}
1852
2349
  const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
@@ -1888,6 +2385,7 @@ return nd
1888
2385
  ? !structural || grammarCapture || hostCapturesThisType !== false
1889
2386
  : hostCapturesThisType === true
1890
2387
  const keepChildren = !structural || cfg.hostReadsChildren !== false || collapse || unwrap
2388
+ const omitsRaw = !hostCst && build !== undefined && proj < 0 && (flags & 2) !== 0
1891
2389
  const ty = q(type)
1892
2390
  const stArg = readsState ? 'st' : '(ctx.state!==undefined?Object.assign({},ctx.state):undefined)'
1893
2391
  const hostCall = `_pfHost(${ty},hostKids,fieldMap,span,rawKids,tlog,${stArg},${tags})`
@@ -1906,7 +2404,14 @@ return nd
1906
2404
  // HOST COLLAPSE applies wherever the node's VALUE comes from the host —
1907
2405
  // any node under a CST host, not only the builder-less ones.
1908
2406
  const collapsible = keepChildren && (hostCst || (build === undefined && proj < 0))
1909
- const openCapture = keepChildren
2407
+ const openCapture = omitsRaw
2408
+ ? `const buf={noRaw:true,rawLen:0}
2409
+ ctx._cstBuf=buf
2410
+ ctx._cstChildren=undefined
2411
+ ctx._cstLeaves=undefined
2412
+ ctx._cstRawChildren=undefined
2413
+ ctx._cstTriviaLog=undefined`
2414
+ : keepChildren
1910
2415
  ? `const buf={}
1911
2416
  ctx._cstBuf=buf
1912
2417
  ctx._cstChildren=undefined
@@ -1919,7 +2424,12 @@ ctx._cstChildren=undefined
1919
2424
  ctx._cstLeaves=undefined
1920
2425
  ctx._cstRawChildren=undefined
1921
2426
  ctx._cstTriviaLog=undefined`
1922
- const finishCapture = keepChildren
2427
+ const finishCapture = omitsRaw
2428
+ ? `const kids=buf.ch??(buf.single!==undefined?[buf.single]:EMPTY_CH)
2429
+ const hostKids=kids
2430
+ const rawKids=EMPTY_CH
2431
+ const tlog=buf.tl??EMPTY_TLOG`
2432
+ : keepChildren
1923
2433
  ? `const kids=buf.ch??(buf.single!==undefined?[buf.single]:EMPTY_CH)
1924
2434
  const hostKids=kids
1925
2435
  const rawKids=buf.raw??(buf.rawSingle!==undefined?[buf.rawSingle]:EMPTY_CH)
@@ -1956,11 +2466,15 @@ const st=${readsState ? '(ctx.state!==undefined?Object.assign({},ctx.state):unde
1956
2466
  let nd
1957
2467
  ${unwrap ? 'if(kids.length===1)nd=unwrapChild(kids[0])\nelse ' : ''}${collapse ? 'if(kids.length===1)nd=kids[0]\nelse ' : ''}${collapsible ? `if(_pfHost!==undefined&&_pfHost._parsemanCstCollapse!==undefined&&kids.length===1&&rawKids.length===1&&_pfHost._parsemanCstCollapse(${ty},kids[0],kids,rawKids))nd=kids[0]
1958
2468
  else ` : ''}{${value}}
1959
- ${L.buf
2469
+ ${L.buf && L.raw === RAW_OMIT
2470
+ ? '_pushNodeNoRawBuf(ctx,nd)'
2471
+ : L.buf && L.raw === RAW_CAPTURE
1960
2472
  // The OUTER buffer, which this body saved into `sBuf` before opening its own —
1961
2473
  // so an in-node site's parent collector is present by the same fact.
1962
2474
  ? 'pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'
1963
- : 'if(sBuf!==undefined||sCh!==undefined)pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'}
2475
+ : staticBuild
2476
+ ? 'if(sBuf!==undefined||sCh!==undefined)pushCstChild(ctx,nd,sBuf!==undefined?(sBuf.noRaw===true?undefined:rawEntry(nd,input,pos,end)):sRaw!==undefined?rawEntry(nd,input,pos,end):undefined)'
2477
+ : 'if(sBuf!==undefined||sCh!==undefined)pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'}
1964
2478
  EC.e=end
1965
2479
  return nd
1966
2480
  }`
@@ -1989,7 +2503,7 @@ return nd
1989
2503
  // are `function` declarations, so a body may call one that is textually below
1990
2504
  // it, but the `const _ts<N>` each one reads must be initialised before any
1991
2505
  // parse runs, not merely before the declaration is evaluated.
1992
- const source = `${RUNTIME_PRELUDE}
2506
+ const source = `${RUNTIME_PRELUDE}${needsNoRawPrelude ? NO_RAW_RUNTIME_PRELUDE : ''}
1993
2507
  ${prelude.join('\n')}
1994
2508
  ${skipDefs.join('\n')}
1995
2509
  ${choiceDefs.join('\n')}
@@ -2004,7 +2518,13 @@ _pfHost=host
2004
2518
  function _finish(){
2005
2519
  if(_pfDepth<=0)throw new Error('parseman emitted table assembly frame underflow')
2006
2520
  _pfDepth--
2007
- if(_pfDepth===0)return
2521
+ if(_pfDepth===0){
2522
+ _pfTokInput=undefined
2523
+ _pfTokBody=-1
2524
+ _pfTokValue=undefined
2525
+ _pfTokDispatch=-1
2526
+ return
2527
+ }
2008
2528
  const prior=_pfFrames.pop()
2009
2529
  _pfScan=prior[0]
2010
2530
  _pfHost=prior[1]