parseman 0.47.1 → 0.48.1

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 (126) hide show
  1. package/README.md +27 -21
  2. package/dist/analysis/diagnostics.js +2964 -347
  3. package/dist/analysis/diagnostics.js.map +3 -3
  4. package/dist/cli/index.js +2960 -343
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/combinators/choice.d.ts.map +1 -1
  7. package/dist/combinators/expect.d.ts +1 -0
  8. package/dist/combinators/expect.d.ts.map +1 -1
  9. package/dist/combinators/expected.d.ts +8 -0
  10. package/dist/combinators/expected.d.ts.map +1 -0
  11. package/dist/combinators/first-set.d.ts +8 -0
  12. package/dist/combinators/first-set.d.ts.map +1 -1
  13. package/dist/combinators/grammar.d.ts.map +1 -1
  14. package/dist/combinators/keywords.d.ts +4 -0
  15. package/dist/combinators/keywords.d.ts.map +1 -1
  16. package/dist/combinators/literal.d.ts.map +1 -1
  17. package/dist/combinators/not.d.ts.map +1 -1
  18. package/dist/combinators/peek.d.ts.map +1 -1
  19. package/dist/combinators/regex.d.ts.map +1 -1
  20. package/dist/combinators/repeat.d.ts.map +1 -1
  21. package/dist/combinators/sequence.d.ts.map +1 -1
  22. package/dist/combinators/trivia-skip.d.ts +16 -3
  23. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  24. package/dist/compiler/direct-projection.d.ts +12 -0
  25. package/dist/compiler/direct-projection.d.ts.map +1 -0
  26. package/dist/compiler/token-alphabet.d.ts +845 -5
  27. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  28. package/dist/compiler/token-capability.d.ts +3 -0
  29. package/dist/compiler/token-capability.d.ts.map +1 -0
  30. package/dist/compiler/token-capability.js +3192 -0
  31. package/dist/compiler/token-capability.js.map +6 -0
  32. package/dist/cst/trivia-charscan.d.ts +3 -0
  33. package/dist/cst/trivia-charscan.d.ts.map +1 -1
  34. package/dist/cst/trivia-css-scanner.d.ts +5 -0
  35. package/dist/cst/trivia-css-scanner.d.ts.map +1 -0
  36. package/dist/cst/trivia-kinds.d.ts.map +1 -1
  37. package/dist/index.js +2973 -343
  38. package/dist/index.js.map +3 -3
  39. package/dist/language-service/index.js +5 -1
  40. package/dist/language-service/index.js.map +1 -1
  41. package/dist/plugin/index.js +2982 -352
  42. package/dist/plugin/index.js.map +3 -3
  43. package/dist/spec/index.js +325 -47
  44. package/dist/spec/index.js.map +3 -3
  45. package/dist/table/assemble.d.ts.map +1 -1
  46. package/dist/table/child-slots.d.ts.map +1 -1
  47. package/dist/table/compile-rule-map.d.ts +3 -1
  48. package/dist/table/compile-rule-map.d.ts.map +1 -1
  49. package/dist/table/emit-assembly.d.ts +12 -13
  50. package/dist/table/emit-assembly.d.ts.map +1 -1
  51. package/dist/table/emit.d.ts.map +1 -1
  52. package/dist/table/encode.d.ts.map +1 -1
  53. package/dist/table/exec-baseline.d.ts.map +1 -1
  54. package/dist/table/exec.d.ts.map +1 -1
  55. package/dist/table/index.js +2937 -311
  56. package/dist/table/index.js.map +3 -3
  57. package/dist/table/lex-body.d.ts +9 -0
  58. package/dist/table/lex-body.d.ts.map +1 -0
  59. package/dist/table/lex-program.d.ts +25 -0
  60. package/dist/table/lex-program.d.ts.map +1 -0
  61. package/dist/table/ops.d.ts +47 -11
  62. package/dist/table/ops.d.ts.map +1 -1
  63. package/dist/table/program.d.ts +50 -2
  64. package/dist/table/program.d.ts.map +1 -1
  65. package/dist/table/scalar-terminal.d.ts +13 -0
  66. package/dist/table/scalar-terminal.d.ts.map +1 -0
  67. package/dist/table/site-labels.d.ts.map +1 -1
  68. package/dist/types.d.ts +2 -0
  69. package/dist/types.d.ts.map +1 -1
  70. package/dist/version.d.ts +1 -1
  71. package/package.json +11 -11
  72. package/src/combinators/choice.ts +10 -1
  73. package/src/combinators/expect.ts +1 -0
  74. package/src/combinators/expected.ts +17 -0
  75. package/src/combinators/first-set.ts +58 -5
  76. package/src/combinators/grammar.ts +8 -3
  77. package/src/combinators/keywords.ts +22 -6
  78. package/src/combinators/literal.ts +2 -1
  79. package/src/combinators/not.ts +5 -1
  80. package/src/combinators/peek.ts +5 -1
  81. package/src/combinators/regex.ts +5 -3
  82. package/src/combinators/repeat.ts +4 -10
  83. package/src/combinators/sequence.ts +17 -14
  84. package/src/combinators/trivia-skip.ts +46 -13
  85. package/src/compiler/direct-projection.ts +43 -0
  86. package/src/compiler/token-alphabet.ts +4316 -12
  87. package/src/compiler/token-capability.ts +6 -0
  88. package/src/cst/trivia-charscan.ts +34 -0
  89. package/src/cst/trivia-css-scanner.ts +142 -0
  90. package/src/cst/trivia-kinds.ts +3 -1
  91. package/src/plugin/index.ts +4 -4
  92. package/src/table/assemble.ts +1253 -240
  93. package/src/table/child-slots.ts +2 -1
  94. package/src/table/compile-rule-map.ts +21 -3
  95. package/src/table/emit-assembly.ts +335 -87
  96. package/src/table/emit.ts +16 -2
  97. package/src/table/encode.ts +248 -60
  98. package/src/table/exec-baseline.ts +3 -7
  99. package/src/table/exec.ts +134 -25
  100. package/src/table/lex-body.ts +30 -0
  101. package/src/table/lex-program.ts +431 -0
  102. package/src/table/ops.ts +49 -11
  103. package/src/table/program.ts +46 -2
  104. package/src/table/scalar-terminal.ts +51 -0
  105. package/src/table/site-labels.ts +11 -1
  106. package/src/table/stamp.ts +1 -1
  107. package/src/types.ts +1 -1
  108. package/src/version.ts +1 -1
  109. package/dist/analysis/diagnostics.cjs +0 -13824
  110. package/dist/analysis/diagnostics.cjs.map +0 -6
  111. package/dist/cli/index.cjs +0 -14925
  112. package/dist/cli/index.cjs.map +0 -6
  113. package/dist/index.cjs +0 -16391
  114. package/dist/index.cjs.map +0 -6
  115. package/dist/language-service/index.cjs +0 -1304
  116. package/dist/language-service/index.cjs.map +0 -6
  117. package/dist/oracle/index.cjs +0 -254
  118. package/dist/oracle/index.cjs.map +0 -6
  119. package/dist/plugin/index.cjs +0 -16097
  120. package/dist/plugin/index.cjs.map +0 -6
  121. package/dist/run/index.cjs +0 -577
  122. package/dist/run/index.cjs.map +0 -6
  123. package/dist/spec/index.cjs +0 -6236
  124. package/dist/spec/index.cjs.map +0 -6
  125. package/dist/table/index.cjs +0 -11463
  126. 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, rollbackScannedTriviaAt, 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,
