parseman 0.47.1 → 0.48.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 (124) hide show
  1. package/README.md +27 -21
  2. package/dist/analysis/diagnostics.js +2863 -353
  3. package/dist/analysis/diagnostics.js.map +3 -3
  4. package/dist/cli/index.js +2854 -344
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/combinators/expect.d.ts +1 -0
  7. package/dist/combinators/expect.d.ts.map +1 -1
  8. package/dist/combinators/expected.d.ts +8 -0
  9. package/dist/combinators/expected.d.ts.map +1 -0
  10. package/dist/combinators/first-set.d.ts +8 -0
  11. package/dist/combinators/first-set.d.ts.map +1 -1
  12. package/dist/combinators/grammar.d.ts.map +1 -1
  13. package/dist/combinators/keywords.d.ts +4 -0
  14. package/dist/combinators/keywords.d.ts.map +1 -1
  15. package/dist/combinators/literal.d.ts.map +1 -1
  16. package/dist/combinators/not.d.ts.map +1 -1
  17. package/dist/combinators/peek.d.ts.map +1 -1
  18. package/dist/combinators/regex.d.ts.map +1 -1
  19. package/dist/combinators/repeat.d.ts.map +1 -1
  20. package/dist/combinators/sequence.d.ts.map +1 -1
  21. package/dist/combinators/trivia-skip.d.ts +16 -3
  22. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  23. package/dist/compiler/direct-projection.d.ts +12 -0
  24. package/dist/compiler/direct-projection.d.ts.map +1 -0
  25. package/dist/compiler/token-alphabet.d.ts +845 -5
  26. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  27. package/dist/compiler/token-capability.d.ts +3 -0
  28. package/dist/compiler/token-capability.d.ts.map +1 -0
  29. package/dist/compiler/token-capability.js +3192 -0
  30. package/dist/compiler/token-capability.js.map +6 -0
  31. package/dist/cst/trivia-charscan.d.ts +3 -0
  32. package/dist/cst/trivia-charscan.d.ts.map +1 -1
  33. package/dist/cst/trivia-css-scanner.d.ts +5 -0
  34. package/dist/cst/trivia-css-scanner.d.ts.map +1 -0
  35. package/dist/cst/trivia-kinds.d.ts.map +1 -1
  36. package/dist/index.js +2862 -339
  37. package/dist/index.js.map +3 -3
  38. package/dist/language-service/index.js +5 -1
  39. package/dist/language-service/index.js.map +1 -1
  40. package/dist/plugin/index.js +2870 -347
  41. package/dist/plugin/index.js.map +3 -3
  42. package/dist/spec/index.js +315 -46
  43. package/dist/spec/index.js.map +2 -2
  44. package/dist/table/assemble.d.ts.map +1 -1
  45. package/dist/table/child-slots.d.ts.map +1 -1
  46. package/dist/table/compile-rule-map.d.ts +3 -1
  47. package/dist/table/compile-rule-map.d.ts.map +1 -1
  48. package/dist/table/emit-assembly.d.ts +12 -13
  49. package/dist/table/emit-assembly.d.ts.map +1 -1
  50. package/dist/table/emit.d.ts.map +1 -1
  51. package/dist/table/encode.d.ts.map +1 -1
  52. package/dist/table/exec-baseline.d.ts.map +1 -1
  53. package/dist/table/exec.d.ts.map +1 -1
  54. package/dist/table/index.js +2819 -300
  55. package/dist/table/index.js.map +3 -3
  56. package/dist/table/lex-body.d.ts +9 -0
  57. package/dist/table/lex-body.d.ts.map +1 -0
  58. package/dist/table/lex-program.d.ts +25 -0
  59. package/dist/table/lex-program.d.ts.map +1 -0
  60. package/dist/table/ops.d.ts +43 -7
  61. package/dist/table/ops.d.ts.map +1 -1
  62. package/dist/table/program.d.ts +50 -2
  63. package/dist/table/program.d.ts.map +1 -1
  64. package/dist/table/scalar-terminal.d.ts +13 -0
  65. package/dist/table/scalar-terminal.d.ts.map +1 -0
  66. package/dist/table/site-labels.d.ts.map +1 -1
  67. package/dist/types.d.ts +2 -0
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/version.d.ts +1 -1
  70. package/package.json +11 -11
  71. package/src/combinators/expect.ts +1 -0
  72. package/src/combinators/expected.ts +17 -0
  73. package/src/combinators/first-set.ts +58 -5
  74. package/src/combinators/grammar.ts +8 -3
  75. package/src/combinators/keywords.ts +22 -6
  76. package/src/combinators/literal.ts +2 -1
  77. package/src/combinators/not.ts +5 -1
  78. package/src/combinators/peek.ts +5 -1
  79. package/src/combinators/regex.ts +5 -3
  80. package/src/combinators/repeat.ts +4 -10
  81. package/src/combinators/sequence.ts +17 -14
  82. package/src/combinators/trivia-skip.ts +46 -13
  83. package/src/compiler/direct-projection.ts +43 -0
  84. package/src/compiler/token-alphabet.ts +4316 -12
  85. package/src/compiler/token-capability.ts +6 -0
  86. package/src/cst/trivia-charscan.ts +34 -0
  87. package/src/cst/trivia-css-scanner.ts +142 -0
  88. package/src/cst/trivia-kinds.ts +3 -1
  89. package/src/plugin/index.ts +4 -4
  90. package/src/table/assemble.ts +1211 -221
  91. package/src/table/child-slots.ts +2 -1
  92. package/src/table/compile-rule-map.ts +21 -3
  93. package/src/table/emit-assembly.ts +328 -85
  94. package/src/table/emit.ts +16 -2
  95. package/src/table/encode.ts +242 -50
  96. package/src/table/exec-baseline.ts +3 -7
  97. package/src/table/exec.ts +121 -7
  98. package/src/table/lex-body.ts +30 -0
  99. package/src/table/lex-program.ts +422 -0
  100. package/src/table/ops.ts +45 -7
  101. package/src/table/program.ts +46 -2
  102. package/src/table/scalar-terminal.ts +51 -0
  103. package/src/table/site-labels.ts +11 -1
  104. package/src/table/stamp.ts +1 -1
  105. package/src/types.ts +1 -1
  106. package/src/version.ts +1 -1
  107. package/dist/analysis/diagnostics.cjs +0 -13824
  108. package/dist/analysis/diagnostics.cjs.map +0 -6
  109. package/dist/cli/index.cjs +0 -14925
  110. package/dist/cli/index.cjs.map +0 -6
  111. package/dist/index.cjs +0 -16391
  112. package/dist/index.cjs.map +0 -6
  113. package/dist/language-service/index.cjs +0 -1304
  114. package/dist/language-service/index.cjs.map +0 -6
  115. package/dist/oracle/index.cjs +0 -254
  116. package/dist/oracle/index.cjs.map +0 -6
  117. package/dist/plugin/index.cjs +0 -16097
  118. package/dist/plugin/index.cjs.map +0 -6
  119. package/dist/run/index.cjs +0 -577
  120. package/dist/run/index.cjs.map +0 -6
  121. package/dist/spec/index.cjs +0 -6236
  122. package/dist/spec/index.cjs.map +0 -6
  123. package/dist/table/index.cjs +0 -11463
  124. package/dist/table/index.cjs.map +0 -6
