parseman 0.59.2 → 0.60.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 (118) hide show
  1. package/dist/analysis/commitment.d.ts +1 -1
  2. package/dist/analysis/diagnostics.js +877 -191
  3. package/dist/analysis/diagnostics.js.map +3 -3
  4. package/dist/cli/index.js +877 -191
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/combinators/completions.d.ts +3 -3
  7. package/dist/combinators/completions.d.ts.map +1 -1
  8. package/dist/combinators/dispatch.d.ts +13 -1
  9. package/dist/combinators/dispatch.d.ts.map +1 -1
  10. package/dist/combinators/first-set.d.ts +35 -2
  11. package/dist/combinators/first-set.d.ts.map +1 -1
  12. package/dist/combinators/gate.d.ts +1 -1
  13. package/dist/combinators/regex.d.ts.map +1 -1
  14. package/dist/combinators/trivia-boundary.d.ts +11 -0
  15. package/dist/combinators/trivia-boundary.d.ts.map +1 -1
  16. package/dist/combinators/trivia-skip.d.ts +1 -1
  17. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  18. package/dist/compiler/compile-linkable-table.d.ts +2 -3
  19. package/dist/compiler/compile-linkable-table.d.ts.map +1 -1
  20. package/dist/compiler/linker.d.ts.map +1 -1
  21. package/dist/compiler/token-alphabet.d.ts +2 -0
  22. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  23. package/dist/compiler/token-capability.js +65 -53
  24. package/dist/compiler/token-capability.js.map +2 -2
  25. package/dist/cst/host-mode.d.ts +1 -1
  26. package/dist/cst/root-trivia-scope.d.ts +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +897 -200
  30. package/dist/index.js.map +3 -3
  31. package/dist/language-service/index.d.ts +1 -1
  32. package/dist/language-service/index.js.map +1 -1
  33. package/dist/plugin/entry-supercompiler.d.ts.map +1 -1
  34. package/dist/plugin/evaluator.d.ts.map +1 -1
  35. package/dist/plugin/index.d.ts +3 -7
  36. package/dist/plugin/index.d.ts.map +1 -1
  37. package/dist/plugin/index.js +904 -202
  38. package/dist/plugin/index.js.map +3 -3
  39. package/dist/regex/first-set.d.ts +19 -0
  40. package/dist/regex/first-set.d.ts.map +1 -1
  41. package/dist/spec/index.js.map +1 -1
  42. package/dist/table/assemble.d.ts +16 -3
  43. package/dist/table/assemble.d.ts.map +1 -1
  44. package/dist/table/child-slots.d.ts.map +1 -1
  45. package/dist/table/compile-rule-map.d.ts +3 -2
  46. package/dist/table/compile-rule-map.d.ts.map +1 -1
  47. package/dist/table/compile.d.ts +2 -2
  48. package/dist/table/emit-assembly.d.ts +8 -2
  49. package/dist/table/emit-assembly.d.ts.map +1 -1
  50. package/dist/table/emit.d.ts +8 -8
  51. package/dist/table/emit.d.ts.map +1 -1
  52. package/dist/table/encode.d.ts +6 -7
  53. package/dist/table/encode.d.ts.map +1 -1
  54. package/dist/table/exec.d.ts.map +1 -1
  55. package/dist/table/factor-choice.d.ts +27 -0
  56. package/dist/table/factor-choice.d.ts.map +1 -1
  57. package/dist/table/gap-gate.d.ts +11 -0
  58. package/dist/table/gap-gate.d.ts.map +1 -0
  59. package/dist/table/index.js +927 -202
  60. package/dist/table/index.js.map +3 -3
  61. package/dist/table/lex-program.d.ts.map +1 -1
  62. package/dist/table/ops.d.ts +48 -20
  63. package/dist/table/ops.d.ts.map +1 -1
  64. package/dist/table/program.d.ts +13 -8
  65. package/dist/table/program.d.ts.map +1 -1
  66. package/dist/types.d.ts +17 -5
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/version.d.ts +6 -6
  69. package/package.json +1 -1
  70. package/src/analysis/commitment.ts +1 -1
  71. package/src/analysis/duplication.ts +1 -1
  72. package/src/analysis/terminal.ts +1 -1
  73. package/src/combinators/attempt.ts +1 -1
  74. package/src/combinators/choice.ts +1 -1
  75. package/src/combinators/completions.ts +3 -3
  76. package/src/combinators/dispatch.ts +30 -6
  77. package/src/combinators/first-set.ts +55 -12
  78. package/src/combinators/gate.ts +1 -1
  79. package/src/combinators/node.ts +1 -1
  80. package/src/combinators/regex.ts +9 -4
  81. package/src/combinators/repeat.ts +6 -6
  82. package/src/combinators/sequence.ts +1 -1
  83. package/src/combinators/trivia-boundary.ts +18 -0
  84. package/src/combinators/trivia-skip.ts +1 -1
  85. package/src/compiler/compile-linkable-table.ts +2 -3
  86. package/src/compiler/degradation.ts +2 -2
  87. package/src/compiler/ir-serialize.ts +1 -1
  88. package/src/compiler/linker.ts +5 -3
  89. package/src/compiler/token-alphabet.ts +3 -1
  90. package/src/coverage.ts +1 -1
  91. package/src/cst/capture-buffer.ts +2 -2
  92. package/src/cst/host-mode.ts +1 -1
  93. package/src/cst/root-trivia-scope.ts +1 -1
  94. package/src/index.ts +1 -1
  95. package/src/language-service/index.ts +1 -1
  96. package/src/plugin/entry-supercompiler.ts +2 -1
  97. package/src/plugin/evaluator.ts +8 -3
  98. package/src/plugin/index.ts +11 -16
  99. package/src/regex/first-set.ts +37 -3
  100. package/src/table/assemble.ts +180 -35
  101. package/src/table/child-slots.ts +8 -3
  102. package/src/table/compile-rule-map.ts +10 -6
  103. package/src/table/compile.ts +3 -3
  104. package/src/table/emit-assembly.ts +186 -35
  105. package/src/table/emit.ts +19 -18
  106. package/src/table/encode.ts +482 -88
  107. package/src/table/exec.ts +42 -18
  108. package/src/table/factor-choice.ts +62 -0
  109. package/src/table/fail-clean.ts +2 -2
  110. package/src/table/gap-gate.ts +55 -0
  111. package/src/table/hold-gap.ts +2 -2
  112. package/src/table/lex-program.ts +15 -6
  113. package/src/table/ops.ts +51 -20
  114. package/src/table/program.ts +14 -9
  115. package/src/table/select-hot.ts +7 -2
  116. package/src/table/stamp.ts +1 -1
  117. package/src/types.ts +13 -6
  118. package/src/version.ts +6 -6
