parseman 0.47.0 → 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 +2955 -357
  3. package/dist/analysis/diagnostics.js.map +3 -3
  4. package/dist/cli/index.js +2946 -348
  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 +19 -1
  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 +2959 -348
  37. package/dist/index.js.map +3 -3
  38. package/dist/language-service/index.js +7 -1
  39. package/dist/language-service/index.js.map +1 -1
  40. package/dist/plugin/index.js +2969 -358
  41. package/dist/plugin/index.js.map +3 -3
  42. package/dist/spec/index.js +366 -49
  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 +13 -14
  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 +4839 -2232
  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 +47 -4
  61. package/dist/table/ops.d.ts.map +1 -1
  62. package/dist/table/program.d.ts +53 -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 +12 -11
  71. package/src/combinators/expect.ts +1 -0
  72. package/src/combinators/expected.ts +17 -0
  73. package/src/combinators/first-set.ts +82 -3
  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 +35 -16
  82. package/src/combinators/trivia-skip.ts +75 -11
  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 +1224 -222
  91. package/src/table/child-slots.ts +4 -3
  92. package/src/table/compile-rule-map.ts +21 -3
  93. package/src/table/emit-assembly.ts +363 -109
  94. package/src/table/emit.ts +26 -1
  95. package/src/table/encode.ts +246 -54
  96. package/src/table/exec-baseline.ts +3 -7
  97. package/src/table/exec.ts +140 -18
  98. package/src/table/lex-body.ts +30 -0
  99. package/src/table/lex-program.ts +422 -0
  100. package/src/table/ops.ts +50 -4
  101. package/src/table/program.ts +67 -2
  102. package/src/table/scalar-terminal.ts +51 -0
  103. package/src/table/site-labels.ts +13 -3
  104. package/src/table/stamp.ts +3 -3
  105. package/src/types.ts +1 -1
  106. package/src/version.ts +1 -1
  107. package/dist/analysis/diagnostics.cjs +0 -13736
  108. package/dist/analysis/diagnostics.cjs.map +0 -6
  109. package/dist/cli/index.cjs +0 -14837
  110. package/dist/cli/index.cjs.map +0 -6
  111. package/dist/index.cjs +0 -16303
  112. package/dist/index.cjs.map +0 -6
  113. package/dist/language-service/index.cjs +0 -1302
  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 -16009
  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 -6188
  122. package/dist/spec/index.cjs.map +0 -6
  123. package/dist/table/index.cjs +0 -11375
  124. package/dist/table/index.cjs.map +0 -6
package/src/table/exec.ts CHANGED
@@ -6,7 +6,10 @@ import { projectChild, unwrapChild } from '../combinators/node.ts'
6
6
  import { asciiFoldEq } from '../combinators/literal.ts'
7
7
  import { cstOutputHost } from '../compiler/build-arity.ts'
8
8
  import { consumeTrivia } from '../combinators/trivia-skip.ts'