@@ -9,16 +9,11 @@
9
9
  *
10
10
  * ## Why text, and not a rearrangement of `assemble.ts`
11
11
  *
12
- * V8 attaches inline-cache feedback per FUNCTION LITERAL, not per closure. The
13
- * second closure minted from a `CreateClosure` site moves its `FeedbackCell` to
14
- * `kManyClosures`, and the vector is shared from then on. `assemble.ts` mints 18
15
- * arity-2 `SEQV` closures for `less/stylesheet` from ONE literal, so the two
16
- * child call sites inside that literal see 18 unrelated callees and go
17
- * megamorphic — a hash lookup in the megamorphic stub cache, no inlining, no
18
- * type feedback for the callee body. No rearrangement of a file that MINTS
19
- * closures can produce a distinct literal per site. Emitting one
20
- * `function _pf<N>` per site can: inside `_pf12` the call `_pf7(…)` names ONE
21
- * binding and stays monomorphic.
12
+ * Emission makes each fixed grammar edge a direct generated identifier and is
13
+ * also the form a build can serialise as an ordinary function literal for CSP.
14
+ * The closure assembler can bind the same topology directly through scalar
15
+ * captures; emission's distinct job is to print that binding, not to assert a
16
+ * V8 property that the semantic design depends on.
22
17
  *
23
18
  * ## What may be shared, and what must be emitted
24
19
  *
@@ -55,12 +50,14 @@ import {
55
50
  OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
56
51
  OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX,
57
52
  OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
53
+ OP_LEX_BODY, OP_LEX_PROGRAM,
58
54
  } from './ops.ts'
59
55
  import { validateDispatchSpec, type ResolvedClass, type ResolvedTable, type TableProgram } from './program.ts'
60
56
  import { emitShapeMatch, scanShapeFromRegex } from './scan-shapes.ts'
61
57
  import {
62
- CAP_OFF, CAP_ON, TRI_NONE, TRI_UNKNOWN, TOP, computeSiteLabels, reachableSites, type SiteLabel,
58
+ CAP_OFF, CAP_ON, TRI_NONE, TRI_UNKNOWN, computeSiteLabels, reachableSites, type SiteLabel,
63
59
  } from './site-labels.ts'
60
+ import { scalarTerminalNodeChild, scalarTerminalNotChild } from './scalar-terminal.ts'
64
61
 
65
62
  /** What the compiled factory hands back — the emitted twin of `Assembly`. */
66
63
  export type EmittedPiece = (input: string, pos: number, ctx: ParseContext) => unknown
@@ -106,6 +103,16 @@ export const EMITTED_PARAMS = [
106
103
  // span, expected set and CST embedding are produced by the SAME code in every
107
104
  // engine and cannot drift.
108
105
  'SENTS', 'matchesAt', 'recoverScan', 'orSentinel', 'captureError',
106
+ // Pure scalar terminal recognizers, indexed by the terminal's existing const
107
+ // operand. Appended for compatibility with older precompiled factories.
108
+ 'RECOG',
109
+ // Appended so precompiled factories produced by earlier runtimes keep every
110
+ // positional helper binding. Older factories ignore this trailing argument.
111
+ 'commitTriviaScan', 'scanTriviaCompact', 'LEX',
112
+ // Appended for old precompiled-factory ABI: old factories ignore it.
113
+ 'adjacencyHolds',
114
+ // Appended for selected composite lexical programs; old factories ignore it.
115
+ 'LEXPROG',
109
116
  ] as const
110
117
 
111
118
  /**
@@ -131,7 +138,7 @@ function _skipTrivia(input,cur,ctx){
131
138
  const s=_pfScan
132
139
  if(s!==null&&ctx._triviaLog===undefined&&!(ctx.captureTrivia===true&&(ctx._cstBuf!==undefined||ctx._cstTriviaLog!==undefined)))return s(input,cur)
133
140
  if(s!==null)return skipTriviaScanned(s,input,cur,ctx)
134
- if(needsDeferredTriviaCommit(ctx)){const sc=scanTrivia(input,cur,ctx);sc.commit();return sc.end}
141
+ if(needsDeferredTriviaCommit(ctx))return commitTriviaScan(scanTriviaCompact(input,cur,ctx))
135
142
  return advanceTrivia(input,cur,ctx)
136
143
  }
137
144
  function _pushLeaf(ctx,value,s,e){pushCstLeaf(ctx,{_tag:'leaf',value,span:{start:s,end:e}})}
@@ -145,6 +152,13 @@ if(b.raw!==undefined)b.raw.push(l)
145
152
  else if(b.rawSingle!==undefined){b.raw=[b.rawSingle,l];b.rawSingle=undefined}
146
153
  else b.rawSingle=l
147
154
  }
155
+ function _capturedFlatChildren(children){
156
+ if(children.length===0)return EMPTY_CH
157
+ if(children[0]!==undefined)return children
158
+ let first=1
159
+ while(first<children.length&&children[first]===undefined)first++
160
+ return first===children.length?EMPTY_CH:children.slice(first)
161
+ }
148
162
  function _accSet(ax,acc){
149
163
  if(ax===undefined||ax.length===0)return acc
150
164
  if(acc===undefined)return ax.slice()
@@ -370,7 +384,11 @@ export type PoolPlan = {
370
384
  readonly classes: readonly (readonly number[])[]
371
385
  /** Per `AFX` row: the `fx` index of each arm's expected set. */
