parseman 0.59.3 → 0.60.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/diagnostics.js +876 -190
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +876 -190
- package/dist/cli/index.js.map +3 -3
- 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/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/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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +896 -199
- package/dist/index.js.map +3 -3
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.js +903 -201
- 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/table/assemble.d.ts +15 -1
- 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 +1 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/compile.d.ts +1 -1
- 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 +4 -3
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/exec.d.ts +0 -35
- 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 +926 -201
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-program.d.ts.map +1 -1
- package/dist/table/ops.d.ts +35 -16
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +6 -1
- package/dist/table/program.d.ts.map +1 -1
- package/dist/types.d.ts +16 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -4
- package/src/analysis/duplication.ts +1 -1
- package/src/combinators/attempt.ts +1 -1
- package/src/combinators/dispatch.ts +30 -6
- package/src/combinators/first-set.ts +55 -12
- 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/ir-serialize.ts +1 -1
- package/src/compiler/token-alphabet.ts +3 -1
- package/src/coverage.ts +1 -1
- package/src/index.ts +1 -1
- package/src/plugin/evaluator.ts +8 -3
- package/src/regex/first-set.ts +37 -3
- package/src/table/assemble.ts +176 -30
- package/src/table/child-slots.ts +8 -3
- package/src/table/compile-rule-map.ts +7 -4
- package/src/table/compile.ts +1 -1
- package/src/table/emit-assembly.ts +184 -33
- package/src/table/emit.ts +6 -4
- package/src/table/encode.ts +469 -74
- package/src/table/exec.ts +38 -82
- 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 +38 -16
- package/src/table/program.ts +7 -2
- package/src/table/select-hot.ts +7 -2
- package/src/types.ts +12 -5
- package/src/version.ts +1 -1
- package/dist/table/encode-baseline.d.ts +0 -16
- package/dist/table/encode-baseline.d.ts.map +0 -1
- package/dist/table/exec-baseline.d.ts +0 -9
- package/dist/table/exec-baseline.d.ts.map +0 -1
- package/src/table/encode-baseline.ts +0 -346
- package/src/table/exec-baseline.ts +0 -557
package/src/table/assemble.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* making the swaps on rules / sub-rules (leaves) at that point, then run with NO
|
|
6
6
|
* logic branching for that option input"*. `exec.ts` builds the same table and
|
|
7
7
|
* then interprets it: one `switch (code[ip])` over every opcode (29 then), executed ONCE PER
|
|
8
|
-
* ROW — 497,360 rows for `benchmark.less` (`
|
|
8
|
+
* ROW — 497,360 rows for `benchmark.less` (measured with a one-off `g5-rows` bench, last at `4a5d0341`) — each
|
|
9
9
|
* re-reading its opcode and re-decoding its operands from the `Int32Array`, and
|
|
10
10
|
* re-testing the same per-parse options. That is the per-node branching the
|
|
11
11
|
* design exists to remove.
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
*
|
|
21
21
|
* The numbers that make this obviously the right trade, measured not assumed:
|
|
22
22
|
*
|
|
23
|
-
* 2,241 distinct reachable sites in the less table (`
|
|
23
|
+
* 2,241 distinct reachable sites in the less table (`g5-sites`)
|
|
24
24
|
* 497,360 rows executed for one parse of `benchmark.less`
|
|
25
25
|
*
|
|
26
26
|
* Pieces are GRAMMAR-sized; rows are INPUT-sized. Assembly allocates ~2.2k
|
|
27
27
|
* closures once per process and removes a dispatch plus an operand decode from
|
|
28
28
|
* ~497k executions — a 222x ratio. Assembly cost is paid once and is not the
|
|
29
|
-
* metric; it is measured anyway (`
|
|
29
|
+
* metric; it is measured anyway (`g5-ms`, `g5-profile`).
|
|
30
30
|
*
|
|
31
31
|
* ## Why this is not "just tuning the switch"
|
|
32
32
|
*
|
|
@@ -73,10 +73,11 @@ import { skipDeferredTrivia, triviaLeadTable } from './trivia-gap.ts'
|
|
|
73
73
|
import { asciiFoldKey } from '../combinators/dispatch.ts'
|
|
74
74
|
import { projectChild, unwrapChild } from '../combinators/node.ts'
|
|
75
75
|
import { cstOutputHost } from '../compiler/build-arity.ts'
|
|
76
|
+
import { PARSEMAN_VERSION } from '../version.ts'
|
|
76
77
|
import { consumeTrivia } from '../combinators/trivia-skip.ts'
|
|
77
78
|
import {
|
|
78
|
-
advanceTrivia,
|
|
79
|
-
|
|
79
|
+
advanceTrivia, needsDeferredTriviaCommit, rollbackScannedTriviaAt, rollbackTriviaAt,
|
|
80
|
+
skipTriviaScanned, type FastTriviaScanner,
|
|
80
81
|
} from '../combinators/trivia-skip.ts'
|
|
81
82
|
import {
|
|
82
83
|
cstCaptureActive, cstLeavesLen, cstTlLen,
|
|
@@ -90,8 +91,8 @@ import {
|
|
|
90
91
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
91
92
|
OP_LIVE,
|
|
92
93
|
OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD, OP_ATTEMPT, OP_LABEL,
|
|
93
|
-
OP_COV,
|
|
94
|
-
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE,
|
|
94
|
+
OP_COV, OP_GAPGATE, OP_STRICT,
|
|
95
|
+
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE, OP_RGATE2,
|
|
95
96
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
96
97
|
} from './ops.ts'
|
|
97
98
|
import { adjacencyHolds, adjacencyMisuse } from '../combinators/adjacency.ts'
|
|
@@ -110,6 +111,7 @@ import {
|
|
|
110
111
|
} from './emit-assembly.ts'
|
|
111
112
|
import { collapseEmpty, lead, rawEntry, spanLines } from './run-support.ts'
|
|
112
113
|
import { holdsGap, rowNullable } from './hold-gap.ts'
|
|
114
|
+
import { gapRows, newGapSlot } from './gap-gate.ts'
|
|
113
115
|
import { failCleanAnalysis } from './fail-clean.ts'
|
|
114
116
|
/**
|
|
115
117
|
* THE COMPLETIONS PROBE, at the terminal fail sites and nowhere else.
|
|
@@ -412,6 +414,28 @@ export type Assembly = {
|
|
|
412
414
|
* ONE walk. Each site is lowered at most once and memoised by its code offset,
|
|
413
415
|
* so a subtree shared by two parents is one piece with two references to it.
|
|
414
416
|
*/
|
|
417
|
+
/**
|
|
418
|
+
* A printed factory takes its helpers BY POSITION (`EMITTED_PARAMS`), so one printed
|
|
419
|
+
* against a different helper list would bind the wrong function to a name and run
|
|
420
|
+
* wrong rather than fail. A factory printed by 0.59.x guards only against FEWER
|
|
421
|
+
* helpers, and one printed before 0.59.0 has no guard at all. So the runtime checks
|
|
422
|
+
* too, before calling it: the assembly's printed `arity` must be exactly the list it
|
|
423
|
+
* passes. It reads that printed number, never `factory.length`, which a minifier that
|
|
424
|
+
* drops unused trailing parameters lowers on a valid artifact. An assembly printed
|
|
425
|
+
* before 0.60.0 carries no `arity` and is refused. A factory this version prints also
|
|
426
|
+
* checks the count it is called with (`FACTORY_ARITY_GUARD`, `emit.ts`).
|
|
427
|
+
*/
|
|
428
|
+
export function assertFactoryArity(pre: { readonly arity?: number }): void {
|
|
429
|
+
if (pre.arity === EMITTED_PARAMS.length) return
|
|
430
|
+
const printed = pre.arity === undefined
|
|
431
|
+
? 'carries no helper count, so it was printed before parseman 0.60.0'
|
|
432
|
+
: `takes ${pre.arity} helpers, but parseman ${PARSEMAN_VERSION} passes ${EMITTED_PARAMS.length}: it was printed by an older or newer parseman`
|
|
433
|
+
throw new Error(
|
|
434
|
+
`parseman: this parser's printed assembly ${printed}. Reprint the parser with `
|
|
435
|
+
+ `parseman ${PARSEMAN_VERSION}, or run it on the parseman version that printed it.`,
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
|
|
415
439
|
export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Assembly {
|
|
416
440
|
const { code, k, fns, cc, fx, disp, dsp, trivia, triviaLabelled } = t
|
|
417
441
|
// The leaf swap is only legal when line tracking is off, and that is a property
|
|
@@ -502,6 +526,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
502
526
|
}
|
|
503
527
|
|
|
504
528
|
const labelExtraIps = assemblyExtraIps(prog)
|
|
529
|
+
// The regex rows an optional repeat hands a scanned gap to (`gap-gate.ts`),
|
|
530
|
+
// and the one slot it hands it through, shared with the emitted pieces.
|
|
531
|
+
const GAP_ROWS = gapRows(code, [...Object.values(prog.rules), ...labelExtraIps])
|
|
532
|
+
const GAP = newGapSlot()
|
|
505
533
|
const closureLabels = computeSiteLabels(
|
|
506
534
|
code, [...Object.values(prog.rules), ...labelExtraIps], hostCst,
|
|
507
535
|
)
|
|
@@ -772,6 +800,32 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
772
800
|
}
|
|
773
801
|
}
|
|
774
802
|
|
|
803
|
+
/**
|
|
804
|
+
* A regex row a repeat may hand a scanned gap to (`gap-gate.ts`): the end comes
|
|
805
|
+
* from the slot when the slot is this row's, at this position, in this input,
|
|
806
|
+
* and from the regex otherwise. Everything after the end is the row's own.
|
|
807
|
+
*/
|
|
808
|
+
function gapRegexPiece(row: number, re: RegExp, xf: string[], track: boolean): Piece {
|
|
809
|
+
return (input, pos, ctx) => {
|
|
810
|
+
let e: number
|
|
811
|
+
if (GAP.row === row && GAP.at === pos && GAP.input === input) e = GAP.end
|
|
812
|
+
else {
|
|
813
|
+
re.lastIndex = pos
|
|
814
|
+
e = re.test(input) ? re.lastIndex : -1
|
|
815
|
+
}
|
|
816
|
+
if (e >= 0) {
|
|
817
|
+
const v = input.slice(pos, e)
|
|
818
|
+
if (cstCaptureActive(ctx)) pushLeaf(ctx, v, pos, e)
|
|
819
|
+
if (track) trackLinesInto(ctx, input, e)
|
|
820
|
+
EC.e = e
|
|
821
|
+
return v
|
|
822
|
+
}
|
|
823
|
+
ctx._fe = pos; ctx._fx = xf
|
|
824
|
+
if (ctx._probe !== undefined) failAt(ctx, xf, pos)
|
|
825
|
+
return FAIL
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
775
829
|
/**
|
|
776
830
|
* A collapse over one node is that node — `run-support.ts` `collapseEmpty` has
|
|
777
831
|
* the contract, and the one case the node's value cannot stand for itself.
|
|
@@ -1353,7 +1407,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1353
1407
|
}
|
|
1354
1408
|
}
|
|
1355
1409
|
|
|
1356
|
-
function dispatchBranch(child: Piece, usesRouted: boolean): DispatchBranch {
|
|
1410
|
+
function dispatchBranch(child: Piece, usesRouted: boolean, commits = true): DispatchBranch {
|
|
1357
1411
|
if (!usesRouted) {
|
|
1358
1412
|
// The arm runs after the selector's match, so it marks where it starts.
|
|
1359
1413
|
return (input, _pos, selEnd, key, ctx) => {
|
|
@@ -1368,7 +1422,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1368
1422
|
const v = child(input, selEnd, ctx)
|
|
1369
1423
|
if (v === FAIL) {
|
|
1370
1424
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1371
|
-
ctx._fc = true
|
|
1425
|
+
if (commits) ctx._fc = true
|
|
1372
1426
|
return FAIL
|
|
1373
1427
|
}
|
|
1374
1428
|
return [key, v]
|
|
@@ -1388,7 +1442,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1388
1442
|
}
|
|
1389
1443
|
if (v === FAIL) {
|
|
1390
1444
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1391
|
-
ctx._fc = true
|
|
1445
|
+
if (commits) ctx._fc = true
|
|
1392
1446
|
return FAIL
|
|
1393
1447
|
}
|
|
1394
1448
|
return [key, v]
|
|
@@ -1662,6 +1716,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1662
1716
|
case OP_RX: {
|
|
1663
1717
|
const re = k[code[ip + 1]!] as RegExp
|
|
1664
1718
|
const xf = fx[code[ip + 2]!] as string[]
|
|
1719
|
+
if (GAP_ROWS.has(ip)) return gapRegexPiece(ip, re, xf, false)
|
|
1665
1720
|
const shared = rawScalarSpecs.has(code[ip + 1]!) ? scalarFor(ip) : undefined
|
|
1666
1721
|
if (shared !== undefined) {
|
|
1667
1722
|
return (input, pos, ctx) => {
|
|
@@ -1700,6 +1755,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1700
1755
|
case OP_RX_TRACK: {
|
|
1701
1756
|
const re = k[code[ip + 1]!] as RegExp
|
|
1702
1757
|
const xf = fx[code[ip + 2]!] as string[]
|
|
1758
|
+
if (GAP_ROWS.has(ip)) return gapRegexPiece(ip, re, xf, true)
|
|
1703
1759
|
return (input, pos, ctx) => {
|
|
1704
1760
|
re.lastIndex = pos
|
|
1705
1761
|
if (re.test(input)) {
|
|
@@ -1877,6 +1933,14 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1877
1933
|
}
|
|
1878
1934
|
}
|
|
1879
1935
|
|
|
1936
|
+
// Transparent: only the repeat holding it reads its operands.
|
|
1937
|
+
case OP_GAPGATE:
|
|
1938
|
+
return link(code[ip + 1]!)
|
|
1939
|
+
|
|
1940
|
+
// One encoding per assembly: strict, or the construct as written.
|
|
1941
|
+
case OP_STRICT:
|
|
1942
|
+
return link(code[cfg.tolerant || cfg.probe ? ip + 2 : ip + 1]!)
|
|
1943
|
+
|
|
1880
1944
|
case OP_RULE:
|
|
1881
1945
|
// Pure indirection in the tape; in the assembled graph the parent simply
|
|
1882
1946
|
// holds the target's piece. The trampoline row disappears entirely — this
|
|
@@ -2949,7 +3013,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2949
3013
|
// `arms[]` merely to rediscover the same two or three children at
|
|
2950
3014
|
// every dispatch. The classifier remains table data; only its result
|
|
2951
3015
|
// is turned into a direct captured call.
|
|
2952
|
-
if (n === 2) {
|
|
3016
|
+
if (n === 2 && !cfg.probe) {
|
|
2953
3017
|
const a0 = link(code[base]!), a1 = link(code[base + 1]!)
|
|
2954
3018
|
return (input, pos, ctx) => {
|
|
2955
3019
|
const c = lead(input, pos)
|
|
@@ -2974,7 +3038,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2974
3038
|
return FAIL
|
|
2975
3039
|
}
|
|
2976
3040
|
}
|
|
2977
|
-
if (n === 3) {
|
|
3041
|
+
if (n === 3 && !cfg.probe) {
|
|
2978
3042
|
const a0 = link(code[base]!), a1 = link(code[base + 1]!), a2 = link(code[base + 2]!)
|
|
2979
3043
|
return (input, pos, ctx) => {
|
|
2980
3044
|
const c = lead(input, pos)
|
|
@@ -3007,6 +3071,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3007
3071
|
)
|
|
3008
3072
|
}
|
|
3009
3073
|
const selected = bindExclusive(0)
|
|
3074
|
+
// Under a completions probe the interpreter runs every arm on a miss
|
|
3075
|
+
// (`choice.ts`), so each opener reaches the probe.
|
|
3076
|
+
const probeArms = cfg.probe ? Array.from({ length: n }, (_, i) => link(code[base + i]!)) : undefined
|
|
3010
3077
|
// THERE ARE NO OPEN ARMS HERE, and the piece is written to say so. An
|
|
3011
3078
|
// ungated arm is one whose class is −1, and `resolveDispatch` clears
|
|
3012
3079
|
// `exclusive` for exactly those arms (program.ts) — so `table.open` is
|
|
@@ -3038,6 +3105,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3038
3105
|
// THE UNION IS REPORTED ONLY ON A DISPATCH MISS, where the interpreter's
|
|
3039
3106
|
// `parsers.flatMap` over non-nullable, char-excluded arms yields exactly
|
|
3040
3107
|
// each arm's own opener — this set, without running the arms.
|
|
3108
|
+
if (arm < 0 && probeArms !== undefined) for (const a of probeArms) a(input, pos, ctx)
|
|
3041
3109
|
ctx._fe = pos; ctx._fx = choiceFx
|
|
3042
3110
|
return FAIL
|
|
3043
3111
|
}
|
|
@@ -3045,8 +3113,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3045
3113
|
|
|
3046
3114
|
// THE PER-ARM GATE. Arms in source order, each skipped when its own class
|
|
3047
3115
|
// excludes the char at `pos`. `null` means nullable or unmappable, and
|
|
3048
|
-
// those arms are always entered.
|
|
3049
|
-
|
|
3116
|
+
// those arms are always entered. A probe assembly enters every arm, as
|
|
3117
|
+
// the interpreter's `firstMatch` does, so each opener reaches `failAt`.
|
|
3118
|
+
const armCls = cfg.probe ? table.armCls.map(() => null) : table.armCls
|
|
3050
3119
|
if (n === 2 || n === 3) {
|
|
3051
3120
|
const a0 = link(code[base]!), a1 = link(code[base + 1]!)
|
|
3052
3121
|
const a2 = n === 3 ? link(code[base + 2]!) : undefined
|
|
@@ -3354,6 +3423,43 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3354
3423
|
}
|
|
3355
3424
|
}
|
|
3356
3425
|
|
|
3426
|
+
case OP_RGATE2: {
|
|
3427
|
+
const cls = cc[code[ip + 1]!]!
|
|
3428
|
+
const xf = fx[code[ip + 3]!] as string[]
|
|
3429
|
+
const first = cc[code[ip + 4]!]!
|
|
3430
|
+
const second = cc[code[ip + 5]!]!
|
|
3431
|
+
const child = link(code[ip + 2]!)
|
|
3432
|
+
if (cfg.tolerant || cfg.probe) return child
|
|
3433
|
+
// ASCII-only classes are read as code units against their tables.
|
|
3434
|
+
if (cls.hi.length === 0 && first.hi.length === 0 && second.hi.length === 0) {
|
|
3435
|
+
const a0 = cls.ascii, a1 = first.ascii, a2 = second.ascii
|
|
3436
|
+
return (input, pos, ctx) => {
|
|
3437
|
+
const item = ctx._routed
|
|
3438
|
+
if (item !== undefined && item.span.start === pos) {
|
|
3439
|
+
const e = item.span.end
|
|
3440
|
+
const c = input.charCodeAt(e)
|
|
3441
|
+
if (a0[c] !== 1 || (a1[c] === 1 && a2[input.charCodeAt(e + 1)] !== 1)) {
|
|
3442
|
+
ctx._fe = e; ctx._fx = xf
|
|
3443
|
+
return FAIL
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
return child(input, pos, ctx)
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
return (input, pos, ctx) => {
|
|
3450
|
+
const item = ctx._routed
|
|
3451
|
+
if (item !== undefined && item.span.start === pos) {
|
|
3452
|
+
const e = item.span.end
|
|
3453
|
+
const c = lead(input, e)
|
|
3454
|
+
if (!classHas(cls, c) || (classHas(first, c) && !classHas(second, lead(input, e + 1)))) {
|
|
3455
|
+
ctx._fe = e; ctx._fx = xf
|
|
3456
|
+
return FAIL
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
return child(input, pos, ctx)
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3357
3463
|
case OP_ARMGATE: {
|
|
3358
3464
|
// Predicate, arm and expected set all bound HERE — the body reads no
|
|
3359
3465
|
// opcode, decodes no operand and tests no setting.
|
|
@@ -3450,6 +3556,19 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3450
3556
|
// `many()` — the min-0, separator-less repeat — is the only shape that runs
|
|
3451
3557
|
// its FIRST item through `repItem` and so skips leading trivia. The shape
|
|
3452
3558
|
// identifies itself, and it is table data, so it is decided here.
|
|
3559
|
+
// A separated list's ip+8 (flag bit 8) is its separator's first set where the encoder
|
|
3560
|
+
// found the test cheaper than entering it (`Encoder.sepGate`). Where the
|
|
3561
|
+
// separator cannot match empty and reads no pre-scan cursor, a lead
|
|
3562
|
+
// outside it stops the list at the separator, exactly as the strict
|
|
3563
|
+
// optional item stops at its own class above.
|
|
3564
|
+
const sepClassIndex = sepIp >= 0 && !sepLeads && (flags & 8) !== 0 ? code[ip + 8]! : -1
|
|
3565
|
+
const sepClass = !REC && sepClassIndex >= 0 && !rowNullable(code, k, sepIp, NULLABLE) ? cc[sepClassIndex]! : undefined
|
|
3566
|
+
// THE GAP GATE (`gap-gate.ts`): read the item's leading gap once, here.
|
|
3567
|
+
const gapIp = sepIp < 0 && min === 0 && !REC && code[code[ip + 1]!] === OP_GAPGATE ? code[ip + 1]! : -1
|
|
3568
|
+
const gapRow = gapIp >= 0 ? code[gapIp + 2]! : -1
|
|
3569
|
+
const gapRe = gapIp >= 0 ? k[code[gapRow + 1]!] as RegExp : undefined
|
|
3570
|
+
const gapFollow = gapIp >= 0 ? cc[code[gapIp + 3]!]! : undefined
|
|
3571
|
+
const gapOther = gapIp >= 0 && code[gapIp + 4]! >= 0 ? cc[code[gapIp + 4]!]! : undefined
|
|
3453
3572
|
const skipBeforeFirst = sepIp < 0 && min === 0
|
|
3454
3573
|
const itemClean = sepIp < 0 && CLEAN(code[ip + 1]!) && !rowNullable(code, k, code[ip + 1]!, NULLABLE)
|
|
3455
3574
|
|
|
@@ -3486,6 +3605,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3486
3605
|
*/
|
|
3487
3606
|
if (REC) {
|
|
3488
3607
|
const itemFx = fx[code[ip + 6]!] as string[]
|
|
3608
|
+
// A resync error carries the item's set as derived (bit 16: empty).
|
|
3609
|
+
const recFx = (flags & 16) !== 0 ? EMPTY_FX : itemFx
|
|
3489
3610
|
const sepSent = sentinelFor(code[ip + 7]!)
|
|
3490
3611
|
return (input, pos, ctx) => {
|
|
3491
3612
|
const out: unknown[] | undefined = collect ? [] : undefined
|
|
@@ -3546,7 +3667,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3546
3667
|
const rr = recoverScan(
|
|
3547
3668
|
input, itemStart, ctx,
|
|
3548
3669
|
sep === undefined ? mySync : orSentinel(sepSent ?? mySync, sepSent === undefined ? undefined : mySync),
|
|
3549
|
-
|
|
3670
|
+
recFx,
|
|
3550
3671
|
)
|
|
3551
3672
|
if (out !== undefined) out.push(rr.error)
|
|
3552
3673
|
captureError(ctx, rr.error)
|
|
@@ -3586,6 +3707,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3586
3707
|
// Keep the ordinary child path while one is active so this structural
|
|
3587
3708
|
// fast stop cannot erase an item completion at the cursor.
|
|
3588
3709
|
const gateItems = itemMayStart !== undefined && ctx._probe === undefined
|
|
3710
|
+
const gateSep = sepClass !== undefined && ctx._probe === undefined
|
|
3589
3711
|
let cur = pos
|
|
3590
3712
|
let count = 0
|
|
3591
3713
|
for (;;) {
|
|
@@ -3617,6 +3739,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3617
3739
|
const leavesBefore = cstLeavesLen(ctx)
|
|
3618
3740
|
let sp = cur
|
|
3619
3741
|
if (hasTrivia) sp = skipTrivia(input, sp, ctx)
|
|
3742
|
+
if (count >= min && gateSep && !classHas(sepClass!, lead(input, sp))) {
|
|
3743
|
+
if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
3744
|
+
break
|
|
3745
|
+
}
|
|
3620
3746
|
ctx._fc = false
|
|
3621
3747
|
if (sepLeads) LEAD.at = cur
|
|
3622
3748
|
const sv = sep(input, sp, ctx)
|
|
@@ -3636,13 +3762,33 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3636
3762
|
if (trailingAllowed && sepEnd >= 0) cur = sepEnd
|
|
3637
3763
|
break
|
|
3638
3764
|
}
|
|
3639
|
-
|
|
3765
|
+
// Without a separator or trivia in front, the loop head already
|
|
3766
|
+
// tested this same position.
|
|
3767
|
+
if (count >= min && gateItems && (sep !== undefined || hasTrivia) && !itemMayStart!(input, itemStart)) {
|
|
3640
3768
|
if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
3641
3769
|
if (trailingAllowed && sepEnd >= 0) cur = sepEnd
|
|
3642
3770
|
break
|
|
3643
3771
|
}
|
|
3772
|
+
let gapped = false
|
|
3773
|
+
if (gapRe !== undefined && ctx._probe === undefined) {
|
|
3774
|
+
gapRe.lastIndex = itemStart
|
|
3775
|
+
const q = gapRe.test(input) ? gapRe.lastIndex : -1
|
|
3776
|
+
// No arm with the gap can match where the gap fails, or where the
|
|
3777
|
+
// character after it opens nothing; an arm without it needs its
|
|
3778
|
+
// own lead at the item's start.
|
|
3779
|
+
if ((q < 0 || !classHas(gapFollow!, lead(input, q)))
|
|
3780
|
+
&& (gapOther === undefined || !classHas(gapOther, lead(input, itemStart)))) {
|
|
3781
|
+
if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
3782
|
+
break
|
|
3783
|
+
}
|
|
3784
|
+
if (q >= 0) {
|
|
3785
|
+
GAP.row = gapRow; GAP.at = itemStart; GAP.end = q; GAP.input = input
|
|
3786
|
+
gapped = true
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3644
3789
|
ctx._fc = false
|
|
3645
3790
|
const v = child(input, itemStart, ctx)
|
|
3791
|
+
if (gapped) GAP.at = -1
|
|
3646
3792
|
if (v === FAIL) {
|
|
3647
3793
|
if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
3648
3794
|
if (committed(ctx)) return FAIL
|
|
@@ -3675,7 +3821,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3675
3821
|
const selector = link(code[ip + 1]!)
|
|
3676
3822
|
const otherIp = code[ip + 3]!
|
|
3677
3823
|
const other = otherIp >= 0 ? link(otherIp) : undefined
|
|
3678
|
-
const otherRouted = code[ip + 4]! === 1
|
|
3824
|
+
const otherRouted = (code[ip + 4]! & 1) === 1
|
|
3825
|
+
const otherCommits = (code[ip + 4]! & 2) === 0
|
|
3679
3826
|
const n = code[ip + 5]!
|
|
3680
3827
|
const armBase = ip + 6
|
|
3681
3828
|
const byKey = spec.byKey
|
|
@@ -3722,7 +3869,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3722
3869
|
)
|
|
3723
3870
|
}
|
|
3724
3871
|
const runArm = n === 0 ? undefined : bindArms(0)
|
|
3725
|
-
const runOther = other === undefined ? undefined : dispatchBranch(other, otherRouted)
|
|
3872
|
+
const runOther = other === undefined ? undefined : dispatchBranch(other, otherRouted, otherCommits)
|
|
3726
3873
|
|
|
3727
3874
|
return (input, pos, ctx) => {
|
|
3728
3875
|
const need = markCst(ctx)
|
|
@@ -4099,22 +4246,22 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
4099
4246
|
? prog.asm?.find(a => a.key === cfgKey(cfg))
|
|
4100
4247
|
: undefined
|
|
4101
4248
|
if (pre !== undefined) {
|
|
4249
|
+
assertFactoryArity(pre)
|
|
4102
4250
|
const pools = rebuildPools(t.cc, t.fx, t.disp, pre.plan)
|
|
4103
4251
|
emitted = pre.factory(
|
|
4104
|
-
EC, FAIL, k, fx, fns, pools.masks, pools.classes, pools.armExpected, trivia,
|
|
4105
|
-
trivia.map(tv => tv?._meta.triviaKindLabels), triviaScan,
|
|
4252
|
+
EC, FAIL, k, fx, fns, pools.masks, pools.classes, pools.armExpected, trivia, triviaScan,
|
|
4106
4253
|
scansArr, disp, dsp, EMPTY_FX, EMPTY_CH, EMPTY_TLOG, EMPTY_TL,
|
|
4107
4254
|
cstCaptureActive, pushCstLeaf, pushCstChild, rollbackTriviaAt, rollbackScannedTriviaAt, failAt,
|
|
4108
4255
|
classHas, consumeTrivia, buildFieldMap, projectChild, unwrapChild,
|
|
4109
4256
|
demoteCapturedToRaw, cstLeavesLen, skipTriviaScanned, needsDeferredTriviaCommit,
|
|
4110
|
-
|
|
4257
|
+
advanceTrivia, refuseUnclassifiedRootScope, spanLines, rawEntry, lead,
|
|
4111
4258
|
asciiFoldKey, ROUTED_FX,
|
|
4112
4259
|
REC ? prog.cc.map((_, i) => sentinelFor(i)) : EMPTY_SENTS,
|
|
4113
4260
|
matchesAt, recoverScan, orSentinel, captureError,
|
|
4114
|
-
scalarRecognizers,
|
|
4261
|
+
scalarRecognizers, t.lex, adjacencyHolds, t.lexPrograms,
|
|
4115
4262
|
LEAD, triviaFieldCapture, skipDeferredTrivia,
|
|
4116
4263
|
collapseEmpty,
|
|
4117
|
-
PlainSpan, PlainLeaf, PlainNode, PlainField, PlainBuf,
|
|
4264
|
+
PlainSpan, PlainLeaf, PlainNode, PlainField, PlainBuf, GAP,
|
|
4118
4265
|
)
|
|
4119
4266
|
emitReached = new Set(pre.reached)
|
|
4120
4267
|
if (emitted.ext !== undefined) HYBRID = emitted
|
|
@@ -4151,7 +4298,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
4151
4298
|
// Two engines that can only be compared across two checkouts cannot be
|
|
4152
4299
|
// compared at all: the bench harness's own guidance is that a
|
|
4153
4300
|
// cross-worktree measurement carries a bias no repetition removes. Same
|
|
4154
|
-
// spelling as `PM_TABLE_COUNT`
|
|
4301
|
+
// spelling as the removed `PM_TABLE_COUNT` counters.
|
|
4155
4302
|
if (!EMIT_ENABLED) throw new Unemittable('PM_TABLE_EMIT=0 (measurement toggle)')
|
|
4156
4303
|
const em = emitAssemblySource(t, prog, cfg, extraIps)
|
|
4157
4304
|
/**
|
|
@@ -4181,13 +4328,12 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
4181
4328
|
throw new Unemittable(`the Function constructor (${String(e)})`)
|
|
4182
4329
|
}
|
|
4183
4330
|
emitted = factory(
|
|
4184
|
-
EC, FAIL, k, fx, fns, em.masks, em.classes, em.armExpected, trivia,
|
|
4185
|
-
trivia.map(tv => tv?._meta.triviaKindLabels), triviaScan,
|
|
4331
|
+
EC, FAIL, k, fx, fns, em.masks, em.classes, em.armExpected, trivia, triviaScan,
|
|
4186
4332
|
scansArr, disp, dsp, EMPTY_FX, EMPTY_CH, EMPTY_TLOG, EMPTY_TL,
|
|
4187
4333
|
cstCaptureActive, pushCstLeaf, pushCstChild, rollbackTriviaAt, rollbackScannedTriviaAt, failAt,
|
|
4188
4334
|
classHas, consumeTrivia, buildFieldMap, projectChild, unwrapChild,
|
|
4189
4335
|
demoteCapturedToRaw, cstLeavesLen, skipTriviaScanned, needsDeferredTriviaCommit,
|
|
4190
|
-
|
|
4336
|
+
advanceTrivia, refuseUnclassifiedRootScope, spanLines, rawEntry, lead,
|
|
4191
4337
|
asciiFoldKey, ROUTED_FX,
|
|
4192
4338
|
// THE SENTINEL POOL, dense over char-class indices so the emitted text can
|
|
4193
4339
|
// index it the way it indexes `SCANS`. Built only for a recovery assembly
|
|
@@ -4196,10 +4342,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
4196
4342
|
// object rather than to two separately constructed ones.
|
|
4197
4343
|
REC ? prog.cc.map((_, i) => sentinelFor(i)) : EMPTY_SENTS,
|
|
4198
4344
|
matchesAt, recoverScan, orSentinel, captureError,
|
|
4199
|
-
scalarRecognizers,
|
|
4345
|
+
scalarRecognizers, t.lex, adjacencyHolds, t.lexPrograms,
|
|
4200
4346
|
LEAD, triviaFieldCapture, skipDeferredTrivia,
|
|
4201
4347
|
collapseEmpty,
|
|
4202
|
-
PlainSpan, PlainLeaf, PlainNode, PlainField, PlainBuf,
|
|
4348
|
+
PlainSpan, PlainLeaf, PlainNode, PlainField, PlainBuf, GAP,
|
|
4203
4349
|
)
|
|
4204
4350
|
emitReached = em.reached
|
|
4205
4351
|
} catch (e) {
|
|
@@ -4423,7 +4569,7 @@ export class AssemblyCache {
|
|
|
4423
4569
|
* That is not a hole in G5, it is G5's own first clause: "quickly building the
|
|
4424
4570
|
* grammar reference ON RUN START, making some swaps on rules or sub-rules, and
|
|
4425
4571
|
* then the run actually runs with no logic branching for that option input"
|
|
4426
|
-
* (`notes/TABLE-DRIVER.md`). This IS the run-start step. What the criterion
|
|
4572
|
+
* (`notes/history/TABLE-DRIVER.md`). This IS the run-start step. What the criterion
|
|
4427
4573
|
* forbids is the second sentence, and past this call there is no option read
|
|
4428
4574
|
* anywhere — `scripts/check-invariants.mjs` INV-6 decides that mechanically.
|
|
4429
4575
|
*
|
package/src/table/child-slots.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_LIVE, OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT,
|
|
5
5
|
OP_PEEK, OP_REJECT, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX, OP_RX_TRACK, OP_SCAN,
|
|
6
6
|
OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
7
|
-
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
7
|
+
OP_LEX_BODY, OP_LEX_PROGRAM, OP_GAPGATE, OP_RGATE2, OP_STRICT,
|
|
8
8
|
} from './ops.ts'
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -47,16 +47,21 @@ export function childSlots(code: ArrayLike<number>, ip: number, out: number[]):
|
|
|
47
47
|
// rest — a coverage-encoded program is a program, and omitting it made
|
|
48
48
|
// `opHistogram` throw on every table the coverage build produces.
|
|
49
49
|
case OP_RULE: case OP_OPT: case OP_NOT: case OP_PEEK: case OP_EXPECT:
|
|
50
|
-
case OP_TOKEN: case OP_ATTEMPT: case OP_LABEL: case OP_COV: case OP_REJECT:
|
|
50
|
+
case OP_TOKEN: case OP_ATTEMPT: case OP_LABEL: case OP_COV: case OP_REJECT: case OP_GAPGATE:
|
|
51
51
|
out.push(code[ip + 1]!)
|
|
52
52
|
return true
|
|
53
53
|
|
|
54
54
|
// Single child at `ip + 2`.
|
|
55
55
|
case OP_GATE: case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM:
|
|
56
|
-
case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_SOURCE_LEAF: case OP_ARMGATE: case OP_RGATE:
|
|
56
|
+
case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_SOURCE_LEAF: case OP_ARMGATE: case OP_RGATE: case OP_RGATE2:
|
|
57
57
|
out.push(code[ip + 2]!)
|
|
58
58
|
return true
|
|
59
59
|
|
|
60
|
+
// `STRICT strict tolerant` — both encodings are reachable; an assembly keeps one.
|
|
61
|
+
case OP_STRICT:
|
|
62
|
+
out.push(code[ip + 1]!, code[ip + 2]!)
|
|
63
|
+
return true
|
|
64
|
+
|
|
60
65
|
// `ROUTED fallback` — the operand is an offset or −1. The routed token itself is
|
|
61
66
|
// data the enclosing `dispatch()` already consumed, so the only edge is the
|
|
62
67
|
// fallback.
|
|
@@ -68,7 +68,7 @@ export type TableRuleMapOptions = {
|
|
|
68
68
|
* `coverage: grammarCoverage` here for every `rules()` grammar — so this is what
|
|
69
69
|
* makes `{ grammarCoverage: true }` a working build under the table lowering.
|
|
70
70
|
*
|
|
71
|
-
* COUNTERS ONLY, per the owner ruling recorded in `notes/RELEASE-0.48-TARGET.md`
|
|
71
|
+
* COUNTERS ONLY, per the owner ruling recorded in `notes/history/RELEASE-0.48-TARGET.md`
|
|
72
72
|
* §1; `_grammarTrace` parity is 0.48 work. The plugin's coverage path needs the
|
|
73
73
|
* DEFINITIONS STAMP and the HITS, and both are here.
|
|
74
74
|
*/
|
|
@@ -356,9 +356,12 @@ export function compileRuleMap(
|
|
|
356
356
|
const roots = assemblyExtraIps(artifact)
|
|
357
357
|
let siteBytes, fixed
|
|
358
358
|
try {
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
|
|
359
|
+
// RANKED with every gap gate lowered as its item (`gapNeutral`), so the
|
|
360
|
+
// gates move no site's place in the order; the fit below is judged by the
|
|
361
|
+
// source that ships, gates included. Priced throughout without the
|
|
362
|
+
// `_errors` marks a lexical program over a scan adds (`scanSinkNeutral`),
|
|
363
|
+
// so they move no site in or out of the budget.
|
|
364
|
+
siteBytes = emitAssemblySource(table, artifact, cfg, roots, true, undefined, true, true).siteBytes
|
|
362
365
|
// What any selective assembly costs before its first site body: the shared
|
|
363
366
|
// prelude and the factory's tail. A budget that cannot cover it buys nothing.
|
|
364
367
|
fixed = emitAssemblySource(table, artifact, cfg, roots, true, new Set(), true).source.length
|
package/src/table/compile.ts
CHANGED
|
@@ -90,7 +90,7 @@ export type TableCompileOptions = {
|
|
|
90
90
|
*
|
|
91
91
|
* COUNTERS ONLY — owner ruling. `_grammarTrace`'s six phases are emitted at
|
|
92
92
|
* ~40 fine-grained sites by codegen and are deferred to 0.48
|
|
93
|
-
* (`notes/RELEASE-0.48-TARGET.md` §1). A table entry rejects a context carrying
|
|
93
|
+
* (`notes/history/RELEASE-0.48-TARGET.md` §1). A table entry rejects a context carrying
|
|
94
94
|
* a trace sink before parsing: silently returning an empty trace would look
|
|
95
95
|
* exactly like a real trace whose grammar sites were never visited. The
|
|
96
96
|
* plugin's coverage path reads `_grammarCoverage` and the definitions stamp,
|