parseman 0.47.1 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +27 -21
  2. package/dist/analysis/diagnostics.js +2863 -353
  3. package/dist/analysis/diagnostics.js.map +3 -3
  4. package/dist/cli/index.js +2854 -344
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/combinators/expect.d.ts +1 -0
  7. package/dist/combinators/expect.d.ts.map +1 -1
  8. package/dist/combinators/expected.d.ts +8 -0
  9. package/dist/combinators/expected.d.ts.map +1 -0
  10. package/dist/combinators/first-set.d.ts +8 -0
  11. package/dist/combinators/first-set.d.ts.map +1 -1
  12. package/dist/combinators/grammar.d.ts.map +1 -1
  13. package/dist/combinators/keywords.d.ts +4 -0
  14. package/dist/combinators/keywords.d.ts.map +1 -1
  15. package/dist/combinators/literal.d.ts.map +1 -1
  16. package/dist/combinators/not.d.ts.map +1 -1
  17. package/dist/combinators/peek.d.ts.map +1 -1
  18. package/dist/combinators/regex.d.ts.map +1 -1
  19. package/dist/combinators/repeat.d.ts.map +1 -1
  20. package/dist/combinators/sequence.d.ts.map +1 -1
  21. package/dist/combinators/trivia-skip.d.ts +16 -3
  22. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  23. package/dist/compiler/direct-projection.d.ts +12 -0
  24. package/dist/compiler/direct-projection.d.ts.map +1 -0
  25. package/dist/compiler/token-alphabet.d.ts +845 -5
  26. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  27. package/dist/compiler/token-capability.d.ts +3 -0
  28. package/dist/compiler/token-capability.d.ts.map +1 -0
  29. package/dist/compiler/token-capability.js +3192 -0
  30. package/dist/compiler/token-capability.js.map +6 -0
  31. package/dist/cst/trivia-charscan.d.ts +3 -0
  32. package/dist/cst/trivia-charscan.d.ts.map +1 -1
  33. package/dist/cst/trivia-css-scanner.d.ts +5 -0
  34. package/dist/cst/trivia-css-scanner.d.ts.map +1 -0
  35. package/dist/cst/trivia-kinds.d.ts.map +1 -1
  36. package/dist/index.js +2862 -339
  37. package/dist/index.js.map +3 -3
  38. package/dist/language-service/index.js +5 -1
  39. package/dist/language-service/index.js.map +1 -1
  40. package/dist/plugin/index.js +2870 -347
  41. package/dist/plugin/index.js.map +3 -3
  42. package/dist/spec/index.js +315 -46
  43. package/dist/spec/index.js.map +2 -2
  44. package/dist/table/assemble.d.ts.map +1 -1
  45. package/dist/table/child-slots.d.ts.map +1 -1
  46. package/dist/table/compile-rule-map.d.ts +3 -1
  47. package/dist/table/compile-rule-map.d.ts.map +1 -1
  48. package/dist/table/emit-assembly.d.ts +12 -13
  49. package/dist/table/emit-assembly.d.ts.map +1 -1
  50. package/dist/table/emit.d.ts.map +1 -1
  51. package/dist/table/encode.d.ts.map +1 -1
  52. package/dist/table/exec-baseline.d.ts.map +1 -1
  53. package/dist/table/exec.d.ts.map +1 -1
  54. package/dist/table/index.js +2819 -300
  55. package/dist/table/index.js.map +3 -3
  56. package/dist/table/lex-body.d.ts +9 -0
  57. package/dist/table/lex-body.d.ts.map +1 -0
  58. package/dist/table/lex-program.d.ts +25 -0
  59. package/dist/table/lex-program.d.ts.map +1 -0
  60. package/dist/table/ops.d.ts +43 -7
  61. package/dist/table/ops.d.ts.map +1 -1
  62. package/dist/table/program.d.ts +50 -2
  63. package/dist/table/program.d.ts.map +1 -1
  64. package/dist/table/scalar-terminal.d.ts +13 -0
  65. package/dist/table/scalar-terminal.d.ts.map +1 -0
  66. package/dist/table/site-labels.d.ts.map +1 -1
  67. package/dist/types.d.ts +2 -0
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/version.d.ts +1 -1
  70. package/package.json +11 -11
  71. package/src/combinators/expect.ts +1 -0
  72. package/src/combinators/expected.ts +17 -0
  73. package/src/combinators/first-set.ts +58 -5
  74. package/src/combinators/grammar.ts +8 -3
  75. package/src/combinators/keywords.ts +22 -6
  76. package/src/combinators/literal.ts +2 -1
  77. package/src/combinators/not.ts +5 -1
  78. package/src/combinators/peek.ts +5 -1
  79. package/src/combinators/regex.ts +5 -3
  80. package/src/combinators/repeat.ts +4 -10
  81. package/src/combinators/sequence.ts +17 -14
  82. package/src/combinators/trivia-skip.ts +46 -13
  83. package/src/compiler/direct-projection.ts +43 -0
  84. package/src/compiler/token-alphabet.ts +4316 -12
  85. package/src/compiler/token-capability.ts +6 -0
  86. package/src/cst/trivia-charscan.ts +34 -0
  87. package/src/cst/trivia-css-scanner.ts +142 -0
  88. package/src/cst/trivia-kinds.ts +3 -1
  89. package/src/plugin/index.ts +4 -4
  90. package/src/table/assemble.ts +1211 -221
  91. package/src/table/child-slots.ts +2 -1
  92. package/src/table/compile-rule-map.ts +21 -3
  93. package/src/table/emit-assembly.ts +328 -85
  94. package/src/table/emit.ts +16 -2
  95. package/src/table/encode.ts +242 -50
  96. package/src/table/exec-baseline.ts +3 -7
  97. package/src/table/exec.ts +121 -7
  98. package/src/table/lex-body.ts +30 -0
  99. package/src/table/lex-program.ts +422 -0
  100. package/src/table/ops.ts +45 -7
  101. package/src/table/program.ts +46 -2
  102. package/src/table/scalar-terminal.ts +51 -0
  103. package/src/table/site-labels.ts +11 -1
  104. package/src/table/stamp.ts +1 -1
  105. package/src/types.ts +1 -1
  106. package/src/version.ts +1 -1
  107. package/dist/analysis/diagnostics.cjs +0 -13824
  108. package/dist/analysis/diagnostics.cjs.map +0 -6
  109. package/dist/cli/index.cjs +0 -14925
  110. package/dist/cli/index.cjs.map +0 -6
  111. package/dist/index.cjs +0 -16391
  112. package/dist/index.cjs.map +0 -6
  113. package/dist/language-service/index.cjs +0 -1304
  114. package/dist/language-service/index.cjs.map +0 -6
  115. package/dist/oracle/index.cjs +0 -254
  116. package/dist/oracle/index.cjs.map +0 -6
  117. package/dist/plugin/index.cjs +0 -16097
  118. package/dist/plugin/index.cjs.map +0 -6
  119. package/dist/run/index.cjs +0 -577
  120. package/dist/run/index.cjs.map +0 -6
  121. package/dist/spec/index.cjs +0 -6236
  122. package/dist/spec/index.cjs.map +0 -6
  123. package/dist/table/index.cjs +0 -11463
  124. package/dist/table/index.cjs.map +0 -6
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
  }
