parseman 0.48.1 → 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 (83) 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 +1236 -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/linker.d.ts.map +1 -1
  16. package/dist/compiler/token-alphabet.d.ts +11 -0
  17. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  18. package/dist/compiler/token-capability.js +185 -20
  19. package/dist/compiler/token-capability.js.map +1 -1
  20. package/dist/cst/capture-buffer.d.ts +5 -1
  21. package/dist/cst/capture-buffer.d.ts.map +1 -1
  22. package/dist/index.js +1354 -278
  23. package/dist/index.js.map +3 -3
  24. package/dist/language-service/index.js +2 -1
  25. package/dist/language-service/index.js.map +1 -1
  26. package/dist/plugin/direct-builder-static.d.ts +20 -0
  27. package/dist/plugin/direct-builder-static.d.ts.map +1 -1
  28. package/dist/plugin/entry-supercompiler.d.ts +48 -0
  29. package/dist/plugin/entry-supercompiler.d.ts.map +1 -0
  30. package/dist/plugin/evaluator.d.ts +13 -0
  31. package/dist/plugin/evaluator.d.ts.map +1 -1
  32. package/dist/plugin/index.d.ts.map +1 -1
  33. package/dist/plugin/index.js +2118 -376
  34. package/dist/plugin/index.js.map +3 -3
  35. package/dist/run/index.js +2 -1
  36. package/dist/run/index.js.map +1 -1
  37. package/dist/spec/index.js +29 -7
  38. package/dist/spec/index.js.map +1 -1
  39. package/dist/table/assemble.d.ts.map +1 -1
  40. package/dist/table/compile-rule-map.d.ts +6 -5
  41. package/dist/table/compile-rule-map.d.ts.map +1 -1
  42. package/dist/table/emit-assembly.d.ts +6 -1
  43. package/dist/table/emit-assembly.d.ts.map +1 -1
  44. package/dist/table/encode.d.ts.map +1 -1
  45. package/dist/table/index.js +1298 -231
  46. package/dist/table/index.js.map +3 -3
  47. package/dist/table/ops.d.ts +3 -2
  48. package/dist/table/ops.d.ts.map +1 -1
  49. package/dist/table/program.d.ts +15 -1
  50. package/dist/table/program.d.ts.map +1 -1
  51. package/dist/table/scalar-terminal.d.ts +30 -0
  52. package/dist/table/scalar-terminal.d.ts.map +1 -1
  53. package/dist/table/site-labels.d.ts +8 -0
  54. package/dist/table/site-labels.d.ts.map +1 -1
  55. package/dist/types.d.ts +8 -2
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/version.d.ts +1 -1
  58. package/package.json +1 -1
  59. package/src/analysis/commitment.ts +19 -8
  60. package/src/analysis/rebuild.ts +1 -0
  61. package/src/combinators/trivia-skip.ts +2 -2
  62. package/src/compiler/build-arity.ts +50 -0
  63. package/src/compiler/degradation.ts +0 -6
  64. package/src/compiler/ir-serialize.ts +17 -3
  65. package/src/compiler/linker.ts +14 -1
  66. package/src/compiler/token-alphabet.ts +322 -28
  67. package/src/cst/capture-buffer.ts +34 -11
  68. package/src/plugin/direct-builder-static.ts +116 -12
  69. package/src/plugin/entry-supercompiler.ts +567 -0
  70. package/src/plugin/evaluator.ts +60 -2
  71. package/src/plugin/index.ts +147 -4
  72. package/src/table/assemble.ts +125 -42
  73. package/src/table/compile-rule-map.ts +8 -7
  74. package/src/table/compile.ts +7 -8
  75. package/src/table/emit-assembly.ts +646 -126
  76. package/src/table/emit.ts +1 -1
  77. package/src/table/encode.ts +132 -7
  78. package/src/table/ops.ts +3 -2
  79. package/src/table/program.ts +44 -3
  80. package/src/table/scalar-terminal.ts +213 -1
  81. package/src/table/site-labels.ts +22 -9
  82. package/src/types.ts +1 -1
  83. package/src/version.ts +1 -1
@@ -23,13 +23,14 @@ import { createUnplugin } from 'unplugin'
23
23
  import { parseSync } from 'oxc-parser'
24
24
  import { ResolverFactory } from 'oxc-resolver'
25
25
  import MagicString from 'magic-string'
26
- import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, propName, type Scope, type ScopeEntry } from './evaluator.ts'
26
+ import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, setBuilderImportResolver, propName, type Scope, type ScopeEntry } from './evaluator.ts'
27
27
  import { classifyRuleMap } from '../analysis/commitment.ts'
28
28
  import { compile } from '../table/compile.ts'
29
29
  import { compileRuleMap } from '../table/compile-rule-map.ts'
30
30
  import { compileLinkableTable } from '../compiler/compile-linkable-table.ts'
31
31
  import { createReducerResolver } from './reducer-resolver.ts'
32
32
  import { findFreeIdentifiers } from './free-identifiers.ts'
