parseman 0.57.0 → 0.58.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/diagnostics.js +1799 -962
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +1928 -1091
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +11 -1
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +69 -16
- package/dist/compiler/token-capability.js.map +2 -2
- package/dist/cst/capture-buffer.d.ts +6 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/index.js +1016 -177
- package/dist/index.js.map +3 -3
- package/dist/plugin/index.d.ts +25 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1209 -236
- package/dist/plugin/index.js.map +3 -3
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +1 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts.map +1 -1
- 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 +42 -0
- package/dist/table/factor-choice.d.ts.map +1 -0
- package/dist/table/fail-clean.d.ts +3 -0
- package/dist/table/fail-clean.d.ts.map +1 -0
- package/dist/table/hold-gap.d.ts +8 -0
- package/dist/table/hold-gap.d.ts.map +1 -0
- package/dist/table/index.js +1369 -259
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-program.d.ts +7 -3
- package/dist/table/lex-program.d.ts.map +1 -1
- package/dist/table/ops.d.ts +8 -0
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/run-support.d.ts +19 -0
- package/dist/table/run-support.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +2 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -1
- package/dist/table/site-labels.d.ts +66 -0
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/combinators/dispatch.ts +12 -3
- package/src/compiler/token-alphabet.ts +57 -17
- package/src/cst/capture-buffer.ts +25 -0
- package/src/plugin/index.ts +230 -35
- package/src/table/assemble.ts +252 -62
- package/src/table/child-slots.ts +2 -2
- package/src/table/compile-rule-map.ts +14 -5
- package/src/table/emit-assembly.ts +112 -23
- package/src/table/emit.ts +3 -1
- package/src/table/encode.ts +80 -14
- package/src/table/exec.ts +27 -8
- package/src/table/factor-choice.ts +173 -0
- package/src/table/fail-clean.ts +130 -0
- package/src/table/hold-gap.ts +68 -0
- package/src/table/lex-program.ts +197 -43
- package/src/table/ops.ts +9 -1
- package/src/table/run-support.ts +35 -0
- package/src/table/scalar-terminal.ts +8 -0
- package/src/table/site-labels.ts +135 -4
- package/src/version.ts +1 -1
package/src/table/child-slots.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
OP_ADJ, OP_ARMGATE, OP_ATTEMPT, OP_CHOICE, OP_COV, OP_DISPATCH, OP_EMPTY, OP_EXPECT,
|
|
2
|
+
OP_ADJ, OP_ARMGATE, OP_RGATE, OP_ATTEMPT, OP_CHOICE, OP_COV, OP_DISPATCH, OP_EMPTY, OP_EXPECT,
|
|
3
3
|
OP_FIELD, OP_GATE, OP_GREEDY, OP_GUARD, OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_LIT_CI,
|
|
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,
|
|
@@ -53,7 +53,7 @@ export function childSlots(code: ArrayLike<number>, ip: number, out: number[]):
|
|
|
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:
|
|
56
|
+
case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_SOURCE_LEAF: case OP_ARMGATE: case OP_RGATE:
|
|
57
57
|
out.push(code[ip + 2]!)
|
|
58
58
|
return true
|
|
59
59
|
|
|
@@ -369,18 +369,27 @@ export function compileRuleMap(
|
|
|
369
369
|
// the selection is judged by the source it actually emits, scaled for that 5%,
|
|
370
370
|
// and narrowed until that fits.
|
|
371
371
|
const ceiling = budget / SUPERCOMPILE_GROWTH
|
|
372
|
+
// A budget that cannot cover the prelude buys nothing, by design: no refusal.
|
|
373
|
+
if (ceiling <= fixed) return none
|
|
374
|
+
// Narrowed until it fits, with no attempt limit: the target shrinks by at
|
|
375
|
+
// least 1% a round, so the selection either fits or empties. An empty one
|
|
376
|
+
// says why — a build that asked for an assembly and silently shipped none is
|
|
377
|
+
// a regression no test sees (the artifact still parses, on the closures).
|
|
372
378
|
let target = ceiling
|
|
373
|
-
for (
|
|
379
|
+
for (;;) {
|
|
374
380
|
const sites = selectHotSites(artifact.code, Object.values(artifact.rules), siteBytes, target - fixed, profile)
|
|
375
|
-
if (sites.size === 0)
|
|
381
|
+
if (sites.size === 0) {
|
|
382
|
+
return {
|
|
383
|
+
...none,
|
|
384
|
+
refusal: `a site within its ${Math.floor(ceiling)} B selection ceiling (the prelude alone is ${fixed} B)`,
|
|
385
|
+
}
|
|
386
|
+
}
|
|
376
387
|
const bytes = emitAssemblySource(table, artifact, cfg, roots, true, sites).source.length
|
|
377
388
|
if (bytes <= ceiling) return { sites, bytes: Math.ceil(bytes * SUPERCOMPILE_GROWTH) }
|
|
378
389
|
// Scale by the overshoot, and by at least 1%: near the ceiling a site is
|
|
379
|
-
// the unit, and the ratio alone can stall a few bytes over it
|
|
380
|
-
// attempts run out and nothing ships.
|
|
390
|
+
// the unit, and the ratio alone can stall a few bytes over it.
|
|
381
391
|
target *= Math.min(ceiling / bytes, 0.99)
|
|
382
392
|
}
|
|
383
|
-
return none
|
|
384
393
|
}
|
|
385
394
|
|
|
386
395
|
return {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
import type { ParseContext } from '../types.ts'
|
|
48
48
|
import { regexCanMatchEmpty } from '../regex/first-set.ts'
|
|
49
49
|
import {
|
|
50
|
-
OP_ADJ, OP_ARMGATE, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
50
|
+
OP_ADJ, OP_ARMGATE, OP_RGATE, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
51
51
|
OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
|
|
52
52
|
OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX,
|
|
53
53
|
OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
@@ -60,13 +60,14 @@ import {
|
|
|
60
60
|
import { emitShapeMatch, scanShapeFromRegex } from './scan-shapes.ts'
|
|
61
61
|
import {
|
|
62
62
|
CAP_OFF, CAP_ON, RAW_CAPTURE, RAW_OMIT, TRI_NONE, TRI_UNKNOWN,
|
|
63
|
-
computeSiteLabels, reachableSites, type SiteLabel,
|
|
63
|
+
computeSiteLabels, gatedPassCollapse, passCollapse, reachableSites, scopeIsIdentity, triviaLeadRanges, type SiteLabel,
|
|
64
64
|
} from './site-labels.ts'
|
|
65
65
|
import {
|
|
66
66
|
largeChoiceLiteralPretest, leadingScalarSequence, leadingScalarTerminal,
|
|
67
|
-
scalarTerminalNodeChild, scalarTerminalNotChild, type LeadingScalarSequence,
|
|
67
|
+
scalarTerminalNodeChild, scalarTerminalNotChild, scalarTerminalPeekChild, type LeadingScalarSequence,
|
|
68
68
|
} from './scalar-terminal.ts'
|
|
69
69
|
import { childSlots } from './child-slots.ts'
|
|
70
|
+
import { holdsGap } from './hold-gap.ts'
|
|
70
71
|
import { fieldSpec, leadingTriviaFieldAt } from './field-spec.ts'
|
|
71
72
|
import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA } from '../combinators/field-trivia.ts'
|
|
72
73
|
|
|
@@ -138,6 +139,9 @@ export const EMITTED_PARAMS = [
|
|
|
138
139
|
// Appended for the deferred-commit trivia skip (`trivia-gap.ts`); old
|
|
139
140
|
// factories ignore it.
|
|
140
141
|
'skipDeferredTrivia',
|
|
142
|
+
// Appended for the pass-through collapse (`run-support.ts`); old factories
|
|
143
|
+
// ignore it.
|
|
144
|
+
'collapseEmpty',
|
|
141
145
|
] as const
|
|
142
146
|
|
|
143
147
|
/**
|
|
@@ -436,6 +440,30 @@ ${scanned}
|
|
|
436
440
|
}`
|
|
437
441
|
}
|
|
438
442
|
|
|
443
|
+
/**
|
|
444
|
+
* A lookahead term that HOLDS its gap (`hold-gap.ts`): the gap is scanned and
|
|
445
|
+
* kept, the lookahead runs at its end, and the cursor stays there for the next
|
|
446
|
+
* term, which cannot match empty.
|
|
447
|
+
*/
|
|
448
|
+
function emitHoldTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string): string {
|
|
449
|
+
const fast = `const ${t}v=${callee}(input,cur,ctx)
|
|
450
|
+
if(${t}v===FAIL)return FAIL
|
|
451
|
+
${dst}=${t}v
|
|
452
|
+
cur=EC.e`
|
|
453
|
+
if (l.tri === TRI_NONE) return fast
|
|
454
|
+
const held = `const ${t}s=${skip}(input,cur,ctx)
|
|
455
|
+
const ${t}v=${callee}(input,${t}s,ctx)
|
|
456
|
+
if(${t}v===FAIL)return FAIL
|
|
457
|
+
${dst}=${t}v
|
|
458
|
+
cur=${t}s`
|
|
459
|
+
if (l.tri !== TRI_UNKNOWN) return held
|
|
460
|
+
return `if(ctx.trivia===undefined){
|
|
461
|
+
${fast}
|
|
462
|
+
}else{
|
|
463
|
+
${held}
|
|
464
|
+
}`
|
|
465
|
+
}
|
|
466
|
+
|
|
439
467
|
/**
|
|
440
468
|
* The repetition's LEAD-TRIVIA skip, as a BLOCK.
|
|
441
469
|
*
|
|
@@ -603,6 +631,8 @@ export function emitAssemblySource(
|
|
|
603
631
|
// the closure recognizer pool: distinct terminal rows sharing one spec share
|
|
604
632
|
// both the recognizer and the ordinary-terminal lowering.
|
|
605
633
|
const scalarSpecs = new Set<number>()
|
|
634
|
+
// Row nullability for `holdsGap`, memoised for this emit.
|
|
635
|
+
const nullable = new Map<number, boolean>()
|
|
606
636
|
// Large choices are different: admitting every wrapped scalar would add a
|
|
607
637
|
// recognizer call to many arms whose one-character gate already did all the
|
|
608
638
|
// useful work. A multi-character literal can still refine an overlapping
|
|
@@ -1115,17 +1145,9 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1115
1145
|
// The label is what makes this decidable: `tri >= 0` can ONLY have come from
|
|
1116
1146
|
// an enclosing scope carrying that slot, and that scope set
|
|
1117
1147
|
// `ctx.triviaKindLabels` and `_pfScan` from the same slot, so all three are
|
|
1118
|
-
// already the values this row would write.
|
|
1119
|
-
//
|
|
1120
|
-
|
|
1121
|
-
//
|
|
1122
|
-
// `ki >= 0` is required rather than implied: `TRI_NONE` and `TRI_UNKNOWN` are
|
|
1123
|
-
// themselves negative, so comparing a negative operand against a lattice
|
|
1124
|
-
// element would read "unknown" as a match.
|
|
1125
|
-
if ((op === OP_SCOPE_PLAIN || (op === OP_SCOPE && code[ip + 3]! === 0)) && code[ip + 1]! >= 0
|
|
1126
|
-
&& labels.at(ip).tri === code[ip + 1]!) {
|
|
1127
|
-
return code[ip + 2]!
|
|
1128
|
-
}
|
|
1148
|
+
// already the values this row would write. A scope with no trivia under
|
|
1149
|
+
// `TRI_NONE` is the same case (`scopeIsIdentity`).
|
|
1150
|
+
if (scopeIsIdentity(code, ip, labels.at(ip))) return code[ip + 2]!
|
|
1129
1151
|
return undefined
|
|
1130
1152
|
}
|
|
1131
1153
|
|
|
@@ -1325,6 +1347,23 @@ return ${child}(input,pos,ctx)
|
|
|
1325
1347
|
}`
|
|
1326
1348
|
}
|
|
1327
1349
|
|
|
1350
|
+
case OP_RGATE: {
|
|
1351
|
+
const child = link(code[ip + 2]!)
|
|
1352
|
+
if (cfg.tolerant || cfg.probe) return `${head}
|
|
1353
|
+
return ${child}(input,pos,ctx)
|
|
1354
|
+
}`
|
|
1355
|
+
const resolved = t.cc[code[ip + 1]!]!
|
|
1356
|
+
const ci = classes.push([resolved]) - 1
|
|
1357
|
+
classPlan.push([code[ip + 1]!])
|
|
1358
|
+
const cls = hoist('cl', `CLS[${ci}][0]`)
|
|
1359
|
+
const xf = fxRef(code[ip + 3]!)
|
|
1360
|
+
return `${head}
|
|
1361
|
+
const it=ctx._routed
|
|
1362
|
+
if(it!==undefined&&it.span.start===pos&&!classHas(${cls},lead(input,it.span.end))){ctx._fe=it.span.end;ctx._fx=${xf};return FAIL}
|
|
1363
|
+
return ${child}(input,pos,ctx)
|
|
1364
|
+
}`
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1328
1367
|
case OP_ARMGATE: {
|
|
1329
1368
|
// `choice({ gate, combinator })`, as `assemble.ts` lowers it. A blocked
|
|
1330
1369
|
// arm is SKIPPED, not failed (choice.ts `continue`): it clears the cut,
|
|
@@ -1663,9 +1702,9 @@ return FAIL
|
|
|
1663
1702
|
const p = tmp()
|
|
1664
1703
|
return `${head}
|
|
1665
1704
|
${lookaheadMiss(code[ip + 1]!, 'EC.e=pos;return null')}${emitMark(p, L.buf, L.raw, sinks)}
|
|
1666
|
-
const v=${child}(input,pos,ctx)
|
|
1705
|
+
${cfg.probe ? 'const pb=ctx._probe?.best\n' : ''}const v=${child}(input,pos,ctx)
|
|
1667
1706
|
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1668
|
-
if(v===FAIL){EC.e=pos;return null}
|
|
1707
|
+
${cfg.probe ? 'if(ctx._probe!==undefined)ctx._probe.best=pb??null\n' : ''}if(v===FAIL){EC.e=pos;return null}
|
|
1669
1708
|
ctx._fe=pos
|
|
1670
1709
|
ctx._fx=${xf}
|
|
1671
1710
|
return FAIL
|
|
@@ -1673,14 +1712,24 @@ return FAIL
|
|
|
1673
1712
|
}
|
|
1674
1713
|
|
|
1675
1714
|
case OP_PEEK: {
|
|
1715
|
+
const scalarChild = scalarTerminalPeekChild(code, ip)
|
|
1716
|
+
if (scalarChild >= 0) {
|
|
1717
|
+
const recognize = recognizerRef(code[scalarChild + 1]!)
|
|
1718
|
+
const xf = fxRef(code[ip + 2]!)
|
|
1719
|
+
return `${head}
|
|
1720
|
+
if(${recognize}(input,pos)<0){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1721
|
+
EC.e=pos
|
|
1722
|
+
return null
|
|
1723
|
+
}`
|
|
1724
|
+
}
|
|
1676
1725
|
const child = link(code[ip + 1]!)
|
|
1677
1726
|
const xf = fxRef(code[ip + 2]!)
|
|
1678
1727
|
const p = tmp()
|
|
1679
1728
|
return `${head}
|
|
1680
1729
|
${lookaheadMiss(code[ip + 1]!, `ctx._fe=pos;ctx._fx=${xf};return FAIL`)}${emitMark(p, L.buf, L.raw, sinks)}
|
|
1681
|
-
const v=${child}(input,pos,ctx)
|
|
1730
|
+
${cfg.probe ? 'const pb=ctx._probe?.best\n' : ''}const v=${child}(input,pos,ctx)
|
|
1682
1731
|
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1683
|
-
if(v===FAIL){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1732
|
+
${cfg.probe ? 'if(ctx._probe!==undefined)ctx._probe.best=pb??null\n' : ''}if(v===FAIL){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1684
1733
|
EC.e=pos
|
|
1685
1734
|
return null
|
|
1686
1735
|
}`
|
|
@@ -1779,7 +1828,9 @@ return v
|
|
|
1779
1828
|
// The callee expression hands it over AFTER the scan statement, right
|
|
1780
1829
|
// before the call, so a field inside the trivia grammar cannot take it.
|
|
1781
1830
|
const callee = leadingTriviaFieldAt(code, k, childIp) ? `(LEAD.at=cur,${kids[i]!})` : kids[i]!
|
|
1782
|
-
parts.push(
|
|
1831
|
+
parts.push(holdsGap(code, k, base, n, i, nullable)
|
|
1832
|
+
? emitHoldTerm(callee, vn, tmp(), L, skipFor(L))
|
|
1833
|
+
: emitTerm(callee, vn, tmp(), L, skipFor(L)))
|
|
1783
1834
|
}
|
|
1784
1835
|
names.push(vn)
|
|
1785
1836
|
}
|
|
@@ -2433,6 +2484,35 @@ return nd
|
|
|
2433
2484
|
// The trailing-trivia bit (128) rides on the children-only shape as it
|
|
2434
2485
|
// does in the closure assembler: a consume after the body, no capture.
|
|
2435
2486
|
const leanFlags = flags & ~128
|
|
2487
|
+
// THE PASS-THROUGH COLLAPSE (`run-support.ts` `collapseEmpty`): a
|
|
2488
|
+
// collapse over one node is that node. `passCollapse` is the closure
|
|
2489
|
+
// engine's test too.
|
|
2490
|
+
if (build !== undefined && passCollapse(code, ip, hostCst)) {
|
|
2491
|
+
return `${head}
|
|
2492
|
+
const v=${child}(input,pos,ctx)
|
|
2493
|
+
if(v!==undefined)return v
|
|
2494
|
+
return collapseEmpty(ctx,${build},input,pos,EC.e,${flags === 32})
|
|
2495
|
+
}`
|
|
2496
|
+
}
|
|
2497
|
+
// A GATED PASS COLLAPSE (`site-labels.ts` `gatedPassCollapse`): where the
|
|
2498
|
+
// character at `pos` opens none of the body's optional terms and no
|
|
2499
|
+
// trivia, the body is its node term, run as a pass-through collapse.
|
|
2500
|
+
// Not under recovery or a probe: there a gated-out term still runs.
|
|
2501
|
+
const gated = build !== undefined && !cfg.tolerant && !cfg.probe ? gatedPassCollapse(code, ip, hostCst) : undefined
|
|
2502
|
+
const triviaLead = gated === undefined ? undefined : triviaLeadRanges(labels.at(code[ip + 2]!).tri, t.trivia)
|
|
2503
|
+
const gp = gated !== undefined && triviaLead !== undefined && triviaLead.length <= 16
|
|
2504
|
+
? `const gc=lead(input,pos)
|
|
2505
|
+
if(!(${[
|
|
2506
|
+
...gated.leads.map(g => `!(${gateMissTest(g)})`),
|
|
2507
|
+
...gated.lits.map(i => `gc===${(k[i] as string).codePointAt(0)!}`),
|
|
2508
|
+
...triviaLead.map(r => r.lo === r.hi ? `gc===${r.lo}` : `(gc>=${r.lo}&&gc<=${r.hi})`),
|
|
2509
|
+
].join('||') || 'false'})){
|
|
2510
|
+
const gv=${link(gated.last)}(input,pos,ctx)
|
|
2511
|
+
if(gv!==undefined)return gv
|
|
2512
|
+
return collapseEmpty(ctx,${build},input,pos,EC.e,${flags === 32})
|
|
2513
|
+
}
|
|
2514
|
+
`
|
|
2515
|
+
: ''
|
|
2436
2516
|
if (!hostCst && !tracked && build !== undefined && proj < 0
|
|
2437
2517
|
&& (leanFlags === 2 || (flags === 18 || flags === 34))) {
|
|
2438
2518
|
const fields = flags === 18
|
|
@@ -2470,7 +2550,7 @@ sCh.push(nd)
|
|
|
2470
2550
|
if(sRaw!==undefined)sRaw.push(rawEntry(nd,input,pos,end))
|
|
2471
2551
|
}`
|
|
2472
2552
|
return `${head}
|
|
2473
|
-
const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
|
|
2553
|
+
${gp}const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
|
|
2474
2554
|
const sCap=ctx.captureTrivia,sBuf=ctx._cstBuf,sFields=ctx._fields
|
|
2475
2555
|
const flat=[]
|
|
2476
2556
|
ctx._cstBuf=undefined
|
|
@@ -2565,7 +2645,7 @@ const hostKids=kids
|
|
|
2565
2645
|
const rawKids=buf.raw??(buf.rawSingle!==undefined?[buf.rawSingle]:EMPTY_CH)
|
|
2566
2646
|
const tlog=buf.tl??EMPTY_TLOG`
|
|
2567
2647
|
return `${head}
|
|
2568
|
-
const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
|
|
2648
|
+
${!hostCst && !tracked ? gp : ''}const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
|
|
2569
2649
|
const sCap=ctx.captureTrivia,sBuf=ctx._cstBuf
|
|
2570
2650
|
${openCapture}
|
|
2571
2651
|
ctx.captureTrivia=${captureWide}
|
|
@@ -2656,10 +2736,19 @@ bind:function(xs,ps){_xScan=xs
|
|
|
2656
2736
|
${externals.map((ip, i) => `_xp${ip}=ps[${i}]`).join('\n')}},
|
|
2657
2737
|
scan:function(s){const o=_pfScan;_pfScan=s;return o}`
|
|
2658
2738
|
// One wrapper per external: the closure engine reads its OWN installed trivia
|
|
2659
|
-
// scanner, so the emitted one is handed across for the call and restored.
|
|
2739
|
+
// scanner, so the emitted one is handed across for the call and restored. A
|
|
2740
|
+
// site whose label names its trivia hands that trivia's own scanner, which is
|
|
2741
|
+
// what any scope installing it would have installed: the emitted slot is read
|
|
2742
|
+
// only where the labels cannot say (`site-labels.ts` `scanHandoffNeeded`).
|
|
2743
|
+
const scannerFor = (ip: number): string => {
|
|
2744
|
+
const ki = labels.at(ip).tri
|
|
2745
|
+
if (ki === TRI_UNKNOWN) return '_pfScan'
|
|
2746
|
+
if (ki === TRI_NONE) return 'null'
|
|
2747
|
+
return swapLegal && !triviaLabelled[ki]! && t.triviaScan[ki] != null ? hoist('ts', `TRIVIASCAN[${ki}]`) : 'null'
|
|
2748
|
+
}
|
|
2660
2749
|
const externalDefs = select === undefined ? '' : `let _xScan
|
|
2661
2750
|
${externals.map(ip => `let _xp${ip}
|
|
2662
|
-
function _px${ip}(input,pos,ctx){const o=_xScan(
|
|
2751
|
+
function _px${ip}(input,pos,ctx){const o=_xScan(${scannerFor(ip)});const v=_xp${ip}(input,pos,ctx);_xScan(o);return v}`).join('\n')}
|
|
2663
2752
|
`
|
|
2664
2753
|
|
|
2665
2754
|
// The per-label trivia scans sit AFTER the hoisted pool they close over: they
|
package/src/table/emit.ts
CHANGED
|
@@ -35,7 +35,9 @@ function emitClassPool(classes: readonly string[]): string {
|
|
|
35
35
|
if (classes.length < 2) return array
|
|
36
36
|
const delimiter = '|/~?^`'.split('').find(ch => classes.every(spec => !spec.includes(ch)))
|
|
37
37
|
if (delimiter === undefined) return array
|
|
38
|
-
|
|
38
|
+
// A call a bundler cannot prove pure is kept even when nothing reads its result,
|
|
39
|
+
// so a sibling variant shaken out of a bundle left its whole pool behind.
|
|
40
|
+
const split = `/* @__PURE__ */ ${jsString(classes.join(delimiter))}.split(${jsString(delimiter)})`
|
|
39
41
|
return split.length < array.length ? split : array
|
|
40
42
|
}
|
|
41
43
|
|
package/src/table/encode.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { scanRefs } from './program.ts'
|
|
2
|
+
import { factorSharedEnds } from './factor-choice.ts'
|
|
2
3
|
import type { AutoNotCheck, Combinator, FirstSet, ParserDef } from '../types.ts'
|
|
3
4
|
import { classifyFinalChoice, firstSetOf, intersects, matchesEmpty, union, type RefResolver } from '../combinators/first-set.ts'
|
|
4
5
|
import { childrenOf } from '../analysis/gating.ts'
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
17
18
|
OP_LIVE, OP_ATTEMPT, OP_LABEL,
|
|
18
19
|
OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD,
|
|
19
|
-
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_COV,
|
|
20
|
+
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE, OP_COV,
|
|
20
21
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
21
22
|
} from './ops.ts'
|
|
22
23
|
import { adjacencyExpected } from '../combinators/adjacency.ts'
|
|
@@ -27,8 +28,8 @@ import type { BalancedSpec } from '../combinators/scanTo.ts'
|
|
|
27
28
|
import type { DispatchSpec, LexBodySpec, LexProgramSpec, ScanSpec, SubtreeRef, TableProgram, TriviaSpec } from './program.ts'
|
|
28
29
|
import {
|
|
29
30
|
lexProgramDigest,
|
|
30
|
-
|
|
31
|
-
LEX_NODE_NOT, LEX_NODE_OPTIONAL,
|
|
31
|
+
LEX_NODE_CHOICE,
|
|
32
|
+
LEX_NODE_NOT, LEX_NODE_OPTIONAL, LEX_NODE_REPEAT,
|
|
32
33
|
LEX_NODE_SEQUENCE2, LEX_NODE_SEQUENCE3, LEX_NODE_SEQUENCE4, LEX_NODE_SEQUENCE5,
|
|
33
34
|
LEX_NODE_TERMINAL,
|
|
34
35
|
} from './lex-program.ts'
|
|
@@ -298,15 +299,18 @@ class Encoder {
|
|
|
298
299
|
}
|
|
299
300
|
case 'choice': {
|
|
300
301
|
for (const arm of node.arms) if (!emitNode(arm)) return false
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
302
|
+
// The arm classes and the exclusive (one-arm) decision are the
|
|
303
|
+
// character choice's own (`case 'choice'`), under the trivia the
|
|
304
|
+
// encoder would hold at that row, so both lowerings rank the same
|
|
305
|
+
// failures.
|
|
306
|
+
const final = classifyFinalChoice(
|
|
307
|
+
node.parsers, this.refResolver(), node.cleared ? undefined : this.activeTrivia,
|
|
308
|
+
)
|
|
309
|
+
const classes = final.firstSets.map((first, i) => final.nullable[i] ? -1 : this.charClass(first))
|
|
310
|
+
const exclusive = final.exclusive && classes.every(cls => cls >= 0)
|
|
311
|
+
words.push(LEX_NODE_CHOICE, node.arms.length, exclusive ? 1 : 0, this.expected(node.expected))
|
|
306
312
|
for (let i = 0; i < node.arms.length; i++) {
|
|
307
|
-
|
|
308
|
-
words.push(first.kind === 'ranges' ? this.charClass(first) : -1)
|
|
309
|
-
words.push(this.expected(node.armExpected[i]!))
|
|
313
|
+
words.push(classes[i]!, this.expected(node.armExpected[i]!))
|
|
310
314
|
}
|
|
311
315
|
return true
|
|
312
316
|
}
|
|
@@ -318,6 +322,10 @@ class Encoder {
|
|
|
318
322
|
if (!emitNode(node.body)) return false
|
|
319
323
|
words.push(LEX_NODE_OPTIONAL)
|
|
320
324
|
return true
|
|
325
|
+
case 'repeat':
|
|
326
|
+
if (!emitNode(node.body)) return false
|
|
327
|
+
words.push(LEX_NODE_REPEAT, node.min, node.max)
|
|
328
|
+
return true
|
|
321
329
|
}
|
|
322
330
|
}
|
|
323
331
|
if (!emitNode(body.root)) return undefined
|
|
@@ -940,6 +948,54 @@ class Encoder {
|
|
|
940
948
|
return this.emit(OP_SCOPE_PLAIN, this.triviaSlot(amb), ip)
|
|
941
949
|
}
|
|
942
950
|
|
|
951
|
+
/**
|
|
952
|
+
* The `OP_RGATE` operands of a choice arm that opens with `routed()`, or
|
|
953
|
+
* `undefined`. Through `node()`/scope wrappers and first sequence terms, the
|
|
954
|
+
* arm's next term after the routed token is found; the gate is that term's
|
|
955
|
+
* first characters, with the trivia in force at the boundary before it, so
|
|
956
|
+
* the arm is entered only where it can get past the token. The term must be
|
|
957
|
+
* unable to match empty or to commit, so a miss is exactly that term failing
|
|
958
|
+
* at the token's end with its own set.
|
|
959
|
+
*/
|
|
960
|
+
private routedArmGate(arm: Combinator<unknown>): { cls: number; fx: number } | undefined {
|
|
961
|
+
type Follow = 'end' | { next: Combinator<unknown>; boundary: Combinator<unknown> | undefined } | undefined
|
|
962
|
+
const walk = (p: Combinator<unknown>, trivia: Combinator<unknown> | undefined, depth: number): Follow => {
|
|
963
|
+
if (depth > 16) return undefined
|
|
964
|
+
const d = p._def as ParserDef
|
|
965
|
+
switch (d.tag) {
|
|
966
|
+
case 'routed': return 'end'
|
|
967
|
+
case 'node': return walk(d.parser, trivia, depth + 1)
|
|
968
|
+
case 'grammar':
|
|
969
|
+
return walk(d.parser, d.clearTrivia === true ? undefined : d.triviaParser ?? trivia, depth + 1)
|
|
970
|
+
case 'sequence': {
|
|
971
|
+
const head = d.parsers[0]
|
|
972
|
+
if (head === undefined) return undefined
|
|
973
|
+
const r = walk(head, trivia, depth + 1)
|
|
974
|
+
if (r !== 'end') return r
|
|
975
|
+
const next = d.parsers[1]
|
|
976
|
+
return next === undefined ? 'end' : { next, boundary: trivia }
|
|
977
|
+
}
|
|
978
|
+
default: return undefined
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const follow = walk(arm, this.activeTrivia, 0)
|
|
982
|
+
if (follow === undefined || follow === 'end') return undefined
|
|
983
|
+
const rr = this.refResolver()
|
|
984
|
+
const { next, boundary } = follow
|
|
985
|
+
if (matchesEmpty(next, new Set(), rr) || mayCommitFailure(next, rr, this.commitFailureMemo)) return undefined
|
|
986
|
+
// A miss reports `deriveExpected(next)`, a static set. Only a terminal or a
|
|
987
|
+
// label fails with exactly that set: a sequence over an `optional` prefix
|
|
988
|
+
// reports the prefix's labels statically but not when it runs, and a choice
|
|
989
|
+
// whose arms share an opener reports that opener once statically, once per
|
|
990
|
+
// arm when it runs.
|
|
991
|
+
const nextTag = (next._def as ParserDef).tag
|
|
992
|
+
if (nextTag !== 'literal' && nextTag !== 'regex' && nextTag !== 'keywords' && nextTag !== 'label') return undefined
|
|
993
|
+
const first = firstSetOf(next, new Set(), rr, boundary)
|
|
994
|
+
const cls = this.charClass(boundary === undefined ? first : union(first, firstSetOf(boundary, new Set(), rr)))
|
|
995
|
+
if (cls < 0) return undefined
|
|
996
|
+
return { cls, fx: this.expected(deriveExpected(next)) }
|
|
997
|
+
}
|
|
998
|
+
|
|
943
999
|
node(p: Combinator<unknown>): Emitted {
|
|
944
1000
|
// See `refMemo`: a reference is memoised per REFERENCING trivia scope, every
|
|
945
1001
|
// other construct once for the program.
|
|
@@ -1101,6 +1157,12 @@ class Encoder {
|
|
|
1101
1157
|
return head
|
|
1102
1158
|
}
|
|
1103
1159
|
case 'choice': {
|
|
1160
|
+
// Arms that differ in one term are that term's choice (`factor-choice.ts`).
|
|
1161
|
+
// Not under a coverage plan, which credits the arms as written.
|
|
1162
|
+
if (this.plan === undefined) {
|
|
1163
|
+
const factored = factorSharedEnds(d)
|
|
1164
|
+
if (factored !== undefined) return this.node(factored).ip
|
|
1165
|
+
}
|
|
1104
1166
|
// `greedyClassify` is NOT a choice at all — one arm runs and another arm
|
|
1105
1167
|
// is credited — so it gets its own row rather than an arm ordering.
|
|
1106
1168
|
if (d.strategy.tag === 'greedyClassify') {
|
|
@@ -1169,11 +1231,15 @@ class Encoder {
|
|
|
1169
1231
|
const kids = arms.map((c, k) => {
|
|
1170
1232
|
const src = order[k]!
|
|
1171
1233
|
const checks = d.autoNot[src]
|
|
1172
|
-
const
|
|
1234
|
+
const gate = d.gates[src]
|
|
1235
|
+
const plain = (checks === null || checks === undefined || checks.length === 0)
|
|
1236
|
+
&& (gate === null || gate === undefined)
|
|
1237
|
+
const routedGate = plain ? this.routedArmGate(c) : undefined
|
|
1238
|
+
const body = this.node(c).ip
|
|
1239
|
+
const inner = routedGate === undefined ? body : this.emit(OP_RGATE, routedGate.cls, body, routedGate.fx)
|
|
1173
1240
|
const ip = checks === null || checks === undefined || checks.length === 0
|
|
1174
1241
|
? inner
|
|
1175
1242
|
: this.reject(inner, checks)
|
|
1176
|
-
const gate = d.gates[src]
|
|
1177
1243
|
const gated = gate === null || gate === undefined
|
|
1178
1244
|
? ip
|
|
1179
1245
|
: this.emit(OP_ARMGATE, this.fn(gate, d.gateSrcs?.[src] ?? null), ip, this.expected(deriveExpected(c)))
|
|
@@ -1794,7 +1860,7 @@ class Encoder {
|
|
|
1794
1860
|
}
|
|
1795
1861
|
const slots = (ip: number): number[] => {
|
|
1796
1862
|
switch (this.code[ip]) {
|
|
1797
|
-
case OP_GATE: return [ip + 2]
|
|
1863
|
+
case OP_GATE: case OP_RGATE: return [ip + 2]
|
|
1798
1864
|
case OP_RULE: case OP_OPT: case OP_NOT: case OP_PEEK: case OP_EXPECT: case OP_ATTEMPT: case OP_LABEL: case OP_COV: return [ip + 1]
|
|
1799
1865
|
case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM: case OP_LEAF: case OP_SOURCE_LEAF: case OP_NODE: case OP_NODE_TRACK: return [ip + 2]
|
|
1800
1866
|
case OP_SEQ: case OP_SEQV: return Array.from({ length: this.code[ip + 1]! }, (_, i) => ip + 2 + i)
|
package/src/table/exec.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
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
24
|
OP_COV,
|
|
25
|
-
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE,
|
|
25
|
+
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE,
|
|
26
26
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
27
27
|
} from './ops.ts'
|
|
28
28
|
import { adjacencyHolds, adjacencyMisuse } from '../combinators/adjacency.ts'
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
type CompactProgram, type ResolvedClass, type ResolvedDispatch, type ResolvedDispatchSpec, type ResolvedTable,
|
|
50
50
|
type SubtreeRef, type TableProgram, type TableRule,
|
|
51
51
|
} from './program.ts'
|
|
52
|
-
import { scalarTerminalNotChild } from './scalar-terminal.ts'
|
|
52
|
+
import { scalarTerminalNotChild, scalarTerminalPeekChild } from './scalar-terminal.ts'
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* THE SHARED DRIVER.
|
|
@@ -923,6 +923,7 @@ function makeDriver(
|
|
|
923
923
|
const lit = k[code[child + 1]!] as string
|
|
924
924
|
if (!input.startsWith(lit, cur)) {
|
|
925
925
|
ctx._fe = cur; ctx._fx = fx[code[child + 2]!] as string[]
|
|
926
|
+
if (ctx._probe !== undefined) failAt(ctx, ctx._fx, cur)
|
|
926
927
|
{ if (REC) ctx._sync = inheritedSync; return FAIL }
|
|
927
928
|
}
|
|
928
929
|
const e = cur + lit.length
|
|
@@ -937,6 +938,7 @@ function makeDriver(
|
|
|
937
938
|
const m = re.exec(input)
|
|
938
939
|
if (m === null) {
|
|
939
940
|
ctx._fe = cur; ctx._fx = fx[code[child + 2]!] as string[]
|
|
941
|
+
if (ctx._probe !== undefined) failAt(ctx, ctx._fx, cur)
|
|
940
942
|
{ if (REC) ctx._sync = inheritedSync; return FAIL }
|
|
941
943
|
}
|
|
942
944
|
const mv = m[0]
|
|
@@ -1118,6 +1120,16 @@ function makeDriver(
|
|
|
1118
1120
|
return sup
|
|
1119
1121
|
}
|
|
1120
1122
|
|
|
1123
|
+
case OP_RGATE: {
|
|
1124
|
+
const item = ctx._routed
|
|
1125
|
+
if (item !== undefined && item.span.start === pos && ctx._probe === undefined && !ctx._tolerant
|
|
1126
|
+
&& !classHas(cc[code[ip + 1]!]!, lead(input, item.span.end))) {
|
|
1127
|
+
ctx._fe = item.span.end; ctx._fx = fx[code[ip + 3]!] as string[]
|
|
1128
|
+
return FAIL
|
|
1129
|
+
}
|
|
1130
|
+
return exec(code[ip + 2]!, input, pos, ctx)
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1121
1133
|
case OP_ARMGATE: {
|
|
1122
1134
|
if (!(fns[code[ip + 1]!] as (s: unknown) => boolean)(ctx.state)) {
|
|
1123
1135
|
// choice.ts:150 is a `continue`, not a failure — the arm is treated as
|
|
@@ -1574,15 +1586,14 @@ function makeDriver(
|
|
|
1574
1586
|
}
|
|
1575
1587
|
|
|
1576
1588
|
case OP_NOT: {
|
|
1577
|
-
// The
|
|
1578
|
-
//
|
|
1579
|
-
//
|
|
1580
|
-
// needs the reference oracle to hide the terminal's diagnostic writes.
|
|
1589
|
+
// The generic closure/emitted NOT bodies leave the child's failure
|
|
1590
|
+
// registers; only the direct-terminal shape hides them. Every NOT hides
|
|
1591
|
+
// the child's completions from the probe, as `not.ts` does.
|
|
1581
1592
|
const scalarChild = scalarTerminalNotChild(code, ip) >= 0
|
|
1582
1593
|
const savedFc = scalarChild ? ctx._fc : false
|
|
1583
1594
|
const savedFe = scalarChild ? ctx._fe : -1
|
|
1584
1595
|
const savedFx = scalarChild ? ctx._fx : undefined
|
|
1585
|
-
const savedProbe =
|
|
1596
|
+
const savedProbe = ctx._probe?.best
|
|
1586
1597
|
const need = rollbackNeeded(ctx)
|
|
1587
1598
|
const mRaw = need ? cstRawLen(ctx) : 0
|
|
1588
1599
|
const mTl = need ? cstTlLen(ctx) : 0
|
|
@@ -1597,8 +1608,8 @@ function makeDriver(
|
|
|
1597
1608
|
ctx._fc = savedFc
|
|
1598
1609
|
ctx._fe = savedFe
|
|
1599
1610
|
ctx._fx = savedFx
|
|
1600
|
-
if (ctx._probe !== undefined) ctx._probe.best = savedProbe ?? null
|
|
1601
1611
|
}
|
|
1612
|
+
if (ctx._probe !== undefined) ctx._probe.best = savedProbe ?? null
|
|
1602
1613
|
if (v === FAIL) { EC.e = pos; return null }
|
|
1603
1614
|
// `not.ts:50` — the ASSERTION's own set, at the assertion's position.
|
|
1604
1615
|
ctx._fe = pos
|
|
@@ -1607,6 +1618,12 @@ function makeDriver(
|
|
|
1607
1618
|
}
|
|
1608
1619
|
|
|
1609
1620
|
case OP_PEEK: {
|
|
1621
|
+
// A direct terminal is reduced to its recognizer in both assemblies
|
|
1622
|
+
// (`scalarTerminalPeekChild`), as under `not()`: hide its own diagnostic
|
|
1623
|
+
// writes here too.
|
|
1624
|
+
const scalarChild = scalarTerminalPeekChild(code, ip) >= 0
|
|
1625
|
+
const savedFc = scalarChild ? ctx._fc : false
|
|
1626
|
+
const savedProbe = ctx._probe?.best
|
|
1610
1627
|
const need = rollbackNeeded(ctx)
|
|
1611
1628
|
const mRaw = need ? cstRawLen(ctx) : 0
|
|
1612
1629
|
const mTl = need ? cstTlLen(ctx) : 0
|
|
@@ -1617,6 +1634,8 @@ function makeDriver(
|
|
|
1617
1634
|
const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
1618
1635
|
const v = exec(code[ip + 1]!, input, pos, ctx)
|
|
1619
1636
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1637
|
+
if (scalarChild) ctx._fc = savedFc
|
|
1638
|
+
if (ctx._probe !== undefined) ctx._probe.best = savedProbe ?? null
|
|
1620
1639
|
// `peek.ts:60` — the ASSERTION's own set, at the assertion's position.
|
|
1621
1640
|
if (v === FAIL) { ctx._fe = pos; ctx._fx = fx[code[ip + 2]!] as string[]; return FAIL }
|
|
1622
1641
|
EC.e = pos
|