@@ -47,11 +47,11 @@
47
47
  import type { ParseContext } from '../types.ts'
48
48
  import { regexCanMatchEmpty } from '../regex/first-set.ts'
49
49
  import {
50
- OP_ADJ, OP_ARMGATE, OP_RGATE, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
50
+ OP_ADJ, OP_ARMGATE, OP_RGATE, OP_RGATE2, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
51
51
  OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
52
52
  OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX,
53
53
  OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
54
- OP_LEX_BODY, OP_LEX_PROGRAM,
54
+ OP_LEX_BODY, OP_LEX_PROGRAM, OP_GAPGATE, OP_STRICT,
55
55
  } from './ops.ts'
56
56
  import {
57
57
  choiceCannotCommit, choiceRollbackMask, failureRollbackClean, validateDispatchSpec,
@@ -67,10 +67,16 @@ import {
67
67
  scalarTerminalNodeChild, scalarTerminalNotChild, scalarTerminalPeekChild, type LeadingScalarSequence,
68
68
  } from './scalar-terminal.ts'
69
69
  import { childSlots } from './child-slots.ts'
70
- import { holdsGap } from './hold-gap.ts'
70
+ import { holdsGap, rowNullable } from './hold-gap.ts'
71
+ import { gapRows } from './gap-gate.ts'
71
72
  import { fieldSpec, leadingTriviaFieldAt } from './field-spec.ts'
72
73
  import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA } from '../combinators/field-trivia.ts'
73
74
 
75
+ /** Does a gap's regex lower to an inline scan (`scanShapeFromRegex`)? */
76
+ function gapRegexLowers(re: unknown): boolean {
77
+ return re instanceof RegExp && scanShapeFromRegex(re.source, re.flags) !== null
78
+ }
79
+
74
80
  /** What the compiled factory hands back — the emitted twin of `Assembly`. */
75
81
  export type EmittedPiece = (input: string, pos: number, ctx: ParseContext) => unknown
