parseman 0.45.0 → 0.47.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.
- package/README.md +8 -3
- package/dist/analysis/choice-cost-gate.d.ts +158 -0
- package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
- package/dist/analysis/choice-cost-render.d.ts +96 -0
- package/dist/analysis/choice-cost-render.d.ts.map +1 -0
- package/dist/analysis/choice-cost.d.ts +383 -0
- package/dist/analysis/choice-cost.d.ts.map +1 -0
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/corpus.d.ts +73 -0
- package/dist/analysis/corpus.d.ts.map +1 -0
- package/dist/analysis/diagnose-render.d.ts +65 -0
- package/dist/analysis/diagnose-render.d.ts.map +1 -0
- package/dist/analysis/diagnose.d.ts +15 -0
- package/dist/analysis/diagnose.d.ts.map +1 -1
- package/dist/analysis/diagnostics.cjs +13736 -0
- package/dist/analysis/diagnostics.cjs.map +6 -0
- package/dist/analysis/diagnostics.d.ts +27 -0
- package/dist/analysis/diagnostics.d.ts.map +1 -0
- package/dist/analysis/diagnostics.js +13713 -0
- package/dist/analysis/diagnostics.js.map +6 -0
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix-render.d.ts +43 -0
- package/dist/analysis/fix-render.d.ts.map +1 -0
- package/dist/analysis/fix.d.ts +172 -0
- package/dist/analysis/fix.d.ts.map +1 -0
- package/dist/analysis/format-number.d.ts +23 -0
- package/dist/analysis/format-number.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +47 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts +61 -0
- package/dist/analysis/rebuild.d.ts.map +1 -0
- package/dist/analysis/terminal.d.ts +145 -0
- package/dist/analysis/terminal.d.ts.map +1 -0
- package/dist/cli/index.cjs +14837 -0
- package/dist/cli/index.cjs.map +6 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +14814 -0
- package/dist/cli/index.js.map +6 -0
- package/dist/combinators/adjacency.d.ts +100 -0
- package/dist/combinators/adjacency.d.ts.map +1 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +8 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +22 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts +8 -0
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +0 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +13 -3
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +1 -1
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts +1 -16
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts +30 -2
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +71 -1
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +26 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -0
- package/dist/combinators/trivia-skip.d.ts +40 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +104 -0
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +13 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +79 -47
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +81 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -0
- package/dist/compiler/token-dispatch.d.ts +95 -0
- package/dist/compiler/token-dispatch.d.ts.map +1 -0
- package/dist/compiler/token-scanner.d.ts +53 -0
- package/dist/compiler/token-scanner.d.ts.map +1 -0
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts +35 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/reflection-attach.d.ts +8 -0
- package/dist/cst/reflection-attach.d.ts.map +1 -0
- package/dist/cst/reflection-symbols.d.ts +10 -0
- package/dist/cst/reflection-symbols.d.ts.map +1 -0
- package/dist/cst/reflection.d.ts +2 -4
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/root-trivia-scope.d.ts +26 -0
- package/dist/cst/root-trivia-scope.d.ts.map +1 -0
- package/dist/cst/trivia-charscan.d.ts +18 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts +19 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run-tabled.d.ts +12 -0
- package/dist/functional/run-tabled.d.ts.map +1 -0
- package/dist/functional/run.d.ts +25 -42
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/grammar-metadata.d.ts +3 -0
- package/dist/grammar-metadata.d.ts.map +1 -0
- package/dist/index.cjs +10499 -7478
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +24 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10499 -7478
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +311 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +311 -121
- package/dist/language-service/index.js.map +3 -4
- package/dist/oracle/index.cjs.map +0 -1
- package/dist/oracle/index.js.map +0 -1
- package/dist/parse-context.d.ts +49 -0
- package/dist/parse-context.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts +41 -2
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/free-identifiers.d.ts +23 -0
- package/dist/plugin/free-identifiers.d.ts.map +1 -0
- package/dist/plugin/index.cjs +11381 -9442
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11381 -9442
- package/dist/plugin/index.js.map +3 -4
- package/dist/plugin/reducer-resolver.d.ts +17 -1
- package/dist/plugin/reducer-resolver.d.ts.map +1 -1
- package/dist/regex/classes.d.ts +28 -0
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +14 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.cjs +136 -51
- package/dist/run/index.cjs.map +2 -3
- package/dist/run/index.js +136 -51
- package/dist/run/index.js.map +2 -3
- package/dist/spec/index.cjs +2898 -1622
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2898 -1622
- package/dist/spec/index.js.map +3 -4
- package/dist/spec/model.d.ts +8 -0
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +307 -0
- package/dist/table/assemble.d.ts.map +1 -0
- package/dist/table/cell.d.ts +42 -0
- package/dist/table/cell.d.ts.map +1 -0
- package/dist/table/child-slots.d.ts +30 -0
- package/dist/table/child-slots.d.ts.map +1 -0
- package/dist/table/compile-rule-map.d.ts +136 -0
- package/dist/table/compile-rule-map.d.ts.map +1 -0
- package/dist/table/compile.d.ts +56 -0
- package/dist/table/compile.d.ts.map +1 -0
- package/dist/table/duplication-hook.d.ts +32 -0
- package/dist/table/duplication-hook.d.ts.map +1 -0
- package/dist/table/emit-assembly.d.ts +139 -0
- package/dist/table/emit-assembly.d.ts.map +1 -0
- package/dist/table/emit.d.ts +113 -0
- package/dist/table/emit.d.ts.map +1 -0
- package/dist/table/encode-baseline.d.ts +16 -0
- package/dist/table/encode-baseline.d.ts.map +1 -0
- package/dist/table/encode.d.ts +71 -0
- package/dist/table/encode.d.ts.map +1 -0
- package/dist/table/exec-baseline.d.ts +9 -0
- package/dist/table/exec-baseline.d.ts.map +1 -0
- package/dist/table/exec.d.ts +72 -0
- package/dist/table/exec.d.ts.map +1 -0
- package/dist/table/fold.d.ts +25 -0
- package/dist/table/fold.d.ts.map +1 -0
- package/dist/table/index.cjs +11375 -0
- package/dist/table/index.cjs.map +6 -0
- package/dist/table/index.d.ts +100 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +11352 -0
- package/dist/table/index.js.map +6 -0
- package/dist/table/inspect.d.ts +17 -0
- package/dist/table/inspect.d.ts.map +1 -0
- package/dist/table/ops.d.ts +446 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +493 -0
- package/dist/table/program.d.ts.map +1 -0
- package/dist/table/run-support.d.ts +38 -0
- package/dist/table/run-support.d.ts.map +1 -0
- package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
- package/dist/table/scan-shapes.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +58 -0
- package/dist/table/site-labels.d.ts.map +1 -0
- package/dist/table/stamp.d.ts +70 -0
- package/dist/table/stamp.d.ts.map +1 -0
- package/dist/types.d.ts +80 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +41 -3
- package/src/analysis/choice-cost-gate.ts +359 -0
- package/src/analysis/choice-cost-render.ts +293 -0
- package/src/analysis/choice-cost.ts +1008 -0
- package/src/analysis/commitment.ts +557 -0
- package/src/analysis/corpus.ts +130 -0
- package/src/analysis/diagnose-render.ts +625 -0
- package/src/analysis/diagnose.ts +317 -0
- package/src/analysis/diagnostics.ts +33 -0
- package/src/analysis/duplication.ts +1955 -0
- package/src/analysis/fix-render.ts +217 -0
- package/src/analysis/fix.ts +669 -0
- package/src/analysis/format-number.ts +31 -0
- package/src/analysis/gating.ts +792 -0
- package/src/analysis/grammar.ts +72 -0
- package/src/analysis/rebuild.ts +287 -0
- package/src/analysis/terminal.ts +336 -0
- package/src/cli/index.ts +413 -0
- package/src/combinators/adjacency.ts +171 -0
- package/src/combinators/attempt.ts +41 -0
- package/src/combinators/case-fold.ts +90 -0
- package/src/combinators/choice.ts +466 -0
- package/src/combinators/completions.ts +64 -0
- package/src/combinators/dispatch.ts +358 -0
- package/src/combinators/expect.ts +159 -0
- package/src/combinators/first-set.ts +322 -0
- package/src/combinators/gate.ts +50 -0
- package/src/combinators/grammar.ts +272 -0
- package/src/combinators/keywords.ts +145 -0
- package/src/combinators/literal.ts +102 -0
- package/src/combinators/map.ts +106 -0
- package/src/combinators/node.ts +345 -0
- package/src/combinators/not.ts +55 -0
- package/src/combinators/parser.ts +266 -0
- package/src/combinators/peek.ts +63 -0
- package/src/combinators/probe.ts +25 -0
- package/src/combinators/ref.ts +109 -0
- package/src/combinators/regex.ts +170 -0
- package/src/combinators/repeat.ts +561 -0
- package/src/combinators/scanTo.ts +393 -0
- package/src/combinators/sequence.ts +221 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +821 -0
- package/src/combinators/withCtx.ts +45 -0
- package/src/compiler/build-arity.ts +330 -0
- package/src/compiler/compile-linkable-table.ts +209 -0
- package/src/compiler/degradation.ts +266 -0
- package/src/compiler/fields.ts +191 -0
- package/src/compiler/grammar-coverage-ids.ts +129 -0
- package/src/compiler/ir-serialize.ts +615 -0
- package/src/compiler/linker.ts +954 -0
- package/src/compiler/token-alphabet.ts +209 -0
- package/src/compiler/token-dispatch.ts +353 -0
- package/src/compiler/token-scanner.ts +318 -0
- package/src/compiler/value-usage.ts +147 -0
- package/src/coverage.ts +383 -0
- package/src/cst/capture-buffer.ts +297 -0
- package/src/cst/host-mode.ts +69 -0
- package/src/cst/reflection-attach.ts +11 -0
- package/src/cst/reflection-symbols.ts +9 -0
- package/src/cst/reflection.ts +149 -0
- package/src/cst/relative-spans.ts +207 -0
- package/src/cst/root-trivia-scope.ts +30 -0
- package/src/cst/trivia-charscan.ts +312 -0
- package/src/cst/trivia-entries.ts +285 -0
- package/src/cst/trivia-index.ts +131 -0
- package/src/cst/trivia-kinds.ts +358 -0
- package/src/cst/types.ts +76 -0
- package/src/cst/walk.ts +101 -0
- package/src/functional/doc.ts +959 -0
- package/src/functional/run-tabled.ts +153 -0
- package/src/functional/run.ts +558 -0
- package/src/grammar-metadata.ts +2 -0
- package/src/index.ts +189 -0
- package/src/language-service/index.ts +221 -0
- package/src/line-index.ts +131 -0
- package/src/oracle/digest.ts +620 -0
- package/src/oracle/index.ts +55 -0
- package/src/parse-context.ts +91 -0
- package/src/plugin/direct-builder-static.ts +102 -0
- package/src/plugin/evaluator.ts +1505 -0
- package/src/plugin/free-identifiers.ts +338 -0
- package/src/plugin/index.ts +2281 -0
- package/src/plugin/reducer-resolver.ts +606 -0
- package/src/recovery/scan.ts +155 -0
- package/src/regex/classes.ts +183 -0
- package/src/regex/first-set.ts +316 -0
- package/src/run/index.ts +31 -0
- package/src/spec/ebnf.ts +128 -0
- package/src/spec/index.ts +54 -0
- package/src/spec/model.ts +636 -0
- package/src/spec/railroad-lib.ts +559 -0
- package/src/spec/railroad.ts +256 -0
- package/src/table/assemble.ts +2994 -0
- package/src/table/cell.ts +42 -0
- package/src/table/child-slots.ts +113 -0
- package/src/table/compile-rule-map.ts +336 -0
- package/src/table/compile.ts +244 -0
- package/src/table/duplication-hook.ts +89 -0
- package/src/table/emit-assembly.ts +1766 -0
- package/src/table/emit.ts +418 -0
- package/src/table/encode-baseline.ts +340 -0
- package/src/table/encode.ts +1569 -0
- package/src/table/exec-baseline.ts +537 -0
- package/src/table/exec.ts +1672 -0
- package/src/table/fold.ts +38 -0
- package/src/table/index.ts +108 -0
- package/src/table/inspect.ts +52 -0
- package/src/table/ops.ts +459 -0
- package/src/table/program.ts +855 -0
- package/src/table/run-support.ts +66 -0
- package/src/table/scan-shapes.ts +1578 -0
- package/src/table/site-labels.ts +228 -0
- package/src/table/stamp.ts +229 -0
- package/src/types.ts +563 -0
- package/src/version.ts +16 -0
- package/dist/compiler/codegen.d.ts +0 -334
- package/dist/compiler/codegen.d.ts.map +0 -1
- package/dist/compiler/inline-build.d.ts +0 -11
- package/dist/compiler/inline-build.d.ts.map +0 -1
- package/dist/compiler/inline-callback.d.ts +0 -21
- package/dist/compiler/inline-callback.d.ts.map +0 -1
- package/dist/compiler/line-index.d.ts +0 -2
- package/dist/compiler/line-index.d.ts.map +0 -1
- package/dist/compiler/scannable-run.d.ts.map +0 -1
- package/dist/compiler/scannable-terminal.d.ts +0 -23
- package/dist/compiler/scannable-terminal.d.ts.map +0 -1
- package/dist/compiler/trivia-fast-path.d.ts +0 -38
- package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
import type { Combinator, ParseContext, ParseResult } from '../types.ts'
|
|
2
|
+
import { classifiedTrivia, trivia as triviaOf } from '../combinators/map.ts'
|
|
3
|
+
import { fastTriviaScanner, type FastTriviaScanner } from '../combinators/trivia-skip.ts'
|
|
4
|
+
import { regex } from '../combinators/regex.ts'
|
|
5
|
+
import { choice } from '../combinators/choice.ts'
|
|
6
|
+
import { many } from '../combinators/repeat.ts'
|
|
7
|
+
import type { EmittedFactory, PoolPlan } from './emit-assembly.ts'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One assembly the BUILD already compiled, so the run does not have to.
|
|
11
|
+
*
|
|
12
|
+
* `key` is `cfgKey(RunCfg)` — the scalar option bits an assembly is specialised
|
|
13
|
+
* for. `factory` is the emitted scope as a real function literal, taking
|
|
14
|
+
* `EMITTED_PARAMS` in order. `plan` rebuilds the three data pools the factory
|
|
15
|
+
* expects (see `PoolPlan`), and `reached` is the emitter's site set, which
|
|
16
|
+
* `Assembly.reached` publishes and `test/unit/table-assemble.test.ts`
|
|
17
|
+
* asserts on.
|
|
18
|
+
*/
|
|
19
|
+
export type PrecompiledAssembly = {
|
|
20
|
+
readonly key: number
|
|
21
|
+
readonly factory: EmittedFactory
|
|
22
|
+
readonly plan: PoolPlan
|
|
23
|
+
readonly reached: readonly number[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The emitted form of a grammar under the table lowering.
|
|
28
|
+
*
|
|
29
|
+
* This object is ALL a grammar contributes to a bundle. Every line of
|
|
30
|
+
* recognition logic lives in `exec.ts`, which ships once in the runtime and is
|
|
31
|
+
* shared by every grammar and every variant. A rule costs its row, not a
|
|
32
|
+
* function.
|
|
33
|
+
*
|
|
34
|
+
* Serialized shape is deliberately JSON-ish (numbers, strings, and a pool of
|
|
35
|
+
* author callbacks) so the emitter can print it as a literal.
|
|
36
|
+
*/
|
|
37
|
+
export type TableProgram = {
|
|
38
|
+
/** Flat instruction stream. See `ops.ts` for operand layouts. */
|
|
39
|
+
readonly code: readonly number[]
|
|
40
|
+
/** Const pool: literal strings and STICKY regexes. */
|
|
41
|
+
readonly k: readonly unknown[]
|
|
42
|
+
/** Author callbacks: `transform`/`leaf` reducers and `node()` builders. */
|
|
43
|
+
readonly fns: readonly unknown[]
|
|
44
|
+
/**
|
|
45
|
+
* Char classes, one string per class, as consecutive `[lo, hi]` code-point
|
|
46
|
+
* pairs. Compact in source and expanded to an ASCII lookup at load.
|
|
47
|
+
*/
|
|
48
|
+
readonly cc: readonly string[]
|
|
49
|
+
/** Expected-set pool for failure reporting; indices appear on terminals. */
|
|
50
|
+
readonly fx: readonly (readonly string[])[]
|
|
51
|
+
/** Dispatch tables: per choice, the arms' char-class indices (−1 = no gate). */
|
|
52
|
+
readonly disp: readonly (readonly number[])[]
|
|
53
|
+
/**
|
|
54
|
+
* `dispatch()` tables. Deliberately plain arrays of strings and numbers rather
|
|
55
|
+
* than prepared `Map`s: the emitted module has to be able to PRINT this, and a
|
|
56
|
+
* Map in the const pool would fail `emitConst` closed. The Maps are built once
|
|
57
|
+
* at `resolveTable`, exactly like the char-class ASCII lookups.
|
|
58
|
+
*/
|
|
59
|
+
readonly dsp: readonly DispatchSpec[]
|
|
60
|
+
/** Rule name → entry offset in `code`. */
|
|
61
|
+
readonly rules: Readonly<Record<string, number>>
|
|
62
|
+
/**
|
|
63
|
+
* 1 when this table's terminals are the line-tracking rows. It is TABLE DATA,
|
|
64
|
+
* read once when the rule map is built, so the entry wrapper is chosen at load
|
|
65
|
+
* rather than branched on per parse.
|
|
66
|
+
*/
|
|
67
|
+
readonly lines?: 0 | 1
|
|
68
|
+
/**
|
|
69
|
+
* The grammar trivia's kind labels, and whether it is root-classified.
|
|
70
|
+
*
|
|
71
|
+
* `run()` reads these off the ENTRY (`triviaKindLabelsFromRunnable`,
|
|
72
|
+
* run.ts:220) and takes the `typeof r === 'function'` branch for a compiled
|
|
73
|
+
* entry, which codegen stamps with `_meta`. A table entry is also a function,
|
|
74
|
+
* so without carrying and stamping these, `run({ rootTrivia })` throws
|
|
75
|
+
* "requires labeled grammar trivia" for a grammar that plainly has them.
|
|
76
|
+
*/
|
|
77
|
+
readonly labels?: readonly string[]
|
|
78
|
+
readonly classified?: 0 | 1
|
|
79
|
+
/**
|
|
80
|
+
* Grammar-level ambient `scanSkip` sets, as SUBTREE REFERENCES. Same
|
|
81
|
+
* entry-metadata trap as `labels`: `run.ts:270` reads
|
|
82
|
+
* `entry._meta.grammarScanSkip` only for a NON-function entry, so a table entry
|
|
83
|
+
* never got it and `ctx.scanSkip` stayed empty — silently changing what
|
|
84
|
+
* `scanTo`/`balanced` skip over.
|
|
85
|
+
*
|
|
86
|
+
* WAS a list of live combinators: unemittable, and not even listed in
|
|
87
|
+
* `runtimeOnly`, so a module would have parsed with an empty skip list. It was
|
|
88
|
+
* sound only because `scanTo()`/`balanced()` — the sole readers of
|
|
89
|
+
* `ctx.scanSkip` — were themselves emit-blocked. They no longer are, so this is
|
|
90
|
+
* data.
|
|
91
|
+
*
|
|
92
|
+
* A POOL, not one set, and `scanSkipOf` says which rules carry which. The set is
|
|
93
|
+
* a property of the RULE, not of the program: `rules({ scanSkip }, …)` stamps
|
|
94
|
+
* `_meta.grammarScanSkip` on each of ITS rules
|
|
95
|
+
* (`src/combinators/parser.ts:210`), a `parser()` scope has no `scanSkip` field
|
|
96
|
+
* at all, and `run()` installs the set belonging to the ENTRY rule
|
|
97
|
+
* (`src/combinators/grammar.ts:203`). In a `composeLeaf` grammar the pieces do
|
|
98
|
+
* not agree — 67 of jess's 195 css rules carry no ambient set — so installing
|
|
99
|
+
* one program-wide set gave those entries a skip list the interpreter does not
|
|
100
|
+
* give them.
|
|
101
|
+
*/
|
|
102
|
+
readonly scanSkip?: readonly (readonly SubtreeRef[])[]
|
|
103
|
+
/**
|
|
104
|
+
* Per rule, the index into `scanSkip` its entry installs, or −1. Parallel to
|
|
105
|
+
* `Object.keys(rules)`.
|
|
106
|
+
*/
|
|
107
|
+
readonly scanSkipOf?: readonly number[]
|
|
108
|
+
/** Scan specs, referenced by index from `OP_SCAN`. */
|
|
109
|
+
readonly scans?: readonly ScanSpec[]
|
|
110
|
+
/**
|
|
111
|
+
* The host mode this table was BUILT for.
|
|
112
|
+
*
|
|
113
|
+
* `run()` reads it off the entry via `FUSED_HOST_MODE` and
|
|
114
|
+
* `assertHostModeCompatible` throws when a `'cst'` artifact is run without a
|
|
115
|
+
* CST host. Without the stamp a `hostMode: 'cst'` table returned the
|
|
116
|
+
* grammar's own AST objects with `ok: true` while paying full CST capture —
|
|
117
|
+
* the compiled engine throws on the same input.
|
|
118
|
+
*/
|
|
119
|
+
readonly hostMode?: 'ast' | 'cst'
|
|
120
|
+
/**
|
|
121
|
+
* Why this program cannot be EMITTED, if it cannot. Populated at encode time
|
|
122
|
+
* so the failure names the construct instead of surfacing as a generic
|
|
123
|
+
* "cannot serialise [object Object]" from deep inside the printer.
|
|
124
|
+
*/
|
|
125
|
+
readonly runtimeOnly?: readonly string[]
|
|
126
|
+
/**
|
|
127
|
+
* Trivia specs, referenced by index from `OP_SCOPE` and from the rule entries.
|
|
128
|
+
*
|
|
129
|
+
* `classifiedTrivia()` is `trivia(oneOrMore(choice(label(name, arm)…)))` with
|
|
130
|
+
* regex arms — entirely structural, so it lowers to DATA and is rebuilt at
|
|
131
|
+
* load with the shared `classifiedTrivia`/`trivia`/`regex`. That keeps the one
|
|
132
|
+
* trivia implementation (labels, root log, cst log, masks, the fast scanner)
|
|
133
|
+
* instead of growing a second one over the table, while making the program
|
|
134
|
+
* printable. Pooling the live combinator was what broke emit for every
|
|
135
|
+
* `rules({ trivia }, …)` grammar.
|
|
136
|
+
*/
|
|
137
|
+
readonly triviaSpecs?: readonly TriviaSpec[]
|
|
138
|
+
/**
|
|
139
|
+
* 1 when this table was encoded with `TableSettings.recovery` — the sequences
|
|
140
|
+
* carry their inferred follow-set classes and the repetitions their item
|
|
141
|
+
* expected-set and separator sentinel class.
|
|
142
|
+
*
|
|
143
|
+
* TABLE DATA, read once when the driver/assembly is built. It selects the
|
|
144
|
+
* recovery pieces; it is not a per-parse option, and a recovery table still
|
|
145
|
+
* recovers only when a parse sets `ctx._tolerant`.
|
|
146
|
+
*/
|
|
147
|
+
readonly rec?: 0 | 1
|
|
148
|
+
/**
|
|
149
|
+
* THE GRAMMAR-COVERAGE DEFINITION POOL, present only on a table encoded with
|
|
150
|
+
* `TableSettings.coverage`. `[id, kind]` per definition, in the order
|
|
151
|
+
* `buildGrammarPlan` produces (sorted by id), and `OP_COV`'s `id` operand
|
|
152
|
+
* indexes it.
|
|
153
|
+
*
|
|
154
|
+
* DEFINITIONS SHIP AS TABLE DATA rather than being scraped back out of the
|
|
155
|
+
* emitted text. The source lowering's macro path recovers its denominator by
|
|
156
|
+
* regex-scanning the generated hooks (`plugin/index.ts` `emittedCoverageDefinitions`),
|
|
157
|
+
* which it has to do because its IDs only exist inside generated statements. A
|
|
158
|
+
* table has no statements, so there is nothing to scan — and an empty scrape is
|
|
159
|
+
* NO MEASUREMENT reported as 100% coverage, which is the exact failure
|
|
160
|
+
* `'coverage-definitions-unavailable'` was declared for. Carrying the pool is
|
|
161
|
+
* what lets `compileRuleMap` hand back `coverageDefinitions` directly.
|
|
162
|
+
*
|
|
163
|
+
* THE POOL IS THE WHOLE DENOMINATOR, not merely the instrumented sites. A
|
|
164
|
+
* definition with no `OP_COV` row can never be hit, so it drags the ratio DOWN.
|
|
165
|
+
* That is the direction this project requires an instrumentation gap to fail in.
|
|
166
|
+
*/
|
|
167
|
+
readonly cov?: readonly (readonly [id: string, kind: 0 | 1 | 2 | 3])[]
|
|
168
|
+
/**
|
|
169
|
+
* Optional emitted-factory inventory, keyed by `cfgKey(RunCfg)`.
|
|
170
|
+
*
|
|
171
|
+
* Normal `compile()` and macro output both carry an explicit empty inventory
|
|
172
|
+
* (`a:[]`) and select the compact closure assembler. Presence of this field
|
|
173
|
+
* permanently disables runtime `Function` construction: an unserved option
|
|
174
|
+
* set uses closures. A missing field is reserved for an explicit low-level
|
|
175
|
+
* hand-built `tableRules(program)` caller, never a compiler-created artifact.
|
|
176
|
+
*/
|
|
177
|
+
readonly asm?: readonly PrecompiledAssembly[]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The one compact artifact shape produced by the table compiler.
|
|
182
|
+
*
|
|
183
|
+
* Compiler and macro artifacts are explicitly closure-backed: an empty
|
|
184
|
+
* inventory is materially different from an absent one, which is reserved for
|
|
185
|
+
* a caller that deliberately hands `tableRules()` a live runtime program.
|
|
186
|
+
*/
|
|
187
|
+
export function closureArtifact(prog: TableProgram): TableProgram {
|
|
188
|
+
return { ...prog, asm: [] }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* `GrammarCoverageDefinition['kind']` as the small integer `prog.cov` carries,
|
|
193
|
+
* and back. Two functions rather than a shared array literal so the mapping is
|
|
194
|
+
* exhaustive over the union in BOTH directions — a fifth kind added to
|
|
195
|
+
* `grammar-coverage-ids.ts` fails to compile here instead of silently encoding
|
|
196
|
+
* as `undefined` and reading back as `'rule'`.
|
|
197
|
+
*/
|
|
198
|
+
export function covKindCode(kind: 'rule' | 'choice-arm' | 'dispatch-arm' | 'label'): 0 | 1 | 2 | 3 {
|
|
199
|
+
switch (kind) {
|
|
200
|
+
case 'rule': return 0
|
|
201
|
+
case 'choice-arm': return 1
|
|
202
|
+
case 'dispatch-arm': return 2
|
|
203
|
+
case 'label': return 3
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function covKindName(code: 0 | 1 | 2 | 3): 'rule' | 'choice-arm' | 'dispatch-arm' | 'label' {
|
|
208
|
+
switch (code) {
|
|
209
|
+
case 0: return 'rule'
|
|
210
|
+
case 1: return 'choice-arm'
|
|
211
|
+
case 2: return 'dispatch-arm'
|
|
212
|
+
case 3: return 'label'
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The pool read back as the public definition shape `compiledGrammarCoverageDefinitions` validates. */
|
|
217
|
+
export function covDefinitions(prog: TableProgram): readonly { id: string; kind: 'rule' | 'choice-arm' | 'dispatch-arm' | 'label' }[] {
|
|
218
|
+
return (prog.cov ?? []).map(([id, kind]) => ({ id, kind: covKindName(kind) }))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* A reference to a table SUBTREE, standing in for a combinator.
|
|
223
|
+
*
|
|
224
|
+
* `[ip, cls]`: `ip` is the subtree's offset in `code`; `cls` indexes its first set
|
|
225
|
+
* in `cc`, −1 for `any` and −2 for `empty`. The first set is carried because
|
|
226
|
+
* `buildBalancedInterior` reads each skipper's own first set to decide whether its
|
|
227
|
+
* content run can be a bounded regex (`src/combinators/scanTo.ts:280`) — a
|
|
228
|
+
* reference that reported `any` would silently rebuild a DIFFERENT, slower
|
|
229
|
+
* interior than the grammar's own.
|
|
230
|
+
*/
|
|
231
|
+
export type SubtreeRef = readonly [ip: number, cls: number]
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* A scanning construct as its CONSTRUCTOR ARGUMENTS.
|
|
235
|
+
*
|
|
236
|
+
* `kind` 0 is `scanTo`, 1 is `balanced`. `flags` bit 0 = `raw`, bit 1 = `orEOF`
|
|
237
|
+
* (scanTo), bit 2 = `strict` (balanced). `sent` is the sentinel's literal text
|
|
238
|
+
* when it is a `literal()`, so the rebuilt scan reports the same expected set as
|
|
239
|
+
* the interpreter, and `null` otherwise (which reports `"sentinel"`).
|
|
240
|
+
*/
|
|
241
|
+
export type ScanSpec = {
|
|
242
|
+
readonly kind: 0 | 1
|
|
243
|
+
readonly flags: number
|
|
244
|
+
readonly skip: readonly SubtreeRef[]
|
|
245
|
+
readonly sentinel?: SubtreeRef
|
|
246
|
+
readonly sent?: string | null
|
|
247
|
+
readonly open?: string
|
|
248
|
+
readonly close?: string
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** A trivia combinator as data. `arms` empty means a plain `trivia(regex)`. */
|
|
252
|
+
export type TriviaSpec = {
|
|
253
|
+
/** `[label, regexSource, regexFlags]` per classified arm. */
|
|
254
|
+
readonly arms: readonly (readonly [string, string, string])[]
|
|
255
|
+
/** Set for a plain `trivia(regex)`: `[source, flags]`. */
|
|
256
|
+
readonly plain?: readonly [string, string]
|
|
257
|
+
/**
|
|
258
|
+
* Set for an UNLABELLED alternation body — `trivia(oneOrMore(choice(ws,
|
|
259
|
+
* comment)))`, which is what a grammar that does not need trivia CATEGORIES
|
|
260
|
+
* writes and what `examples/css/parser.ts` has always written. `[source,
|
|
261
|
+
* flags]` per arm, in order, because a PEG choice is ordered.
|
|
262
|
+
*
|
|
263
|
+
* Distinct from `arms` rather than folded into it with a synthetic label:
|
|
264
|
+
* `classifiedTrivia()` puts its labels in the CST trivia log, so a made-up name
|
|
265
|
+
* would appear in a consumer's tree for a category the grammar never declared.
|
|
266
|
+
*/
|
|
267
|
+
readonly alts?: readonly (readonly [string, string])[]
|
|
268
|
+
/** The `alts` repetition floor — 1 for `oneOrMore`, 0 for `many`. */
|
|
269
|
+
readonly min?: number
|
|
270
|
+
/**
|
|
271
|
+
* A trivia combinator whose shape this encoder cannot express as data, kept
|
|
272
|
+
* live so the program still RUNS. Its presence is recorded in `runtimeOnly`,
|
|
273
|
+
* so emit refuses and names it rather than failing inside the printer.
|
|
274
|
+
*/
|
|
275
|
+
readonly live?: unknown
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The wire form an artifact actually prints — same table, short keys. Kept
|
|
280
|
+
* separate from `TableProgram` so the encoder stays readable and the emitted
|
|
281
|
+
* module stays small.
|
|
282
|
+
*/
|
|
283
|
+
export type CompactProgram = {
|
|
284
|
+
readonly c: readonly number[]
|
|
285
|
+
readonly k: readonly unknown[]
|
|
286
|
+
readonly x: readonly string[]
|
|
287
|
+
readonly e: readonly (readonly string[])[]
|
|
288
|
+
readonly d: readonly (readonly number[])[]
|
|
289
|
+
readonly r: Readonly<Record<string, number>>
|
|
290
|
+
readonly f: readonly unknown[]
|
|
291
|
+
readonly l?: 0 | 1
|
|
292
|
+
readonly p?: readonly DispatchSpec[]
|
|
293
|
+
readonly lb?: readonly string[]
|
|
294
|
+
readonly rc?: 0 | 1
|
|
295
|
+
readonly h?: 'ast' | 'cst'
|
|
296
|
+
readonly tv?: readonly TriviaSpec[]
|
|
297
|
+
readonly sc?: readonly ScanSpec[]
|
|
298
|
+
readonly ss?: readonly (readonly SubtreeRef[])[]
|
|
299
|
+
readonly so?: readonly number[]
|
|
300
|
+
readonly rv?: 0 | 1
|
|
301
|
+
readonly cv?: readonly (readonly [string, 0 | 1 | 2 | 3])[]
|
|
302
|
+
/** Pre-compiled assemblies — see `TableProgram.asm`. */
|
|
303
|
+
readonly a?: readonly PrecompiledAssembly[]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function expandCompact(p: TableProgram | CompactProgram): TableProgram {
|
|
307
|
+
if ('code' in p) return p
|
|
308
|
+
return ownTableProgram({
|
|
309
|
+
code: p.c, k: p.k, cc: p.x, fx: p.e, disp: p.d, rules: p.r, fns: p.f,
|
|
310
|
+
lines: p.l ?? 0, dsp: p.p ?? [],
|
|
311
|
+
...(p.lb === undefined ? {} : { labels: p.lb }),
|
|
312
|
+
...(p.rc === undefined ? {} : { classified: p.rc }),
|
|
313
|
+
...(p.h === undefined ? {} : { hostMode: p.h }),
|
|
314
|
+
...(p.tv === undefined ? {} : { triviaSpecs: p.tv }),
|
|
315
|
+
...(p.sc === undefined ? {} : { scans: p.sc }),
|
|
316
|
+
...(p.ss === undefined ? {} : { scanSkip: p.ss }),
|
|
317
|
+
...(p.so === undefined ? {} : { scanSkipOf: p.so }),
|
|
318
|
+
...(p.rv === undefined ? {} : { rec: p.rv }),
|
|
319
|
+
...(p.cv === undefined ? {} : { cov: p.cv }),
|
|
320
|
+
...(p.a === undefined ? {} : { asm: p.a }),
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** One `dispatch()`'s routing tables, in serialisable form. */
|
|
325
|
+
export type DispatchSpec = {
|
|
326
|
+
/** Exact keys, parallel to `keyArm`. */
|
|
327
|
+
readonly key: readonly string[]
|
|
328
|
+
readonly keyArm: readonly number[]
|
|
329
|
+
/** ASCII-folded keys for case-insensitive cases, parallel to `foldArm`. */
|
|
330
|
+
readonly fold: readonly string[]
|
|
331
|
+
readonly foldArm: readonly number[]
|
|
332
|
+
/**
|
|
333
|
+
* `[kind, value, flags, arm]` per matcher. `kind` is 0/1/2 for
|
|
334
|
+
* startsWith/endsWith/matches, and 3/4 for the ASCII-FOLDED startsWith/endsWith
|
|
335
|
+
* a `{ caseInsensitive: true }` matcher arm encodes to — `value` is pre-folded
|
|
336
|
+
* for those and the driver folds the selector key before comparing. A
|
|
337
|
+
* case-insensitive `matches` needs no kind of its own: it folds into the
|
|
338
|
+
* regex's own `i` flag.
|
|
339
|
+
*/
|
|
340
|
+
readonly match: readonly (readonly [number, string, string, number])[]
|
|
341
|
+
/** 1 when that arm consumes the routed token. */
|
|
342
|
+
readonly routed: readonly number[]
|
|
343
|
+
/** Expected set when nothing matches — every case key, JSON-quoted. */
|
|
344
|
+
readonly expected: readonly string[]
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export type ResolvedDispatchSpec = {
|
|
348
|
+
readonly byKey: ReadonlyMap<string, number>
|
|
349
|
+
readonly byFold: ReadonlyMap<string, number>
|
|
350
|
+
readonly match: readonly (readonly [number, string, string, number])[]
|
|
351
|
+
readonly routed: readonly number[]
|
|
352
|
+
readonly expected: readonly string[]
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** A char class expanded for execution: O(1) for ASCII, ranges above it. */
|
|
356
|
+
export type ResolvedClass = {
|
|
357
|
+
readonly ascii: Uint8Array
|
|
358
|
+
readonly hi: readonly number[]
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** A dispatch table expanded for execution. */
|
|
362
|
+
export type ResolvedDispatch = {
|
|
363
|
+
/** charCode → arm index + 1, 0 = no arm claims it. Read only when `exclusive`. */
|
|
364
|
+
readonly ascii: Uint8Array
|
|
365
|
+
/** `[lo, hi, arm]` triples for code points ≥ 128. Read only when `exclusive`. */
|
|
366
|
+
readonly hi: readonly number[]
|
|
367
|
+
/** Arms with no gate at all — tried in order after a dispatch miss. */
|
|
368
|
+
readonly open: readonly number[]
|
|
369
|
+
/**
|
|
370
|
+
* The arms' OWN classes, positionally — `null` where the arm is nullable or
|
|
371
|
+
* its first set does not map to a class, and so cannot be gated.
|
|
372
|
+
*
|
|
373
|
+
* This is the per-arm gate, and it is what the compiled engine has always had:
|
|
374
|
+
* `emitFirstMatch` guards each arm on its own first set, which is sound
|
|
375
|
+
* whatever the other arms do. The table used to demand that ALL arms be
|
|
376
|
+
* disjoint, non-nullable and mappable before it would gate ANY of them, so a
|
|
377
|
+
* single unmappable arm made the whole site speculative — 98.6% of arm entries
|
|
378
|
+
* on `benchmark.less` were at such sites, and 88.8% of those failed.
|
|
379
|
+
*/
|
|
380
|
+
readonly armCls: readonly (ResolvedClass | null)[]
|
|
381
|
+
/**
|
|
382
|
+
* True when the arms' classes are pairwise disjoint and every arm has one, so
|
|
383
|
+
* "the first arm whose class holds this char" IS "the first arm that matches"
|
|
384
|
+
* and the O(1) `ascii`/`hi` selection is sound. When false the arms are tried
|
|
385
|
+
* in order, each skipped by its own `armCls` entry — same order, same result,
|
|
386
|
+
* fewer entries.
|
|
387
|
+
*
|
|
388
|
+
* Computed from the RESOLVED classes rather than read off the encoder's
|
|
389
|
+
* `disjointSets` on first sets: the classes are what selection actually reads,
|
|
390
|
+
* so testing them is both tighter and the property that has to hold.
|
|
391
|
+
*/
|
|
392
|
+
readonly exclusive: boolean
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The `(grammar, settings)` reference table, built ONCE and cached.
|
|
397
|
+
*
|
|
398
|
+
* This is the object G5 names: the run path reads it and never branches on an
|
|
399
|
+
* option. Two settings pairs produce two of these; the driver is the same code.
|
|
400
|
+
*/
|
|
401
|
+
export type ResolvedTable = {
|
|
402
|
+
readonly prog: TableProgram
|
|
403
|
+
readonly code: Int32Array
|
|
404
|
+
readonly k: readonly unknown[]
|
|
405
|
+
readonly fns: readonly unknown[]
|
|
406
|
+
readonly cc: readonly ResolvedClass[]
|
|
407
|
+
readonly fx: readonly (readonly string[])[]
|
|
408
|
+
readonly disp: readonly ResolvedDispatch[]
|
|
409
|
+
readonly dsp: readonly ResolvedDispatchSpec[]
|
|
410
|
+
/** Trivia combinators rebuilt from `triviaSpecs`, once per table. */
|
|
411
|
+
readonly trivia: readonly Combinator<unknown>[]
|
|
412
|
+
/**
|
|
413
|
+
* THE TRIVIA LEAF SWAP (G5: "some swaps on rules or sub-rules (leafs)").
|
|
414
|
+
*
|
|
415
|
+
* Each trivia entry's SPECIALISED scanner, resolved here rather than looked up
|
|
416
|
+
* per sequence term, and `null` where the shape has no lowering. Parallel to
|
|
417
|
+
* `trivia`, so a scope installs both by the same index.
|
|
418
|
+
*/
|
|
419
|
+
readonly triviaScan: readonly (FastTriviaScanner | null)[]
|
|
420
|
+
/** Per trivia entry: does it carry kind labels? Decided here, never per parse. */
|
|
421
|
+
readonly triviaLabelled: readonly boolean[]
|
|
422
|
+
readonly rules: Readonly<Record<string, number>>
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Runtime state owned by a table object from the moment that object is made.
|
|
427
|
+
* The symbol stays out of wire data; the nested cell lets resolution fill the
|
|
428
|
+
* cache without changing the program object's V8 shape.
|
|
429
|
+
*/
|
|
430
|
+
const TABLE_RUNTIME = Symbol('parseman.tableRuntime')
|
|
431
|
+
type RuntimeTableProgram = TableProgram & {
|
|
432
|
+
readonly [TABLE_RUNTIME]?: { resolved: ResolvedTable | undefined }
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Give an internally-created program its fixed-shape runtime owner. */
|
|
436
|
+
export function ownTableProgram(prog: TableProgram, resolved?: ResolvedTable): TableProgram {
|
|
437
|
+
return { ...prog, [TABLE_RUNTIME]: { resolved } } as RuntimeTableProgram
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** A compiled entry, shaped exactly like a codegen rule function. */
|
|
441
|
+
export type TableRule = (input: string, pos: number, ctx: ParseContext) => ParseResult<unknown>
|
|
442
|
+
|
|
443
|
+
// A valid range always has lo <= hi, so this pair cannot be the first encoded
|
|
444
|
+
// range. It marks the wide format without stealing or escaping any BMP code
|
|
445
|
+
// point (notably U+FFFF, which appears often in generated negated classes).
|
|
446
|
+
const WIDE_CLASS_PREFIX_LO = 0xffff
|
|
447
|
+
const WIDE_CLASS_PREFIX_HI = 0
|
|
448
|
+
|
|
449
|
+
/** Fail closed on a class range before it reaches either wire format. */
|
|
450
|
+
function validateClassRange(lo: number, hi: number): void {
|
|
451
|
+
for (const cp of [lo, hi]) {
|
|
452
|
+
if (!Number.isInteger(cp) || cp < 0 || cp > 0x10ffff) {
|
|
453
|
+
throw new TypeError(`table char-class endpoint is outside Unicode: ${cp}`)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (lo > hi) {
|
|
457
|
+
throw new TypeError(`table char-class range is descending: ${lo} > ${hi}`)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Encode consecutive `[lo, hi]` code-point pairs without truncating astral endpoints. */
|
|
462
|
+
export function encodeClassSpec(ranges: readonly { lo: number; hi: number }[]): string {
|
|
463
|
+
let wide = false
|
|
464
|
+
for (const range of ranges) {
|
|
465
|
+
validateClassRange(range.lo, range.hi)
|
|
466
|
+
if (range.hi > 0xffff) wide = true
|
|
467
|
+
}
|
|
468
|
+
if (!wide) {
|
|
469
|
+
let spec = ''
|
|
470
|
+
for (const range of ranges) spec += String.fromCharCode(range.lo, range.hi)
|
|
471
|
+
return spec
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
let spec = String.fromCharCode(WIDE_CLASS_PREFIX_LO, WIDE_CLASS_PREFIX_HI)
|
|
475
|
+
for (const range of ranges) {
|
|
476
|
+
spec += String.fromCharCode(
|
|
477
|
+
range.lo >>> 16, range.lo & 0xffff,
|
|
478
|
+
range.hi >>> 16, range.hi & 0xffff,
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
return spec
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** Decode the consecutive `[lo, hi]` code-point pairs in a class spec. */
|
|
485
|
+
export function decodeClassSpec(spec: string): Array<{ lo: number; hi: number }> {
|
|
486
|
+
if (spec.charCodeAt(0) === WIDE_CLASS_PREFIX_LO && spec.charCodeAt(1) === WIDE_CLASS_PREFIX_HI) {
|
|
487
|
+
if ((spec.length - 2) % 4 !== 0) throw new TypeError('table char-class spec has a truncated wide range')
|
|
488
|
+
if (spec.length === 2) throw new TypeError('table char-class spec has a wide prefix but no ranges')
|
|
489
|
+
const ranges: Array<{ lo: number; hi: number }> = []
|
|
490
|
+
for (let i = 2; i < spec.length; i += 4) {
|
|
491
|
+
const lo = spec.charCodeAt(i) * 0x10000 + spec.charCodeAt(i + 1)
|
|
492
|
+
const hi = spec.charCodeAt(i + 2) * 0x10000 + spec.charCodeAt(i + 3)
|
|
493
|
+
validateClassRange(lo, hi)
|
|
494
|
+
ranges.push({ lo, hi })
|
|
495
|
+
}
|
|
496
|
+
return ranges
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (spec.length % 2 !== 0) throw new TypeError('table char-class spec has an unmatched range endpoint')
|
|
500
|
+
const ranges: Array<{ lo: number; hi: number }> = []
|
|
501
|
+
for (let i = 0; i < spec.length; i += 2) {
|
|
502
|
+
const lo = spec.charCodeAt(i)
|
|
503
|
+
const hi = spec.charCodeAt(i + 1)
|
|
504
|
+
validateClassRange(lo, hi)
|
|
505
|
+
ranges.push({ lo, hi })
|
|
506
|
+
}
|
|
507
|
+
return ranges
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function resolveClass(spec: string): ResolvedClass {
|
|
511
|
+
const ascii = new Uint8Array(128)
|
|
512
|
+
const hi: number[] = []
|
|
513
|
+
for (const { lo, hi: up } of decodeClassSpec(spec)) {
|
|
514
|
+
if (lo < 128) for (let c = lo; c <= Math.min(up, 127); c++) ascii[c] = 1
|
|
515
|
+
if (up >= 128) hi.push(Math.max(lo, 128), up)
|
|
516
|
+
}
|
|
517
|
+
return { ascii, hi }
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export function classHas(cls: ResolvedClass, code: number): boolean {
|
|
521
|
+
if (code < 0) return false
|
|
522
|
+
if (code < 128) return cls.ascii[code] === 1
|
|
523
|
+
const hi = cls.hi
|
|
524
|
+
for (let i = 0; i < hi.length; i += 2) if (code >= hi[i]! && code <= hi[i + 1]!) return true
|
|
525
|
+
return false
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function resolveDispatch(arms: readonly number[], cc: readonly ResolvedClass[]): ResolvedDispatch {
|
|
529
|
+
const ascii = new Uint8Array(128)
|
|
530
|
+
const hi: number[] = []
|
|
531
|
+
const open: number[] = []
|
|
532
|
+
const armCls: (ResolvedClass | null)[] = []
|
|
533
|
+
// Disjointness over the ASCII plane, accumulated as the arms are laid in: a
|
|
534
|
+
// second arm claiming a char another already claims makes O(1) selection
|
|
535
|
+
// unsound, because "first class that holds c" stops being "first arm that
|
|
536
|
+
// matches". Above ASCII the `hi` ranges are not indexed, so any arm with a
|
|
537
|
+
// high range beyond the first is treated as overlapping — conservative, and
|
|
538
|
+
// the per-arm path below is correct for it either way.
|
|
539
|
+
let exclusive = true
|
|
540
|
+
let hiOwners = 0
|
|
541
|
+
for (let a = 0; a < arms.length; a++) {
|
|
542
|
+
const ci = arms[a]!
|
|
543
|
+
if (ci < 0) { open.push(a); armCls.push(null); exclusive = false; continue }
|
|
544
|
+
const cls = cc[ci]!
|
|
545
|
+
armCls.push(cls)
|
|
546
|
+
for (let c = 0; c < 128; c++) {
|
|
547
|
+
if (cls.ascii[c] !== 1) continue
|
|
548
|
+
if (ascii[c] === 0) ascii[c] = a + 1
|
|
549
|
+
else exclusive = false
|
|
550
|
+
}
|
|
551
|
+
if (cls.hi.length > 0 && ++hiOwners > 1) exclusive = false
|
|
552
|
+
for (let i = 0; i < cls.hi.length; i += 2) hi.push(cls.hi[i]!, cls.hi[i + 1]!, a)
|
|
553
|
+
}
|
|
554
|
+
return { ascii, hi, open, armCls, exclusive }
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Build the reference table for a program. Memoized per program OBJECT, which
|
|
559
|
+
* is the `(grammar, settings)` pair: the emitter produces one program per pair,
|
|
560
|
+
* so two variants are two cache entries and the run path never sees an option.
|
|
561
|
+
*/
|
|
562
|
+
/** Rebuild a trivia combinator from its spec using the SHARED constructors. */
|
|
563
|
+
function buildTrivia(spec: TriviaSpec): Combinator<unknown> {
|
|
564
|
+
if (spec.live !== undefined) return spec.live as Combinator<unknown>
|
|
565
|
+
if (spec.plain !== undefined) return triviaOf(regex(new RegExp(spec.plain[0], spec.plain[1])))
|
|
566
|
+
if (spec.alts !== undefined) {
|
|
567
|
+
const [head, ...rest] = spec.alts.map(([source, flags]) => regex(new RegExp(source, flags)))
|
|
568
|
+
if (head === undefined) throw new TypeError('trivia spec has an EMPTY alternation, which matches nothing')
|
|
569
|
+
const body = rest.length === 0 ? head : choice(head, ...rest)
|
|
570
|
+
// `many(x, { min: 1 })` and `oneOrMore(x)` are the IDENTICAL combinator, not
|
|
571
|
+
// merely equivalent — min>=1 routes to the same `atLeast` implementation
|
|
572
|
+
// (combinators/repeat.ts:126-131). So this rebuilds the source shape exactly
|
|
573
|
+
// for both floors and there is no second constructor to keep in step.
|
|
574
|
+
return triviaOf(many(body, { min: spec.min ?? 1 }))
|
|
575
|
+
}
|
|
576
|
+
const arms: Record<string, Combinator<unknown>> = {}
|
|
577
|
+
for (const [name, source, flags] of spec.arms) arms[name] = regex(new RegExp(source, flags))
|
|
578
|
+
return classifiedTrivia(arms)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export function resolveTable(prog: TableProgram): ResolvedTable {
|
|
582
|
+
const owner = (prog as RuntimeTableProgram)[TABLE_RUNTIME]
|
|
583
|
+
const hit = owner?.resolved
|
|
584
|
+
if (hit !== undefined) return hit
|
|
585
|
+
const cc = prog.cc.map(resolveClass)
|
|
586
|
+
const triviaBuilt = (prog.triviaSpecs ?? []).map(buildTrivia)
|
|
587
|
+
const built: ResolvedTable = {
|
|
588
|
+
prog,
|
|
589
|
+
code: Int32Array.from(prog.code),
|
|
590
|
+
k: prog.k,
|
|
591
|
+
fns: prog.fns,
|
|
592
|
+
cc,
|
|
593
|
+
fx: prog.fx,
|
|
594
|
+
disp: prog.disp.map(d => resolveDispatch(d, cc)),
|
|
595
|
+
trivia: triviaBuilt,
|
|
596
|
+
triviaScan: triviaBuilt.map(fastTriviaScanner),
|
|
597
|
+
triviaLabelled: triviaBuilt.map(t => t._meta.triviaKindLabels !== undefined),
|
|
598
|
+
dsp: prog.dsp.map(d => ({
|
|
599
|
+
byKey: new Map(d.key.map((x, i) => [x, d.keyArm[i]!])),
|
|
600
|
+
byFold: new Map(d.fold.map((x, i) => [x, d.foldArm[i]!])),
|
|
601
|
+
match: d.match,
|
|
602
|
+
routed: d.routed,
|
|
603
|
+
expected: d.expected,
|
|
604
|
+
})),
|
|
605
|
+
rules: prog.rules,
|
|
606
|
+
}
|
|
607
|
+
if (owner !== undefined) owner.resolved = built
|
|
608
|
+
return built
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/* ── THE VARIANT FOLD (ledger row G4) ────────────────────────────────────── */
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* One variant's whole difference from the base table, as SPARSE ROW EDITS.
|
|
615
|
+
*
|
|
616
|
+
* G5 says the variants *"differ in table contents, not in code"*, and G4 asks
|
|
617
|
+
* for ONE compiled output per input grammar. Measured across jess's four
|
|
618
|
+
* dialects, the four `trackLines` x `hostMode` tables differ in `code` and in
|
|
619
|
+
* two scalars and in NOTHING else — `k`, `cc`, `fx`, `disp`, `dsp`, `rules`,
|
|
620
|
+
* `fns`, `labels`, `classified`, `scanSkip`, `scanSkipOf`, `scans` and
|
|
621
|
+
* `triviaSpecs` are byte-identical in all four, and the code streams are the
|
|
622
|
+
* same LENGTH. So a variant is not another table. It is a list of words to
|
|
623
|
+
* overwrite.
|
|
624
|
+
*
|
|
625
|
+
* `at` is stored as ASCENDING GAPS, not absolute offsets: the edits are dense
|
|
626
|
+
* enough (mean gap under 10 on every dialect) that gaps are one or two digits
|
|
627
|
+
* where offsets are four, and the load-time cost of the difference is one `+=`.
|
|
628
|
+
*/
|
|
629
|
+
export type TableDelta = {
|
|
630
|
+
/** Gaps between successive edited word offsets; the first is absolute. */
|
|
631
|
+
readonly at: readonly number[]
|
|
632
|
+
/** Replacement words, parallel to `at`. */
|
|
633
|
+
readonly to: readonly number[]
|
|
634
|
+
readonly lines?: 0 | 1
|
|
635
|
+
readonly hostMode?: 'ast' | 'cst'
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** A base table plus the row edits that turn it into each named variant. */
|
|
639
|
+
export type FoldedProgram = {
|
|
640
|
+
readonly base: TableProgram
|
|
641
|
+
/** Variant name → its edits. The base's own name maps to an empty delta. */
|
|
642
|
+
readonly variants: Readonly<Record<string, TableDelta>>
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const FOLDED_RUNTIME = Symbol('parseman.foldedRuntime')
|
|
646
|
+
type RuntimeFoldedProgram = FoldedProgram & {
|
|
647
|
+
readonly [FOLDED_RUNTIME]?: { byName: Map<string, TableProgram> }
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function ownFoldedProgram(
|
|
651
|
+
base: TableProgram,
|
|
652
|
+
variants: Readonly<Record<string, TableDelta>>,
|
|
653
|
+
): FoldedProgram {
|
|
654
|
+
return { base, variants, [FOLDED_RUNTIME]: { byName: new Map() } } as RuntimeFoldedProgram
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** The wire form a folded artifact prints — short keys, as `CompactProgram`. */
|
|
658
|
+
export type CompactFolded = {
|
|
659
|
+
readonly b: CompactProgram
|
|
660
|
+
readonly v: Readonly<Record<string, { a?: readonly number[]; t?: readonly number[]; l?: 0 | 1; h?: 'ast' | 'cst' }>>
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* The fields a fold requires to be IDENTICAL across every variant.
|
|
665
|
+
*
|
|
666
|
+
* Named rather than derived so the refusal can say which one broke.
|
|
667
|
+
*/
|
|
668
|
+
const SHARED_FIELDS = [
|
|
669
|
+
'k', 'cc', 'fx', 'disp', 'dsp', 'rules', 'labels', 'classified',
|
|
670
|
+
'scanSkip', 'scanSkipOf', 'scans', 'triviaSpecs', 'runtimeOnly',
|
|
671
|
+
// `rec` is a property of the GRAMMAR BUILD, not of the trackLines/hostMode
|
|
672
|
+
// axis a fold varies — every variant of one export is encoded with the same
|
|
673
|
+
// recovery setting, and a mismatch is two tables, which is what the refusal says.
|
|
674
|
+
'rec',
|
|
675
|
+
// `cov` is a property of the GRAMMAR and its coverage plan, for the same reason
|
|
676
|
+
// `rec` is a property of the build: the plan is built from the rule map, which
|
|
677
|
+
// every variant of one export shares. It ships ONCE, and a variant that
|
|
678
|
+
// disagrees is two tables — which is what the refusal says.
|
|
679
|
+
'cov',
|
|
680
|
+
] as const satisfies readonly (keyof TableProgram)[]
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Every field of `TableProgram`, classified: shared once, compared by identity
|
|
684
|
+
* (`fns`), overwritten per variant (`code`), or carried on the delta as a
|
|
685
|
+
* scalar (`lines`, `hostMode`).
|
|
686
|
+
*
|
|
687
|
+
* `FoldExhaustive` fails to COMPILE the moment a field is added to
|
|
688
|
+
* `TableProgram` without a decision, which is the only way this list can be kept
|
|
689
|
+
* honest. A new variant-dependent field landing in `SHARED_FIELDS`' blind spot
|
|
690
|
+
* would ship one variant's data under every variant's name, and every parse
|
|
691
|
+
* would succeed.
|
|
692
|
+
*/
|
|
693
|
+
/**
|
|
694
|
+
* `asm` is classified as DROPPED, and it is the only field in that class.
|
|
695
|
+
*
|
|
696
|
+
* A pre-compiled assembly is emitted FROM a `code` stream, and `code` is exactly
|
|
697
|
+
* what a fold overwrites per variant — so a base assembly is wrong for every
|
|
698
|
+
* variant but the base, and silently so: it would parse, and parse the base
|
|
699
|
+
* grammar. Folding therefore happens BEFORE pre-compiling, and `foldPrograms`
|
|
700
|
+
* refuses an input that already carries one rather than dropping it quietly.
|
|
701
|
+
*/
|
|
702
|
+
type FoldDropped = 'asm'
|
|
703
|
+
type FoldClassified = 'code' | 'fns' | 'lines' | 'hostMode' | FoldDropped | (typeof SHARED_FIELDS)[number]
|
|
704
|
+
type AssertNever<T extends never> = T
|
|
705
|
+
export type FoldExhaustive = AssertNever<Exclude<keyof TableProgram, FoldClassified>>
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Fold N encoded programs into ONE table plus per-variant row edits.
|
|
709
|
+
*
|
|
710
|
+
* FAILS CLOSED, and that is the point. Every assumption the fold rests on is
|
|
711
|
+
* asserted here against the actual programs, so a grammar or a setting that
|
|
712
|
+
* breaks one produces a build error naming the field — not an artifact that
|
|
713
|
+
* loads and parses with the wrong table. The alternative, trusting a measurement
|
|
714
|
+
* taken once on four grammars, is how a variant axis acquires a silent third
|
|
715
|
+
* dimension.
|
|
716
|
+
*
|
|
717
|
+
* `fns` is compared by IDENTITY, not by source text. Two closures can print the
|
|
718
|
+
* same and capture different scopes; sharing one pool between variants is only
|
|
719
|
+
* sound when they are literally the same functions, which they are when the
|
|
720
|
+
* variants are encodings of ONE grammar export.
|
|
721
|
+
*/
|
|
722
|
+
export function foldPrograms(
|
|
723
|
+
programs: Readonly<Record<string, TableProgram>>,
|
|
724
|
+
baseName: string,
|
|
725
|
+
): FoldedProgram {
|
|
726
|
+
const suppliedBase = programs[baseName]
|
|
727
|
+
if (suppliedBase === undefined) throw new TypeError(`foldPrograms: no program named ${JSON.stringify(baseName)}`)
|
|
728
|
+
// The in-memory fold and its emitted form must be the same artifact: the
|
|
729
|
+
// emitter prints `a:[]`, so preserve that policy when a caller folds tables
|
|
730
|
+
// directly instead of loading a module.
|
|
731
|
+
const base = closureArtifact(suppliedBase)
|
|
732
|
+
const variants: Record<string, TableDelta> = {}
|
|
733
|
+
for (const name of Object.keys(programs)) {
|
|
734
|
+
const p = programs[name]!
|
|
735
|
+
// See `FoldDropped`. Refused rather than dropped: a base assembly carried
|
|
736
|
+
// onto a variant parses the BASE grammar under the variant's name, which no
|
|
737
|
+
// test that only asks "did it parse" would ever see.
|
|
738
|
+
if (p.asm !== undefined && p.asm.length > 0) {
|
|
739
|
+
throw new TypeError(
|
|
740
|
+
`foldPrograms: program ${JSON.stringify(name)} already carries pre-compiled assemblies. `
|
|
741
|
+
+ 'An assembly is emitted from a `code` stream and a fold overwrites `code` per variant, so '
|
|
742
|
+
+ 'pre-compiling must happen AFTER the fold, per variant — not before it.',
|
|
743
|
+
)
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* AN EMPTY `asm` IS NOT AN ASSEMBLY, it is the artifact saying "a build made
|
|
747
|
+
* me" — the flag that switches the runtime `Function` constructor off. It
|
|
748
|
+
* carries through the fold, because a folded artifact is no less a build
|
|
749
|
+
* product than an unfolded one. `unfoldVariant` spreads the base, so the
|
|
750
|
+
* variant inherits it.
|
|
751
|
+
*
|
|
752
|
+
* This mattered the moment `65fc9a4` moved `fold.ts` off the bytecode
|
|
753
|
+
* interpreter onto `tableRules`: the interpreter never evaluated
|
|
754
|
+
* anything, so the fold path was trivially clean, and swapping the driver
|
|
755
|
+
* silently handed every folded artifact a `new Function` on its first parse.
|
|
756
|
+
* `test/unit/no-function-constructor.test.ts` caught it on the merge.
|
|
757
|
+
*/
|
|
758
|
+
if (p.code.length !== base.code.length) {
|
|
759
|
+
throw new TypeError(
|
|
760
|
+
`foldPrograms: variant ${JSON.stringify(name)} has ${p.code.length} code words, base `
|
|
761
|
+
+ `${JSON.stringify(baseName)} has ${base.code.length}. A fold overwrites words; it cannot `
|
|
762
|
+
+ 'resize the stream. These are two different tables, not one table and its edits.',
|
|
763
|
+
)
|
|
764
|
+
}
|
|
765
|
+
for (const f of SHARED_FIELDS) {
|
|
766
|
+
if (JSON.stringify(p[f]) !== JSON.stringify(base[f])) {
|
|
767
|
+
throw new TypeError(
|
|
768
|
+
`foldPrograms: variant ${JSON.stringify(name)} differs from base ${JSON.stringify(baseName)} `
|
|
769
|
+
+ `in ${JSON.stringify(f)}, which the fold ships ONCE. Either the variants are not encodings `
|
|
770
|
+
+ 'of one grammar, or this field has become variant-dependent and needs its own delta.',
|
|
771
|
+
)
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (p.fns.length !== base.fns.length || p.fns.some((f, i) => f !== base.fns[i])) {
|
|
775
|
+
throw new TypeError(
|
|
776
|
+
`foldPrograms: variant ${JSON.stringify(name)} does not share the base's reducer pool BY `
|
|
777
|
+
+ 'IDENTITY. Equal source text is not enough — two closures can print alike and capture '
|
|
778
|
+
+ 'different scopes. Encode every variant from ONE grammar export.',
|
|
779
|
+
)
|
|
780
|
+
}
|
|
781
|
+
const at: number[] = []
|
|
782
|
+
const to: number[] = []
|
|
783
|
+
let prev = 0
|
|
784
|
+
for (let i = 0; i < p.code.length; i++) {
|
|
785
|
+
if (p.code[i] === base.code[i]) continue
|
|
786
|
+
at.push(i - prev)
|
|
787
|
+
to.push(p.code[i]!)
|
|
788
|
+
prev = i
|
|
789
|
+
}
|
|
790
|
+
variants[name] = {
|
|
791
|
+
at, to,
|
|
792
|
+
...(p.lines === undefined ? {} : { lines: p.lines }),
|
|
793
|
+
...(p.hostMode === undefined ? {} : { hostMode: p.hostMode }),
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return ownFoldedProgram(base, variants)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Materialise ONE variant of a folded table, at load, when it is selected.
|
|
801
|
+
*
|
|
802
|
+
* Every field but `code` is shared BY REFERENCE with the base — no copying, no
|
|
803
|
+
* re-parsing. The resolved table is derived from the base's, so N variants cost
|
|
804
|
+
* ONE char-class expansion, ONE dispatch build and ONE trivia rebuild between
|
|
805
|
+
* them instead of N. Selecting a variant is strictly LESS load work than
|
|
806
|
+
* building an Nth independent table, which is the opposite of the usual
|
|
807
|
+
* size-versus-load-time trade.
|
|
808
|
+
*
|
|
809
|
+
* The driver still sees nothing but a `TableProgram`. There is no variant flag
|
|
810
|
+
* on the parse path and nothing here is consulted again after load — the option
|
|
811
|
+
* half of G5 is untouched.
|
|
812
|
+
*/
|
|
813
|
+
export function unfoldVariant(folded: FoldedProgram, name: string): TableProgram {
|
|
814
|
+
const byName = (folded as RuntimeFoldedProgram)[FOLDED_RUNTIME]?.byName
|
|
815
|
+
const hit = byName?.get(name)
|
|
816
|
+
if (hit !== undefined) return hit
|
|
817
|
+
const d = folded.variants[name]
|
|
818
|
+
if (d === undefined) {
|
|
819
|
+
throw new TypeError(
|
|
820
|
+
`unfoldVariant: no variant ${JSON.stringify(name)}. This table carries: `
|
|
821
|
+
+ `${Object.keys(folded.variants).map(n => JSON.stringify(n)).join(', ')}.`,
|
|
822
|
+
)
|
|
823
|
+
}
|
|
824
|
+
const base = folded.base
|
|
825
|
+
const code = [...base.code]
|
|
826
|
+
let ip = 0
|
|
827
|
+
for (let i = 0; i < d.at.length; i++) { ip += d.at[i]!; code[ip] = d.to[i]! }
|
|
828
|
+
const raw: TableProgram = {
|
|
829
|
+
...base,
|
|
830
|
+
code,
|
|
831
|
+
...(d.lines === undefined ? {} : { lines: d.lines }),
|
|
832
|
+
...(d.hostMode === undefined ? {} : { hostMode: d.hostMode }),
|
|
833
|
+
}
|
|
834
|
+
// Seeded from the BASE's resolved table: `cc`, `disp`, `dsp` and the rebuilt
|
|
835
|
+
// trivia are identical by `foldPrograms`' own assertion, so N variants cost
|
|
836
|
+
// ONE of each between them. Only the code stream is per variant.
|
|
837
|
+
const prog = ownTableProgram(raw)
|
|
838
|
+
const owner = (prog as RuntimeTableProgram)[TABLE_RUNTIME]!
|
|
839
|
+
owner.resolved = { ...resolveTable(base), prog, code: Int32Array.from(code) }
|
|
840
|
+
byName?.set(name, prog)
|
|
841
|
+
return prog
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
export function expandCompactFolded(f: CompactFolded): FoldedProgram {
|
|
845
|
+
const variants: Record<string, TableDelta> = {}
|
|
846
|
+
for (const name of Object.keys(f.v)) {
|
|
847
|
+
const d = f.v[name]!
|
|
848
|
+
variants[name] = {
|
|
849
|
+
at: d.a ?? [], to: d.t ?? [],
|
|
850
|
+
...(d.l === undefined ? {} : { lines: d.l }),
|
|
851
|
+
...(d.h === undefined ? {} : { hostMode: d.h }),
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return ownFoldedProgram(expandCompact(f.b), variants)
|
|
855
|
+
}
|