@@ -19,6 +22,7 @@ import {
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
  }
@@ -622,6 +627,8 @@ function makeDriver(
622
627
  ctx._routed = savedRouted
623
628
  }
624
629
  } else {
630
+ // The reference engine mirrors dispatch.parse(): plain arms do not
631
+ // install routed state and stay outside the exception guard.
625
632
  v = exec(target, input, selEnd, ctx)
626
633
  }
627
634
  if (v === FAIL) {
@@ -692,6 +699,84 @@ function makeDriver(
692
699
  return value
693
700
  }
694
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
+
695
780
  case OP_SCAN: {
696
781
  const c = scans[code[ip + 1]!]!
697
782
  const r = c.parse(input, pos, ctx)
@@ -854,7 +939,9 @@ function makeDriver(
854
939
  if (REC) ctx._sync = inheritedSync
855
940
  EC.e = cur
856
941
  if (fused) {
857
- 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
858
945
  if (COUNT) siteFn('SEQX fn()', fn)
859
946
  return fn(values, { start: pos, end: cur })
860
947
  }
@@ -865,9 +952,8 @@ function makeDriver(
865
952
  const d = code[ip + 1]!
866
953
  const base = ip + 4
867
954
  const choiceFx = fx[code[ip + 3]!] as string[]
868
- // Report the union, at the choice's own position, on every exit that
869
- // fails — matching both engines and, more importantly, never handing a
870
- // user an empty expected set.
955
+ // A pure dispatch miss has no attempted arm to rank. Report the static
956
+ // opener union at the choice's own position so the result is meaningful.
871
957
  const failChoice = (): typeof FAIL => {
872
958
  ctx._fe = pos
873
959
  ctx._fx = choiceFx
@@ -916,12 +1002,9 @@ function makeDriver(
916
1002
  // union IS that flatMap's answer, without running seven parsers to
917
1003
  // rediscover it.
918
1004
  //
919
- // NOT furthest-failure merging, which no engine on the `expected` path
920
- // performs. `failAt` (combinators/probe.ts:13-24) and its compiled mirror
921
- // `probeUpdate` (codegen.ts:692) are the only positional merges in the
922
- // library, they are gated on `_ctx._probe` and they surface as
923
- // `RunResult.furthestFail` under `{recover:true}` — a different field.
924
- // Every `_fe`/`_fx` site in all three drivers is last-write-wins.
1005
+ // There is only one selected arm on this path, so ordered-arm depth
1006
+ // ranking does not apply. `furthestFail` under `{recover:true}` remains
1007
+ // a separate probe result.
925
1008
  //
926
1009
  // A residual JSON divergence survives this and is NOT a failure-reporting
927
1010
  // bug: on `{"a":]` this driver reports `["}"]` (the Obj arm's own
@@ -947,18 +1030,15 @@ function makeDriver(
947
1030
  const mEr = need ? ctx._errors?.length ?? 0 : 0
948
1031
  const mLog = need ? ctx._triviaLog?.length ?? 0 : 0
949
1032
  const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
950
- // THE ORDERED PATH REPORTS THE ARMS THAT ACTUALLY RAN, accumulated in
951
- // order — `choice.ts:167`'s `expected.push(...result.expected)` per failed
952
- // arm, reported at the choice's own position. The static `choiceFx` union
953
- // is a set of OPENERS, so a left-factored or shared-prefix choice whose
954
- // arms all failed PAST their common opener reported that opener instead of
955
- // the tokens the arms were actually waiting for: `['/::?/']` where the
956
- // interpreter says `['"before"', '"hover"']`.
1033
+ // The ordered path keeps only arms at the deepest failure offset. Exact
1034
+ // ties merge in source order. The final failure is still anchored at the
1035
+ // choice's own position; the deeper offset ranks diagnostics only.
957
1036
  //
958
1037
  // `choiceFx` is still the answer when nothing ran or every arm reported
959
1038
  // nothing — the char gate can skip arms the interpreter would have entered,
960
1039
  // and the union is what those arms' start-failures would have contributed.
961
1040
  let acc: string[] | undefined
1041
+ let best = pos
962
1042
  const push = (ax: readonly string[] | undefined): void => {
963
1043
  if (ax === undefined || ax.length === 0) return
964
1044
  if (acc === undefined) acc = ax.slice()
@@ -973,7 +1053,7 @@ function makeDriver(
973
1053
  // enters the arm, the arm's opener fails, and its static set is what
974
1054
  // lands in the accumulation. Declining to enter must not also decline
975
1055
  // to say what the arm wanted.
976
- push(fx[code[armFx + i]!] as string[])
1056
+ if (best === pos) push(fx[code[armFx + i]!] as string[])
977
1057
  continue
978
1058
  }
979
1059
  ctx._fc = false
@@ -982,7 +1062,9 @@ function makeDriver(
982
1062
  const v = exec(code[base + i]!, input, pos, ctx)
983
1063
  if (v !== FAIL) return v
984
1064
  if (COUNT) { tableCounters.ungatedFails++; tableCounters.ungatedFailRows += tableCounters.rows - rows0 }
985
- push(ctx._fx)
1065
+ const at = ctx._fe ?? pos
1066
+ if (at > best) { best = at; acc = undefined }
1067
+ if (at === best) push(ctx._fx)
986
1068
  // A committed arm cuts, and the interpreter reports the accumulation at
987
1069
  // the arm's OWN span (`choice.ts:170`), not at the choice's position.
988
1070
  if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
@@ -1116,6 +1198,11 @@ function makeDriver(
1116
1198
  const mySync = REC ? ctx._sync : undefined
1117
1199
  const recFx = REC ? fx[code[ip + 6]!] as string[] : undefined
1118
1200
  const sepSent = REC ? sentinelFor(code[ip + 7]!) : undefined
1201
+ const itemClassIndex = sep < 0 ? code[ip + 7]! : -1
1202
+ const itemClass = REC && itemClassIndex >= 0 ? cc[itemClassIndex]! : undefined
1203
+ // `completionsAt` observes failures swallowed by an optional iteration;
1204
+ // tolerant recovery does too. Both keep the ordinary child path.
1205
+ const gateItems = itemClass !== undefined && ctx._tolerant !== true && ctx._probe === undefined
1119
1206
  let cur = pos
1120
1207
  let count = 0
1121
1208
  for (;;) {
@@ -1128,6 +1215,8 @@ function makeDriver(
1128
1215
  // must attempt the separator so its failure sets the expected set, which
1129
1216
  // is the only thing an under-`min` list has to report.
1130
1217
  if (sep >= 0 && count > 0 && count >= min && cur >= input.length) break
1218
+ if (count >= min && sep < 0 && !hasTrivia
1219
+ && gateItems && !classHas(itemClass!, lead(input, cur))) break
1131
1220
  // One mark pair for the whole loop when a rollback is even possible,
1132
1221
  // refreshed per iteration rather than reallocated.
1133
1222
  // SCALAR MARKS. This loop took TWO allocations per item (a CST mark and
@@ -1199,6 +1288,11 @@ function makeDriver(
1199
1288
  if (trailingAllowed && sepEnd >= 0) cur = sepEnd
1200
1289
  break
1201
1290
  }
1291
+ if (count >= min && gateItems && !classHas(itemClass!, lead(input, itemStart))) {
1292
+ if (needMark) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
1293
+ if (trailingAllowed && sepEnd >= 0) cur = sepEnd
1294
+ break
1295
+ }
1202
1296
  ctx._fc = false
1203
1297
  const v = exec(child, input, itemStart, ctx)
1204
1298
  if (v === FAIL) {
@@ -1454,6 +1548,15 @@ function makeDriver(
1454
1548
  }
1455
1549
 
1456
1550
  case OP_NOT: {
1551
+ // The shipping generic closure/emitted NOT bodies predate pure-global
1552
+ // lookahead rollback. Keep the reference driver aligned with them for
1553
+ // every generic child; only the direct-terminal shape this lane replaces
1554
+ // needs the reference oracle to hide the terminal's diagnostic writes.
1555
+ const scalarChild = scalarTerminalNotChild(code, ip) >= 0
1556
+ const savedFc = scalarChild ? ctx._fc : false
1557
+ const savedFe = scalarChild ? ctx._fe : -1
1558
+ const savedFx = scalarChild ? ctx._fx : undefined
1559
+ const savedProbe = scalarChild ? ctx._probe?.best : undefined
1457
1560
  const need = rollbackNeeded(ctx)
1458
1561
  const mRaw = need ? cstRawLen(ctx) : 0
1459
1562
  const mTl = need ? cstTlLen(ctx) : 0
@@ -1464,6 +1567,12 @@ function makeDriver(
1464
1567
  const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
1465
1568
  const v = exec(code[ip + 1]!, input, pos, ctx)
1466
1569
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
1570
+ if (scalarChild) {
1571
+ ctx._fc = savedFc
1572
+ ctx._fe = savedFe
1573
+ ctx._fx = savedFx
1574
+ if (ctx._probe !== undefined) ctx._probe.best = savedProbe ?? null
1575
+ }
1467
1576
  if (v === FAIL) { EC.e = pos; return null }
1468
1577
  // `not.ts:50` — the ASSERTION's own set, at the assertion's position.
1469
1578
  ctx._fe = pos
@@ -1651,7 +1760,7 @@ export function execRules(
1651
1760
  const prog = expandCompact(source)
1652
1761
  const t = resolveTable(prog)
1653
1762
  const scan = opts.leafSwap === false ? t.triviaScan.map(() => null) : t.triviaScan
1654
- const d = makeDriver(t.code, t.k, t.fns, t.cc, t.fx, t.disp, t.dsp, t.trivia, scan, t.triviaLabelled, prog, newEndCell())
1763
+ 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())
1655
1764
  const names = Object.keys(prog.rules)
1656
1765
  const entries = names.map(n => prog.rules[n]!)
1657
1766
  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
+ }