76
82
  export type EmittedAssembly = {
@@ -109,12 +115,12 @@ export class Unemittable extends Error {
109
115
  * runs and is wrong, which no type in this file would catch.
110
116
  */
111
117
  export const EMITTED_PARAMS = [
112
- 'EC', 'FAIL', 'K', 'FX', 'FNS', 'MASK', 'CLS', 'AFX', 'TRIVIA', 'TRIVIALABELS', 'TRIVIASCAN',
118
+ 'EC', 'FAIL', 'K', 'FX', 'FNS', 'MASK', 'CLS', 'AFX', 'TRIVIA', 'TRIVIASCAN',
113
119
  'SCANS', 'DISP', 'DSP', 'EMPTY_FX', 'EMPTY_CH', 'EMPTY_TLOG', 'EMPTY_TL',
114
120
  'cstCaptureActive', 'pushCstLeaf', 'pushCstChild', 'rollbackTriviaAt', 'rollbackScannedTriviaAt', 'failAt',
115
121
  'classHas', 'consumeTrivia', 'buildFieldMap', 'projectChild', 'unwrapChild',
116
122
  'demoteCapturedToRaw', 'cstLeavesLen', 'skipTriviaScanned', 'needsDeferredTriviaCommit',
117
- 'scanTrivia', 'advanceTrivia', 'refuseUnclassifiedRootScope', 'spanLines', 'rawEntry', 'lead',
123
+ 'advanceTrivia', 'refuseUnclassifiedRootScope', 'spanLines', 'rawEntry', 'lead',
118
124
  'asciiFoldKey', 'ROUTED_FX',
119
125
  // RECOVERY, bound rather than reimplemented. `SENTS` is the sync sentinel per
120
126
  // char-class index — the emitted twin of `assemble.ts`'s `sentinelFor` memo,
@@ -124,27 +130,24 @@ export const EMITTED_PARAMS = [
124
130
  // engine and cannot drift.
125
131
  'SENTS', 'matchesAt', 'recoverScan', 'orSentinel', 'captureError',
126
132
  // Pure scalar terminal recognizers, indexed by the terminal's existing const
127
- // operand. Appended for compatibility with older precompiled factories.
128
- 'RECOG',
129
- // Appended so precompiled factories produced by earlier runtimes keep every
130
- // positional helper binding. Older factories ignore this trailing argument.
131
- 'commitTriviaScan', 'scanTriviaCompact', 'LEX',
132
- // Appended for old precompiled-factory ABI: old factories ignore it.
133
- 'adjacencyHolds',
134
- // Appended for selected composite lexical programs; old factories ignore it.
133
+ // operand; selected lexical bodies; the adjacency test.
134
+ 'RECOG', 'LEX', 'adjacencyHolds',
135
+ // Selected composite lexical programs.
135
136
  'LEXPROG',
136
- // Appended for trivia-attributing fields (`field(…, { leadingTrivia })`);
137
- // old factories ignore them.
137
+ // Trivia-attributing fields (`field(…, { leadingTrivia })`).
138
138
  'LEAD', 'triviaFieldCapture',
139
- // Appended for the deferred-commit trivia skip (`trivia-gap.ts`); old
140
- // factories ignore it.
139
+ // The deferred-commit trivia skip (`trivia-gap.ts`).
141
140
  'skipDeferredTrivia',
142
- // Appended for the pass-through collapse (`run-support.ts`); old factories
143
- // ignore it.
141
+ // The pass-through collapse (`run-support.ts`).
144
142
  'collapseEmpty',
145
- // Appended for constructor-built tree objects (`cst/plain.d.ts`): a `new` call has
146
- // no allocation site, so V8 cannot pretenure them. Old factories ignore them.
143
+ // Constructor-built tree objects (`cst/plain.d.ts`): a `new` call has no
144
+ // allocation site, so V8 cannot pretenure them.
147
145
  '_SP', '_LF', '_ND', '_FC', '_BUF',
146
+ // The gap gate's slot (`gap-gate.ts`).
147
+ 'GAP',
148
+ // Artifacts are version-locked (docs/design/artifact-format.md): a factory is
149
+ // only ever called by the runtime that emitted it, so a name nothing emitted
150
+ // reads is removed rather than kept for an older factory's positions.
148
151
  ] as const
149
152
 
150
153
  /**
@@ -605,6 +608,12 @@ export function emitAssemblySource(
605
608
  * then emitted with them.
606
609
  */
607
610
  scanSinkNeutral = false,
611
+ /**
612
+ * Lower every gap gate (`gap-gate.ts`) as the item it wraps. A selection is
613
+ * ranked on this lowering, so adding the gates moves no site's place in the
614
+ * order; the fit is judged by the source that ships, gates included.
615
+ */
616
+ gapNeutral = false,
608
617
  ): EmitResult {
609
618
  const { code, k, fx, disp, dsp, triviaLabelled } = t
610
619
  const swapLegal = !cfg.trackLines
@@ -643,6 +652,10 @@ export function emitAssemblySource(
643
652
  // emitted scope supplies a context this pass cannot see.
644
653
  const roots = [...Object.values(prog.rules), ...extraIps]
645
654
  const reachable = [...reachableSites(code, roots)]
655
+ // A gap whose regex lowers to an inline scan is scanned again by its row, for
656
+ // less than reading the slot costs; only a gap matched by its RegExp takes the
657
+ // slot (`gapRegexLowers`).
658
+ const GAP_ROWS = gapNeutral ? new Set<number>() : new Set([...gapRows(code, roots)].filter(row => !gapRegexLowers(k[code[row + 1]!])))
646
659
  const labels = computeSiteLabels(code, roots, hostCst)
647
660
  // Eligibility is pooled by the terminal's existing constant operand, matching
648
661
  // the closure recognizer pool: distinct terminal rows sharing one spec share
@@ -807,7 +820,7 @@ export function emitAssemblySource(
807
820
  const armExpectedPlan: number[][] = []
808
821
  const maskPlan: number[] = []
809
822
 
810
- /** `codegen.ts`'s hoisted pools, same spelling: `_k<N>` / `_fx<N>` / `_fn<N>`. */
823
+ /** Hoisted pools, in the source lowering's spelling: `_k<N>` / `_fx<N>` / `_fn<N>`. */
811
824
  function hoist(kind: string, expr: string): string {
812
825
  const hit = pool.get(expr)
813
826
  if (hit !== undefined) return hit
@@ -1172,7 +1185,8 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
1172
1185
  // `OP_GATE` under a probe or a tolerant recovery is a no-op that forwards to
1173
1186
  // its child, exactly as `assemble.ts:952` resolves it.
1174
1187
  if (op === OP_GATE && (cfg.tolerant || cfg.probe)) return code[ip + 2]!
1175
- if (op === OP_RULE) return code[ip + 1]!
1188
+ if (op === OP_RULE || op === OP_GAPGATE) return code[ip + 1]!
1189
+ if (op === OP_STRICT) return code[cfg.tolerant || cfg.probe ? ip + 2 : ip + 1]!
1176
1190
  // A SCOPE THAT INSTALLS WHAT IS ALREADY INSTALLED. `encode.ts:520` wraps
1177
1191
  // EVERY rule of a `rules({ trivia }, …)` map in its own `OP_SCOPE_PLAIN`, so a
1178
1192
  // grammar with one ambient trivia re-installs the same slot at every rule
@@ -1191,6 +1205,7 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
1191
1205
  function lower(ip: number, fname: string): string {
1192
1206
  const op = code[ip]
1193
1207
  const head = `function ${fname}(input,pos,ctx){`
1208
+ const outerHead = head
1194
1209
  const L = labels.at(ip)
1195
1210
  const sinks = sinksAt(ip)
1196
1211
  /**
@@ -1310,6 +1325,26 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
1310
1325
  case OP_RX_TRACK: {
1311
1326
  const xf = fxRef(code[ip + 2]!)
1312
1327
  const track = op === OP_RX_TRACK
1328
+ // A gap row takes the end a repeat already scanned (`gap-gate.ts`), and
1329
+ // otherwise matches its sticky regex: one compact body, no second copy of
1330
+ // the success path.
1331
+ if (GAP_ROWS.has(ip)) {
1332
+ const re = hoist('gx', `K[${code[ip + 1]!}]`)
1333
+ return `${outerHead}
1334
+ let e
1335
+ if(GAP.row===${ip}&&GAP.at===pos&&GAP.input===input)e=GAP.end
1336
+ else{${re}.lastIndex=pos;e=${re}.test(input)?${re}.lastIndex:-1}
1337
+ if(e>=0){
1338
+ const v=input.slice(pos,e)
1339
+ ${captureLeaf('v')}
1340
+ ${track ? '_trackLines(ctx,input,e)\n' : ''}EC.e=e
1341
+ return v
1342
+ }
1343
+ ctx._fe=pos;ctx._fx=${xf}
1344
+ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
1345
+ }`
1346
+ }
1347
+ const head = outerHead
1313
1348
  if (!track && scalarSpecs.has(code[ip + 1]!)) {
1314
1349
  const recognize = recognizerRef(code[ip + 1]!)
1315
1350
  return `${head}
@@ -1401,6 +1436,31 @@ return ${child}(input,pos,ctx)
1401
1436
  }`
1402
1437
  }
1403
1438
 
1439
+ case OP_RGATE2: {
1440
+ const child = link(code[ip + 2]!)
1441
+ if (cfg.tolerant || cfg.probe) return `${head}
1442
+ return ${child}(input,pos,ctx)
1443
+ }`
1444
+ // ASCII-only classes (the usual case: `:` then whitespace) are read as
1445
+ // code units against their ASCII tables, with no call.
1446
+ const idxs = [code[ip + 1]!, code[ip + 4]!, code[ip + 5]!]
1447
+ const ascii = idxs.every(i => isAsciiOnlyClass(t.cc[i]!))
1448
+ const ref = (idx: number, tag: string): string => {
1449
+ const ci = classes.push([t.cc[idx]!]) - 1
1450
+ classPlan.push([idx])
1451
+ return hoist(tag, `CLS[${ci}][0]`)
1452
+ }
1453
+ const [cls, first, second] = [ref(idxs[0]!, 'cl'), ref(idxs[1]!, 'c1'), ref(idxs[2]!, 'c2')]
1454
+ const has = (c: string, at: string): string => ascii ? `${c}.ascii[${at}]===1` : `classHas(${c},${at})`
1455
+ const xf = fxRef(code[ip + 3]!)
1456
+ return `${head}
1457
+ const it=ctx._routed
1458
+ if(it!==undefined&&it.span.start===pos){const e=it.span.end;const c=${ascii ? 'input.charCodeAt(e)' : 'lead(input,e)'}
1459
+ if(!(${has(cls, 'c')})||(${has(first, 'c')}&&!(${has(second, ascii ? 'input.charCodeAt(e+1)' : 'lead(input,e+1)')}))){ctx._fe=e;ctx._fx=${xf};return FAIL}}
1460
+ return ${child}(input,pos,ctx)
1461
+ }`
1462
+ }
1463
+
1404
1464
  case OP_ARMGATE: {
1405
1465
  // `choice({ gate, combinator })`, as `assemble.ts` lowers it. A blocked
1406
1466
  // arm is SKIPPED, not failed (choice.ts `continue`): it clears the cut,
@@ -1489,8 +1549,8 @@ return v
1489
1549
  const xf = fxRef(code[ip + 2]!)
1490
1550
  // TWO ARMS, mirroring `assemble.ts:1050`. A TOLERANT `expect()` EMBEDS
1491
1551
  // ITS ERROR IN THE TREE and not only in the flat `_errors` side-channel
1492
- // (`combinators/expect.ts:150`, codegen's `_ctx._rec.capture` at
1493
- // codegen.ts:4470) — so a tree walk finds every diagnostic and the node
1552
+ // (`combinators/expect.ts:150`, the source lowering's
1553
+ // `_ctx._rec.capture`) — so a tree walk finds every diagnostic and the node
1494
1554
  // survives incremental subtree reuse. `captureError` is the shared
1495
1555
  // function, called; the embedding is not restated here.
1496
1556
  //
@@ -1914,7 +1974,30 @@ if(ctx._fc===true)return FAIL
1914
1974
  const af=ctx._fx
1915
1975
  if(af!==undefined&&af.length>0){ctx._fe=pos;return FAIL}
1916
1976
  }
1917
- ctx._fe=pos;ctx._fx=${choiceFx}
1977
+ ${cfg.probe ? `if(arm<0){${arms.map(a => `${a}(input,pos,ctx)`).join(';')}}
1978
+ ` : ''}ctx._fe=pos;ctx._fx=${choiceFx}
1979
+ return FAIL
1980
+ }`
1981
+ }
1982
+
1983
+ if (cfg.probe) {
1984
+ // A probe assembly enters every arm in source order, as the
1985
+ // interpreter's `firstMatch` does, so each opener reaches `failAt`.
1986
+ const p = tmp()
1987
+ return `${head}
1988
+ ${emitMark(p, L.buf, L.raw, sinks)}
1989
+ let acc
1990
+ let best=pos
1991
+ ${arms.map(arm => `{ctx._fc=false
1992
+ const v=${arm}(input,pos,ctx)
1993
+ if(v!==FAIL)return v
1994
+ const at=ctx._fe??pos
1995
+ if(at>best){best=at;acc=undefined}
1996
+ if(at===best)acc=_accSet(ctx._fx,acc)
1997
+ if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
1998
+ ${emitRollback(p, L.buf, L.raw, sinks)}
1999
+ }`).join('\n')}
2000
+ ctx._fe=pos;ctx._fx=acc??${choiceFx}
1918
2001
  return FAIL
1919
2002
  }`
1920
2003
  }
@@ -2194,7 +2277,8 @@ return FAIL
2194
2277
  const selector = link(code[ip + 1]!)
2195
2278
  const otherIp = code[ip + 3]!
2196
2279
  const other = otherIp >= 0 ? link(otherIp) : undefined
2197
- const otherRouted = code[ip + 4]! === 1
2280
+ const otherRouted = (code[ip + 4]! & 1) === 1
2281
+ const otherCommits = (code[ip + 4]! & 2) === 0
2198
2282
  const n = code[ip + 5]!
2199
2283
  validateDispatchSpec(spec, n, code[ip + 4]!)
2200
2284
  const armBase = ip + 6
@@ -2258,7 +2342,7 @@ ${fallbackCase}
2258
2342
  }
2259
2343
  if(v===FAIL){
2260
2344
  ${emitRollback(m2, L.buf, L.raw, sinks)}
2261
- ctx._fc=true
2345
+ ${other !== undefined && !otherCommits ? 'if(arm!==undefined)' : ''}ctx._fc=true
2262
2346
  return FAIL
2263
2347
  }
2264
2348
  return [key,v]
@@ -2293,6 +2377,69 @@ return [key,v]
2293
2377
  const itemMiss = (at: string): string => itemAscii
2294
2378
  ? `${itemCls}.ascii[input.charCodeAt(${at})]!==1`
2295
2379
  : `!classHas(${itemCls},lead(input,${at}))`
2380
+ // The separator's own first set (ip+7, `assemble.ts` `sepClass`).
2381
+ let sepCls: string | undefined
2382
+ let sepAscii = false
2383
+ if (!REC && sepIp >= 0 && (code[ip + 5]! & 8) !== 0 && !leadingTriviaFieldAt(code, k, sepIp) && !rowNullable(code, k, sepIp, nullable)) {
2384
+ const resolved = t.cc[code[ip + 8]!]!
2385
+ const ci = classes.push([resolved]) - 1
2386
+ classPlan.push([code[ip + 8]!])
2387
+ sepCls = hoist('rs', `CLS[${ci}][0]`)
2388
+ sepAscii = isAsciiOnlyClass(resolved)
2389
+ }
2390
+ // THE GAP GATE (`gap-gate.ts`, `assemble.ts`): the item's leading gap is
2391
+ // read once here and handed to its regex row through the slot.
2392
+ const gapIp = !gapNeutral && sepIp < 0 && min === 0 && !REC && code[code[ip + 1]!] === OP_GAPGATE ? code[ip + 1]! : -1
2393
+ let gapCode = (_rb: string): string => ''
2394
+ if (gapIp >= 0) {
2395
+ const row = code[gapIp + 2]!
2396
+ const rx = k[code[row + 1]!]
2397
+ const shape = gapRegexLowers(rx) ? scanShapeFromRegex((rx as RegExp).source, (rx as RegExp).flags) : null
2398
+ const cls = (idx: number): string => {
2399
+ const resolved = t.cc[idx]!
2400
+ const ci = classes.push([resolved]) - 1
2401
+ classPlan.push([idx])
2402
+ const ref = hoist('gc', `CLS[${ci}][0]`)
2403
+ return isAsciiOnlyClass(resolved) ? `${ref}.ascii[input.charCodeAt(${'@'})]!==1` : `!classHas(${ref},lead(input,${'@'}))`
2404
+ }
2405
+ const follow = cls(code[gapIp + 3]!).replaceAll('@', 'gq')
2406
+ const other = code[gapIp + 4]! >= 0 ? cls(code[gapIp + 4]!).replaceAll('@', 'itemStart') : 'true'
2407
+ if (shape !== null) {
2408
+ // Scanned inline, as the row scans it: no slot to fill or read.
2409
+ let n = 0
2410
+ const q = tmp()
2411
+ const m = emitShapeMatch(shape, 'itemStart', (prefix = '_v') => `${q}${prefix}${n++}`, '')
2412
+ gapCode = (rbCode: string): string => `let gapped=false
2413
+ if(ctx._probe===undefined){
2414
+ ${m.setup.join('\n')}
2415
+ const gq=${m.ok}?${m.end}:-1
2416
+ if((gq<0||${follow})&&${other}){
2417
+ ${rbCode}
2418
+ break
2419
+ }
2420
+ }
2421
+ `
2422
+ } else {
2423
+ const re = hoist('gr', `K[${code[row + 1]!}]`)
2424
+ gapCode = (rbCode: string): string => `let gapped=false
2425
+ if(ctx._probe===undefined){
2426
+ ${re}.lastIndex=itemStart
2427
+ const gq=${re}.test(input)?${re}.lastIndex:-1
2428
+ if((gq<0||${follow})&&${other}){
2429
+ ${rbCode}
2430
+ break
2431
+ }
2432
+ if(gq>=0){
2433
+ GAP.row=${row};GAP.at=itemStart;GAP.end=gq;GAP.input=input
2434
+ gapped=true
2435
+ }
2436
+ }
2437
+ `
2438
+ }
2439
+ }
2440
+ const sepMiss = (at: string): string => sepAscii
2441
+ ? `${sepCls}.ascii[input.charCodeAt(${at})]!==1`
2442
+ : `!classHas(${sepCls},lead(input,${at}))`
2296
2443
  const skipBeforeFirst = sepIp < 0 && min === 0
2297
2444
  const p = tmp()
2298
2445
  // `viaRepItem` is `sep === undefined && count >= min && (count > 0 ||
@@ -2387,7 +2534,7 @@ ${p}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${markSinks}
2387
2534
  // is taken unconditionally by the closure engine (`assemble.ts:2067`), so
2388
2535
  // it is resolved separately from `itemFx` — reusing that one would hand
2389
2536
  // `EMPTY_FX` to every recovered error in a grammar that does not report.
2390
- const recFx = REC ? fxRef(code[ip + 6]!) : ''
2537
+ const recFx = !REC ? '' : (flags & 16) !== 0 ? '[]' : fxRef(code[ip + 6]!)
2391
2538
  const sepSent = REC ? sentRef(code[ip + 7]!) : 'undefined'
2392
2539
  const recSent = sep === undefined
2393
2540
  ? my
@@ -2405,7 +2552,7 @@ continue
2405
2552
  `
2406
2553
  return `${head}
2407
2554
  const out=${collect ? '[]' : 'undefined'}
2408
- ${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
2555
+ ${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}${itemCls === undefined && sepCls === undefined ? '' : `const ${p}gate=ctx._probe===undefined\n`}let cur=pos
2409
2556
  let count=0
2410
2557
  for(;;){
2411
2558
  ${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'}&&${itemMiss('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` : ''}
@@ -2415,7 +2562,11 @@ let sepEnd=-1
2415
2562
  ${sep !== undefined ? `if(count>0){
2416
2563
  const lb=cstLeavesLen(ctx)
2417
2564
  let sp=cur
2418
- ${hasTrivia === 'false' ? '' : hasTrivia === 'true' ? `sp=${skip}(input,sp,ctx)\n` : `if(hasTrivia)sp=${skip}(input,sp,ctx)\n`}ctx._fc=false
2565
+ ${hasTrivia === 'false' ? '' : hasTrivia === 'true' ? `sp=${skip}(input,sp,ctx)\n` : `if(hasTrivia)sp=${skip}(input,sp,ctx)\n`}${sepCls === undefined ? '' : `if(count>=${min}&&${p}gate&&${sepMiss('sp')}){
2566
+ ${rb}
2567
+ break
2568
+ }
2569
+ `}ctx._fc=false
2419
2570
  ${sepIp >= 0 && leadingTriviaFieldAt(code, k, sepIp) ? 'LEAD.at=cur\n' : ''}const sv=${sep}(input,sp,ctx)
2420
2571
  if(sv===FAIL){
2421
2572
  ${rb}
@@ -2430,13 +2581,13 @@ itemStart=${hasTrivia === 'false' ? 'EC.e' : hasTrivia === 'true' ? `${skip}(inp
2430
2581
  ${rb}
2431
2582
  ${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
2432
2583
  }
2433
- ${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate&&${itemMiss('itemStart')}){
2584
+ ${itemCls === undefined || (sep === undefined && hasTrivia === 'false') ? '' : `if(count>=${min}&&${p}gate&&${sep === undefined && hasTrivia === 'hasTrivia' ? 'hasTrivia&&' : ''}${itemMiss('itemStart')}){
2434
2585
  ${rb}
2435
2586
  ${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
2436
2587
  }
2437
- `}ctx._fc=false
2588
+ `}${gapCode(rb)}ctx._fc=false
2438
2589
  const v=${child}(input,itemStart,ctx)
2439
- if(v===FAIL){
2590
+ ${gapIp >= 0 ? 'if(gapped)GAP.at=-1\n' : ''}if(v===FAIL){
2440
2591
  ${REC
2441
2592
  // THE ROLLBACK MOVES INSIDE THE ARMS. The strict loop rolls back
2442
2593
  // unconditionally before testing `_fc`, which is sound because every path out
@@ -2734,7 +2885,7 @@ return nd
2734
2885
  const ruleEntries: string[] = []
2735
2886
  for (const [rname, entryIp] of Object.entries(prog.rules)) {
2736
2887
  if (!served(entryIp)) continue
2737
- // `_r_<Name>` — the composition surface, in `codegen.ts`'s own spelling and
2888
+ // `_r_<Name>` — the composition surface, in the source lowering's spelling and
2738
2889
  // deliberately NOT namespaced, so a sibling calls it by name.
2739
2890
  const target = link(entryIp)
2740
2891
  const rn = `_r_${rname.replace(/[^A-Za-z0-9_$]/g, '_')}`
package/src/table/emit.ts CHANGED
@@ -6,18 +6,20 @@ import { cfgKey, type RunCfg } from './assemble.ts'
6
6
  import { PARSEMAN_VERSION } from '../version.ts'
7
7
 
8
8
  /**
9
- * Opens every printed assembly factory. A runtime older than the printer passes fewer
10
- * helpers; refuse it when the assembly is built, instead of failing partway through the
11
- * first parse on whichever helper is reached first. Runs once per assembly.
9
+ * Opens every printed assembly factory. Helpers are bound BY POSITION, so a runtime that
10
+ * passes any other number of them, fewer or more, binds the wrong function to a name;
11
+ * refuse it when the assembly is built, instead of failing partway through the first parse
12
+ * on whichever helper is reached first. Runs once per assembly.
12
13
  */
13
- export const FACTORY_ARITY_GUARD = `if(arguments.length<${EMITTED_PARAMS.length})throw new Error(${JSON.stringify(`parseman: this parser was printed by parseman ${PARSEMAN_VERSION}; upgrade the parseman runtime to at least that version`)});`
14
+ export const FACTORY_ARITY_GUARD = `if(arguments.length!==${EMITTED_PARAMS.length})throw new Error(${JSON.stringify(`parseman: this parser was printed by parseman ${PARSEMAN_VERSION}; run it on parseman ${PARSEMAN_VERSION} or reprint it`)});`
14
15
 
15
16
  /**
16
17
  * Print a program as the module a build emits.
17
18
  *
18
- * Everything here is DATA plus the author's own reducers. The recognizer is not
19
- * in this string — it is `exec.ts`, imported from the runtime, shared by every
20
- * grammar in the bundle and by every variant of each.
19
+ * Everything here is DATA plus the author's own reducers, and any assemblies
20
+ * the build pre-compiled (`a:`). The recognizer is not in this string: it is
21
+ * `tableRules` from `parseman/table`, shared by every grammar in the bundle and
22
+ * by every variant of each.
21
23
  */
22
24
 
23
25
  function jsString(s: string): string {
@@ -170,11 +172,10 @@ export function defaultAssemblyCfgs(prog: TableProgram): RunCfg[] {
170
172
  * Print the assemblies a build pre-compiled, as the `a:` field of the program
171
173
  * literal — see `TableProgram.asm`.
172
174
  *
173
- * THE FACTORY IS A REAL FUNCTION LITERAL. This is a low-level experiment, not
174
- * a normal compiler route: normal `compile()` and macro output both serialize
175
- * `a:[]` and use the compact closure assembler. If a future specialization can
176
- * meet semantic identity and the size budget, it must preserve that one public
177
- * artifact contract rather than recreate the old runtime/macro split.
175
+ * THE FACTORY IS A REAL FUNCTION LITERAL. A macro build emits one here for the
176
+ * AST, no-line-tracking option set, holding only the sites it selected within its
177
+ * budget (`compile-rule-map.ts`, `selectAssembly`); every other option set gets
178
+ * no entry and runs the closure assembler.
178
179
  *
179
180
  * A refusal is not an error. `Unemittable` names a construct the emitter does
180
181
  * not lower; that option set simply gets no entry, and `assemble.ts` runs the
@@ -186,7 +187,7 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly AssemblyCfg[]): strin
186
187
  *
187
188
  * The field's PRESENCE is the compiler artifact contract. That is what switches
188
189
  * the runtime `Function` constructor off (`assemble.ts`), and it costs four
189
- * bytes. Its CONTENTS are optional experimental factories; with none, the
190
+ * bytes. Its CONTENTS are the build's selected factories; with none, the
190
191
  * artifact runs the closure engine — no eval, no
191
192
  * size growth, and the refusal readable on `Assembly.emitRefusal`.
192
193
  *
@@ -229,6 +230,7 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly AssemblyCfg[]): strin
229
230
  const plan = em.plan
230
231
  out.push('{'
231
232
  + `key:${key},`
233
+ + `arity:${EMITTED_PARAMS.length},`
232
234
  + `factory:function(${EMITTED_PARAMS.join(',')}){${FACTORY_ARITY_GUARD}${em.source}\n},`
233
235
  + 'plan:{'
234
236
  + `classes:[${plan.classes.map(r => `[${r.join(',')}]`).join(',')}],`
@@ -248,12 +250,11 @@ export type EmitOptions = {
248
250
  /** Name of the exported binding. */
249
251
  readonly name?: string
250
252
  /**
251
- * Optional experimental emitted factories — see `defaultAssemblyCfgs`.
253
+ * Emitted factories to pre-compile — see `defaultAssemblyCfgs`.
252
254
  *
253
- * The normal compiler and macro both emit `a:[]`: that is the canonical
254
- * compact closure artifact and it never reaches the `Function` constructor.
255
- * Supplying factories here is a low-level serialization experiment, not a
256
- * second normal compilation path; it is not used by the macro plugin.
255
+ * Without them the artifact prints `a:[]`, the compact closure artifact, which
256
+ * never reaches the `Function` constructor. The macro plugin passes one
257
+ * selective assembly here (`compile-rule-map.ts`, `replacementWithMetadata`).
257
258
  */
258
259
  readonly assemblies?: readonly AssemblyCfg[]
259
260
  /**