33
+ import { supercompileRuleMapReplacement } from './entry-supercompiler.ts'
33
34
  import {
34
35
  beginDegradationCapture, endDegradationCapture, formatDegradation, formatDegradations,
35
36
  resolveDegradationLevel, recordDegradation, degradationCaptureDepth, unwindDegradationCapture,
@@ -516,6 +517,7 @@ export function transformMacro(
516
517
  return transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recovery, grammarCoverage)
517
518
  } finally {
518
519
  setReducerResolver(null)
520
+ setBuilderImportResolver(null)
519
521
  // Both are idempotent: on the success path the body already released them and these
520
522
  // are no-ops. On an aborted transform they are what stops the leak.
521
523
  for (const d of unwindDegradationCapture(depth)) console.warn(formatDegradation(d))
@@ -666,6 +668,10 @@ function transformMacroImpl(
666
668
  */
667
669
  const reducerResolver = createReducerResolver(id, body as unknown[], code)
668
670
  setReducerResolver(reducerResolver, code)
671
+ /* A direct builder's free name is rescuable when THIS module imported it — the
672
+ * artifact carries `{ source, imported }` and a downstream compose() re-emits the
673
+ * import (see the re-lower pass below). Module-private consts stay refusals. */
674
+ setBuilderImportResolver(name => importBindings.get(name) ?? null)
669
675
 
670
676
  const topLevelFunction = (moduleBody: AnyNode[], name: string): AnyNode | null => {
671
677
  for (const st of moduleBody) {
@@ -797,6 +803,69 @@ function transformMacroImpl(
797
803
  * at the end of this transform is what proves it was actually emitted.
798
804
  */
799
805
  let usedTableRuntime = false
806
+ /**
807
+ * `source` -> (local -> imported) for direct-builder free names that rode in on a
808
+ * carried piece's IR (a composed base grammar whose reducers call imported AST
809
+ * factories). Re-emitted as real imports into THIS module so the inlined builder
810
+ * source has bindings to resolve. A name this module already imports is skipped.
811
+ */
812
+ const pendingBuilderImports = new Map<string, Map<string, string>>()
813
+ /**
814
+ * Harvest direct-builder import provenance off a MERGED rule graph so the re-lower
815
+ * pass re-emits those imports. `buildImports` rides on a `node()`'s `_def`, but a
816
+ * rule that ANOTHER rule references (`g.X`) is materialized in the evaluated map as
817
+ * a `lazy` proxy whose definition node sits behind `_def.thunk()` — never an own
818
+ * property. A generic `Object.values` descent therefore skips it entirely, silently
819
+ * dropping the imports of every cross-referenced rule (the css base's `VarCall`,
820
+ * referenced by its selector/value parents, is the canonical case: its `funcCall` /
821
+ * `isValueSlotValue` reads were lost and the fused downstream module threw "bound by
822
+ * nothing" / left a runtime compose()).
823
+ *
824
+ * The fused table encodes exactly the WINNER of each rule name and binds every
825
+ * reference BY NAME to that winner (`enc.winners`), so the imports it inlines are
826
+ * exactly the union over the winning rule bodies. Harvest that set: walk each rule
827
+ * entry's own body (following the entry lazy's thunk), and STOP at a named `lazy`
828
+ * reference inside a body — it resolves to a winner already covered as its own
829
+ * entry, so descending it would reach an OVERRIDDEN rule's shadowed original and
830
+ * re-emit a dead import. An anonymous `lazy` is inline recursion and is followed.
831
+ * One `seen` set is shared across every rule so a cyclic grammar terminates.
832
+ */
833
+ const collectBuilderImports = (ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>): void => {
834
+ const seen = new Set<unknown>()
835
+ const walk = (v: unknown): void => {
836
+ if (v === null || typeof v !== 'object' || seen.has(v)) return
837
+ seen.add(v)
838
+ const def = (v as { _def?: { tag?: string; thunk?: () => unknown; buildImports?: ReadonlyArray<{ local: string; source: string; imported: string }> } })._def
839
+ for (const bi of def?.buildImports ?? []) {
840
+ let m = pendingBuilderImports.get(bi.source)
841
+ if (!m) { m = new Map(); pendingBuilderImports.set(bi.source, m) }
842
+ m.set(bi.local, bi.imported)
843
+ }
844
+ // An ANONYMOUS lazy is inline recursion — follow the thunk. A NAMED lazy is a
845
+ // rule reference bound by name to a winner covered as its own entry; do not
846
+ // descend it (that path reaches an overridden rule's shadowed original).
847
+ if (def?.tag === 'lazy' && (v as { _ruleName?: string })._ruleName === undefined && typeof def.thunk === 'function') {
848
+ let target: unknown = null
849
+ try { target = def.thunk() } catch { /* external ref — no definition to walk */ }
850
+ walk(target)
851
+ }
852
+ for (const child of Object.values(v as Record<string, unknown>)) {
853
+ if (Array.isArray(child)) { for (const c of child) walk(c) } else walk(child)
854
+ }
855
+ }
856
+ for (const [, rule] of ruleMap) {
857
+ walk(rule)
858
+ // A referenced rule entry is a named lazy whose body node (with its builder
859
+ // imports) is behind the thunk; `walk` skips a named lazy's thunk, so follow
860
+ // the WINNER'S body explicitly here. A raw-node entry was already fully walked.
861
+ const def = rule._def as { tag?: string; thunk?: () => unknown }
862
+ if (def?.tag === 'lazy' && typeof def.thunk === 'function') {
863
+ let body: unknown = null
864
+ try { body = def.thunk() } catch { /* unresolved winner — nothing to walk */ }
865
+ walk(body)
866
+ }
867
+ }
868
+ }
800
869
  /** Exported `rules()` factories, whose bodies are left verbatim. See the push site. */
801
870
  const exportedFactories: Array<{ name: string; pos: number }> = []
802
871
  let runtimeComposeFallback = false
@@ -1457,6 +1526,9 @@ function transformMacroImpl(
1457
1526
  const merged = mergedCarriedRules(carried)
1458
1527
  if (merged !== null) {
1459
1528
  if (composing) applyComposingTrivia(merged.rules, composing)
1529
+ // Harvest the direct-builder import provenance carried on the merged graph so
1530
+ // the re-lower pass at the end of this transform re-emits those imports.
1531
+ collectBuilderImports(merged.rules)
1460
1532
  const refusals: string[] = []
1461
1533
  const compiled = compileRuleMap(merged.rules, {
1462
1534
  ...(composing ? { trivia: composing } : {}),
@@ -1625,9 +1697,12 @@ function transformMacroImpl(
1625
1697
  if (compiled !== null) {
1626
1698
  usedTableRuntime = true
1627
1699
  return {
1628
- replacement: compiled.replacementWithMetadata(
1629
- staticTableMetadataSource({ reflection: compiled.reflection, leaf: true }),
1630
- { precompileDefault: true },
1700
+ replacement: supercompileRuleMapReplacement(
1701
+ compiled.prog,
1702
+ compiled.replacementWithMetadata(
1703
+ staticTableMetadataSource({ reflection: compiled.reflection, leaf: true }),
1704
+ { precompileDefault: true },
1705
+ ),
1631
1706
  ),
1632
1707
  ...(importedFactories.length ? { importedFactories } : {}),
1633
1708
  }
@@ -2186,6 +2261,73 @@ function transformMacroImpl(
2186
2261
  if (usedTableRuntime && applied.length > 0) {
2187
2262
  ms.prepend(`import { tableRules } from ${JSON.stringify(TABLE_RUNTIME_SPECIFIER)}\n`)
2188
2263
  }
2264
+ /* Re-bind the direct-builder free names carried in on composed IR: a base
2265
+ * grammar's reducer calls `dimension` from '@jesscss/core/ast', that provenance
2266
+ * rode in on the carried piece, and the fused table inlines the reducer source
2267
+ * here — so this module must import the same binding.
2268
+ *
2269
+ * The inlined builder source spells the free name VERBATIM, so a re-emitted import
2270
+ * cannot be aliased away: the local name is load-bearing. Two things therefore make
2271
+ * a carried import unsatisfiable, and both are refused rather than silently mis-bound
2272
+ * or emitted as a duplicate declaration:
2273
+ *
2274
+ * - the name is ALREADY BOUND in this module to something else — a named import
2275
+ * from a DIFFERENT source/symbol (the inlined builder would call the wrong
2276
+ * binding), a default/namespace import, or a top-level const/function/class
2277
+ * (re-emitting the import would be a redeclaration `SyntaxError`). The
2278
+ * free-identifier net below cannot catch either: a wrong-binding read is bound,
2279
+ * and a redeclaration is the opposite of an unbound read.
2280
+ * - two DISTINCT carried sources need the SAME local name (the map is keyed by
2281
+ * source, so each would emit its own `import { X } from …`).
2282
+ *
2283
+ * A name this module already imports from the SAME source under the SAME symbol is
2284
+ * the benign case — the exact import the builder needs is already present, so it is
2285
+ * skipped (no duplicate). */
2286
+ if (applied.length > 0 && pendingBuilderImports.size > 0) {
2287
+ // Every top-level binding of THIS module, not just its named imports.
2288
+ const boundLocally = new Set<string>()
2289
+ for (const stmt of body as Statement[]) {
2290
+ if (stmt.type === 'ImportDeclaration') {
2291
+ for (const sp of (stmt as unknown as ImportDeclaration).specifiers) boundLocally.add(sp.local.name)
2292
+ continue
2293
+ }
2294
+ const decl = stmt.type === 'ExportNamedDeclaration'
2295
+ ? ((stmt as unknown as ExportNamedDeclaration).declaration as unknown as AnyNode | undefined)
2296
+ : (stmt as unknown as AnyNode)
2297
+ if (decl?.type === 'FunctionDeclaration' || decl?.type === 'ClassDeclaration') {
2298
+ const n = (decl as unknown as { id?: { name?: string } }).id?.name
2299
+ if (n) boundLocally.add(n)
2300
+ continue
2301
+ }
2302
+ const vd = unwrapVd(stmt)
2303
+ for (const d of vd?.declarations ?? []) {
2304
+ const n = (d.id as unknown as { name?: string }).name
2305
+ if (n) boundLocally.add(n)
2306
+ }
2307
+ }
2308
+ const emitted = new Set<string>()
2309
+ for (const [source, locals] of pendingBuilderImports) {
2310
+ const needed: Array<[string, string]> = []
2311
+ for (const [local, imported] of locals) {
2312
+ const own = importBindings.get(local)
2313
+ // Exact same import already present: the builder's need is satisfied. Skip.
2314
+ if (own && own.source === source && own.imported === imported) continue
2315
+ if (boundLocally.has(local) || emitted.has(local)) {
2316
+ throw new Error(
2317
+ `${id} — parseman will not emit this module: a fused builder needs \`${imported}\` from `
2318
+ + `${JSON.stringify(source)} bound as \`${local}\`, but this module already binds \`${local}\` `
2319
+ + `to something else. The inlined builder source names \`${local}\` verbatim, so the import `
2320
+ + `cannot be aliased; rename the conflicting local binding so the builder's import can be re-emitted.`,
2321
+ )
2322
+ }
2323
+ emitted.add(local)
2324
+ needed.push([local, imported])
2325
+ }
2326
+ if (needed.length === 0) continue
2327
+ const specs = needed.map(([local, imported]) => imported === local ? local : `${imported} as ${local}`)
2328
+ ms.prepend(`import { ${specs.join(', ')} } from ${JSON.stringify(source)}\n`)
2329
+ }
2330
+ }
2189
2331
  if (applied.length > 0) {
2190
2332
  ms.prepend(
2191
2333
  `// Generated by parseman v${PARSEMAN_VERSION} — DO NOT EDIT.\n` +
@@ -2259,6 +2401,7 @@ function transformMacroImpl(
2259
2401
  // caller's config does not break; it is now inert.
2260
2402
 
2261
2403
  setReducerResolver(null)
2404
+ setBuilderImportResolver(null)
2262
2405
  // Every place the compiler chose a correct-but-slower path for this module. Reported
2263
2406
  // on the ordinary warning channel and greppable on `[parseman] degraded`, so a
2264
2407
  // consumer's build gate can assert zero of them the way jess's `check:macro` already
@@ -125,11 +125,12 @@ import {
125
125
  type SubtreeRef, type TableProgram, type TableRule,
126
126
  } from './program.ts'
127
127
  import { stampRuleMap } from './stamp.ts'
128
- import { reachableSites } from './site-labels.ts'
128
+ import { computeSiteLabels, reachableSites } from './site-labels.ts'
129
129
  import { refuseUnclassifiedRootScope } from '../cst/root-trivia-scope.ts'
130
130
  import { captureError, firstSetSentinel, matchesAt, orSentinel, recoverScan } from '../recovery/scan.ts'
131
131
  import {
132
- makeScalarRecognizer, scalarTerminalNodeChild, scalarTerminalNotChild, type ScalarRecognizer,
132
+ leadingLiteralFamily, leadingScalarSequence, leadingScalarTerminal, makeScalarRecognizer, scalarTerminalNodeChild,
133
+ scalarTerminalNotChild, type ScalarRecognizer,
133
134
  } from './scalar-terminal.ts'
134
135
 
135
136
  /**
@@ -482,6 +483,16 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
482
483
  return recognize
483
484
  }
484
485
 
486
+ const labelExtraIps: number[] = []
487
+ for (const s of prog.scans ?? []) {
488
+ for (const r of s.skip) labelExtraIps.push(r[0])
489
+ if (s.sentinel !== undefined) labelExtraIps.push(s.sentinel[0])
490
+ }
491
+ for (const set of prog.scanSkip ?? []) for (const r of set) labelExtraIps.push(r[0])
492
+ const closureLabels = computeSiteLabels(
493
+ code, [...Object.values(prog.rules), ...labelExtraIps], hostCst,
494
+ )
495
+
485
496
  /**
486
497
  * The INSTALLED trivia scanner for the scope currently running.
487
498
  *
@@ -818,7 +829,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
818
829
  // has no single form. `rollbackNeeded` short-circuits true on a live buffer,
819
830
  // so the other seven sinks are not consulted — as before.
820
831
  const raw = b.raw
821
- MRAW = raw !== undefined ? raw.length : b.rawSingle !== undefined ? 1 : 0
832
+ MRAW = b.noRaw === true
833
+ ? b.rawLen ?? 0
834
+ : raw !== undefined ? raw.length : b.rawSingle !== undefined ? 1 : 0
822
835
  const ch = b.ch
823
836
  MLV = ch !== undefined ? ch.length : b.single !== undefined ? 1 : 0
824
837
  const tl = b.tl
@@ -1004,6 +1017,33 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
1004
1017
  return cur
1005
1018
  }
1006
1019
 
1020
+ /** A non-first term at a site proven to run under an open node capture buffer. */
1021
+ function nextTermBuffered(child: Piece, input: string, cur: number, ctx: ParseContext): number {
1022
+ if (ctx.trivia === undefined) {
1023
+ const v = child(input, cur, ctx)
1024
+ if (v === FAIL) return -1
1025
+ TERMV = v
1026
+ return EC.e
1027
+ }
1028
+ const before = ctx._cstBuf!
1029
+ const beforeTl = before.tl
1030
+ const mTl = beforeTl === undefined ? 0 : beforeTl.length
1031
+ const mLog = ctx._triviaLog?.length ?? 0
1032
+ const mRoot = ctx._rootTriviaLog?.length ?? 0
1033
+ const scanEnd = skipTrivia(input, cur, ctx)
1034
+ const scanned = ctx._cstBuf!
1035
+ const scannedTl = scanned.tl
1036
+ const scanTl = scannedTl === undefined ? 0 : scannedTl.length
1037
+ const scanLog = ctx._triviaLog?.length ?? 0
1038
+ const scanRoot = ctx._rootTriviaLog?.length ?? 0
1039
+ const v = child(input, scanEnd, ctx)
1040
+ if (v === FAIL) return -1
1041
+ TERMV = v
1042
+ if (EC.e > scanEnd) return EC.e
1043
+ rollbackScannedTriviaAt(ctx, mTl, scanTl, mLog, scanLog, mRoot, scanRoot)
1044
+ return cur
1045
+ }
1046
+
1007
1047
  function committed(c: ParseContext): boolean {
1008
1048
  return c._fc === true
1009
1049
  }
@@ -1288,7 +1328,16 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
1288
1328
  const holder = { fwd, set: (p: Piece) => { target = p } }
1289
1329
  inFlight.set(ip, holder)
1290
1330
 
1291
- const piece = lower(ip)
1331
+ const op = code[ip]
1332
+ // Match the emitted assembly's label-proven scope alias: rule entries in a
1333
+ // single ambient trivia scope otherwise reinstall and restore the exact
1334
+ // same trivia, labels and scanner on every call. Install the forwarding
1335
+ // holder first so a recursive child can still link back through this site.
1336
+ const piece = (op === OP_SCOPE_PLAIN || (op === OP_SCOPE && code[ip + 3]! === 0))
1337
+ && code[ip + 1]! >= 0
1338
+ && closureLabels.at(ip).tri === code[ip + 1]!
1339
+ ? link(code[ip + 2]!)
1340
+ : lower(ip)
1292
1341
 
1293
1342
  inFlight.delete(ip)
1294
1343
  holder.set(piece)
@@ -2148,6 +2197,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2148
2197
  const fn = fused && projection < 0
2149
2198
  ? fns[reducer] as (value: unknown, span: { start: number; end: number }) => unknown
2150
2199
  : undefined
2200
+ const step = closureLabels.at(ip).buf ? nextTermBuffered : nextTerm
2151
2201
 
2152
2202
  const runnerBlock = (
2153
2203
  count: number,
@@ -2255,7 +2305,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2255
2305
  const kid = link(kidIp)
2256
2306
  return (input, cur, ctx, inherited) => {
2257
2307
  ctx._sync = sync ?? inherited
2258
- return nextTerm(kid, input, cur, ctx)
2308
+ return step(kid, input, cur, ctx)
2259
2309
  }
2260
2310
  }
2261
2311
  const negated = code[kidIp + 1] === 1
@@ -2328,7 +2378,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2328
2378
  const kidIp = code[base + i]!
2329
2379
  if (code[kidIp] !== OP_ADJ) {
2330
2380
  const kid = link(kidIp)
2331
- return (input, cur, ctx) => nextTerm(kid, input, cur, ctx)
2381
+ return (input, cur, ctx) => step(kid, input, cur, ctx)
2332
2382
  }
2333
2383
  const negated = code[kidIp + 1] === 1
2334
2384
  const ki = code[kidIp + 2]!
@@ -2436,7 +2486,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2436
2486
  return (input, pos, ctx) => {
2437
2487
  const v0 = k0(input, pos, ctx)
2438
2488
  if (v0 === FAIL) return FAIL
2439
- const cur = nextTerm(k1, input, EC.e, ctx)
2489
+ const cur = step(k1, input, EC.e, ctx)
2440
2490
  if (cur < 0) return FAIL
2441
2491
  EC.e = cur
2442
2492
  return projection === 0 ? v0
@@ -2448,7 +2498,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2448
2498
  return (input, pos, ctx) => {
2449
2499
  const v0 = k0(input, pos, ctx)
2450
2500
  if (v0 === FAIL) return FAIL
2451
- const cur = nextTerm(k1, input, EC.e, ctx)
2501
+ const cur = step(k1, input, EC.e, ctx)
2452
2502
  if (cur < 0) return FAIL
2453
2503
  EC.e = cur
2454
2504
  return [v0, TERMV]
@@ -2457,7 +2507,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2457
2507
  return (input, pos, ctx) => {
2458
2508
  const v0 = k0(input, pos, ctx)
2459
2509
  if (v0 === FAIL) return FAIL
2460
- const cur = nextTerm(k1, input, EC.e, ctx)
2510
+ const cur = step(k1, input, EC.e, ctx)
2461
2511
  if (cur < 0) return FAIL
2462
2512
  EC.e = cur
2463
2513
  return undefined
@@ -2470,10 +2520,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2470
2520
  return (input, pos, ctx) => {
2471
2521
  const v0 = k0(input, pos, ctx)
2472
2522
  if (v0 === FAIL) return FAIL
2473
- let cur = nextTerm(k1, input, EC.e, ctx)
2523
+ let cur = step(k1, input, EC.e, ctx)
2474
2524
  if (cur < 0) return FAIL
2475
2525
  const v1 = TERMV
2476
- cur = nextTerm(k2, input, cur, ctx)
2526
+ cur = step(k2, input, cur, ctx)
2477
2527
  if (cur < 0) return FAIL
2478
2528
  EC.e = cur
2479
2529
  return projection === 0 ? v0
@@ -2486,10 +2536,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2486
2536
  return (input, pos, ctx) => {
2487
2537
  const v0 = k0(input, pos, ctx)
2488
2538
  if (v0 === FAIL) return FAIL
2489
- let cur = nextTerm(k1, input, EC.e, ctx)
2539
+ let cur = step(k1, input, EC.e, ctx)
2490
2540
  if (cur < 0) return FAIL
2491
2541
  const v1 = TERMV
2492
- cur = nextTerm(k2, input, cur, ctx)
2542
+ cur = step(k2, input, cur, ctx)
2493
2543
  if (cur < 0) return FAIL
2494
2544
  EC.e = cur
2495
2545
  return [v0, v1, TERMV]
@@ -2498,9 +2548,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2498
2548
  return (input, pos, ctx) => {
2499
2549
  const v0 = k0(input, pos, ctx)
2500
2550
  if (v0 === FAIL) return FAIL
2501
- let cur = nextTerm(k1, input, EC.e, ctx)
2551
+ let cur = step(k1, input, EC.e, ctx)
2502
2552
  if (cur < 0) return FAIL
2503
- cur = nextTerm(k2, input, cur, ctx)
2553
+ cur = step(k2, input, cur, ctx)
2504
2554
  if (cur < 0) return FAIL
2505
2555
  EC.e = cur
2506
2556
  return undefined
@@ -2516,43 +2566,43 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2516
2566
  next: SequenceTermBlock | undefined,
2517
2567
  ): SequenceTermBlock => {
2518
2568
  if (count === 1) return (input, cur, ctx, values) => {
2519
- cur = nextTerm(k0, input, cur, ctx)
2569
+ cur = step(k0, input, cur, ctx)
2520
2570
  if (cur < 0) return -1
2521
2571
  if (values !== undefined) values.push(TERMV)
2522
2572
  return next === undefined ? cur : next(input, cur, ctx, values)
2523
2573
  }
2524
2574
  if (count === 2) return (input, cur, ctx, values) => {
2525
- cur = nextTerm(k0, input, cur, ctx)
2575
+ cur = step(k0, input, cur, ctx)
2526
2576
  if (cur < 0) return -1
2527
2577
  if (values !== undefined) values.push(TERMV)
2528
- cur = nextTerm(k1, input, cur, ctx)
2578
+ cur = step(k1, input, cur, ctx)
2529
2579
  if (cur < 0) return -1
2530
2580
  if (values !== undefined) values.push(TERMV)
2531
2581
  return next === undefined ? cur : next(input, cur, ctx, values)
2532
2582
  }
2533
2583
  if (count === 3) return (input, cur, ctx, values) => {
2534
- cur = nextTerm(k0, input, cur, ctx)
2584
+ cur = step(k0, input, cur, ctx)
2535
2585
  if (cur < 0) return -1
2536
2586
  if (values !== undefined) values.push(TERMV)
2537
- cur = nextTerm(k1, input, cur, ctx)
2587
+ cur = step(k1, input, cur, ctx)
2538
2588
  if (cur < 0) return -1
2539
2589
  if (values !== undefined) values.push(TERMV)
2540
- cur = nextTerm(k2, input, cur, ctx)
2590
+ cur = step(k2, input, cur, ctx)
2541
2591
  if (cur < 0) return -1
2542
2592
  if (values !== undefined) values.push(TERMV)
2543
2593
  return next === undefined ? cur : next(input, cur, ctx, values)
2544
2594
  }
2545
2595
  return (input, cur, ctx, values) => {
2546
- cur = nextTerm(k0, input, cur, ctx)
2596
+ cur = step(k0, input, cur, ctx)
2547
2597
  if (cur < 0) return -1
2548
2598
  if (values !== undefined) values.push(TERMV)
2549
- cur = nextTerm(k1, input, cur, ctx)
2599
+ cur = step(k1, input, cur, ctx)
2550
2600
  if (cur < 0) return -1
2551
2601
  if (values !== undefined) values.push(TERMV)
2552
- cur = nextTerm(k2, input, cur, ctx)
2602
+ cur = step(k2, input, cur, ctx)
2553
2603
  if (cur < 0) return -1
2554
2604
  if (values !== undefined) values.push(TERMV)
2555
- cur = nextTerm(k3, input, cur, ctx)
2605
+ cur = step(k3, input, cur, ctx)
2556
2606
  if (cur < 0) return -1
2557
2607
  if (values !== undefined) values.push(TERMV)
2558
2608
  return next === undefined ? cur : next(input, cur, ctx, values)
@@ -2722,6 +2772,13 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2722
2772
  if (n === 2 || n === 3) {
2723
2773
  const a0 = link(code[base]!), a1 = link(code[base + 1]!)
2724
2774
  const a2 = n === 3 ? link(code[base + 2]!) : undefined
2775
+ const predecide = !hostCst && !REC && !cfg.probe && !cfg.coverage && !cfg.trackLines
2776
+ const p0ip = predecide ? leadingScalarTerminal(code, code[base]!, 2, true, true) : -1
2777
+ const p1ip = predecide ? leadingScalarTerminal(code, code[base + 1]!, 2, true, true) : -1
2778
+ const p2ip = predecide && n === 3 ? leadingScalarTerminal(code, code[base + 2]!, 2, true, true) : -1
2779
+ const p0 = p0ip < 0 ? undefined : scalarFor(p0ip)
2780
+ const p1 = p1ip < 0 ? undefined : scalarFor(p1ip)
2781
+ const p2 = p2ip < 0 ? undefined : scalarFor(p2ip)
2725
2782
  const e0 = fx[code[base + n]!] as string[]
2726
2783
  const e1 = fx[code[base + n + 1]!] as string[]
2727
2784
  const e2 = n === 3 ? fx[code[base + n + 2]!] as string[] : undefined
@@ -2751,7 +2808,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2751
2808
  let acc: string[] | undefined
2752
2809
  let best = pos
2753
2810
  if (c < 128) {
2754
- const bits = mask[c < 0 ? 128 : c]!
2811
+ let bits = mask[c < 0 ? 128 : c]!
2812
+ if ((bits & 1) !== 0 && p0 !== undefined && p0(input, pos) < 0) bits &= ~1
2813
+ if ((bits & 2) !== 0 && p1 !== undefined && p1(input, pos) < 0) bits &= ~2
2814
+ if ((bits & 4) !== 0 && p2 !== undefined && p2(input, pos) < 0) bits &= ~4
2755
2815
  let prev = 0
2756
2816
  if ((bits & 1) !== 0) {
2757
2817
  ctx._fc = false
@@ -2796,7 +2856,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2796
2856
  return FAIL
2797
2857
  }
2798
2858
 
2799
- if (g0 === null || classHas(g0, c)) {
2859
+ if ((g0 === null || classHas(g0, c)) && (p0 === undefined || p0(input, pos) >= 0)) {
2800
2860
  ctx._fc = false
2801
2861
  const v = a0(input, pos, ctx)
2802
2862
  if (v !== FAIL) return v
@@ -2806,7 +2866,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2806
2866
  if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
2807
2867
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
2808
2868
  } else if (best === pos) acc = accSet(e0, acc)
2809
- if (g1 === null || classHas(g1, c)) {
2869
+ if ((g1 === null || classHas(g1, c)) && (p1 === undefined || p1(input, pos) >= 0)) {
2810
2870
  ctx._fc = false
2811
2871
  const v = a1(input, pos, ctx)
2812
2872
  if (v !== FAIL) return v
@@ -2817,7 +2877,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2817
2877
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
2818
2878
  } else if (best === pos) acc = accSet(e1, acc)
2819
2879
  if (n === 3) {
2820
- if (g2 === null || classHas(g2, c)) {
2880
+ if ((g2 === null || classHas(g2, c)) && (p2 === undefined || p2(input, pos) >= 0)) {
2821
2881
  ctx._fc = false
2822
2882
  const v = a2!(input, pos, ctx)
2823
2883
  if (v !== FAIL) return v
@@ -3432,6 +3492,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
3432
3492
  // grammar collapse form and the host collapse predicate.
3433
3493
  const keepChildren = !structural || cfg.hostReadsChildren !== false || collapse || unwrap
3434
3494
  const rawOnly = !keepChildren
3495
+ const omitsRaw = !hostCst && build !== undefined && proj < 0 && (flags & 2) !== 0
3435
3496
  return (input, pos, ctx) => {
3436
3497
  const host = HOST
3437
3498
  // `beginCstNodeCapture`/`endCstNodeCapture` INLINED, and the two objects
@@ -3444,17 +3505,12 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
3444
3505
  // allocations a parse that the compiled engine (which inlines its own
3445
3506
  // capture prologue and epilogue) never makes.
3446
3507
  const sCh = ctx._cstChildren
3447
- const sLv = ctx._cstLeaves
3448
- const sRaw = ctx._cstRawChildren
3449
- const sTl = ctx._cstTriviaLog
3450
3508
  const sCap = ctx.captureTrivia
3451
3509
  const sBuf = ctx._cstBuf
3452
- const buf: CstCaptureBuf = rawOnly ? { rawOnly: true } : {}
3510
+ const buf: CstCaptureBuf = rawOnly
3511
+ ? { rawOnly: true }
3512
+ : omitsRaw ? { noRaw: true, rawLen: 0 } : {}
3453
3513
  ctx._cstBuf = buf
3454
- ctx._cstChildren = undefined
3455
- ctx._cstLeaves = undefined
3456
- ctx._cstRawChildren = undefined
3457
- ctx._cstTriviaLog = undefined
3458
3514
  // `begin` sets this true and the caller immediately cleared it when the
3459
3515
  // node does not capture wide. Net: the flag IS `captureWide`, a const.
3460
3516
  ctx.captureTrivia = captureWide
@@ -3476,13 +3532,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
3476
3532
  if (structural) ctx._triviaCaptureMask = savedMask
3477
3533
  const kids = rawOnly ? EMPTY_CH : buf.ch ?? (buf.single !== undefined ? [buf.single] : EMPTY_CH)
3478
3534
  const hostKids = kids
3479
- const rawKids = buf.raw ?? (buf.rawSingle !== undefined ? [buf.rawSingle] : EMPTY_CH)
3535
+ const rawKids = omitsRaw
3536
+ ? EMPTY_CH
3537
+ : buf.raw ?? (buf.rawSingle !== undefined ? [buf.rawSingle] : EMPTY_CH)
3480
3538
  const tlog = buf.tl ?? EMPTY_TLOG
3481
3539
  ctx._cstBuf = sBuf
3482
- ctx._cstChildren = sCh
3483
- ctx._cstLeaves = sLv
3484
- ctx._cstRawChildren = sRaw
3485
- ctx._cstTriviaLog = sTl
3486
3540
  ctx.captureTrivia = sCap
3487
3541
  if (v === FAIL) return FAIL
3488
3542
  const end = EC.e
@@ -3620,6 +3674,35 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
3620
3674
  rawScalarSpecs.add(code[child + 1]!)
3621
3675
  scalarFor(child)
3622
3676
  }
3677
+ } else if (code[ip] === OP_CHOICE && !disp[code[ip + 1]!]!.exclusive) {
3678
+ const n = code[ip + 2]!
3679
+ for (let i = 0; i < n; i++) {
3680
+ const armIp = code[ip + 4 + i]!
3681
+ const child = leadingScalarTerminal(code, armIp, 2, true, true)
3682
+ if (n === 2 || n === 3) {
3683
+ if (child >= 0) scalarFor(child)
3684
+ continue
3685
+ }
3686
+ if (child >= 0) {
3687
+ const spec = k[code[child + 1]!]
3688
+ if (code[child] === OP_LIT && typeof spec === 'string' && spec.length >= 2) {
3689
+ scalarFor(child)
3690
+ continue
3691
+ }
3692
+ }
3693
+ const family = leadingLiteralFamily(code, k, armIp)
3694
+ if (family !== undefined && family.length >= 2 && family.every(terminal => {
3695
+ const value = k[code[terminal + 1]!]
3696
+ return typeof value === 'string' && value.length >= 2
3697
+ })) {
3698
+ for (const terminal of family) scalarFor(terminal)
3699
+ }
3700
+ const sequence = leadingScalarSequence(code, armIp)
3701
+ if (sequence !== undefined && (sequence.trivia < 0
3702
+ || triviaScan[sequence.trivia] != null)) {
3703
+ for (const terminal of sequence.terminals) scalarFor(terminal)
3704
+ }
3705
+ }
3623
3706
  }
3624
3707
  }
3625
3708
  const notChild = scalarTerminalNotChild(code, ip)
@@ -116,16 +116,17 @@ export type CompiledRuleMapTable = {
116
116
  hostBranchElided: boolean
117
117
  reflection: GrammarReflection
118
118
  /**
119
- * The RUNNABLE map, which the source lowering has no counterpart for — it
119
+ * The LIVE RUNNABLE map, which the source lowering has no counterpart for — it
120
120
  * hands back text and nothing else, because its artifact only exists once the
121
121
  * emitted source is evaluated. A table exists as data before it is printed, so
122
122
  * the same call that produces the replacement can also hand back the parser.
123
- * That is what makes a differential against the interpreter possible without
124
- * `eval`, and it is why the table-vs-interpreter test can compare `expected`
125
- * sets rather than only accept/reject.
123
+ * It may use runtime assembly specialisation; `prog` and `replacement` remain
124
+ * serialized closure artifacts. That is what makes a differential against the
125
+ * interpreter possible without `eval`, and it is why the table-vs-interpreter
126
+ * test can compare `expected` sets rather than only accept/reject.
126
127
  */
127
128
  rules: Record<string, TableRule>
128
- /** The encoded program, for a caller that wants to fold or inspect it. */
129
+ /** The closure-stamped wire program, for a caller that wants to fold or inspect it. */
129
130
  prog: TableProgram
130
131
  /**
131
132
  * The coverage DENOMINATOR — every id this map can hit — present only under
@@ -237,7 +238,7 @@ export function compileRuleMapRunnable(
237
238
  hostBranchElided: hostMode === 'ast' && ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
238
239
  reflection: collectGrammarReflection(ruleMap),
239
240
  ...(plan === undefined ? {} : { coverageDefinitions: plan.definitions }),
240
- rules: tableRules(artifact),
241
+ rules: tableRules(prog),
241
242
  prog: artifact,
242
243
  }
243
244
  }
@@ -348,7 +349,7 @@ export function compileRuleMap(
348
349
  hostBranchElided: hostMode === 'ast' && ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
349
350
  reflection: collectGrammarReflection(ruleMap),
350
351
  ...(plan === undefined ? {} : { coverageDefinitions: plan.definitions }),
351
- rules: tableRules(artifact),
352
+ rules: tableRules(prog),
352
353
  prog: artifact,
353
354
  }
354
355
  }
@@ -156,17 +156,16 @@ function compileImpl<T>(
156
156
  // this is the same guard on the single-root entry point.
157
157
  const { prog, fnSrcs } = encodeTableProgram({ [ENTRY]: combinator as Combinator<unknown> }, settings)
158
158
  /**
159
- * `compile()` and the macro must hand the driver the SAME artifact shape.
159
+ * LIVE parsers may assemble the encoded program into specialised functions.
160
+ * `tableRules` falls back to closure assembly when dynamic construction is not
161
+ * available, so this remains runnable under a restrictive CSP.
160
162
  *
161
- * An absent `asm` previously selected runtime `new Function` emission while
162
- * macro output printed `a:[]` and selected closure assembly. That made one
163
- * grammar have two normal execution paths. The compact table artifact is now
164
- * canonical: both paths carry an explicit empty inventory and use the shared
165
- * closure assembler. Static factories remain an opt-in experiment until they
166
- * can satisfy both semantic identity and the size budget.
163
+ * PRINTED artifacts are different: they must not require `new Function` after
164
+ * import. Give the emitters the canonical explicit-empty assembly inventory,
165
+ * while the parser returned by this call runs the raw program's fast path.
167
166
  */
168
167
  const artifact = closureArtifact(prog)
169
- const entry = tableRules(artifact)[ENTRY]!
168
+ const entry = tableRules(prog)[ENTRY]!
170
169
 
171
170
  // Captured-first, supplied-as-fill-in. The encoder records a source per author
172
171
  // callback from the def (`fnSrc` / `buildSrc` / `predSrc` / `gateSrcs`), which the