372
386
  readonly armExpected: readonly (readonly number[])[]
373
- /** Per `MASK` row: the `CLS` index the mask is computed from. */
387
+ /**
388
+ * Per `MASK` row: a non-negative legacy `CLS` row index, or `~dispIndex` for
389
+ * a directly bound choice. The negative form names the EXISTING resolved
390
+ * dispatch row rather than serialising a second copy of its arm classes.
391
+ */
374
392
  readonly masks: readonly number[]
375
393
  }
376
394
 
@@ -408,13 +426,18 @@ function maskForClassRow(gates: readonly (ResolvedClass | null)[]): Uint32Array
408
426
  export function rebuildPools(
409
427
  cc: readonly ResolvedClass[],
410
428
  fx: readonly (readonly string[])[],
429
+ disp: ResolvedTable['disp'],
411
430
  plan: PoolPlan,
412
431
  ): { masks: Uint32Array[]; classes: (ResolvedClass | null)[][]; armExpected: (readonly string[])[][] } {
413
432
  const classes = plan.classes.map(row => row.map(i => (i < 0 ? null : cc[i] ?? null)))
414
433
  return {
415
434
  classes,
416
435
  armExpected: plan.armExpected.map(row => row.map(i => fx[i] ?? [])),
417
- masks: plan.masks.map(ci => maskForClassRow(classes[ci]!)),
436
+ masks: plan.masks.map(source => {
437
+ const row = source >= 0 ? classes[source] : disp[~source]?.armCls
438
+ if (row === undefined) throw new TypeError(`table emitter: invalid MASK plan source ${source}`)
439
+ return maskForClassRow(row)
440
+ }),
418
441
  }
419
442
  }
420
443
 
@@ -476,6 +499,17 @@ export function emitAssemblySource(
476
499
  // emitted scope supplies a context this pass cannot see.
477
500
  const roots = [...Object.values(prog.rules), ...extraIps]
478
501
  const labels = computeSiteLabels(code, roots, hostCst)
502
+ // Eligibility is pooled by the terminal's existing constant operand, matching
503
+ // the closure recognizer pool: distinct terminal rows sharing one spec share
504
+ // both the recognizer and the ordinary-terminal lowering.
505
+ const scalarSpecs = new Set<number>()
506
+ 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]!)
511
+ }
512
+ }
479
513
 
480
514
  // THE SIDE-SINK CENSUS, over the same reachable set the labels are computed
481
515
  // from. `OP_FIELD` is the only ROW that writes `_fields` and `OP_EXPECT` the