9
- import { advanceTrivia, needsDeferredTriviaCommit, rollbackTrivia, rollbackTriviaAt, saveTriviaMark, scanTrivia, skipTriviaScanned, type FastTriviaScanner } from '../combinators/trivia-skip.ts'
9
+ import {
10
+ advanceTrivia, commitTriviaScan, needsDeferredTriviaCommit, rollbackScannedTriviaAt, rollbackTrivia,
11
+ rollbackTriviaAt, saveTriviaMark, scanTriviaCompact, skipTriviaScanned, type FastTriviaScanner,
12
+ } from '../combinators/trivia-skip.ts'
10
13
  import {
11
14
  beginCstNodeCapture, cstCaptureActive, cstLeavesLen, cstRawLen, cstTlLen,
12
15
  demoteCapturedToRaw, endCstNodeCapture, pushCstChild, pushCstLeaf,
@@ -14,11 +17,12 @@ import {
14
17
  import {
15
18
  OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
16
19
  OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
17
- OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_EXPECT, OP_SEQX, OP_SCAN,
20
+ OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
18
21
  OP_LIVE,
19
22
  OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD, OP_ATTEMPT, OP_LABEL,
20
23
  OP_COV,
21
24
  OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE,
25
+ OP_LEX_BODY, OP_LEX_PROGRAM,
22
26
  } from './ops.ts'
23
27
  import { adjacencyHolds, adjacencyMisuse } from '../combinators/adjacency.ts'
24
28
  import { failAt } from '../combinators/probe.ts'
@@ -41,9 +45,10 @@ import { refuseUnclassifiedRootScope } from '../cst/root-trivia-scope.ts'
41
45
  import { captureError, firstSetSentinel, matchesAt, orSentinel, recoverScan } from '../recovery/scan.ts'
42
46
  import {
43
47
  decodeClassSpec, expandCompact, resolveTable,
44
- type CompactProgram, type ResolvedClass, type ResolvedDispatch, type ResolvedDispatchSpec,
48
+ type CompactProgram, type ResolvedClass, type ResolvedDispatch, type ResolvedDispatchSpec, type ResolvedTable,
45
49
  type SubtreeRef, type TableProgram, type TableRule,
46
50
  } from './program.ts'
51
+ import { scalarTerminalNotChild } from './scalar-terminal.ts'
47
52
 
48
53
  /**
49
54
  * THE SHARED DRIVER.
@@ -199,6 +204,8 @@ function makeDriver(
199
204
  fx: readonly (readonly string[])[],
200
205
  disp: readonly ResolvedDispatch[],
201
206
  dsp: readonly ResolvedDispatchSpec[],
207
+ lex: readonly ((input: string, pos: number) => number)[],
208
+ lexPrograms: ResolvedTable['lexPrograms'],
202
209
  trivia: readonly unknown[],
203
210
  triviaScan: readonly (FastTriviaScanner | null)[],
204
211
  triviaLabelled: readonly boolean[],
@@ -353,9 +360,7 @@ function makeDriver(
353
360
  // grammar that already had a scanner — keeps the exact branch it had.
354
361
  if (s !== null) return skipTriviaScanned(s, input, cur, ctx)
355
362
  if (needsDeferredTriviaCommit(ctx)) {
356
- const scan = scanTrivia(input, cur, ctx)
357
- scan.commit()
358
- return scan.end
363
+ return commitTriviaScan(scanTriviaCompact(input, cur, ctx))
359
364
  }
360
365
  return advanceTrivia(input, cur, ctx)
361
366
  }
@@ -609,15 +614,23 @@ function makeDriver(
609
614
  usesRouted = spec.routed[arm] === 1
610
615
  }
611
616
 
612
- const savedRouted = ctx._routed
613
617
  let mark = saveTriviaMark(ctx)
618
+ let v: unknown
614
619
  if (usesRouted) {
620
+ const savedRouted = ctx._routed
615
621
  rollbackTrivia(ctx, selectorMark)
616
622
  mark = saveTriviaMark(ctx)
617
623
  ctx._routed = { value: key, span: { start: pos, end: selEnd } }
624
+ try {
625
+ v = exec(target, input, pos, ctx)
626
+ } finally {
627
+ ctx._routed = savedRouted
628
+ }
629
+ } else {
630
+ // The reference engine mirrors dispatch.parse(): plain arms do not
631
+ // install routed state and stay outside the exception guard.
632
+ v = exec(target, input, selEnd, ctx)
618
633
  }
619
- const v = exec(target, input, usesRouted ? pos : selEnd, ctx)
620
- if (usesRouted) ctx._routed = savedRouted
621
634
  if (v === FAIL) {
622
635
  rollbackTrivia(ctx, mark)
623
636
  // The interpreter marks a failed dispatch branch COMMITTED: the
@@ -686,6 +699,84 @@ function makeDriver(
686
699
  return value
687
700
  }
688
701
 
702
+ case OP_LEX_BODY: {
703
+ const recognized = lex[code[ip + 1]!]!(input, pos)
704
+ if (recognized < 0) {
705
+ ctx._fe = pos
706
+ ctx._fx = fx[code[ip + 2]!] as string[]
707
+ if (ctx._probe !== undefined) failAt(ctx, ctx._fx, pos)
708
+ return FAIL
709
+ }
710
+ const lineFlags = code[ip + 4]!
711
+ const hasSuffix = (lineFlags & 4) !== 0
712
+ const suffixMatched = hasSuffix && recognized % 2 === 1
713
+ const end = (recognized - (suffixMatched ? 1 : 0)) / 2
714
+ if ((lineFlags & 1) !== 0) trackLines(ctx, input, suffixMatched ? end - 1 : end)
715
+ if (hasSuffix) ctx._fc = false
716
+ if (hasSuffix && suffixMatched && (lineFlags & 2) !== 0) trackLines(ctx, input, end)
717
+ if (hasSuffix && !suffixMatched) {
718
+ const suffixExpected = fx[code[ip + 3]!] as string[]
719
+ ctx._fe = end
720
+ ctx._fx = suffixExpected
721
+ if (ctx._probe !== undefined) failAt(ctx, suffixExpected, end)
722
+ }
723
+ const value = input.slice(pos, end)
724
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: 'leaf', value, span: { start: pos, end } })
725
+ EC.e = end
726
+ return value
727
+ }
728
+
729
+ case OP_LEX_PROGRAM: {
730
+ const run = lexPrograms[code[ip + 1]!]!
731
+ const scanId = run.scan
732
+ if (scanId === undefined) {
733
+ const end = run(input, pos, ctx)
734
+ if (end < 0) return FAIL
735
+ const value = input.slice(pos, end)
736
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: 'leaf', value, span: { start: pos, end } })
737
+ EC.e = end
738
+ return value
739
+ }
740
+ // The recognizer is the canonical balanced() pool entry. Reproduce the
741
+ // enclosing token boundary here so its internal token leaf and skippers
742
+ // cannot publish into the outer collector; this row publishes one leaf.
743
+ const sTrivia = ctx.trivia, sKinds = ctx.triviaKindLabels
744
+ const sBuf = ctx._cstBuf, sChildren = ctx._cstChildren, sLeaves = ctx._cstLeaves
745
+ const sRaw = ctx._cstRawChildren, sTl = ctx._cstTriviaLog
746
+ const sOuterTl = ctx._triviaLog, sRootTl = ctx._rootTriviaLog
747
+ const wasCapturing = cstCaptureActive(ctx)
748
+ const sScan = SCAN
749
+ SCAN = null
750
+ ctx.trivia = undefined
751
+ ctx.triviaKindLabels = undefined
752
+ ctx._cstBuf = undefined
753
+ ctx._cstChildren = undefined
754
+ ctx._cstLeaves = undefined
755
+ ctx._cstRawChildren = undefined
756
+ ctx._cstTriviaLog = undefined
757
+ ctx._triviaLog = undefined
758
+ ctx._rootTriviaLog = undefined
759
+ let end: number
760
+ try { end = run(input, pos, ctx, scans[scanId]) }
761
+ finally {
762
+ SCAN = sScan
763
+ ctx.trivia = sTrivia
764
+ ctx.triviaKindLabels = sKinds
765
+ ctx._cstBuf = sBuf
766
+ ctx._cstChildren = sChildren
767
+ ctx._cstLeaves = sLeaves
768
+ ctx._cstRawChildren = sRaw
769
+ ctx._cstTriviaLog = sTl
770
+ ctx._triviaLog = sOuterTl
771
+ ctx._rootTriviaLog = sRootTl
772
+ }
773
+ if (end < 0) return FAIL
774
+ const value = input.slice(pos, end)
775
+ if (wasCapturing) pushCstLeaf(ctx, { _tag: 'leaf', value, span: { start: pos, end } })
776
+ EC.e = end
777
+ return value
778
+ }
779
+
689
780
  case OP_SCAN: {
690
781
  const c = scans[code[ip + 1]!]!
691
782
  const r = c.parse(input, pos, ctx)
@@ -706,7 +797,8 @@ function makeDriver(
706
797
  }
707
798
 
708
799
  case OP_SCOPE:
709
- case OP_SCOPE_CAP: {
800
+ case OP_SCOPE_CAP:
801
+ case OP_SCOPE_PLAIN: {
710
802
  const ki = code[ip + 1]!
711
803
  const saved = ctx.trivia
712
804
  const savedLabels = ctx.triviaKindLabels
@@ -735,7 +827,7 @@ function makeDriver(
735
827
  // (`grammar.ts:141`), and the flag is restored here because the table
736
828
  // shares one ctx where `parser()` copies it. Bit 2 = the unclassified-scope
737
829
  // refusal `grammar.ts:98` raises.
738
- const policy = code[ip + 3]!
830
+ const policy = code[ip] === OP_SCOPE_PLAIN ? 0 : code[ip + 3]!
739
831
  if ((policy & 2) !== 0) refuseUnclassifiedRootScope(ctx._rootTriviaStrictScopes)
740
832
  const savedRootCap = ctx._rootTriviaCapture
741
833
  if ((policy & 1) !== 0) ctx._rootTriviaCapture = false
@@ -786,18 +878,19 @@ function makeDriver(
786
878
  // SCALAR MARKS — `saveTriviaMark` allocated TWICE per term (its own
787
879
  // seven-field object plus the five-field CST mark it delegates to).
788
880
  const need = rollbackNeeded(ctx)
789
- const mRaw = need ? cstRawLen(ctx) : 0
790
881
  const mTl = need ? cstTlLen(ctx) : 0
791
- const mLv = need ? cstLeavesLen(ctx) : 0
792
- const mFl = need ? ctx._fields?.length ?? 0 : 0
793
- const mEr = need ? ctx._errors?.length ?? 0 : 0
794
882
  const mLog = need ? ctx._triviaLog?.length ?? 0 : 0
795
883
  const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
796
884
  const scanEnd = skipTrivia(input, cur, ctx)
885
+ const scanTl = need ? cstTlLen(ctx) : 0
886
+ const scanLog = need ? ctx._triviaLog?.length ?? 0 : 0
887
+ const scanRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
797
888
  const v = exec(child, input, scanEnd, ctx)
798
889
  if (v === FAIL) { if (REC) ctx._sync = inheritedSync; return FAIL }
799
890
  if (EC.e > scanEnd) cur = EC.e
800
- else if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
891
+ else if (need) rollbackScannedTriviaAt(
892
+ ctx, mTl, scanTl, mLog, scanLog, mRoot, scanRoot,
893
+ )
801
894
  if (values !== undefined) values.push(v)
802
895
  continue
803
896
  }
@@ -846,7 +939,9 @@ function makeDriver(
846
939
  if (REC) ctx._sync = inheritedSync
847
940
  EC.e = cur
848
941
  if (fused) {
849
- const fn = fns[code[ip + 1]!] as (value: unknown, span: { start: number; end: number }) => unknown
942
+ const reducer = code[ip + 1]!
943
+ if (reducer < 0) return values![~reducer]
944
+ const fn = fns[reducer] as (value: unknown, span: { start: number; end: number }) => unknown
850
945
  if (COUNT) siteFn('SEQX fn()', fn)
851
946
  return fn(values, { start: pos, end: cur })
852
947
  }
@@ -1108,6 +1203,11 @@ function makeDriver(
1108
1203
  const mySync = REC ? ctx._sync : undefined
1109
1204
  const recFx = REC ? fx[code[ip + 6]!] as string[] : undefined
1110
1205
  const sepSent = REC ? sentinelFor(code[ip + 7]!) : undefined
1206
+ const itemClassIndex = sep < 0 ? code[ip + 7]! : -1
1207
+ const itemClass = REC && itemClassIndex >= 0 ? cc[itemClassIndex]! : undefined
1208
+ // `completionsAt` observes failures swallowed by an optional iteration;
1209
+ // tolerant recovery does too. Both keep the ordinary child path.
1210
+ const gateItems = itemClass !== undefined && ctx._tolerant !== true && ctx._probe === undefined
1111
1211
  let cur = pos
1112
1212
  let count = 0
1113
1213
  for (;;) {
@@ -1120,6 +1220,8 @@ function makeDriver(
1120
1220
  // must attempt the separator so its failure sets the expected set, which
1121
1221
  // is the only thing an under-`min` list has to report.
1122
1222
  if (sep >= 0 && count > 0 && count >= min && cur >= input.length) break
1223
+ if (count >= min && sep < 0 && !hasTrivia
1224
+ && gateItems && !classHas(itemClass!, lead(input, cur))) break
1123
1225
  // One mark pair for the whole loop when a rollback is even possible,
1124
1226
  // refreshed per iteration rather than reallocated.
1125
1227
  // SCALAR MARKS. This loop took TWO allocations per item (a CST mark and
@@ -1191,6 +1293,11 @@ function makeDriver(
1191
1293
  if (trailingAllowed && sepEnd >= 0) cur = sepEnd
1192
1294
  break
1193
1295
  }
1296
+ if (count >= min && gateItems && !classHas(itemClass!, lead(input, itemStart))) {
1297
+ if (needMark) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
1298
+ if (trailingAllowed && sepEnd >= 0) cur = sepEnd
1299
+ break
1300
+ }
1194
1301
  ctx._fc = false
1195
1302
  const v = exec(child, input, itemStart, ctx)
1196
1303
  if (v === FAIL) {
@@ -1446,6 +1553,15 @@ function makeDriver(
1446
1553
  }
1447
1554
 
1448
1555
  case OP_NOT: {
1556
+ // The shipping generic closure/emitted NOT bodies predate pure-global
1557
+ // lookahead rollback. Keep the reference driver aligned with them for
1558
+ // every generic child; only the direct-terminal shape this lane replaces
1559
+ // needs the reference oracle to hide the terminal's diagnostic writes.
1560
+ const scalarChild = scalarTerminalNotChild(code, ip) >= 0
1561
+ const savedFc = scalarChild ? ctx._fc : false
1562
+ const savedFe = scalarChild ? ctx._fe : -1
1563
+ const savedFx = scalarChild ? ctx._fx : undefined
1564
+ const savedProbe = scalarChild ? ctx._probe?.best : undefined
1449
1565
  const need = rollbackNeeded(ctx)
1450
1566
  const mRaw = need ? cstRawLen(ctx) : 0
1451
1567
  const mTl = need ? cstTlLen(ctx) : 0
@@ -1456,6 +1572,12 @@ function makeDriver(
1456
1572
  const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
1457
1573
  const v = exec(code[ip + 1]!, input, pos, ctx)
1458
1574
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
1575
+ if (scalarChild) {
1576
+ ctx._fc = savedFc
1577
+ ctx._fe = savedFe
1578
+ ctx._fx = savedFx
1579
+ if (ctx._probe !== undefined) ctx._probe.best = savedProbe ?? null
1580
+ }
1459
1581
  if (v === FAIL) { EC.e = pos; return null }
1460
1582
  // `not.ts:50` — the ASSERTION's own set, at the assertion's position.
1461
1583
  ctx._fe = pos
@@ -1643,7 +1765,7 @@ export function execRules(
1643
1765
  const prog = expandCompact(source)
1644
1766
  const t = resolveTable(prog)
1645
1767
  const scan = opts.leafSwap === false ? t.triviaScan.map(() => null) : t.triviaScan
1646
- const d = makeDriver(t.code, t.k, t.fns, t.cc, t.fx, t.disp, t.dsp, t.trivia, scan, t.triviaLabelled, prog, newEndCell())
1768
+ const d = makeDriver(t.code, t.k, t.fns, t.cc, t.fx, t.disp, t.dsp, t.lex, t.lexPrograms, t.trivia, scan, t.triviaLabelled, prog, newEndCell())
1647
1769
  const names = Object.keys(prog.rules)
1648
1770
  const entries = names.map(n => prog.rules[n]!)
1649
1771
  let last: unknown
@@ -0,0 +1,30 @@
1
+ import type { LexBodySpec } from './program.ts'
2
+
3
+ /** -1 fails. A success is `end * 2 + suffixMatched`, so the reader can
4
+ * reproduce optional()'s swallowed suffix failure without allocating a tuple. */
5
+ export type LexBodyRecognizer = (input: string, pos: number) => number
6
+
7
+ /** Build one selected lexical body from its numeric table row. Recognition is
8
+ * pure; diagnostics, token boundary state, source value and CST publication
9
+ * remain owned by the reader's OP_LEX_BODY shell. */
10
+ export function buildLexBodyRecognizer(
11
+ spec: LexBodySpec,
12
+ constants: readonly unknown[],
13
+ ): LexBodyRecognizer {
14
+ const raw = constants[spec[0]]
15
+ if (!(raw instanceof RegExp)) {
16
+ throw new TypeError('table lexical body does not reference a RegExp constant')
17
+ }
18
+ if (!Number.isInteger(spec[1]) || spec[1] < -1 || spec[1] > 0xFFFF) {
19
+ throw new TypeError('table lexical body suffix is not absent or one UTF-16 code unit')
20
+ }
21
+ const re = raw.sticky ? new RegExp(raw.source, raw.flags) : new RegExp(raw.source, `${raw.flags.replace(/[gy]/g, '')}y`)
22
+ const suffix = spec[1]
23
+ return (input, pos) => {
24
+ re.lastIndex = pos
25
+ if (!re.test(input)) return -1
26
+ const end = re.lastIndex
27
+ if (suffix < 0) return end * 2
28
+ return input.charCodeAt(end) === suffix ? (end + 1) * 2 + 1 : end * 2
29
+ }
30
+ }