parseman 0.59.2 → 0.60.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/dist/analysis/commitment.d.ts +1 -1
- package/dist/analysis/diagnostics.js +877 -191
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +877 -191
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/completions.d.ts +3 -3
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +13 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts +35 -2
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +11 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +1 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +2 -3
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +2 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +65 -53
- package/dist/compiler/token-capability.js.map +2 -2
- package/dist/cst/host-mode.d.ts +1 -1
- package/dist/cst/root-trivia-scope.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +897 -200
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.d.ts +1 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/entry-supercompiler.d.ts.map +1 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +3 -7
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +904 -202
- package/dist/plugin/index.js.map +3 -3
- package/dist/regex/first-set.d.ts +19 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/spec/index.js.map +1 -1
- package/dist/table/assemble.d.ts +16 -3
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/child-slots.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +3 -2
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/compile.d.ts +2 -2
- package/dist/table/emit-assembly.d.ts +8 -2
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts +8 -8
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts +6 -7
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/factor-choice.d.ts +27 -0
- package/dist/table/factor-choice.d.ts.map +1 -1
- package/dist/table/gap-gate.d.ts +11 -0
- package/dist/table/gap-gate.d.ts.map +1 -0
- package/dist/table/index.js +927 -202
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-program.d.ts.map +1 -1
- package/dist/table/ops.d.ts +48 -20
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +13 -8
- package/dist/table/program.d.ts.map +1 -1
- package/dist/types.d.ts +17 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +6 -6
- package/package.json +1 -1
- package/src/analysis/commitment.ts +1 -1
- package/src/analysis/duplication.ts +1 -1
- package/src/analysis/terminal.ts +1 -1
- package/src/combinators/attempt.ts +1 -1
- package/src/combinators/choice.ts +1 -1
- package/src/combinators/completions.ts +3 -3
- package/src/combinators/dispatch.ts +30 -6
- package/src/combinators/first-set.ts +55 -12
- package/src/combinators/gate.ts +1 -1
- package/src/combinators/node.ts +1 -1
- package/src/combinators/regex.ts +9 -4
- package/src/combinators/repeat.ts +6 -6
- package/src/combinators/sequence.ts +1 -1
- package/src/combinators/trivia-boundary.ts +18 -0
- package/src/combinators/trivia-skip.ts +1 -1
- package/src/compiler/compile-linkable-table.ts +2 -3
- package/src/compiler/degradation.ts +2 -2
- package/src/compiler/ir-serialize.ts +1 -1
- package/src/compiler/linker.ts +5 -3
- package/src/compiler/token-alphabet.ts +3 -1
- package/src/coverage.ts +1 -1
- package/src/cst/capture-buffer.ts +2 -2
- package/src/cst/host-mode.ts +1 -1
- package/src/cst/root-trivia-scope.ts +1 -1
- package/src/index.ts +1 -1
- package/src/language-service/index.ts +1 -1
- package/src/plugin/entry-supercompiler.ts +2 -1
- package/src/plugin/evaluator.ts +8 -3
- package/src/plugin/index.ts +11 -16
- package/src/regex/first-set.ts +37 -3
- package/src/table/assemble.ts +180 -35
- package/src/table/child-slots.ts +8 -3
- package/src/table/compile-rule-map.ts +10 -6
- package/src/table/compile.ts +3 -3
- package/src/table/emit-assembly.ts +186 -35
- package/src/table/emit.ts +19 -18
- package/src/table/encode.ts +482 -88
- package/src/table/exec.ts +42 -18
- package/src/table/factor-choice.ts +62 -0
- package/src/table/fail-clean.ts +2 -2
- package/src/table/gap-gate.ts +55 -0
- package/src/table/hold-gap.ts +2 -2
- package/src/table/lex-program.ts +15 -6
- package/src/table/ops.ts +51 -20
- package/src/table/program.ts +14 -9
- package/src/table/select-hot.ts +7 -2
- package/src/table/stamp.ts +1 -1
- package/src/types.ts +13 -6
- package/src/version.ts +6 -6
package/src/table/exec.ts
CHANGED
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
22
22
|
OP_LIVE,
|
|
23
23
|
OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD, OP_ATTEMPT, OP_LABEL,
|
|
24
|
-
OP_COV,
|
|
25
|
-
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE,
|
|
24
|
+
OP_COV, OP_GAPGATE, OP_STRICT,
|
|
25
|
+
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE, OP_RGATE2,
|
|
26
26
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
27
27
|
} from './ops.ts'
|
|
28
28
|
import { adjacencyHolds, adjacencyMisuse } from '../combinators/adjacency.ts'
|
|
@@ -30,9 +30,8 @@ import { failAt } from '../combinators/probe.ts'
|
|
|
30
30
|
/**
|
|
31
31
|
* THE COMPLETIONS PROBE, at the terminal fail sites and nowhere else.
|
|
32
32
|
*
|
|
33
|
-
* `failAt` (combinators/probe.ts) is called
|
|
34
|
-
*
|
|
35
|
-
* mirror (`probeUpdate`) at the same leaf-fail sites. The table recorded
|
|
33
|
+
* `failAt` (combinators/probe.ts) is called at the interpreter's terminal fail
|
|
34
|
+
* sites — `literal.ts`, `regex.ts`, `keywords.ts`, `lines.ts`. The table recorded
|
|
36
35
|
* NOTHING, so `completionsAt` on a table artifact saw only the top-level
|
|
37
36
|
* failure: a swallowed item failure inside a `sepBy` (`'{'` at offset 1 in a
|
|
38
37
|
* `{ decl; }` grammar) never reached the probe and the item's opener vanished
|
|
@@ -376,6 +375,11 @@ function makeDriver(
|
|
|
376
375
|
}
|
|
377
376
|
|
|
378
377
|
function exec(ip: number, input: string, pos: number, ctx: ParseContext): unknown {
|
|
378
|
+
// A row that only hands on to one child at the same position (a gate that
|
|
379
|
+
// passes, a rule trampoline, `OP_STRICT`) continues here rather than
|
|
380
|
+
// recursing, so the reference driver's stack grows with the grammar's
|
|
381
|
+
// nesting, not with its gate rows.
|
|
382
|
+
rows: for (;;) {
|
|
379
383
|
if (COUNT) { tableCounters.rows++; tableCounters.byOp[code[ip]!]!++ }
|
|
380
384
|
switch (code[ip]) {
|
|
381
385
|
case OP_LIT: {
|
|
@@ -493,7 +497,7 @@ function makeDriver(
|
|
|
493
497
|
ctx._fe = pos; ctx._fx = fx[code[ip + 3]!] as string[]
|
|
494
498
|
return FAIL
|
|
495
499
|
}
|
|
496
|
-
|
|
500
|
+
ip = code[ip + 2]!; continue rows
|
|
497
501
|
}
|
|
498
502
|
|
|
499
503
|
case OP_LABEL: {
|
|
@@ -503,7 +507,7 @@ function makeDriver(
|
|
|
503
507
|
return v
|
|
504
508
|
}
|
|
505
509
|
case OP_RULE:
|
|
506
|
-
|
|
510
|
+
ip = code[ip + 1]!; continue rows
|
|
507
511
|
|
|
508
512
|
/**
|
|
509
513
|
* A COUNTER ROW IS TRANSPARENT HERE, and that is the whole of its handling.
|
|
@@ -519,8 +523,14 @@ function makeDriver(
|
|
|
519
523
|
* a second implementation of it here would be a second place for the phase
|
|
520
524
|
* semantics (entry for a rule, success for an arm) to drift.
|
|
521
525
|
*/
|
|
526
|
+
// The reference runs a gap-gated item as written: the gate is the repeat's,
|
|
527
|
+
// in the shipped engines, and this driver is the leg it is compared with.
|
|
522
528
|
case OP_COV:
|
|
523
|
-
|
|
529
|
+
case OP_GAPGATE:
|
|
530
|
+
ip = code[ip + 1]!; continue rows
|
|
531
|
+
|
|
532
|
+
case OP_STRICT:
|
|
533
|
+
ip = code[ctx._tolerant || ctx._probe !== undefined ? ip + 2 : ip + 1]!; continue rows
|
|
524
534
|
|
|
525
535
|
case OP_LIVE: {
|
|
526
536
|
// A HAND-WRITTEN COMBINATOR, run through its own `.parse` — mirrors
|
|
@@ -552,7 +562,7 @@ function makeDriver(
|
|
|
552
562
|
const err = { _tag: 'parseError' as const, span, expected: fx[code[ip + 2]!] as string[] }
|
|
553
563
|
ctx._errors?.push(err)
|
|
554
564
|
// A TOLERANT `expect()` also embeds the error in the TREE (expect.ts:150,
|
|
555
|
-
//
|
|
565
|
+
// as the source lowering did), so a tree walk finds every diagnostic and it survives
|
|
556
566
|
// incremental subtree reuse — the flat sink alone is not the contract.
|
|
557
567
|
if (REC && ctx._tolerant === true) captureError(ctx, err)
|
|
558
568
|
// See assemble.ts OP_EXPECT: the recovered failure is a SUCCESS, so the
|
|
@@ -602,6 +612,7 @@ function makeDriver(
|
|
|
602
612
|
|
|
603
613
|
let target: number
|
|
604
614
|
let usesRouted: boolean
|
|
615
|
+
let commits = true
|
|
605
616
|
if (arm === undefined) {
|
|
606
617
|
const other = code[ip + 3]!
|
|
607
618
|
if (other < 0) {
|
|
@@ -611,7 +622,8 @@ function makeDriver(
|
|
|
611
622
|
return FAIL
|
|
612
623
|
}
|
|
613
624
|
target = other
|
|
614
|
-
usesRouted = code[ip + 4]! === 1
|
|
625
|
+
usesRouted = (code[ip + 4]! & 1) === 1
|
|
626
|
+
commits = (code[ip + 4]! & 2) === 0
|
|
615
627
|
} else {
|
|
616
628
|
target = code[armBase + arm]!
|
|
617
629
|
usesRouted = spec.routed[arm] === 1
|
|
@@ -638,8 +650,8 @@ function makeDriver(
|
|
|
638
650
|
rollbackTrivia(ctx, mark)
|
|
639
651
|
// The interpreter marks a failed dispatch branch COMMITTED: the
|
|
640
652
|
// selector already matched, so an enclosing choice must not treat this
|
|
641
|
-
// as "try the next arm".
|
|
642
|
-
ctx._fc = true
|
|
653
|
+
// as "try the next arm". An uncommitted fallback keeps its own flag.
|
|
654
|
+
if (commits) ctx._fc = true
|
|
643
655
|
return FAIL
|
|
644
656
|
}
|
|
645
657
|
// The end cell already holds the branch's end — dispatch's span runs from `pos`
|
|
@@ -1030,6 +1042,12 @@ function makeDriver(
|
|
|
1030
1042
|
// its `g.X` arms were unresolved refs (choice.ts:35 — the same staleness
|
|
1031
1043
|
// `encode.ts` recomputes around), so they firstMatch all seven arms where
|
|
1032
1044
|
// this driver dispatches to one. The arm sets differ, not the rule.
|
|
1045
|
+
//
|
|
1046
|
+
// Under a completions probe the interpreter runs every arm on a miss
|
|
1047
|
+
// (`choice.ts`), so each opener reaches the probe; so does this.
|
|
1048
|
+
if (arm < 0 && ctx._probe !== undefined) {
|
|
1049
|
+
for (let i = 0; i < code[ip + 2]!; i++) exec(code[base + i]!, input, pos, ctx)
|
|
1050
|
+
}
|
|
1033
1051
|
return failChoice()
|
|
1034
1052
|
}
|
|
1035
1053
|
// THE PER-ARM GATE. Arms in source order, each skipped when its own
|
|
@@ -1064,7 +1082,9 @@ function makeDriver(
|
|
|
1064
1082
|
const armFx = base + n
|
|
1065
1083
|
for (let i = 0; i < n; i++) {
|
|
1066
1084
|
const cls = armCls[i]
|
|
1067
|
-
|
|
1085
|
+
// Under a completion probe every arm is entered, as the interpreter's
|
|
1086
|
+
// `firstMatch` enters it: its opener then reaches the probe (`failAt`).
|
|
1087
|
+
if (ctx._probe === undefined && cls !== undefined && cls !== null && !classHas(cls, c)) {
|
|
1068
1088
|
if (COUNT) tableCounters.armGateSkips++
|
|
1069
1089
|
// THE GATED ARM STILL REPORTS. The interpreter has no char gate: it
|
|
1070
1090
|
// enters the arm, the arm's opener fails, and its static set is what
|
|
@@ -1120,14 +1140,17 @@ function makeDriver(
|
|
|
1120
1140
|
return sup
|
|
1121
1141
|
}
|
|
1122
1142
|
|
|
1123
|
-
|
|
1143
|
+
// The reference tests the first character only: the second is the shipped
|
|
1144
|
+
// engines' (`OP_RGATE2`), and this driver is the leg they are compared with.
|
|
1145
|
+
case OP_RGATE:
|
|
1146
|
+
case OP_RGATE2: {
|
|
1124
1147
|
const item = ctx._routed
|
|
1125
1148
|
if (item !== undefined && item.span.start === pos && ctx._probe === undefined && !ctx._tolerant
|
|
1126
1149
|
&& !classHas(cc[code[ip + 1]!]!, lead(input, item.span.end))) {
|
|
1127
1150
|
ctx._fe = item.span.end; ctx._fx = fx[code[ip + 3]!] as string[]
|
|
1128
1151
|
return FAIL
|
|
1129
1152
|
}
|
|
1130
|
-
|
|
1153
|
+
ip = code[ip + 2]!; continue rows
|
|
1131
1154
|
}
|
|
1132
1155
|
|
|
1133
1156
|
case OP_ARMGATE: {
|
|
@@ -1142,7 +1165,7 @@ function makeDriver(
|
|
|
1142
1165
|
ctx._fx = fx[code[ip + 3]!] as string[]
|
|
1143
1166
|
return FAIL
|
|
1144
1167
|
}
|
|
1145
|
-
|
|
1168
|
+
ip = code[ip + 2]!; continue rows
|
|
1146
1169
|
}
|
|
1147
1170
|
|
|
1148
1171
|
case OP_REJECT: {
|
|
@@ -1222,11 +1245,11 @@ function makeDriver(
|
|
|
1222
1245
|
// TOLERANT RECOVERY, dormant unless `ctx._tolerant`. `mySync` is captured
|
|
1223
1246
|
// at ENTRY because an element's own sequence publishes over `_sync` while
|
|
1224
1247
|
// it runs — the interpreter restores in a `finally` (sequence.ts:105) and
|
|
1225
|
-
//
|
|
1248
|
+
// the source lowering saved at entry; either way this is the list's
|
|
1226
1249
|
// own sync. The item's expected set is at ip + 6 and the separator's
|
|
1227
1250
|
// sentinel class at ip + 7, both laid down only for a recovery table.
|
|
1228
1251
|
const mySync = REC ? ctx._sync : undefined
|
|
1229
|
-
const recFx = REC ? fx[code[ip + 6]!] as string[]
|
|
1252
|
+
const recFx = !REC ? undefined : (code[ip + 5]! & 16) !== 0 ? [] : fx[code[ip + 6]!] as string[]
|
|
1230
1253
|
const sepSent = REC ? sentinelFor(code[ip + 7]!) : undefined
|
|
1231
1254
|
const itemClassIndex = sep < 0 ? code[ip + 7]! : -1
|
|
1232
1255
|
const itemClass = REC && itemClassIndex >= 0 ? cc[itemClassIndex]! : undefined
|
|
@@ -1645,6 +1668,7 @@ function makeDriver(
|
|
|
1645
1668
|
default:
|
|
1646
1669
|
throw new Error(`table driver: unknown opcode ${String(code[ip])} at ${ip}`)
|
|
1647
1670
|
}
|
|
1671
|
+
}
|
|
1648
1672
|
}
|
|
1649
1673
|
|
|
1650
1674
|
/**
|
|
@@ -35,8 +35,16 @@ import { sequence } from '../combinators/sequence.ts'
|
|
|
35
35
|
import { leaf, sourceLeaf } from '../combinators/token.ts'
|
|
36
36
|
import { noTrivia, parser } from '../combinators/grammar.ts'
|
|
37
37
|
import { attempt } from '../combinators/attempt.ts'
|
|
38
|
+
import { optional } from '../combinators/repeat.ts'
|
|
38
39
|
import { mayCommitFailure } from '../analysis/commitment.ts'
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The choices this module builds. The interpreter never runs them: it runs the
|
|
43
|
+
* authored choice, so a factored one's `disjoint` says nothing about which arms
|
|
44
|
+
* the interpreter lets in (`Encoder` classifies them by their derived sets).
|
|
45
|
+
*/
|
|
46
|
+
export const factoredChoices = new WeakSet<Combinator<unknown>>()
|
|
47
|
+
|
|
40
48
|
type ChoiceDef = Extract<ParserDef, { tag: 'choice' }>
|
|
41
49
|
|
|
42
50
|
/** Own keys every combinator may carry beside its definition; all derived from it. */
|
|
@@ -151,6 +159,7 @@ export function factorSharedEnds(d: ChoiceDef): Combinator<unknown> | undefined
|
|
|
151
159
|
// it walks everything the arms reach.
|
|
152
160
|
if (arms.some(a => mayCommitFailure(a))) return undefined
|
|
153
161
|
const middle = choice(...(shapes.map(s => s.terms[at]!) as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
162
|
+
factoredChoices.add(middle)
|
|
154
163
|
// The inner choice must keep arm order: a strategy that reorders (longest
|
|
155
164
|
// literal first) or classifies would pick a different arm. Nor may it add an
|
|
156
165
|
// automatic lookahead the arms as written do not have: `'start'` before
|
|
@@ -171,3 +180,56 @@ export function factorSharedEnds(d: ChoiceDef): Combinator<unknown> | undefined
|
|
|
171
180
|
// `attempt`. The sequence alone would report where its inner choice failed.
|
|
172
181
|
return out === undefined ? undefined : attempt(out)
|
|
173
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* AN ARM WHOSE NEXT SIBLING IS ITS OWN PREFIX — `choice(sequence(X, a), X)`.
|
|
186
|
+
*
|
|
187
|
+
* On input where `X` matches and `a` does not, the choice parses `X`, fails `a`,
|
|
188
|
+
* rolls back, and parses `X` again for the next arm. Where the choice's value is
|
|
189
|
+
* unused (its sequence arms are `valueUnused`, so only their captures reach the
|
|
190
|
+
* enclosing node), the first arm may be `sequence(X, optional(a))`:
|
|
191
|
+
*
|
|
192
|
+
* - `X` then `a` matches: the arm as it was.
|
|
193
|
+
* - `X` matches, `a` fails uncommitted: the optional matches empty, rolls `a`'s
|
|
194
|
+
* captures back and leaves the gap before it unrecorded, so the arm ends where
|
|
195
|
+
* `X` did with `X`'s captures, which is what the retried next arm produced.
|
|
196
|
+
* - `a` fails committed: the arm fails committed, as it did.
|
|
197
|
+
* - `X` fails: the arm fails as before, and the next arm, kept as it is, fails the
|
|
198
|
+
* same way, so the choice merges the same sets in the same order.
|
|
199
|
+
*
|
|
200
|
+
* `X` is the next arm's sequence of terms, or that arm itself as a single term,
|
|
201
|
+
* and "the same" is `sameComb`. Gated arms, `autoNot` and strategies that reorder
|
|
202
|
+
* are left alone.
|
|
203
|
+
*/
|
|
204
|
+
export function factorPrefixArms(d: ChoiceDef): Combinator<unknown> | undefined {
|
|
205
|
+
const arms = d.parsers
|
|
206
|
+
if (arms.length < 2) return undefined
|
|
207
|
+
if (d.strategy.tag !== 'firstMatch' && d.strategy.tag !== 'sharedPrefix') return undefined
|
|
208
|
+
if (d.gates.some(g => g !== null) || d.autoNot.some(a => a !== null)) return undefined
|
|
209
|
+
const out = [...arms]
|
|
210
|
+
let changed = false
|
|
211
|
+
for (let i = 0; i + 1 < arms.length; i++) {
|
|
212
|
+
const arm = arms[i]!
|
|
213
|
+
if (arm._def.tag !== 'sequence' || arm._def.valueUnused !== true) continue
|
|
214
|
+
const terms = arm._def.parsers
|
|
215
|
+
const next = arms[i + 1]!
|
|
216
|
+
const prefix = next._def.tag === 'sequence' && next._def.valueUnused === true ? next._def.parsers : [next]
|
|
217
|
+
if (prefix.length >= terms.length) continue
|
|
218
|
+
if (!prefix.every((t, j) => sameComb(t, terms[j]!, 0))) continue
|
|
219
|
+
const rest = terms.slice(prefix.length)
|
|
220
|
+
// A rest that cannot fail (already `optional`, or `many`) has nothing to factor.
|
|
221
|
+
if (rest.every(never)) continue
|
|
222
|
+
const tail = rest.length === 1 ? rest[0]! : sequence(...(rest as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
223
|
+
if (tail !== rest[0]) (tail._def as { valueUnused?: boolean }).valueUnused = true
|
|
224
|
+
const parts: Combinator<unknown>[] = [...terms.slice(0, prefix.length), optional(tail)]
|
|
225
|
+
const seq = sequence(...(parts as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
226
|
+
;(seq._def as { valueUnused?: boolean }).valueUnused = true
|
|
227
|
+
out[i] = seq
|
|
228
|
+
changed = true
|
|
229
|
+
}
|
|
230
|
+
if (!changed) return undefined
|
|
231
|
+
const rebuilt = choice(...(out as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
232
|
+
factoredChoices.add(rebuilt)
|
|
233
|
+
const st = rebuilt._def.tag === 'choice' ? rebuilt._def.strategy : undefined
|
|
234
|
+
return st !== undefined && (st.tag === 'firstMatch' || st.tag === 'sharedPrefix') ? rebuilt : undefined
|
|
235
|
+
}
|
package/src/table/fail-clean.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
OP_ATTEMPT, OP_CHOICE, OP_COV, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE, OP_GUARD, OP_LABEL,
|
|
25
25
|
OP_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK,
|
|
26
26
|
OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX, OP_RX_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ,
|
|
27
|
-
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
27
|
+
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM, OP_GAPGATE,
|
|
28
28
|
} from './ops.ts'
|
|
29
29
|
|
|
30
30
|
export type FailClean = (ip: number) => boolean
|
|
@@ -35,7 +35,7 @@ export type FailClean = (ip: number) => boolean
|
|
|
35
35
|
*/
|
|
36
36
|
function passChild(code: ArrayLike<number>, ip: number): number {
|
|
37
37
|
switch (code[ip]) {
|
|
38
|
-
case OP_RULE: case OP_LABEL: case OP_COV: case OP_TOKEN:
|
|
38
|
+
case OP_RULE: case OP_LABEL: case OP_COV: case OP_TOKEN: case OP_GAPGATE:
|
|
39
39
|
return code[ip + 1]!
|
|
40
40
|
case OP_GATE: case OP_NODE: case OP_NODE_TRACK: case OP_XFORM: case OP_FIELD: case OP_SCOPE:
|
|
41
41
|
case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_LEAF: case OP_SOURCE_LEAF:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AN OPTIONAL REPEAT ITEM THAT OPENS WITH A GAP (`OP_GAPGATE`).
|
|
3
|
+
*
|
|
4
|
+
* SCSS writes its operators as `sequence(pad, '*', pad)` with `pad` a regex that
|
|
5
|
+
* may match empty, and a value chain has a repeat of them at every level. After
|
|
6
|
+
* a value followed by a space, each level entered its operator, scanned the
|
|
7
|
+
* space and failed on the character after it: the same gap read once per level,
|
|
8
|
+
* and an operator node's frame opened each time.
|
|
9
|
+
*
|
|
10
|
+
* The repeat now reads the gap itself, ONCE, with the item's own regex, and
|
|
11
|
+
* looks at the character after it: an item that opens with the gap must
|
|
12
|
+
* continue with a character of `cls` there (and an arm without the gap must
|
|
13
|
+
* start with one of `other` where the gap starts). Where neither holds the item
|
|
14
|
+
* cannot match, so the repeat ends, exactly as it ends on a strict item whose
|
|
15
|
+
* class excludes the lead. Otherwise it enters the item and leaves the gap's end
|
|
16
|
+
* in the GAP SLOT, from which the regex row takes it instead of scanning again.
|
|
17
|
+
*
|
|
18
|
+
* The slot is a memo of one pure function: the end of regex row `row` matched
|
|
19
|
+
* at `at` in `input`. A row reads it only when all three are its own, so a
|
|
20
|
+
* different row, position or input cannot take it, and a slot left set can
|
|
21
|
+
* only ever give a row the end it would have computed. Only rows the table
|
|
22
|
+
* names as a gap (`gapRows`) test it at all. It is set for the item and cleared
|
|
23
|
+
* when the item returns.
|
|
24
|
+
*
|
|
25
|
+
* Not under tolerant recovery or a completions probe, which observe an item's
|
|
26
|
+
* failure: there the repeat enters the item as written. The reference driver
|
|
27
|
+
* (`exec.ts`) always does, which is what makes it the ungated leg.
|
|
28
|
+
*/
|
|
29
|
+
import { childSlots } from './child-slots.ts'
|
|
30
|
+
import { OP_GAPGATE } from './ops.ts'
|
|
31
|
+
|
|
32
|
+
/** The gap slot one assembly shares between its closure and emitted pieces. */
|
|
33
|
+
export type GapSlot = { row: number; at: number; end: number; input: string | undefined }
|
|
34
|
+
|
|
35
|
+
export function newGapSlot(): GapSlot {
|
|
36
|
+
return { row: -1, at: -1, end: -1, input: undefined }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The regex rows some reachable `OP_GAPGATE` names as its gap. */
|
|
40
|
+
export function gapRows(code: ArrayLike<number>, roots: Iterable<number>): Set<number> {
|
|
41
|
+
const rows = new Set<number>()
|
|
42
|
+
const seen = new Set<number>()
|
|
43
|
+
const stack = [...roots]
|
|
44
|
+
const kids: number[] = []
|
|
45
|
+
while (stack.length > 0) {
|
|
46
|
+
const ip = stack.pop()!
|
|
47
|
+
if (seen.has(ip)) continue
|
|
48
|
+
seen.add(ip)
|
|
49
|
+
if (code[ip] === OP_GAPGATE) rows.add(code[ip + 2]!)
|
|
50
|
+
kids.length = 0
|
|
51
|
+
childSlots(code, ip, kids)
|
|
52
|
+
for (const c of kids) stack.push(c)
|
|
53
|
+
}
|
|
54
|
+
return rows
|
|
55
|
+
}
|
package/src/table/hold-gap.ts
CHANGED
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
import {
|
|
18
18
|
OP_ADJ, OP_ATTEMPT, OP_CHOICE, OP_COV, OP_EXPECT, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI,
|
|
19
19
|
OP_NODE, OP_NOT, OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ,
|
|
20
|
-
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
20
|
+
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM, OP_GAPGATE,
|
|
21
21
|
} from './ops.ts'
|
|
22
22
|
import { childSlots } from './child-slots.ts'
|
|
23
23
|
import { leadingTriviaFieldAt } from './field-spec.ts'
|
|
24
24
|
import { regexCanMatchEmpty } from '../regex/first-set.ts'
|
|
25
25
|
|
|
26
26
|
const WRAPPERS = new Set([OP_NODE, OP_XFORM, OP_LEAF, OP_SOURCE_LEAF, OP_TOKEN, OP_SCOPE, OP_SCOPE_CAP,
|
|
27
|
-
OP_SCOPE_PLAIN, OP_FIELD, OP_LABEL, OP_EXPECT, OP_WITHCTX, OP_RULE, OP_ATTEMPT, OP_COV])
|
|
27
|
+
OP_SCOPE_PLAIN, OP_FIELD, OP_LABEL, OP_EXPECT, OP_WITHCTX, OP_RULE, OP_ATTEMPT, OP_COV, OP_GAPGATE])
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Can the row at `ip` match empty? `true` wherever it cannot tell. A first-set
|
package/src/table/lex-program.ts
CHANGED
|
@@ -187,7 +187,7 @@ function sequence5(
|
|
|
187
187
|
|
|
188
188
|
type ChoiceTail = (
|
|
189
189
|
input: string, pos: number, ctx: ParseContext, lead: number,
|
|
190
|
-
acc: string[] | undefined, best: number,
|
|
190
|
+
acc: string[] | undefined, best: number, entered: boolean,
|
|
191
191
|
) => number
|
|
192
192
|
|
|
193
193
|
function appendExpected(acc: string[] | undefined, values: readonly string[] | undefined): string[] | undefined {
|
|
@@ -211,19 +211,22 @@ function choiceNode(
|
|
|
211
211
|
return end < 0 ? next(input, pos) : end
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
-
let tail: ChoiceTail = (
|
|
214
|
+
let tail: ChoiceTail = (input, pos, ctx, _lead, acc, _best, entered) => {
|
|
215
|
+
// No arm was entered. A completion probe still sees each one fail on its
|
|
216
|
+
// opener, as the interpreter's entered arms leave it.
|
|
217
|
+
if (!entered && ctx._probe !== undefined) for (const arm of arms) arm.run(input, pos, ctx)
|
|
215
218
|
ctx._fe = pos
|
|
216
219
|
ctx._fx = acc ?? choiceExpected as string[]
|
|
217
220
|
return -1
|
|
218
221
|
}
|
|
219
222
|
for (let i = arms.length - 1; i >= 0; i--) {
|
|
220
223
|
const armRun = arms[i]!.run, cls = classes[i], staticExpected = armExpected[i]!, next = tail
|
|
221
|
-
tail = (input, pos, ctx, lead, acc, best) => {
|
|
224
|
+
tail = (input, pos, ctx, lead, acc, best, entered) => {
|
|
222
225
|
if (cls !== undefined && !classHas(cls, lead)) {
|
|
223
226
|
return next(
|
|
224
227
|
input, pos, ctx, lead,
|
|
225
228
|
best === pos ? appendExpected(acc, staticExpected) : acc,
|
|
226
|
-
best,
|
|
229
|
+
best, entered,
|
|
227
230
|
)
|
|
228
231
|
}
|
|
229
232
|
ctx._fc = false
|
|
@@ -236,13 +239,13 @@ function choiceNode(
|
|
|
236
239
|
if (nextAcc !== undefined) ctx._fx = nextAcc
|
|
237
240
|
return -1
|
|
238
241
|
}
|
|
239
|
-
return next(input, pos, ctx, lead, nextAcc, best)
|
|
242
|
+
return next(input, pos, ctx, lead, nextAcc, best, true)
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
245
|
return {
|
|
243
246
|
match: matchTail,
|
|
244
247
|
run: (input, pos, ctx) => tail(
|
|
245
|
-
input, pos, ctx, pos < input.length ? input.codePointAt(pos)! : -1, undefined, pos,
|
|
248
|
+
input, pos, ctx, pos < input.length ? input.codePointAt(pos)! : -1, undefined, pos, false,
|
|
246
249
|
),
|
|
247
250
|
}
|
|
248
251
|
}
|
|
@@ -290,6 +293,9 @@ function exclusiveChoiceNode(
|
|
|
290
293
|
const failed = ctx._fx
|
|
291
294
|
if (failed !== undefined && failed.length > 0) { ctx._fe = pos; return -1 }
|
|
292
295
|
}
|
|
296
|
+
// A miss under a completions probe: each arm fails on its opener, as the
|
|
297
|
+
// interpreter's arms do when it runs them all (`choice.ts`).
|
|
298
|
+
if (arm < 0 && ctx._probe !== undefined) for (const a of arms) a.run(input, pos, ctx)
|
|
293
299
|
ctx._fe = pos
|
|
294
300
|
ctx._fx = choiceFx as string[]
|
|
295
301
|
return -1
|
|
@@ -547,6 +553,9 @@ export function buildLexProgramRunner(
|
|
|
547
553
|
if (!classHas(cls, lead)) {
|
|
548
554
|
ctx._fe = pos
|
|
549
555
|
ctx._fx = choiceFx.slice()
|
|
556
|
+
// Each arm fails here; under a probe their openers are recorded as the
|
|
557
|
+
// interpreter's entered arms record them.
|
|
558
|
+
if (ctx._probe !== undefined) failAt(ctx, [...x0, ...x1, ...x2, ...x3], pos)
|
|
550
559
|
return -1
|
|
551
560
|
}
|
|
552
561
|
let acc: string[] | undefined
|
package/src/table/ops.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Opcodes for the TABLE lowering (design ledger row G5).
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* all variants.
|
|
4
|
+
* The source lowering this replaced (removed in 0.47.0) inlined every rule's
|
|
5
|
+
* recognizer into its own emitted function, so an artifact paid for the
|
|
6
|
+
* recognition machinery once PER RULE (measured then: ~4.9 kB for a `node()`
|
|
7
|
+
* rule; ~186 B for each additional call site). As TABLE ROWS run by one shared
|
|
8
|
+
* driver, that machinery lives in the runtime, once for all grammars and all
|
|
9
|
+
* variants.
|
|
11
10
|
*
|
|
12
11
|
* The instruction stream is a flat `number[]`: `code[ip]` is the opcode and the
|
|
13
12
|
* operands follow it inline. Child instructions are referenced by ABSOLUTE code
|
|
14
|
-
* offset, never by nesting
|
|
15
|
-
* objects, no closures, no per-combinator allocation.
|
|
13
|
+
* offset, never by nesting.
|
|
16
14
|
*
|
|
17
|
-
* Operand layouts are documented per opcode below
|
|
18
|
-
* `
|
|
15
|
+
* Operand layouts are documented per opcode below. `encode.ts` writes them; the
|
|
16
|
+
* engines (`assemble.ts`, `emit-assembly.ts`, the reference `exec.ts`) and the
|
|
17
|
+
* table analyses (`child-slots.ts`, `site-labels.ts`, `fail-clean.ts`, …) read
|
|
18
|
+
* them.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/** `LIT k fx` — `k` indexes a string in the const pool; `fx` the expected set on failure. */
|
|
@@ -38,11 +38,15 @@ export const OP_SEQV = 4
|
|
|
38
38
|
*/
|
|
39
39
|
export const OP_CHOICE = 5
|
|
40
40
|
/**
|
|
41
|
-
* `REP c min max sep flags fx sepClass` —
|
|
42
|
-
*
|
|
43
|
-
* for a separated list,
|
|
44
|
-
* (or −1). `flags
|
|
45
|
-
* `
|
|
41
|
+
* `REP c min max sep flags fx sepClass [sepGate]` — EIGHT words, or NINE with
|
|
42
|
+
* flag bit 3. `sep` is a child offset or −1; `max` −1 = ∞. `fx` is the item's
|
|
43
|
+
* expected set. `sepClass` is the separator sentinel for a separated list,
|
|
44
|
+
* otherwise the finite/non-nullable optional-item class (or −1). `flags`: bit 0
|
|
45
|
+
* (`&1`) trailing separator allowed; bit 1 (`&2`) `keepSeparators`; bit 2 (`&4`)
|
|
46
|
+
* item expected reporting; bit 3 (`&8`) a ninth word at `ip + 8`, the class of
|
|
47
|
+
* characters the separator can start, so a strict list past its minimum ends
|
|
48
|
+
* where it cannot (`sepGate`); bit 4 (`&16`) the item's derived set is empty, so
|
|
49
|
+
* `fx` holds the item's tag for the minimum check and a resync error carries [].
|
|
46
50
|
*/
|
|
47
51
|
export const OP_REP = 6
|
|
48
52
|
/** `REPV …` — `REP` with `valueUnused`. */
|
|
@@ -257,7 +261,7 @@ export const OP_GUARD = 32
|
|
|
257
261
|
* would become a no-op and `notAdjacent()` a guaranteed failure. So the SEQ
|
|
258
262
|
* pieces read this row at assembly and run the test themselves, exactly as
|
|
259
263
|
* `sequence()` forks `parseAdjacent` (combinators/sequence.ts:118) and as
|
|
260
|
-
*
|
|
264
|
+
* the source lowering lowered it at the boundary.
|
|
261
265
|
*
|
|
262
266
|
* Reached as a row in its own right only where there IS no boundary — a bare
|
|
263
267
|
* choice arm, a `node()` body, a repeat item. The interpreter throws there
|
|
@@ -367,7 +371,7 @@ export const OP_ARMGATE = 36
|
|
|
367
371
|
* library never sees.
|
|
368
372
|
*
|
|
369
373
|
* It exists because codegen accepts exactly this and delegates at run time
|
|
370
|
-
* (
|
|
374
|
+
* (the source lowering's `emitRuntimeFallback`). The
|
|
371
375
|
* encoder used to throw `UnsupportedConstruct`, so the table lowering REJECTED
|
|
372
376
|
* grammars the source lowering compiles, and the two lowerings must accept the
|
|
373
377
|
* same language.
|
|
@@ -436,7 +440,7 @@ export const OP_LABEL = 39
|
|
|
436
440
|
* `on` is 0 for ENTRY and 1 for SUCCESS, and it is read at ASSEMBLY TIME, never
|
|
437
441
|
* per parse: `assemble.ts` picks one of two closures from it, exactly as it picks
|
|
438
442
|
* pieces from `RunCfg`. The two phases are not decoration — the source lowering
|
|
439
|
-
*
|
|
443
|
+
* credited a RULE on entry and a choice arm, dispatch arm or
|
|
440
444
|
* label only once it has SUCCEEDED, and a table that credited all four alike
|
|
441
445
|
* would report a different denominator's worth of hits than the engine it is a
|
|
442
446
|
* drop-in for.
|
|
@@ -486,11 +490,35 @@ export const OP_SOURCE_LEAF = 44
|
|
|
486
490
|
* at this position, a completions probe, or tolerant recovery enters the arm.
|
|
487
491
|
*/
|
|
488
492
|
export const OP_RGATE = 45
|
|
493
|
+
/**
|
|
494
|
+
* `GAPGATE c p cls other` — an optional repeat ITEM that opens with a gap: the
|
|
495
|
+
* pure regex row `p`, which may match empty, and then a term whose first set is
|
|
496
|
+
* the class `cls` (`other`: the class of any choice arm that does not open with
|
|
497
|
+
* `p`, or −1). `c` is the item. The row is TRANSPARENT wherever it runs: only
|
|
498
|
+
* the repeat that holds it reads its operands (`gap-gate.ts`).
|
|
499
|
+
*/
|
|
500
|
+
export const OP_GAPGATE = 46
|
|
501
|
+
/**
|
|
502
|
+
* `RGATE2 cls child fx first second` — `OP_RGATE` with a SECOND character: where
|
|
503
|
+
* the character after the routed token is in `first`, the one after it must be
|
|
504
|
+
* in `second` too (the next term reads both, `regexFirstTwo`), or the arm fails
|
|
505
|
+
* there with `fx`, as entering it would.
|
|
506
|
+
*/
|
|
507
|
+
export const OP_RGATE2 = 47
|
|
508
|
+
/**
|
|
509
|
+
* `STRICT strict tolerant` — one construct encoded two ways: `strict` for a
|
|
510
|
+
* strict parse, `tolerant` for tolerant recovery and a completions probe, which
|
|
511
|
+
* must see the construct as written (`factor-choice.ts` `factorPrefixArms`). An
|
|
512
|
+
* assembly resolves it to one child; it has no body of its own.
|
|
513
|
+
*/
|
|
514
|
+
export const OP_STRICT = 48
|
|
489
515
|
/**
|
|
490
516
|
* `DISPATCH sel d other otherRouted n a1 … an` — `dispatch()`.
|
|
491
517
|
*
|
|
492
518
|
* `sel` is the selector's offset, `d` indexes a dispatch table in `prog.dsp`,
|
|
493
|
-
* `other` is the `otherwise()` offset (or −1), `otherRouted`
|
|
519
|
+
* `other` is the `otherwise()` offset (or −1), `otherRouted` bit 1 (`&2`) is set
|
|
520
|
+
* when the fallback's failure is NOT committed (`otherwise(p, { commit: false })`)
|
|
521
|
+
* and bit 0 when the
|
|
494
522
|
* fallback consumes the routed token, and `a1…an` are the arm offsets. Arm
|
|
495
523
|
* `usesRouted` bits live in the `dsp` entry beside the key maps.
|
|
496
524
|
*
|
|
@@ -520,4 +548,7 @@ export const OP_NAMES: Record<number, string> = {
|
|
|
520
548
|
[OP_SCOPE_PLAIN]: 'SCOPE_PLAIN',
|
|
521
549
|
[OP_LEX_BODY]: 'LEX_BODY',
|
|
522
550
|
[OP_LEX_PROGRAM]: 'LEX_PROGRAM',
|
|
551
|
+
[OP_GAPGATE]: 'GAPGATE',
|
|
552
|
+
[OP_RGATE2]: 'RGATE2',
|
|
553
|
+
[OP_STRICT]: 'STRICT',
|
|
523
554
|
}
|
package/src/table/program.ts
CHANGED
|
@@ -17,10 +17,15 @@ import { buildLexProgramRunner, type LexProgramRunner } from './lex-program.ts'
|
|
|
17
17
|
* `EMITTED_PARAMS` in order. `plan` rebuilds the three data pools the factory
|
|
18
18
|
* expects (see `PoolPlan`), and `reached` is the emitter's site set, which
|
|
19
19
|
* `Assembly.reached` publishes and `test/unit/table-assemble.test.ts`
|
|
20
|
-
* asserts on.
|
|
20
|
+
* asserts on. `arity` is the number of helpers the factory was printed against
|
|
21
|
+
* (`EMITTED_PARAMS.length` of the printing parseman); the runtime compares it with
|
|
22
|
+
* its own list before calling the factory. It is printed data, not
|
|
23
|
+
* `factory.length`, which a minifier that drops unused trailing parameters lowers.
|
|
24
|
+
* An assembly printed before 0.60.0 has none.
|
|
21
25
|
*/
|
|
22
26
|
export type PrecompiledAssembly = {
|
|
23
27
|
readonly key: number
|
|
28
|
+
readonly arity?: number
|
|
24
29
|
readonly factory: EmittedFactory
|
|
25
30
|
readonly plan: PoolPlan
|
|
26
31
|
readonly reached: readonly number[]
|
|
@@ -29,10 +34,10 @@ export type PrecompiledAssembly = {
|
|
|
29
34
|
/**
|
|
30
35
|
* The emitted form of a grammar under the table lowering.
|
|
31
36
|
*
|
|
32
|
-
* This object is ALL a grammar contributes to a bundle
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* function.
|
|
37
|
+
* This object is ALL a grammar contributes to a bundle, apart from any
|
|
38
|
+
* build-time assemblies in `asm`. The recognition logic is the shared driver
|
|
39
|
+
* (`tableRules`, `assemble.ts`), which ships once in the runtime for every
|
|
40
|
+
* grammar and every variant. A rule costs its row, not a function.
|
|
36
41
|
*
|
|
37
42
|
* Serialized shape is deliberately JSON-ish (numbers, strings, and a pool of
|
|
38
43
|
* author callbacks) so the emitter can print it as a literal.
|
|
@@ -145,9 +150,9 @@ export type TableProgram = {
|
|
|
145
150
|
*/
|
|
146
151
|
readonly triviaSpecs?: readonly TriviaSpec[]
|
|
147
152
|
/**
|
|
148
|
-
* 1
|
|
149
|
-
*
|
|
150
|
-
*
|
|
153
|
+
* Always 1: every table carries recovery operands — the sequences their inferred
|
|
154
|
+
* follow-set classes, the repetitions their item expected set and separator
|
|
155
|
+
* sentinel class. (`TableSettings.recovery` is a deprecated no-op.)
|
|
151
156
|
*
|
|
152
157
|
* TABLE DATA, read once when the driver/assembly is built. It selects the
|
|
153
158
|
* recovery pieces; it is not a per-parse option, and a recovery table still
|
|
@@ -433,7 +438,7 @@ export function validateDispatchSpec(
|
|
|
433
438
|
return false
|
|
434
439
|
}
|
|
435
440
|
if (spec === undefined || !Number.isInteger(arity) || arity < 0
|
|
436
|
-
|| (fallbackRouted
|
|
441
|
+
|| !Number.isInteger(fallbackRouted) || fallbackRouted < 0 || fallbackRouted > 3
|
|
437
442
|
|| spec.routed.length !== arity
|
|
438
443
|
|| spec.routed.some(value => value !== 0 && value !== 1)
|
|
439
444
|
|| invalid(spec.byKey.values()) || invalid(spec.byFold.values())
|
package/src/table/select-hot.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* A build given a corpus (`assemblyProfile`) ranks by measured calls instead.
|
|
16
16
|
*/
|
|
17
17
|
import { childSlots } from './child-slots.ts'
|
|
18
|
-
import { OP_CHOICE, OP_DISPATCH, OP_REP, OP_REPV } from './ops.ts'
|
|
18
|
+
import { OP_CHOICE, OP_DISPATCH, OP_GAPGATE, OP_REP, OP_REPV } from './ops.ts'
|
|
19
19
|
|
|
20
20
|
const LOOP = 8
|
|
21
21
|
const ROUNDS = 60
|
|
@@ -62,7 +62,12 @@ function staticWeights(code: readonly number[], entries: readonly number[]): Map
|
|
|
62
62
|
const scale = op === OP_REP || op === OP_REPV ? LOOP
|
|
63
63
|
: op === OP_CHOICE || op === OP_DISPATCH ? 1 / Math.max(1, kids.length)
|
|
64
64
|
: 1
|
|
65
|
-
for (const
|
|
65
|
+
for (const k of kids) {
|
|
66
|
+
// A gap gate has no body: weigh its item as the repeat's own child, so
|
|
67
|
+
// adding the row moves no other site's estimate (`gap-gate.ts`).
|
|
68
|
+
const kid = code[k] === OP_GAPGATE ? code[k + 1]! : k
|
|
69
|
+
next.set(kid, Math.min(CAP, (next.get(kid) ?? 0) + w * scale))
|
|
70
|
+
}
|
|
66
71
|
}
|
|
67
72
|
weight = next
|
|
68
73
|
}
|
package/src/table/stamp.ts
CHANGED
|
@@ -173,7 +173,7 @@ export function stampRuleMap(
|
|
|
173
173
|
// `committed` IS PART OF THE FAILURE ENVELOPE, not a diagnostic extra.
|
|
174
174
|
// The interpreter carries it on the result (`{ ok: false, …, committed: true }`)
|
|
175
175
|
// and codegen re-derives it from the same bit this driver uses
|
|
176
|
-
// (
|
|
176
|
+
// (`...(_ctx._fc ? { committed: true } : {})`). The table
|
|
177
177
|
// dropped it, so a table entry embedded as a CHILD of another parser — the
|
|
178
178
|
// one place the field is read rather than merely reported — lost the cut.
|
|
179
179
|
return committed(ctx)
|