@@ -520,6 +554,7 @@ export function emitAssemblySource(
520
554
  const reached = new Set<number>()
521
555
  const prelude: string[] = []
522
556
  const skipDefs: string[] = []
557
+ const choiceDefs: string[] = []
523
558
  const skipPool = new Map<string, string>()
524
559
  const pool = new Map<string, string>()
525
560
  const masks: Uint32Array[] = []
@@ -548,6 +583,7 @@ export function emitAssemblySource(
548
583
  const kRef = (i: number): string => hoist('k', `K[${i}]`)
549
584
  const fxRef = (i: number): string => hoist('fx', `FX[${i}]`)
550
585
  const fnRef = (i: number): string => hoist('fn', `FNS[${i}]`)
586
+ const recognizerRef = (i: number): string => hoist('rec', `RECOG[${i}]`)
551
587
  /**
552
588
  * The sync sentinel for a char-class index, hoisted once per class.
553
589
  *
@@ -598,9 +634,9 @@ export function emitAssemblySource(
598
634
  // through `scanTrivia`. `needsDeferredTriviaCommit` is implied by an open
599
635
  // buffer, so an in-node site skips the call that asks.
600
636
  skipDefs.push(l.buf
601
- ? `function ${nm}(input,cur,ctx){const s=scanTrivia(input,cur,ctx);s.commit();return s.end}`
637
+ ? `function ${nm}(input,cur,ctx){return commitTriviaScan(scanTriviaCompact(input,cur,ctx))}`
602
638
  : `function ${nm}(input,cur,ctx){
603
- if(needsDeferredTriviaCommit(ctx)){const s=scanTrivia(input,cur,ctx);s.commit();return s.end}
639
+ if(needsDeferredTriviaCommit(ctx))return commitTriviaScan(scanTriviaCompact(input,cur,ctx))
604
640
  return advanceTrivia(input,cur,ctx)}`)
605
641
  return nm
606
642
  }
@@ -734,17 +770,17 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
734
770
  if (op === OP_GATE && (cfg.tolerant || cfg.probe)) return code[ip + 2]!
735
771
  if (op === OP_RULE) return code[ip + 1]!
736
772
  // A SCOPE THAT INSTALLS WHAT IS ALREADY INSTALLED. `encode.ts:520` wraps
737
- // EVERY rule of a `rules({ trivia }, …)` map in its own `OP_SCOPE`, so a
773
+ // EVERY rule of a `rules({ trivia }, …)` map in its own `OP_SCOPE_PLAIN`, so a
738
774
  // grammar with one ambient trivia re-installs the same slot at every rule
739
775
  // entry — six context stores, a scanner swap and their six restores, per
740
776
  // call, to arrive at the values already there.
741
777
  //
742
778
  // The label is what makes this decidable: `tri >= 0` can ONLY have come from
743
- // an enclosing `OP_SCOPE` carrying that slot, and that scope set
779
+ // an enclosing scope carrying that slot, and that scope set
744
780
  // `ctx.triviaKindLabels` and `_pfScan` from the same slot, so all three are
745
- // already the values this row would write. Restricted to plain `OP_SCOPE`
746
- // with no root-capture policy: `OP_SCOPE_CAP` also raises `captureTrivia`,
747
- // and the two flag bits are a real refusal and a real save/restore.
781
+ // already the values this row would write. `OP_SCOPE_PLAIN` has no root
782
+ // policy by construction; a policy-bearing `OP_SCOPE` aliases only when its
783
+ // literal policy is zero. `OP_SCOPE_CAP` also raises `captureTrivia`.
748
784
  //
749
785
  // `ki >= 0` is required rather than implied: `TRI_NONE` and `TRI_UNKNOWN` are
750
786
  // themselves negative, so comparing a negative operand against a lattice
@@ -788,6 +824,19 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
788
824
  const s = k[code[ip + 1]!] as string
789
825
  const xf = fxRef(code[ip + 2]!)
790
826
  const track = op === OP_LIT_TRACK
827
+ if (!track && scalarSpecs.has(code[ip + 1]!)) {
828
+ const recognize = recognizerRef(code[ip + 1]!)
829
+ return `${head}
830
+ const e=${recognize}(input,pos)
831
+ if(e>=0){
832
+ ${captureLeaf(q(s))}
833
+ EC.e=e
834
+ return ${q(s)}
835
+ }
836
+ ctx._fe=pos;ctx._fx=${xf}
837
+ return FAIL
838
+ }`
839
+ }
791
840
  // LENGTH IS TABLE DATA, so the compare is chosen here — the emitted twin
792
841
  // of the closure engine's length-keyed literal bodies.
793
842
  const test = s.length === 1
@@ -862,6 +911,20 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
862
911
  case OP_RX_TRACK: {
863
912
  const xf = fxRef(code[ip + 2]!)
864
913
  const track = op === OP_RX_TRACK
914
+ if (!track && scalarSpecs.has(code[ip + 1]!)) {
915
+ const recognize = recognizerRef(code[ip + 1]!)
916
+ return `${head}
917
+ const e=${recognize}(input,pos)
918
+ if(e>=0){
919
+ const v=input.slice(pos,e)
920
+ ${captureLeaf('v')}
921
+ EC.e=e
922
+ return v
923
+ }
924
+ ctx._fe=pos;ctx._fx=${xf}
925
+ return FAIL
926
+ }`
927
+ }
865
928
  // THE MATCH ARRAY IS THE COST, not the matching. `re.exec` allocates one
866
929
  // per row — 6,005 rows per `json/document` parse, 12.9% of everything
867
930
  // executed — and every field of it but `[0]` is discarded here. A shape
@@ -1109,6 +1172,75 @@ return out
1109
1172
  }`
1110
1173
  }
1111
1174
 
1175
+ case OP_LEX_BODY: {
1176
+ const recognize = hoist('lex', `LEX[${code[ip + 1]!}]`)
1177
+ const expected = fxRef(code[ip + 2]!)
1178
+ const suffixExpected = fxRef(code[ip + 3]!)
1179
+ const lineFlags = code[ip + 4]!
1180
+ const hasSuffix = (lineFlags & 4) !== 0
1181
+ return `${head}
1182
+ const r=${recognize}(input,pos)
1183
+ if(r<0){ctx._fe=pos;ctx._fx=${expected};if(ctx._probe!==undefined)failAt(ctx,${expected},pos);return FAIL}
1184
+ const sm=${hasSuffix ? 'r%2===1' : 'false'},e=(r-(sm?1:0))/2
1185
+ ${(lineFlags & 1) !== 0 ? '_trackLines(ctx,input,sm?e-1:e)' : ''}
1186
+ ${hasSuffix ? 'ctx._fc=false' : ''}
1187
+ ${hasSuffix && (lineFlags & 2) !== 0 ? 'if(sm)_trackLines(ctx,input,e)' : ''}
1188
+ ${hasSuffix ? `if(!sm){ctx._fe=e;ctx._fx=${suffixExpected};if(ctx._probe!==undefined)failAt(ctx,${suffixExpected},e)}` : ''}
1189
+ const v=input.slice(pos,e)
1190
+ if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
1191
+ EC.e=e
1192
+ return v
1193
+ }`
1194
+ }
1195
+
1196
+ case OP_LEX_PROGRAM: {
1197
+ const programId = code[ip + 1]!
1198
+ const run = hoist('lexProgram', `LEXPROG[${programId}]`)
1199
+ const scanId = t.lexPrograms[programId]!.scan
1200
+ if (scanId !== undefined) return `${head}
1201
+ const sTri=ctx.trivia,sKinds=ctx.triviaKindLabels,sScan=_pfScan
1202
+ const sBuf=ctx._cstBuf,sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
1203
+ const sOtl=ctx._triviaLog,sRtl=ctx._rootTriviaLog
1204
+ const wasCap=ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined
1205
+ _pfScan=null
1206
+ ctx.trivia=undefined
1207
+ ctx.triviaKindLabels=undefined
1208
+ ctx._cstBuf=undefined
1209
+ ctx._cstChildren=undefined
1210
+ ctx._cstLeaves=undefined
1211
+ ctx._cstRawChildren=undefined
1212
+ ctx._cstTriviaLog=undefined
1213
+ ctx._triviaLog=undefined
1214
+ ctx._rootTriviaLog=undefined
1215
+ let e
1216
+ try{e=${run}(input,pos,ctx,SCANS[${scanId}])}finally{
1217
+ _pfScan=sScan
1218
+ ctx.trivia=sTri
1219
+ ctx.triviaKindLabels=sKinds
1220
+ ctx._cstBuf=sBuf
1221
+ ctx._cstChildren=sCh
1222
+ ctx._cstLeaves=sLv
1223
+ ctx._cstRawChildren=sRaw
1224
+ ctx._cstTriviaLog=sTl
1225
+ ctx._triviaLog=sOtl
1226
+ ctx._rootTriviaLog=sRtl
1227
+ }
1228
+ if(e<0)return FAIL
1229
+ const v=input.slice(pos,e)
1230
+ if(wasCap)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
1231
+ EC.e=e
1232
+ return v
1233
+ }`
1234
+ return `${head}
1235
+ const e=${run}(input,pos,ctx)
1236
+ if(e<0)return FAIL
1237
+ const v=input.slice(pos,e)
1238
+ if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
1239
+ EC.e=e
1240
+ return v
1241
+ }`
1242
+ }
1243
+
1112
1244
  /* ── transaction ─────────────────────────────────────────────────────── */
1113
1245
 
1114
1246
  case OP_ATTEMPT: {
@@ -1126,6 +1258,18 @@ return FAIL
1126
1258
  }
1127
1259
 
1128
1260
  case OP_NOT: {
1261
+ const scalarChild = scalarTerminalNotChild(code, ip)
1262
+ if (scalarChild >= 0) {
1263
+ const recognize = recognizerRef(code[scalarChild + 1]!)
1264
+ const xf = fxRef(code[ip + 2]!)
1265
+ return `${head}
1266
+ if(${recognize}(input,pos)<0){EC.e=pos;return null}
1267
+ ctx._fe=pos
1268
+ ctx._fx=${xf}
1269
+ EC.e=pos
1270
+ return FAIL
1271
+ }`
1272
+ }
1129
1273
  const child = link(code[ip + 1]!)
1130
1274
  const xf = fxRef(code[ip + 2]!)
1131
1275
  const p = tmp()
@@ -1178,19 +1322,14 @@ return v
1178
1322
  const base = fused ? ip + 3 : ip + 2
1179
1323
  const n = code[fused ? ip + 2 : ip + 1]!
1180
1324
  const wantValues = op !== OP_SEQV
1181
- // ADJACENCY changes the PARENT's term-boundary emission: the assertion
1182
- // must be answered at the cursor, BEFORE the ambient trivia scan.
1183
- // Refused rather than lowered wrong — a piece handed the post-scan
1184
- // position finds the gap already consumed and answers "adjacent" every
1185
- // time, SILENTLY (`ops.ts:226-251`).
1186
- for (let i = 1; i < n; i++) {
1187
- if (code[code[base + i]!] === OP_ADJ) {
1188
- throw new Unemittable('a sequence carrying an adjacency assertion (OP_ADJ)')
1189
- }
1190
- }
1191
- const fn = fused ? fnRef(code[ip + 1]!) : undefined
1325
+ const reducer = fused ? code[ip + 1]! : -1
1326
+ const projection = reducer < 0 ? ~reducer : -1
1327
+ const fn = fused && projection < 0 ? fnRef(reducer) : undefined
1192
1328
  const kids: string[] = []
1193
- for (let i = 0; i < n; i++) kids.push(link(code[base + i]!))
1329
+ for (let i = 0; i < n; i++) {
1330
+ const childIp = code[base + i]!
1331
+ kids.push(code[childIp] === OP_ADJ ? '' : link(childIp))
1332
+ }
1194
1333
  /**
1195
1334
  * A RECOVERY SEQUENCE PUBLISHES `ctx._sync`, and that is the WHOLE of
1196
1335
  * "recovery config" — no grammar carries any (`assemble.ts:1446`).
@@ -1225,7 +1364,9 @@ return v
1225
1364
  parts.push(`const v0=${kids[0]}(input,pos,ctx)`, 'if(v0===FAIL)return FAIL')
1226
1365
  const close = (): string => REC ? `${parts.join('\n')}\n}finally{ctx._sync=${sy}}\n}` : `${parts.join('\n')}\n}`
1227
1366
  if (n === 1) {
1228
- parts.push(fused ? `return ${fn}([v0],{start:pos,end:EC.e})` : wantValues ? 'return [v0]' : 'return undefined')
1367
+ parts.push(fused
1368
+ ? projection === 0 ? 'return v0' : `return ${fn}([v0],{start:pos,end:EC.e})`
1369
+ : wantValues ? 'return [v0]' : 'return undefined')
1229
1370
  return close()
1230
1371
  }
1231
1372
  parts.push('let cur=EC.e')
@@ -1234,12 +1375,22 @@ return v
1234
1375
  const vn = `v${i}`
1235
1376
  parts.push(`let ${vn}`)
1236
1377
  if (REC) parts.push(pub(i))
1237
- parts.push(emitTerm(kids[i]!, vn, tmp(), L, skipFor(L)))
1378
+ const childIp = code[base + i]!
1379
+ if (code[childIp] === OP_ADJ) {
1380
+ const negated = code[childIp + 1] === 1
1381
+ const ki = code[childIp + 2]!
1382
+ const kinds = ki < 0 ? 'undefined' : `K[${ki}]`
1383
+ const expected = fxRef(code[childIp + 3]!)
1384
+ parts.push(
1385
+ `if(!adjacencyHolds(input,cur,ctx,${negated ? 'true' : 'false'},${kinds})){ctx._fe=cur;ctx._fx=${expected};return FAIL}`,
1386
+ `${vn}=null`,
1387
+ )
1388
+ } else parts.push(emitTerm(kids[i]!, vn, tmp(), L, skipFor(L)))
1238
1389
  names.push(vn)
1239
1390
  }
1240
1391
  parts.push('EC.e=cur')
1241
1392
  parts.push(fused
1242
- ? `return ${fn}([${names.join(',')}],{start:pos,end:cur})`
1393
+ ? projection >= 0 ? `return ${names[projection]}` : `return ${fn}([${names.join(',')}],{start:pos,end:cur})`
1243
1394
  : wantValues ? `return [${names.join(',')}]` : 'return undefined')
1244
1395
  return close()
1245
1396
  }
@@ -1251,11 +1402,6 @@ return v
1251
1402
  const base = ip + 4
1252
1403
  const arms: string[] = []
1253
1404
  for (let i = 0; i < n; i++) arms.push(link(code[base + i]!))
1254
- const axi = armExpected.push(
1255
- Array.from({ length: n }, (_, i) => t.fx[code[base + n + i]!] as readonly string[]),
1256
- ) - 1
1257
- armExpectedPlan.push(Array.from({ length: n }, (_, i) => code[base + n + i]!))
1258
- const afx = hoist('afx', `AFX[${axi}]`)
1259
1405
 
1260
1406
  if (table.exclusive) {
1261
1407
  // NO OPEN ARMS EXIST under `exclusive` — `resolveDispatch` clears the
@@ -1289,52 +1435,51 @@ return FAIL
1289
1435
  }`
1290
1436
  }
1291
1437
 
1292
- // THE PER-ARM GATE. `assemble.ts` precomputes a candidate bitmask so one
1293
- // `Uint32Array` load replaces `n` class tests, then walks the set bits
1294
- // through `arms[i]`. The mask survives here; the indexed CALL does not.
1295
- const ci = classes.push(Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)) - 1
1296
- // `resolveDispatch` builds `armCls[a]` as `cc[disp[di][a]]`, or null when
1297
- // that index is negative (program.ts:419-423), so the arm's CLASS INDEX
1298
- // is the dispatch row itself. Nothing is re-derived here.
1299
- classPlan.push(Array.from({ length: n }, (_, i) => prog.disp[code[ip + 1]!]![i] ?? -1))
1300
- const gates = classes[ci]!
1301
- const gRef = hoist('g', `CLS[${ci}]`)
1302
- const maskable = n <= 32
1303
- let maskName = ''
1304
- if (maskable) {
1305
- maskName = hoist('mk', `MASK[${masks.push(maskForClassRow(gates)) - 1}]`)
1306
- // The mask is a pure function of `gates`, i.e. of `CLS[ci]`, so the
1307
- // plan records the class-pool index rather than 129 words per site.
1308
- maskPlan.push(ci)
1309
- }
1310
- const p = tmp()
1311
- // THE SKIPPED ARMS' SETS STAY LAZY. `prev` and the catch-up loop are the
1312
- // closure engine's, kept verbatim: they run only on a FAILURE path, and
1313
- // an arm that MATCHES returns without paying for any of it. Unrolling
1314
- // them would put an `_accSet` — and its `slice()` — on the success path.
1315
- const maskArms = arms.map((a, i) => `
1316
- if((bits&${1 << i})!==0){
1438
+ {
1439
+ // Direct topology for every fixed arity. The emitted body names every
1440
+ // child, gate and expected set; no AFX/CLS row survives merely to feed
1441
+ // an indexed winner back into one call site.
1442
+ const di = code[ip + 1]!
1443
+ const expected = Array.from({ length: n }, (_, i) => fxRef(code[base + n + i]!))
1444
+ const gates = Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)
1445
+ const gateRefs = Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
1446
+ const maskable = n <= 32
1447
+ const maskName = maskable
1448
+ ? hoist('mk', `MASK[${masks.push(maskForClassRow(gates)) - 1}]`)
1449
+ : ''
1450
+ // `~di` points back to the authoritative dispatch row. A precompiled
1451
+ // module reconstructs only the input-indexed mask from that row; it
1452
+ // cannot carry a second mutable answer for the arms' classes.
1453
+ if (maskable) maskPlan.push(~di)
1454
+ const p = tmp()
1455
+ const catchName = maskable ? `_cx${uid++}_` : ''
1456
+ if (maskable) {
1457
+ const catchCases = expected.map((e, i) =>
1458
+ `case ${i}:if(target<=${i})return acc;acc=_accSet(${e},acc)`).join('\n')
1459
+ choiceDefs.push(`function ${catchName}(target,prev,acc){switch(prev){\n${catchCases}\n}return acc}`)
1460
+ }
1461
+ const maskArms = maskable ? arms.map((arm, i) => {
1462
+ return `if((bits&${1 << i})!==0){
1317
1463
  ctx._fc=false
1318
- {const v=${a}(input,pos,ctx)
1464
+ {const v=${arm}(input,pos,ctx)
1319
1465
  if(v!==FAIL)return v}
1320
- for(let j=prev;j<${i};j++)if((bits&(1<<j))===0)acc=_accSet(${afx}[j],acc)
1466
+ acc=${catchName}(${i},prev,acc)
1321
1467
  prev=${i + 1}
1322
1468
  acc=_accSet(ctx._fx,acc)
1323
1469
  if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
1324
1470
  ${emitRollback(p, L.buf, sinks)}
1325
- }`).join('')
1326
-
1327
- const generalArms = arms.map((a, i) => `
1328
- if(${gRef}[${i}]===null||classHas(${gRef}[${i}],c)){
1471
+ }`
1472
+ }).join('\n') : ''
1473
+ const generalArms = arms.map((arm, i) => `if(${gateRefs[i]}===null||classHas(${gateRefs[i]},c)){
1329
1474
  ctx._fc=false
1330
- {const v=${a}(input,pos,ctx)
1475
+ {const v=${arm}(input,pos,ctx)
1331
1476
  if(v!==FAIL)return v}
1332
1477
  acc=_accSet(ctx._fx,acc)
1333
1478
  if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
1334
1479
  ${emitRollback(p, L.buf, sinks)}
1335
- }else acc=_accSet(${afx}[${i}],acc)`).join('')
1480
+ }else acc=_accSet(${expected[i]},acc)`).join('\n')
1336
1481
 
1337
- return `${head}
1482
+ return `${head}
1338
1483
  const c=lead(input,pos)
1339
1484
  ${emitMark(p, L.buf, sinks)}
1340
1485
  let acc
@@ -1342,14 +1487,17 @@ ${maskable ? `if(c<128){
1342
1487
  const bits=${maskName}[c<0?128:c]
1343
1488
  let prev=0
1344
1489
  ${maskArms}
1345
- for(let j=prev;j<${n};j++)if((bits&(1<<j))===0)acc=_accSet(${afx}[j],acc)
1490
+ acc=${catchName}(${n},prev,acc)
1346
1491
  ctx._fe=pos;ctx._fx=acc??${choiceFx}
1347
1492
  return FAIL
1348
1493
  }
1349
- ` : ''}${generalArms}
1494
+ ` : ''}
1495
+ ${generalArms}
1350
1496
  ctx._fe=pos;ctx._fx=acc??${choiceFx}
1351
1497
  return FAIL
1352
1498
  }`
1499
+ }
1500
+
1353
1501
  }
1354
1502
 
1355
1503
  case OP_DISPATCH: {
@@ -1371,16 +1519,26 @@ return FAIL
1371
1519
  // are TABLE DATA, so here they are the test itself and no closure, no
1372
1520
  // pool and no indexed call exist. Kinds are `matcherClaims`'s exactly
1373
1521
  // — 0/1 raw prefix/suffix, 3/4 the pre-folded pair, anything else a
1374
- // regex. The regex is built PER TEST, deliberately: a hoisted one
1375
- // carries `lastIndex` across parses whenever the author's pattern is
1376
- // sticky or global (`exec.ts:140-144`).
1522
+ // regex. Public `matches()` refuses global/sticky patterns, so its
1523
+ // RegExp can be compiled once into the assembly prelude. Hand-built
1524
+ // low-level rows with g/y or invalid flags retain the old per-test
1525
+ // construction and therefore the old `lastIndex`/throw behavior.
1377
1526
  const claims = (m: readonly [number, string, string, number]): string => {
1378
1527
  switch (m[0]) {
1379
1528
  case 0: return `key.startsWith(${q(m[1])})`
1380
1529
  case 1: return `key.endsWith(${q(m[1])})`
1381
1530
  case 3: return `asciiFoldKey(key).startsWith(${q(m[1])})`
1382
1531
  case 4: return `asciiFoldKey(key).endsWith(${q(m[1])})`
1383
- default: return `new RegExp(${q(m[1])},${q(m[2])}).test(key)`
1532
+ default: {
1533
+ if (!m[2].includes('g') && !m[2].includes('y')) {
1534
+ try {
1535
+ // Validate without changing malformed-row error timing.
1536
+ new RegExp(m[1], m[2])
1537
+ return `${hoist('dm', `new RegExp(${q(m[1])},${q(m[2])})`)}.test(key)`
1538
+ } catch {}
1539
+ }
1540
+ return `new RegExp(${q(m[1])},${q(m[2])}).test(key)`
1541
+ }
1384
1542
  }
1385
1543
  }
1386
1544
  const chain = spec.match.length === 0
@@ -1447,6 +1605,15 @@ return [key,v]
1447
1605
  const reportItem = (flags & 4) !== 0
1448
1606
  const itemFx = reportItem ? fxRef(code[ip + 6]!) : 'EMPTY_FX'
1449
1607
  const collect = op === OP_REP
1608
+ let itemCls: string | undefined
1609
+ if (!REC) {
1610
+ const itemClassIndex = sepIp < 0 ? code[ip + 7]! : -1
1611
+ if (itemClassIndex >= 0) {
1612
+ const ci = classes.push([t.cc[itemClassIndex]!]) - 1
1613
+ classPlan.push([itemClassIndex])
1614
+ itemCls = hoist('ri', `CLS[${ci}][0]`)
1615
+ }
1616
+ }
1450
1617
  const skipBeforeFirst = sepIp < 0 && min === 0
1451
1618
  const p = tmp()
1452
1619
  // `viaRepItem` is `sep === undefined && count >= min && (count > 0 ||
@@ -1466,7 +1633,6 @@ return [key,v]
1466
1633
  const skip = skipFor(L)
1467
1634
  const knownTrivia = L.tri === TRI_NONE ? false : L.tri !== TRI_UNKNOWN ? true : undefined
1468
1635
  const hasTrivia = knownTrivia === undefined ? 'hasTrivia' : String(knownTrivia)
1469
- const needMark = L.buf ? 'true' : 'needMark'
1470
1636
  // THE `_fields` AND `_errors` MARKS ARE TAKEN ONLY WHERE THE TABLE HAS A
1471
1637
  // WRITER, which is the same emit-time census `emitMark` applies to the
1472
1638
  // non-loop marks. Before `OP_FIELD` and `OP_EXPECT` were emittable the
@@ -1552,10 +1718,10 @@ continue
1552
1718
  `
1553
1719
  return `${head}
1554
1720
  const out=${collect ? '[]' : 'undefined'}
1555
- ${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}let cur=pos
1721
+ ${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}${itemCls === undefined ? '' : `const ${p}gate=ctx._probe===undefined\n`}let cur=pos
1556
1722
  let count=0
1557
1723
  for(;;){
1558
- ${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)break\n` : ''}let ${p}raw=0,${p}tl=0,${p}lv=0,${p}lg=0,${p}rt=0${sinks.fd ? `,${p}fd=0` : ''}${sinks.er ? `,${p}er=0` : ''}
1724
+ ${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)break\n` : ''}${itemCls !== undefined && sep === undefined ? `if(count>=${min}&&${p}gate&&${hasTrivia === 'false' ? 'true' : hasTrivia === 'true' ? 'false' : '!hasTrivia'}&&!classHas(${itemCls},lead(input,cur)))break\n` : ''}let ${p}raw=0,${p}tl=0,${p}lv=0,${p}lg=0,${p}rt=0${sinks.fd ? `,${p}fd=0` : ''}${sinks.er ? `,${p}er=0` : ''}
1559
1725
  ${markBody}
1560
1726
  let itemStart=cur
1561
1727
  let sepEnd=-1
@@ -1577,7 +1743,11 @@ itemStart=${hasTrivia === 'false' ? 'EC.e' : hasTrivia === 'true' ? `${skip}(inp
1577
1743
  ${rb}
1578
1744
  ${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
1579
1745
  }
1580
- ctx._fc=false
1746
+ ${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate&&!classHas(${itemCls},lead(input,itemStart))){
1747
+ ${rb}
1748
+ ${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
1749
+ }
1750
+ `}ctx._fc=false
1581
1751
  const v=${child}(input,itemStart,ctx)
1582
1752
  if(v===FAIL){
1583
1753
  ${REC
@@ -1614,6 +1784,26 @@ return out
1614
1784
  case OP_NODE:
1615
1785
  case OP_NODE_TRACK: {
1616
1786
  const flags = code[ip + 3]!
1787
+ const scalarChild = scalarTerminalNodeChild(code, ip)
1788
+ if (scalarChild >= 0 && scalarSpecs.has(code[scalarChild + 1]!)) {
1789
+ const recognize = recognizerRef(code[scalarChild + 1]!)
1790
+ const spec = code[scalarChild] === OP_RX ? null : k[code[scalarChild + 1]!] as string
1791
+ const value = spec === null ? 'input.slice(pos,end)' : q(spec)
1792
+ const xf = fxRef(code[scalarChild + 2]!)
1793
+ const build = fnRef(code[ip + 1]!)
1794
+ return `${head}
1795
+ const end=${recognize}(input,pos)
1796
+ if(end<0){ctx._fe=pos;ctx._fx=${xf};return FAIL}
1797
+ const value=${value}
1798
+ const leaf={_tag:'leaf',value,span:{start:pos,end}}
1799
+ const kids=[leaf],rawKids=[leaf],span={start:pos,end}
1800
+ EC.e=end
1801
+ const nd=${build}(kids,undefined,span,rawKids,EMPTY_TL,undefined)
1802
+ ${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))'}
1803
+ EC.e=end
1804
+ return nd
1805
+ }`
1806
+ }
1617
1807
  const child = link(code[ip + 2]!)
1618
1808
  const proj = code[ip + 4]!
1619
1809
  const buildIdx = code[ip + 1]!
@@ -1631,6 +1821,58 @@ return out
1631
1821
  // interpreter's node case — the most-executed non-terminal in any of
1632
1822
  // these grammars. It selects the emitted shape instead.
1633
1823
  const build = buildIdx >= 0 ? fnRef(buildIdx) : undefined
1824
+ // The closure assembler's direct-builder capture tiers are table facts,
1825
+ // not closure-only semantics. Print the same body for a precompiled
1826
+ // assembly so a large composeLeaf artifact does not fall back to opening
1827
+ // the generic raw/trivia buffer that its reducer arity proved unread.
1828
+ if (!hostCst && !tracked && build !== undefined && proj < 0
1829
+ && (flags === 2 || flags === 18 || flags === 34)) {
1830
+ const fields = flags === 18
1831
+ const collapseChildren = flags === 34
1832
+ const publish = `if(sBuf!==undefined){
1833
+ if(sBuf.rawOnly!==true){
1834
+ if(sBuf.ch!==undefined)sBuf.ch.push(nd)
1835
+ else if(sBuf.single!==undefined){sBuf.ch=[sBuf.single,nd];sBuf.single=undefined}
1836
+ else sBuf.single=nd
1837
+ }
1838
+ const rawNd=rawEntry(nd,input,pos,end)
1839
+ if(sBuf.raw!==undefined)sBuf.raw.push(rawNd)
1840
+ else if(sBuf.rawSingle!==undefined){sBuf.raw=[sBuf.rawSingle,rawNd];sBuf.rawSingle=undefined}
1841
+ else sBuf.rawSingle=rawNd
1842
+ }else if(sCh!==undefined){
1843
+ sCh.push(nd)
1844
+ if(sRaw!==undefined)sRaw.push(rawEntry(nd,input,pos,end))
1845
+ }`
1846
+ return `${head}
1847
+ const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
1848
+ const sCap=ctx.captureTrivia,sBuf=ctx._cstBuf,sFields=ctx._fields
1849
+ const flat=[]
1850
+ ctx._cstBuf=undefined
1851
+ ctx._cstChildren=flat
1852
+ ctx._cstLeaves=flat
1853
+ ctx._cstRawChildren=undefined
1854
+ ctx._cstTriviaLog=undefined
1855
+ ctx.captureTrivia=false
1856
+ ctx._fields=${fields ? '[]' : 'undefined'}
1857
+ const v=${child}(input,pos,ctx)
1858
+ const captured=_capturedFlatChildren(flat)
1859
+ ${fields ? 'const fieldMap=buildFieldMap(ctx._fields)\n' : ''}ctx._fields=sFields
1860
+ ctx._cstBuf=sBuf
1861
+ ctx._cstChildren=sCh
1862
+ ctx._cstLeaves=sLv
1863
+ ctx._cstRawChildren=sRaw
1864
+ ctx._cstTriviaLog=sTl
1865
+ ctx.captureTrivia=sCap
1866
+ if(v===FAIL)return FAIL
1867
+ const end=EC.e
1868
+ ${collapseChildren
1869
+ ? `const nd=captured.length===1?captured[0]:${build}(captured,undefined,{start:pos,end},EMPTY_CH,EMPTY_TL,undefined)`
1870
+ : `const nd=${build}(captured,${fields ? 'fieldMap' : 'undefined'},{start:pos,end},EMPTY_CH,EMPTY_TL,undefined)`}
1871
+ ${publish}
1872
+ EC.e=end
1873
+ return nd
1874
+ }`
1875
+ }
1634
1876
  const structural = build === undefined && proj < 0
1635
1877
  const grammarCapture = (flags & 1) !== 0 || trailingTrivia
1636
1878
  const hostCapturesThisType = structural && cfg.hostCaptureTrivia !== undefined
@@ -1745,6 +1987,7 @@ return nd
1745
1987
  const source = `${RUNTIME_PRELUDE}
1746
1988
  ${prelude.join('\n')}
1747
1989
  ${skipDefs.join('\n')}
1990
+ ${choiceDefs.join('\n')}
1748
1991
  ${bodies.join('\n')}
1749
1992
  function _begin(ctx){
1750
1993
  const host=ctx.build