@@ -1116,6 +1203,11 @@ function makeDriver(
1116
1203
  const mySync = REC ? ctx._sync : undefined
1117
1204
  const recFx = REC ? fx[code[ip + 6]!] as string[] : undefined
1118
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
1119
1211
  let cur = pos
1120
1212
  let count = 0
1121
1213
  for (;;) {
@@ -1128,6 +1220,8 @@ function makeDriver(
1128
1220
  // must attempt the separator so its failure sets the expected set, which
1129
1221
  // is the only thing an under-`min` list has to report.
1130
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
1131
1225
  // One mark pair for the whole loop when a rollback is even possible,
1132
1226
  // refreshed per iteration rather than reallocated.
1133
1227
  // SCALAR MARKS. This loop took TWO allocations per item (a CST mark and
@@ -1199,6 +1293,11 @@ function makeDriver(
1199
1293
  if (trailingAllowed && sepEnd >= 0) cur = sepEnd
1200
1294
  break
1201
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
+ }
1202
1301
  ctx._fc = false
1203
1302
  const v = exec(child, input, itemStart, ctx)
1204
1303
  if (v === FAIL) {
@@ -1454,6 +1553,15 @@ function makeDriver(
1454
1553
  }
1455
1554
 
1456
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
1457
1565
  const need = rollbackNeeded(ctx)
1458
1566
  const mRaw = need ? cstRawLen(ctx) : 0
1459
1567
  const mTl = need ? cstTlLen(ctx) : 0
@@ -1464,6 +1572,12 @@ function makeDriver(
1464
1572
  const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
1465
1573
  const v = exec(code[ip + 1]!, input, pos, ctx)
1466
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
+ }
1467
1581
  if (v === FAIL) { EC.e = pos; return null }
1468
1582
  // `not.ts:50` — the ASSERTION's own set, at the assertion's position.
1469
1583
  ctx._fe = pos
@@ -1651,7 +1765,7 @@ export function execRules(
1651
1765
  const prog = expandCompact(source)
1652
1766
  const t = resolveTable(prog)
1653
1767
  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())
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())
1655
1769
  const names = Object.keys(prog.rules)
1656
1770
  const entries = names.map(n => prog.rules[n]!)
1657
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
+ }
@@ -0,0 +1,422 @@
1
+ import type { Combinator, ParseContext } from '../types.ts'
2
+ import { failAt } from '../combinators/probe.ts'
3
+ import type { LexBodyRecognizer } from './lex-body.ts'
4
+ import type { LexProgramSpec, ResolvedClass } from './program.ts'
5
+
6
+ /** A selected composite lexical body. Failure state is already published.
7
+ * `scan` is present only for the balanced shell and indexes the canonical
8
+ * driver-owned scan pool; it is not another recognizer implementation. */
9
+ export type LexProgramRunner = {
10
+ (input: string, pos: number, ctx: ParseContext, scan?: Combinator<unknown>): number
11
+ readonly scan?: number
12
+ }
13
+
14
+ export const LEX_NODE_TERMINAL = 0
15
+ export const LEX_NODE_SEQUENCE2 = 1
16
+ export const LEX_NODE_SEQUENCE3 = 2
17
+ export const LEX_NODE_SEQUENCE4 = 3
18
+ export const LEX_NODE_SEQUENCE5 = 4
19
+ export const LEX_NODE_CHOICE2 = 5
20
+ export const LEX_NODE_CHOICE4 = 6
21
+ export const LEX_NODE_CHOICE8 = 7
22
+ export const LEX_NODE_NOT = 8
23
+ export const LEX_NODE_OPTIONAL = 9
24
+
25
+ export function lexProgramDigest(words: readonly number[]): number {
26
+ let hash = 0x811C9DC5
27
+ for (const word of words) {
28
+ hash ^= word | 0
29
+ hash = Math.imul(hash, 0x01000193)
30
+ }
31
+ return hash | 0
32
+ }
33
+
34
+ function classHas(cls: ResolvedClass, code: number): boolean {
35
+ if (code < 0) return false
36
+ if (code < 128) return cls.ascii[code] === 1
37
+ for (let i = 0; i < cls.hi.length; i += 2) {
38
+ if (code >= cls.hi[i]! && code <= cls.hi[i + 1]!) return true
39
+ }
40
+ return false
41
+ }
42
+
43
+ type PureLexRunner = (input: string, pos: number) => number
44
+ type BuiltLexNode = { readonly match: PureLexRunner; readonly run: LexProgramRunner }
45
+ const committed = (ctx: ParseContext): boolean => ctx._fc === true
46
+
47
+ function trackLexLines(ctx: ParseContext, input: string, end: number): void {
48
+ let at = ctx._lineScannedTo ?? 0
49
+ const starts = ctx._lineStarts
50
+ if (starts === undefined || end <= at) return
51
+ while (at < end) {
52
+ if (input.charCodeAt(at) === 10) starts.push(at + 1)
53
+ at++
54
+ }
55
+ ctx._lineScannedTo = end
56
+ }
57
+
58
+ function terminalNode(
59
+ matcher: LexBodyRecognizer,
60
+ failure: readonly string[],
61
+ tracksLines: boolean,
62
+ ): BuiltLexNode {
63
+ const match: PureLexRunner = (input, pos) => {
64
+ const result = matcher(input, pos)
65
+ return result < 0 ? -1 : result / 2
66
+ }
67
+ return {
68
+ match,
69
+ run: (input, pos, ctx) => {
70
+ const end = match(input, pos)
71
+ if (end < 0) {
72
+ ctx._fe = pos
73
+ ctx._fx = failure as string[]
74
+ if (ctx._probe !== undefined) failAt(ctx, ctx._fx, pos)
75
+ return -1
76
+ }
77
+ if (tracksLines) trackLexLines(ctx, input, end)
78
+ return end
79
+ },
80
+ }
81
+ }
82
+
83
+ function sequence2(a: BuiltLexNode, b: BuiltLexNode): BuiltLexNode {
84
+ return {
85
+ match: (input, pos) => {
86
+ const e0 = a.match(input, pos)
87
+ return e0 < 0 ? -1 : b.match(input, e0)
88
+ },
89
+ run: (input, pos, ctx) => {
90
+ const e0 = a.run(input, pos, ctx)
91
+ if (e0 < 0) return -1
92
+ ctx._fc = false
93
+ return b.run(input, e0, ctx)
94
+ },
95
+ }
96
+ }
97
+
98
+ function sequence3(a: BuiltLexNode, b: BuiltLexNode, c: BuiltLexNode): BuiltLexNode {
99
+ return {
100
+ match: (input, pos) => {
101
+ const e0 = a.match(input, pos)
102
+ if (e0 < 0) return -1
103
+ const e1 = b.match(input, e0)
104
+ return e1 < 0 ? -1 : c.match(input, e1)
105
+ },
106
+ run: (input, pos, ctx) => {
107
+ const e0 = a.run(input, pos, ctx)
108
+ if (e0 < 0) return -1
109
+ ctx._fc = false
110
+ const e1 = b.run(input, e0, ctx)
111
+ if (e1 < 0) return -1
112
+ ctx._fc = false
113
+ return c.run(input, e1, ctx)
114
+ },
115
+ }
116
+ }
117
+
118
+ function sequence4(a: BuiltLexNode, b: BuiltLexNode, c: BuiltLexNode, d: BuiltLexNode): BuiltLexNode {
119
+ const abc = sequence3(a, b, c)
120
+ return {
121
+ match: (input, pos) => {
122
+ const end = abc.match(input, pos)
123
+ return end < 0 ? -1 : d.match(input, end)
124
+ },
125
+ run: (input, pos, ctx) => {
126
+ const end = abc.run(input, pos, ctx)
127
+ if (end < 0) return -1
128
+ ctx._fc = false
129
+ return d.run(input, end, ctx)
130
+ },
131
+ }
132
+ }
133
+
134
+ function sequence5(
135
+ a: BuiltLexNode, b: BuiltLexNode, c: BuiltLexNode, d: BuiltLexNode, e: BuiltLexNode,
136
+ ): BuiltLexNode {
137
+ const abcd = sequence4(a, b, c, d)
138
+ return {
139
+ match: (input, pos) => {
140
+ const end = abcd.match(input, pos)
141
+ return end < 0 ? -1 : e.match(input, end)
142
+ },
143
+ run: (input, pos, ctx) => {
144
+ const end = abcd.run(input, pos, ctx)
145
+ if (end < 0) return -1
146
+ ctx._fc = false
147
+ return e.run(input, end, ctx)
148
+ },
149
+ }
150
+ }
151
+
152
+ type ChoiceTail = (
153
+ input: string, pos: number, ctx: ParseContext, lead: number, acc: string[] | undefined,
154
+ ) => number
155
+
156
+ function appendExpected(acc: string[] | undefined, values: readonly string[] | undefined): string[] | undefined {
157
+ if (values === undefined || values.length === 0) return acc
158
+ if (acc === undefined) return values.slice()
159
+ acc.push(...values)
160
+ return acc
161
+ }
162
+
163
+ function choiceNode(
164
+ arms: readonly BuiltLexNode[],
165
+ classes: readonly (ResolvedClass | undefined)[],
166
+ armExpected: readonly (readonly string[])[],
167
+ choiceExpected: readonly string[],
168
+ ): BuiltLexNode {
169
+ let matchTail: PureLexRunner = () => -1
170
+ for (let i = arms.length - 1; i >= 0; i--) {
171
+ const arm = arms[i]!, next = matchTail
172
+ matchTail = (input, pos) => {
173
+ const end = arm.match(input, pos)
174
+ return end < 0 ? next(input, pos) : end
175
+ }
176
+ }
177
+ let tail: ChoiceTail = (_input, pos, ctx, _lead, acc) => {
178
+ ctx._fe = pos
179
+ ctx._fx = acc ?? choiceExpected as string[]
180
+ return -1
181
+ }
182
+ for (let i = arms.length - 1; i >= 0; i--) {
183
+ const arm = arms[i]!, cls = classes[i], staticExpected = armExpected[i]!, next = tail
184
+ tail = (input, pos, ctx, lead, acc) => {
185
+ if (cls !== undefined && !classHas(cls, lead)) {
186
+ return next(input, pos, ctx, lead, appendExpected(acc, staticExpected))
187
+ }
188
+ ctx._fc = false
189
+ const end = arm.run(input, pos, ctx)
190
+ if (end >= 0) return end
191
+ const nextAcc = appendExpected(acc, ctx._fx)
192
+ if (committed(ctx)) {
193
+ if (nextAcc !== undefined) ctx._fx = nextAcc
194
+ return -1
195
+ }
196
+ return next(input, pos, ctx, lead, nextAcc)
197
+ }
198
+ }
199
+ return {
200
+ match: matchTail,
201
+ run: (input, pos, ctx) => tail(input, pos, ctx, pos < input.length ? input.codePointAt(pos)! : -1, undefined),
202
+ }
203
+ }
204
+
205
+ function notNode(body: BuiltLexNode, failure: readonly string[]): BuiltLexNode {
206
+ return {
207
+ match: (input, pos) => body.match(input, pos) < 0 ? pos : -1,
208
+ run: (input, pos, ctx) => {
209
+ if (body.match(input, pos) < 0) return pos
210
+ ctx._fe = pos
211
+ ctx._fx = failure as string[]
212
+ return -1
213
+ },
214
+ }
215
+ }
216
+
217
+ function optionalNode(body: BuiltLexNode): BuiltLexNode {
218
+ return {
219
+ match: (input, pos) => {
220
+ const end = body.match(input, pos)
221
+ return end < 0 ? pos : end
222
+ },
223
+ run: (input, pos, ctx) => {
224
+ ctx._fc = false
225
+ const end = body.run(input, pos, ctx)
226
+ return end < 0 && !committed(ctx) ? pos : end
227
+ },
228
+ }
229
+ }
230
+
231
+ function fixedTreeRunner(
232
+ words: readonly number[],
233
+ matchers: readonly LexBodyRecognizer[],
234
+ classes: readonly ResolvedClass[],
235
+ expected: readonly (readonly string[])[],
236
+ ): LexProgramRunner {
237
+ const stack: BuiltLexNode[] = []
238
+ const pop = (): BuiltLexNode => {
239
+ const value = stack.pop()
240
+ if (value === undefined) throw new TypeError('table lexical tree has an incomplete fixed body')
241
+ return value
242
+ }
243
+ for (let at = 0; at < words.length;) {
244
+ const op = words[at++]!
245
+ if (op === LEX_NODE_TERMINAL) {
246
+ const matcher = matchers[words[at++]!], failure = expected[words[at++]!]
247
+ const tracks = words[at++]
248
+ if (matcher === undefined || failure === undefined || (tracks !== 0 && tracks !== 1)) {
249
+ throw new TypeError('table lexical tree terminal references an invalid pool entry')
250
+ }
251
+ stack.push(terminalNode(matcher, failure, tracks === 1))
252
+ continue
253
+ }
254
+ if (op >= LEX_NODE_SEQUENCE2 && op <= LEX_NODE_SEQUENCE5) {
255
+ const count = op - LEX_NODE_SEQUENCE2 + 2
256
+ const nodes = Array.from({ length: count }, pop).reverse()
257
+ if (count === 2) stack.push(sequence2(nodes[0]!, nodes[1]!))
258
+ else if (count === 3) stack.push(sequence3(nodes[0]!, nodes[1]!, nodes[2]!))
259
+ else if (count === 4) stack.push(sequence4(nodes[0]!, nodes[1]!, nodes[2]!, nodes[3]!))
260
+ else stack.push(sequence5(nodes[0]!, nodes[1]!, nodes[2]!, nodes[3]!, nodes[4]!))
261
+ continue
262
+ }
263
+ if (op >= LEX_NODE_CHOICE2 && op <= LEX_NODE_CHOICE8) {
264
+ const count = op === LEX_NODE_CHOICE2 ? 2 : op === LEX_NODE_CHOICE4 ? 4 : 8
265
+ const choiceFx = expected[words[at++]!]
266
+ if (choiceFx === undefined) throw new TypeError('table lexical tree choice has an invalid expected set')
267
+ const armClasses: Array<ResolvedClass | undefined> = []
268
+ const armFx: Array<readonly string[]> = []
269
+ for (let i = 0; i < count; i++) {
270
+ const cls = words[at++]!, fx = expected[words[at++]!]
271
+ if (cls < -1 || (cls >= 0 && classes[cls] === undefined) || fx === undefined) {
272
+ throw new TypeError('table lexical tree choice references an invalid pool entry')
273
+ }
274
+ armClasses.push(cls < 0 ? undefined : classes[cls])
275
+ armFx.push(fx)
276
+ }
277
+ const arms = Array.from({ length: count }, pop).reverse()
278
+ stack.push(choiceNode(arms, armClasses, armFx, choiceFx))
279
+ continue
280
+ }
281
+ if (op === LEX_NODE_NOT) {
282
+ const failure = expected[words[at++]!]
283
+ if (failure === undefined) throw new TypeError('table lexical tree assertion has an invalid expected set')
284
+ stack.push(notNode(pop(), failure))
285
+ continue
286
+ }
287
+ if (op === LEX_NODE_OPTIONAL) { stack.push(optionalNode(pop())); continue }
288
+ throw new TypeError('table lexical tree has an unknown fixed body')
289
+ }
290
+ if (stack.length !== 1) throw new TypeError('table lexical tree does not resolve to one root body')
291
+ return stack[0]!.run
292
+ }
293
+
294
+ /** Decode one fixed selected-body tuple. There is no lexical opcode loop on
295
+ * the parse path: each supported tuple becomes one scalar closure here. */
296
+ export function buildLexProgramRunner(
297
+ spec: LexProgramSpec,
298
+ matchers: readonly LexBodyRecognizer[],
299
+ classes: readonly ResolvedClass[],
300
+ expected: readonly (readonly string[])[],
301
+ ): LexProgramRunner {
302
+ if ((spec[0] !== 0 && spec[0] !== 1 && spec[0] !== 2 && spec[0] !== 3)
303
+ || (spec[0] === 0 && spec.length !== 13)
304
+ || (spec[0] === 1 && spec.length !== 12)
305
+ || (spec[0] === 2 && spec.length < 6)
306
+ || (spec[0] === 3 && spec.length !== 3)) {
307
+ throw new TypeError('table lexical program has an invalid fixed body width')
308
+ }
309
+ if (spec[1] !== lexProgramDigest(spec.slice(2))) {
310
+ throw new TypeError('table lexical program semantic digest is inconsistent')
311
+ }
312
+ if (spec[0] === 3) {
313
+ if (!Number.isInteger(spec[2]) || spec[2] < 0) {
314
+ throw new TypeError('table balanced lexical program has an invalid scan reference')
315
+ }
316
+ const run: LexProgramRunner = (_input, _pos, ctx, scan) => {
317
+ if (scan === undefined) throw new TypeError('table balanced lexical program lost its scan body')
318
+ const result = scan.parse(_input, _pos, ctx)
319
+ if (!result.ok) {
320
+ ctx._fe = result.span.start
321
+ ctx._fx = (result.expected ?? []) as string[]
322
+ return -1
323
+ }
324
+ return result.span.end
325
+ }
326
+ return Object.assign(run, { scan: spec[2] })
327
+ }
328
+ if (spec[0] === 2) return fixedTreeRunner(spec.slice(2), matchers, classes, expected)
329
+ if (spec[0] === 1) {
330
+ const g00 = matchers[spec[2]], g01 = matchers[spec[3]]
331
+ const g02 = matchers[spec[4]], g03 = matchers[spec[5]]
332
+ const guard0Fx = expected[spec[6]]
333
+ const g1 = matchers[spec[7]], guard1Fx = expected[spec[8]]
334
+ const terminal = matchers[spec[9]], terminalFx = expected[spec[10]]
335
+ const tracks = spec[11]
336
+ if (g00 === undefined || g01 === undefined || g02 === undefined || g03 === undefined
337
+ || guard0Fx === undefined || g1 === undefined || guard1Fx === undefined
338
+ || terminal === undefined || terminalFx === undefined || (tracks !== 0 && tracks !== 1)) {
339
+ throw new TypeError('table lexical program references an invalid pool entry')
340
+ }
341
+ return (input, pos, ctx) => {
342
+ ctx._fc = false
343
+ if (g00(input, pos) >= 0 || g01(input, pos) >= 0
344
+ || g02(input, pos) >= 0 || g03(input, pos) >= 0) {
345
+ ctx._fe = pos
346
+ ctx._fx = guard0Fx as string[]
347
+ return -1
348
+ }
349
+ ctx._fc = false
350
+ if (g1(input, pos) >= 0) {
351
+ ctx._fe = pos
352
+ ctx._fx = guard1Fx as string[]
353
+ return -1
354
+ }
355
+ ctx._fc = false
356
+ const result = terminal(input, pos)
357
+ if (result < 0) {
358
+ ctx._fe = pos
359
+ ctx._fx = terminalFx as string[]
360
+ if (ctx._probe !== undefined) failAt(ctx, ctx._fx, pos)
361
+ return -1
362
+ }
363
+ const end = result / 2
364
+ if (tracks === 1) trackLexLines(ctx, input, end)
365
+ return end
366
+ }
367
+ }
368
+ const cls = classes[spec[2]]
369
+ const choiceFx = expected[spec[3]]
370
+ const m0 = matchers[spec[4]], x0 = expected[spec[5]]
371
+ const m1 = matchers[spec[6]], x1 = expected[spec[7]]
372
+ const m2 = matchers[spec[8]], x2 = expected[spec[9]]
373
+ const m3 = matchers[spec[10]], x3 = expected[spec[11]]
374
+ const tracks = spec[12]
375
+ if (cls === undefined || choiceFx === undefined
376
+ || m0 === undefined || x0 === undefined || m1 === undefined || x1 === undefined
377
+ || m2 === undefined || x2 === undefined || m3 === undefined || x3 === undefined
378
+ || (tracks !== 0 && tracks !== 1)) {
379
+ throw new TypeError('table lexical program references an invalid pool entry')
380
+ }
381
+
382
+ return (input, pos, ctx) => {
383
+ const lead = pos < input.length ? input.codePointAt(pos)! : -1
384
+ if (!classHas(cls, lead)) {
385
+ ctx._fe = pos
386
+ ctx._fx = choiceFx.slice()
387
+ return -1
388
+ }
389
+ let acc: string[] | undefined
390
+ const failed = (fx: readonly string[]): void => {
391
+ ctx._fe = pos
392
+ ctx._fx = fx as string[]
393
+ if (ctx._probe !== undefined) failAt(ctx, ctx._fx, pos)
394
+ if (acc === undefined) acc = fx.slice()
395
+ else acc.push(...fx)
396
+ }
397
+ ctx._fc = false
398
+ const success = (result: number): number => {
399
+ const end = result / 2
400
+ if (tracks === 1) trackLexLines(ctx, input, end)
401
+ return end
402
+ }
403
+ let result = m0(input, pos)
404
+ if (result >= 0) return success(result)
405
+ failed(x0)
406
+ ctx._fc = false
407
+ result = m1(input, pos)
408
+ if (result >= 0) return success(result)
409
+ failed(x1)
410
+ ctx._fc = false
411
+ result = m2(input, pos)
412
+ if (result >= 0) return success(result)
413
+ failed(x2)
414
+ ctx._fc = false
415
+ result = m3(input, pos)
416
+ if (result >= 0) return success(result)
417
+ failed(x3)
418
+ ctx._fe = pos
419
+ ctx._fx = acc ?? choiceFx as string[]
420
+ return -1
421
+ }
422
+ }