parseman 0.60.1 → 0.60.3
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/commitment.d.ts.map +1 -1
- package/dist/analysis/diagnostics.js +128 -43
- package/dist/analysis/diagnostics.js.map +2 -2
- package/dist/cli/index.js +128 -43
- package/dist/cli/index.js.map +2 -2
- package/dist/index.js +128 -43
- package/dist/index.js.map +2 -2
- package/dist/plugin/index.d.ts +18 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +240 -87
- package/dist/plugin/index.js.map +2 -2
- package/dist/table/assemble.d.ts +11 -0
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +1 -0
- package/dist/table/emit-assembly.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/index.js +128 -43
- package/dist/table/index.js.map +2 -2
- package/dist/table/program.d.ts +9 -0
- package/dist/table/program.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/analysis/commitment.ts +29 -15
- package/src/plugin/index.ts +189 -63
- package/src/table/assemble.ts +35 -10
- package/src/table/emit-assembly.ts +41 -11
- package/src/table/encode.ts +57 -9
- package/src/table/exec.ts +5 -2
- package/src/table/program.ts +19 -0
- package/src/version.ts +1 -1
package/src/table/assemble.ts
CHANGED
|
@@ -355,26 +355,38 @@ export type RunCfg = {
|
|
|
355
355
|
* literal bodies to buy nothing measurable.
|
|
356
356
|
*/
|
|
357
357
|
readonly probe: boolean
|
|
358
|
+
/**
|
|
359
|
+
* A caller supplied `ctx.trivia` to a program with no trivia of its own. Such
|
|
360
|
+
* a program records trivia rows only then, so only this assembly restores the
|
|
361
|
+
* trivia logs a failed alternative recorded (`choiceLogRollbackMask`,
|
|
362
|
+
* `failureLogRollback`); every other parse of it takes none of those marks. A
|
|
363
|
+
* program with trivia of its own always restores them and never sets this.
|
|
364
|
+
* Fixed for the parse: a program with no trivia never installs any.
|
|
365
|
+
*/
|
|
366
|
+
readonly inheritedTrivia?: boolean
|
|
358
367
|
}
|
|
359
368
|
|
|
360
|
-
/** THE bit packing for an assembly cache key.
|
|
369
|
+
/** THE bit packing for an assembly cache key. Seven bits, so at most 128
|
|
361
370
|
* ordinary assemblies per table. Host trivia predicates use a host-identity
|
|
362
371
|
* cache in addition to this key because their per-type answer is not one bit.
|
|
363
372
|
*
|
|
364
|
-
* Taken as
|
|
373
|
+
* Taken as seven booleans rather than as a `RunCfg` because `AssemblyCache.forCtx`
|
|
365
374
|
* runs once per entry invocation and must allocate nothing — it derives these bits
|
|
366
375
|
* from the `ctx` and builds a `RunCfg` only on the miss. That constraint is why the
|
|
367
376
|
* packing was written twice, 2500 lines apart in this file, and a bit added to one
|
|
368
377
|
* copy but not the other is a cache COLLISION: a parse served the wrong assembly.
|
|
369
378
|
* Splitting the packing from the object read satisfies both callers from one place. */
|
|
370
|
-
function cfgKeyOf(
|
|
379
|
+
function cfgKeyOf(
|
|
380
|
+
hostCst: boolean, trackLines: boolean, tolerant: boolean, coverage: boolean, probe: boolean,
|
|
381
|
+
hostReadsChildren = true, inheritedTrivia = false,
|
|
382
|
+
): number {
|
|
371
383
|
return (hostCst ? 1 : 0) | (trackLines ? 2 : 0) | (tolerant ? 4 : 0)
|
|
372
|
-
| (coverage ? 8 : 0) | (probe ? 16 : 0) | (hostReadsChildren ? 0 : 32)
|
|
384
|
+
| (coverage ? 8 : 0) | (probe ? 16 : 0) | (hostReadsChildren ? 0 : 32) | (inheritedTrivia ? 64 : 0)
|
|
373
385
|
}
|
|
374
386
|
|
|
375
387
|
/** The cfg key an assembly is cached under. */
|
|
376
388
|
export function cfgKey(c: RunCfg): number {
|
|
377
|
-
return cfgKeyOf(c.hostCst, c.trackLines, c.tolerant, c.coverage, c.probe, c.hostReadsChildren)
|
|
389
|
+
return cfgKeyOf(c.hostCst, c.trackLines, c.tolerant, c.coverage, c.probe, c.hostReadsChildren, c.inheritedTrivia)
|
|
378
390
|
}
|
|
379
391
|
|
|
380
392
|
export type Assembly = {
|
|
@@ -2240,6 +2252,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2240
2252
|
const probeBest = PROBE ? ctx._probe?.best : undefined
|
|
2241
2253
|
const v = child(input, pos, ctx)
|
|
2242
2254
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2255
|
+
// A lookahead contains its child's commitment: `not.ts` and `peek.ts`
|
|
2256
|
+
// return a fresh result, so a committed failure inside the probe never
|
|
2257
|
+
// reaches the grammar around it.
|
|
2258
|
+
ctx._fc = false
|
|
2243
2259
|
if (PROBE && ctx._probe !== undefined) ctx._probe.best = probeBest ?? null
|
|
2244
2260
|
if (v === FAIL) { EC.e = pos; return null }
|
|
2245
2261
|
// `not.ts:50` — the ASSERTION's own set, at the assertion's position.
|
|
@@ -2282,6 +2298,10 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2282
2298
|
const probeBest = PROBE ? ctx._probe?.best : undefined
|
|
2283
2299
|
const v = child(input, pos, ctx)
|
|
2284
2300
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2301
|
+
// A lookahead contains its child's commitment: `not.ts` and `peek.ts`
|
|
2302
|
+
// return a fresh result, so a committed failure inside the probe never
|
|
2303
|
+
// reaches the grammar around it.
|
|
2304
|
+
ctx._fc = false
|
|
2285
2305
|
if (PROBE && ctx._probe !== undefined) ctx._probe.best = probeBest ?? null
|
|
2286
2306
|
// `peek.ts:60` — the ASSERTION's own set, at the assertion's position.
|
|
2287
2307
|
if (v === FAIL) { ctx._fe = pos; ctx._fx = xf; return FAIL }
|
|
@@ -4289,7 +4309,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
4289
4309
|
if (prog.asm !== undefined) {
|
|
4290
4310
|
throw new Unemittable(
|
|
4291
4311
|
`option set ${cfgKey(cfg)} (hostCst=${cfg.hostCst} trackLines=${cfg.trackLines} `
|
|
4292
|
-
+ `tolerant=${cfg.tolerant} coverage=${cfg.coverage} probe=${cfg.probe}
|
|
4312
|
+
+ `tolerant=${cfg.tolerant} coverage=${cfg.coverage} probe=${cfg.probe} `
|
|
4313
|
+
+ `inheritedTrivia=${cfg.inheritedTrivia === true}), which this build `
|
|
4293
4314
|
+ 'did not pre-compile. The Function constructor is NOT used for an artifact that carries '
|
|
4294
4315
|
+ 'assemblies — see `EmitOptions.assemblies` to add this option set to the build',
|
|
4295
4316
|
)
|
|
@@ -4518,7 +4539,9 @@ export function countSiteVisits(
|
|
|
4518
4539
|
export class AssemblyCache {
|
|
4519
4540
|
private readonly t: ResolvedTable
|
|
4520
4541
|
private readonly prog: TableProgram
|
|
4521
|
-
private readonly byCfg: Array<Assembly | undefined> = Array.from({ length:
|
|
4542
|
+
private readonly byCfg: Array<Assembly | undefined> = Array.from({ length: 128 })
|
|
4543
|
+
/** Whether this program installs no trivia of its own (`RunCfg.inheritedTrivia`). */
|
|
4544
|
+
private readonly noOwnTrivia: boolean
|
|
4522
4545
|
/**
|
|
4523
4546
|
* Monomorphic predicate inline cache. An assembly depends on the predicate
|
|
4524
4547
|
* FUNCTION and the scalar cfg key, not on the host object that carries it, so
|
|
@@ -4532,6 +4555,7 @@ export class AssemblyCache {
|
|
|
4532
4555
|
constructor(prog: TableProgram, resolved: ResolvedTable = resolveTable(prog)) {
|
|
4533
4556
|
this.prog = prog
|
|
4534
4557
|
this.t = resolved
|
|
4558
|
+
this.noOwnTrivia = prog.triviaSpecs === undefined || prog.triviaSpecs.length === 0
|
|
4535
4559
|
}
|
|
4536
4560
|
|
|
4537
4561
|
for(cfg: RunCfg): Assembly {
|
|
@@ -4599,7 +4623,8 @@ export class AssemblyCache {
|
|
|
4599
4623
|
const tolerant = ctx._tolerant === true
|
|
4600
4624
|
const coverage = ctx._grammarCoverage !== undefined
|
|
4601
4625
|
const probe = ctx._probe !== undefined
|
|
4602
|
-
const
|
|
4626
|
+
const inheritedTrivia = this.noOwnTrivia && ctx.trivia !== undefined
|
|
4627
|
+
const key = cfgKeyOf(hostCst, trackLines, tolerant, coverage, probe, hostReadsChildren, inheritedTrivia)
|
|
4603
4628
|
let cache = this.byCfg
|
|
4604
4629
|
if (hostCaptureTrivia !== undefined) {
|
|
4605
4630
|
// The predicate is host configuration, not parse state. Specialising each
|
|
@@ -4610,7 +4635,7 @@ export class AssemblyCache {
|
|
|
4610
4635
|
cache = this.hostAssemblies!
|
|
4611
4636
|
}
|
|
4612
4637
|
else {
|
|
4613
|
-
cache = Array.from({ length:
|
|
4638
|
+
cache = Array.from({ length: 128 })
|
|
4614
4639
|
this.hostPredicate = hostCaptureTrivia
|
|
4615
4640
|
this.hostAssemblies = cache
|
|
4616
4641
|
}
|
|
@@ -4619,7 +4644,7 @@ export class AssemblyCache {
|
|
|
4619
4644
|
if (hit !== undefined) return hit
|
|
4620
4645
|
const made = assemble(this.t, this.prog, {
|
|
4621
4646
|
hostCst, hostReadsChildren, hostCaptureTrivia,
|
|
4622
|
-
trackLines, tolerant, coverage, probe,
|
|
4647
|
+
trackLines, tolerant, coverage, probe, inheritedTrivia,
|
|
4623
4648
|
})
|
|
4624
4649
|
cache[key] = made
|
|
4625
4650
|
return made
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
OP_LEX_BODY, OP_LEX_PROGRAM, OP_GAPGATE, OP_STRICT,
|
|
55
55
|
} from './ops.ts'
|
|
56
56
|
import {
|
|
57
|
-
choiceCannotCommit, choiceRollbackMask, failureRollbackClean, validateDispatchSpec,
|
|
57
|
+
choiceCannotCommit, choiceLogRollbackMask, choiceRollbackMask, failureLogRollback, failureRollbackClean, validateDispatchSpec,
|
|
58
58
|
type ResolvedClass, type ResolvedTable, type TableProgram,
|
|
59
59
|
} from './program.ts'
|
|
60
60
|
import { emitShapeMatch, scanShapeFromRegex } from './scan-shapes.ts'
|
|
@@ -406,6 +406,19 @@ function emitRollback(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SI
|
|
|
406
406
|
return `if(${t}n){rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,${s.fd ? `${t}fd` : 'undefined'},${s.er ? `${t}er` : 'undefined'},${t}lg,${t}rt)}`
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
/**
|
|
410
|
+
* A mark of the two trivia logs alone, for a child the census counts as clean but
|
|
411
|
+
* whose failure can leave trivia rows a failed node recorded
|
|
412
|
+
* (`program.ts` `choiceLogRollbackMask`, `failureLogRollback`).
|
|
413
|
+
*/
|
|
414
|
+
function emitLogMark(t: string): string {
|
|
415
|
+
return `const ${t}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0,${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0`
|
|
416
|
+
}
|
|
417
|
+
function emitLogRollback(t: string): string {
|
|
418
|
+
return `{const g=ctx._triviaLog;if(g!==undefined)while(g.length>${t}lg)g.pop()}
|
|
419
|
+
{const r=ctx._rootTriviaLog;if(r!==undefined)while(r.length>${t}rt)r.pop()}`
|
|
420
|
+
}
|
|
421
|
+
|
|
409
422
|
/**
|
|
410
423
|
* ONE SEQUENCE TERM — `assemble.ts`'s `nextTerm`, inlined at the site.
|
|
411
424
|
*
|
|
@@ -597,6 +610,7 @@ export function emitAssemblySource(
|
|
|
597
610
|
tolerant: boolean
|
|
598
611
|
coverage: boolean
|
|
599
612
|
probe: boolean
|
|
613
|
+
inheritedTrivia?: boolean
|
|
600
614
|
},
|
|
601
615
|
extraIps: readonly number[] = [],
|
|
602
616
|
staticBuild = false,
|
|
@@ -618,6 +632,10 @@ export function emitAssemblySource(
|
|
|
618
632
|
const { code, k, fx, disp, dsp, triviaLabelled } = t
|
|
619
633
|
const swapLegal = !cfg.trackLines
|
|
620
634
|
const hostCst = cfg.hostCst
|
|
635
|
+
// Trivia rows are recorded only by a program with trivia of its own, or under
|
|
636
|
+
// trivia a caller supplied (`RunCfg.inheritedTrivia`). Otherwise no site
|
|
637
|
+
// restores the trivia logs alone: nothing can have written them.
|
|
638
|
+
const LOGS = (prog.triviaSpecs?.length ?? 0) > 0 || cfg.inheritedTrivia === true
|
|
621
639
|
|
|
622
640
|
/**
|
|
623
641
|
* RECOVERY IS EMITTED, and the refusal this replaces was wrong in SCOPE.
|
|
@@ -1769,12 +1787,13 @@ return v
|
|
|
1769
1787
|
case OP_ATTEMPT: {
|
|
1770
1788
|
const child = link(code[ip + 1]!)
|
|
1771
1789
|
const clean = !REC && failureRollbackClean(prog, ip)
|
|
1790
|
+
const logs = clean && LOGS && failureLogRollback(prog, ip)
|
|
1772
1791
|
const p = tmp()
|
|
1773
1792
|
return `${head}
|
|
1774
|
-
${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1793
|
+
${clean ? logs ? emitLogMark(p) : '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1775
1794
|
const v=${child}(input,pos,ctx)
|
|
1776
1795
|
if(v!==FAIL)return v
|
|
1777
|
-
${clean ? '' : emitRollback(p, L.buf, L.raw, sinks)}
|
|
1796
|
+
${clean ? logs ? emitLogRollback(p) : '' : emitRollback(p, L.buf, L.raw, sinks)}
|
|
1778
1797
|
${code[ip + 2] === 1 ? 'ctx._fc=false' : 'if(ctx._fc===true)return FAIL'}
|
|
1779
1798
|
ctx._fe=pos
|
|
1780
1799
|
return FAIL
|
|
@@ -1801,6 +1820,7 @@ return FAIL
|
|
|
1801
1820
|
${lookaheadMiss(code[ip + 1]!, 'EC.e=pos;return null')}${emitMark(p, L.buf, L.raw, sinks)}
|
|
1802
1821
|
${cfg.probe ? 'const pb=ctx._probe?.best\n' : ''}const v=${child}(input,pos,ctx)
|
|
1803
1822
|
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1823
|
+
ctx._fc=false
|
|
1804
1824
|
${cfg.probe ? 'if(ctx._probe!==undefined)ctx._probe.best=pb??null\n' : ''}if(v===FAIL){EC.e=pos;return null}
|
|
1805
1825
|
ctx._fe=pos
|
|
1806
1826
|
ctx._fx=${xf}
|
|
@@ -1826,6 +1846,7 @@ return null
|
|
|
1826
1846
|
${lookaheadMiss(code[ip + 1]!, `ctx._fe=pos;ctx._fx=${xf};return FAIL`)}${emitMark(p, L.buf, L.raw, sinks)}
|
|
1827
1847
|
${cfg.probe ? 'const pb=ctx._probe?.best\n' : ''}const v=${child}(input,pos,ctx)
|
|
1828
1848
|
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1849
|
+
ctx._fc=false
|
|
1829
1850
|
${cfg.probe ? 'if(ctx._probe!==undefined)ctx._probe.best=pb??null\n' : ''}if(v===FAIL){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1830
1851
|
EC.e=pos
|
|
1831
1852
|
return null
|
|
@@ -1835,14 +1856,15 @@ return null
|
|
|
1835
1856
|
case OP_OPT: {
|
|
1836
1857
|
const child = link(code[ip + 1]!)
|
|
1837
1858
|
const clean = !REC && failureRollbackClean(prog, ip)
|
|
1859
|
+
const logs = clean && LOGS && failureLogRollback(prog, ip)
|
|
1838
1860
|
const p = tmp()
|
|
1839
1861
|
return `${head}
|
|
1840
|
-
${lookaheadMiss(code[ip + 1]!, 'EC.e=pos;return null')}${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1862
|
+
${lookaheadMiss(code[ip + 1]!, 'EC.e=pos;return null')}${clean ? logs ? emitLogMark(p) : '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1841
1863
|
ctx._fc=false
|
|
1842
1864
|
const v=${child}(input,pos,ctx)
|
|
1843
1865
|
if(v===FAIL){
|
|
1844
1866
|
if(ctx._fc===true)return FAIL
|
|
1845
|
-
${clean ? '' : emitRollback(p, L.buf, L.raw, sinks)}
|
|
1867
|
+
${clean ? logs ? emitLogRollback(p) : '' : emitRollback(p, L.buf, L.raw, sinks)}
|
|
1846
1868
|
EC.e=pos
|
|
1847
1869
|
return null
|
|
1848
1870
|
}
|
|
@@ -2052,6 +2074,13 @@ return FAIL
|
|
|
2052
2074
|
const encodedRollbackMask = choiceRollbackMask(prog, ip) ?? -1
|
|
2053
2075
|
const rollbackMask = REC ? -1 : encodedRollbackMask
|
|
2054
2076
|
const hasRollback = rollbackMask !== 0
|
|
2077
|
+
// Arms that leave only trivia-log rows: restored from the full mark when
|
|
2078
|
+
// one is taken, else from a mark of the two logs taken as that arm is
|
|
2079
|
+
// entered. An earlier arm that failed was restored, so the lengths there
|
|
2080
|
+
// are the choice's own, and an arm the char gate skips takes no mark.
|
|
2081
|
+
const logMask = rollbackMask === -1 || !LOGS ? 0 : choiceLogRollbackMask(prog, ip)
|
|
2082
|
+
const choiceMark = hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''
|
|
2083
|
+
const armMark = (i: number): string => !hasRollback && (logMask & (1 << i)) !== 0 ? `${emitLogMark(p)}\n` : ''
|
|
2055
2084
|
// An always-consuming leading scalar makes a failure at `pos`
|
|
2056
2085
|
// statically exact: it is the arm's derived opener set, and the
|
|
2057
2086
|
// choice's own `choiceFx` already concatenates every such set in
|
|
@@ -2082,7 +2111,8 @@ return FAIL
|
|
|
2082
2111
|
const rollbackFor = (i: number): string =>
|
|
2083
2112
|
rollbackMask === -1 || (rollbackMask & (1 << i)) !== 0
|
|
2084
2113
|
? emitRollback(p, L.buf, L.raw, sinks)
|
|
2085
|
-
: ''
|
|
2114
|
+
: (logMask & (1 << i)) === 0 ? ''
|
|
2115
|
+
: hasRollback ? emitRollback(p, L.buf, L.raw, sinks) : emitLogRollback(p)
|
|
2086
2116
|
if (choiceCannotCommit(prog, ip) && !startFailureExact) {
|
|
2087
2117
|
// A successful ordered choice does not expose any losing arm's
|
|
2088
2118
|
// diagnostic. Snapshot each entered arm's failure in scalar locals;
|
|
@@ -2121,7 +2151,7 @@ ctx._fc=false
|
|
|
2121
2151
|
${remember(i, 'at', pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('') + pretest.sequence.expected.at(-1))}}
|
|
2122
2152
|
}`
|
|
2123
2153
|
return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if(${gate}){
|
|
2124
|
-
ctx._fc=false
|
|
2154
|
+
${armMark(i)}ctx._fc=false
|
|
2125
2155
|
{const v=${arm}(input,pos,ctx)
|
|
2126
2156
|
if(v!==FAIL)return v}
|
|
2127
2157
|
{const at=ctx._fe??pos
|
|
@@ -2140,7 +2170,7 @@ ctx._fe=pos;ctx._fx=acc??${choiceFx}
|
|
|
2140
2170
|
return FAIL`
|
|
2141
2171
|
return `${head}
|
|
2142
2172
|
const c=${directAsciiMask ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
|
|
2143
|
-
${
|
|
2173
|
+
${choiceMark}
|
|
2144
2174
|
${deepAt.map((at, i) => `let ${at},${deepFx[i]}`).join('\n')}
|
|
2145
2175
|
${maskable ? `if(c<128){
|
|
2146
2176
|
const bits=${maskName}[${directAsciiMask ? 'c' : 'c<0?128:c'}]
|
|
@@ -2191,7 +2221,7 @@ if(at>best){best=at;acc=undefined}
|
|
|
2191
2221
|
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('')}${pretest.sequence.expected.at(-1)},acc)}
|
|
2192
2222
|
}`
|
|
2193
2223
|
return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if((bits&${1 << i})!==0${condition}){
|
|
2194
|
-
ctx._fc=false
|
|
2224
|
+
${armMark(i)}ctx._fc=false
|
|
2195
2225
|
{const v=${arm}(input,pos,ctx)
|
|
2196
2226
|
if(v!==FAIL)return v}
|
|
2197
2227
|
${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${i},prev,acc)
|
|
@@ -2231,7 +2261,7 @@ if(at>best){best=at;acc=undefined}
|
|
|
2231
2261
|
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.token.expected},acc)
|
|
2232
2262
|
}${startFailureExact ? '' : `else if(best===pos)acc=_accSet(${expected[i]},acc)`}`
|
|
2233
2263
|
return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if((${gateRefs[i]}===null||classHas(${gateRefs[i]},c))${condition}){
|
|
2234
|
-
ctx._fc=false
|
|
2264
|
+
${armMark(i)}ctx._fc=false
|
|
2235
2265
|
{const v=${arm}(input,pos,ctx)
|
|
2236
2266
|
if(v!==FAIL)return v}
|
|
2237
2267
|
{const at=ctx._fe??pos
|
|
@@ -2250,7 +2280,7 @@ ${rollbackFor(i)}
|
|
|
2250
2280
|
// `choiceFx`, the arms' flatMap, set by set.
|
|
2251
2281
|
return `${head}
|
|
2252
2282
|
const c=${directAsciiMask ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
|
|
2253
|
-
${
|
|
2283
|
+
${choiceMark}
|
|
2254
2284
|
let acc
|
|
2255
2285
|
let best=pos
|
|
2256
2286
|
${maskable ? `if(c<128){
|
package/src/table/encode.ts
CHANGED
|
@@ -190,6 +190,33 @@ class Encoder {
|
|
|
190
190
|
private choiceRollbackMasks = new Map<number, number>()
|
|
191
191
|
private nonCommittingChoiceSites = new Set<number>()
|
|
192
192
|
private failureRollbackCleanSites = new Set<number>()
|
|
193
|
+
/**
|
|
194
|
+
* THE TRIVIA LOGS A FAILED NODE LEAVES. A node drops its own capture frame when
|
|
195
|
+
* it fails, but the trivia its body recorded into `_triviaLog`/`_rootTriviaLog`
|
|
196
|
+
* is not part of that frame. So a child the census above counts as clean can
|
|
197
|
+
* still leave trivia rows when it reaches a node whose body can fail after a
|
|
198
|
+
* trivia scan (`mayLeavePartialCapture`'s `throughNodes`). These are the arms
|
|
199
|
+
* and sites that need only the two logs restored. A program with no trivia of
|
|
200
|
+
* its own records rows only under trivia its caller supplies, so its emitted
|
|
201
|
+
* assembly takes these marks only for such a parse (`RunCfg.inheritedTrivia`).
|
|
202
|
+
*/
|
|
203
|
+
private choiceLogRollbackMasks = new Map<number, number>()
|
|
204
|
+
private failureLogRollbackSites = new Set<number>()
|
|
205
|
+
/**
|
|
206
|
+
* HOW EACH CHOICE WAS LOWERED, for the census above. A choice on the ordered
|
|
207
|
+
* path whose arms cannot commit a failure restores the logs after every arm
|
|
208
|
+
* it enters (its full mark, or its own two-log mark), so it leaves no rows.
|
|
209
|
+
* An exclusive dispatch runs its one arm with no mark, and an ordered choice
|
|
210
|
+
* returns a committed arm's failure before restoring, so either leaves what an
|
|
211
|
+
* arm leaves. A choice in neither set, or in both, is taken to leave rows. Both
|
|
212
|
+
* sets are complete only once every rule is encoded, so the trivia-log census
|
|
213
|
+
* runs then (`logCensus`).
|
|
214
|
+
*/
|
|
215
|
+
private restoringChoices = new Set<Combinator<unknown>>()
|
|
216
|
+
private leakingChoices = new Set<Combinator<unknown>>()
|
|
217
|
+
private logCensus: (() => void)[] = []
|
|
218
|
+
private readonly restoresArms = (choice: Combinator<unknown>): boolean =>
|
|
219
|
+
this.restoringChoices.has(choice) && !this.leakingChoices.has(choice)
|
|
193
220
|
/**
|
|
194
221
|
* `reachesSideWriter`'s answers, shared by every `failureNeedsRollback` call
|
|
195
222
|
* for the lifetime of this `Encoder`. Every entry is exact — it depends only on
|
|
@@ -816,15 +843,25 @@ class Encoder {
|
|
|
816
843
|
private failureNeedsRollback(
|
|
817
844
|
p: Combinator<unknown>,
|
|
818
845
|
autoNot: readonly AutoNotCheck[] | null | undefined,
|
|
846
|
+
throughNodes: false | ((choice: Combinator<unknown>) => boolean),
|
|
819
847
|
): boolean {
|
|
820
848
|
const refName = (p as unknown as { _ruleName?: string })._ruleName
|
|
821
849
|
const winner = refName === undefined ? undefined : this.winners?.[refName]
|
|
822
850
|
const root = winner !== undefined && !winnerWrapsReference(winner, p) ? winner : p
|
|
823
|
-
return mayLeavePartialCapture(root, new Set(), true)
|
|
851
|
+
return mayLeavePartialCapture(root, new Set(), true, throughNodes)
|
|
824
852
|
|| this.reachesSideWriter(root)
|
|
825
853
|
|| ((autoNot?.length ?? 0) > 0 && capturesLeaf(root))
|
|
826
854
|
}
|
|
827
855
|
|
|
856
|
+
/** The rollback census of an `optional()` or `attempt()` row at `ip` over `child`. */
|
|
857
|
+
private censusTransaction(child: Combinator<unknown>, ip: number): void {
|
|
858
|
+
if (this.failureNeedsRollback(child, undefined, false)) return
|
|
859
|
+
this.failureRollbackCleanSites.add(ip)
|
|
860
|
+
this.logCensus.push(() => {
|
|
861
|
+
if (this.failureNeedsRollback(child, undefined, this.restoresArms)) this.failureLogRollbackSites.add(ip)
|
|
862
|
+
})
|
|
863
|
+
}
|
|
864
|
+
|
|
828
865
|
/**
|
|
829
866
|
* Whether a field, recovered error, interpreted scanner or unresolvable shape
|
|
830
867
|
* is reachable from `root` — the writer census of `failureNeedsRollback`,
|
|
@@ -1671,6 +1708,7 @@ class Encoder {
|
|
|
1671
1708
|
}
|
|
1672
1709
|
const dispIdx = this.disp.length
|
|
1673
1710
|
this.disp.push(classes)
|
|
1711
|
+
const exclusive = dispatchIsExclusive(classes, this.cc)
|
|
1674
1712
|
// PER-ARM EXPECTED SETS RIDE ALONG, after the arm offsets. While the best
|
|
1675
1713
|
// failure remains at the choice position, an arm declined by the driver's
|
|
1676
1714
|
// char gate contributes the static opener set the interpreter would get by
|
|
@@ -1683,10 +1721,7 @@ class Encoder {
|
|
|
1683
1721
|
let rollbackMask = kids.length > 31 ? -1 : 0
|
|
1684
1722
|
if (rollbackMask === 0) {
|
|
1685
1723
|
for (let i = 0; i < arms.length; i++) {
|
|
1686
|
-
|
|
1687
|
-
if (this.failureNeedsRollback(arms[i]!, d.autoNot[src])) {
|
|
1688
|
-
rollbackMask |= 1 << i
|
|
1689
|
-
}
|
|
1724
|
+
if (this.failureNeedsRollback(arms[i]!, d.autoNot[order[i]!], false)) rollbackMask |= 1 << i
|
|
1690
1725
|
}
|
|
1691
1726
|
}
|
|
1692
1727
|
const head = this.emitHead(OP_CHOICE, 3 + 2 * kids.length)
|
|
@@ -1701,9 +1736,19 @@ class Encoder {
|
|
|
1701
1736
|
this.code[head + 4 + kids.length + i] = this.expected(deriveExpected(arms[i]!))
|
|
1702
1737
|
}
|
|
1703
1738
|
this.choiceRollbackMasks.set(head, rollbackMask)
|
|
1704
|
-
if (
|
|
1705
|
-
|
|
1739
|
+
if (rollbackMask !== -1) {
|
|
1740
|
+
const full = rollbackMask
|
|
1741
|
+
this.logCensus.push(() => {
|
|
1742
|
+
let logMask = 0
|
|
1743
|
+
for (let i = 0; i < arms.length; i++) {
|
|
1744
|
+
if ((full & (1 << i)) === 0 && this.failureNeedsRollback(arms[i]!, d.autoNot[order[i]!], this.restoresArms)) logMask |= 1 << i
|
|
1745
|
+
}
|
|
1746
|
+
if (logMask !== 0) this.choiceLogRollbackMasks.set(head, logMask)
|
|
1747
|
+
})
|
|
1706
1748
|
}
|
|
1749
|
+
const nonCommitting = arms.every(arm => !mayCommitFailure(arm, rr, this.commitFailureMemo))
|
|
1750
|
+
if (nonCommitting) this.nonCommittingChoiceSites.add(head)
|
|
1751
|
+
;(!exclusive && nonCommitting ? this.restoringChoices : this.leakingChoices).add(p)
|
|
1707
1752
|
return head
|
|
1708
1753
|
}
|
|
1709
1754
|
case 'many':
|
|
@@ -1765,7 +1810,7 @@ class Encoder {
|
|
|
1765
1810
|
}
|
|
1766
1811
|
case 'optional': {
|
|
1767
1812
|
const head = this.emit(OP_OPT, this.lookaheadGate(d.parser, this.node(d.parser).ip))
|
|
1768
|
-
|
|
1813
|
+
this.censusTransaction(d.parser, head)
|
|
1769
1814
|
return head
|
|
1770
1815
|
}
|
|
1771
1816
|
case 'transform': {
|
|
@@ -2095,7 +2140,7 @@ class Encoder {
|
|
|
2095
2140
|
// lowering was correct only for a choice arm.
|
|
2096
2141
|
case 'attempt': {
|
|
2097
2142
|
const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip, d.contain === true ? 1 : 0)
|
|
2098
|
-
|
|
2143
|
+
this.censusTransaction(d.parser, inner)
|
|
2099
2144
|
// THE FIRST-SET FAIL-FAST GUARD, lowered as the `OP_GATE` row the `node()`
|
|
2100
2145
|
// case already uses — it is the same guard, written twice in the
|
|
2101
2146
|
// interpreter (`attempt.ts:22`, `node.ts:239`). It is NOT merely an
|
|
@@ -2295,6 +2340,7 @@ class Encoder {
|
|
|
2295
2340
|
finish(): TableProgram {
|
|
2296
2341
|
if (this.code.length === 0) this.emit(OP_EMPTY)
|
|
2297
2342
|
this.collapseIndirection()
|
|
2343
|
+
for (const census of this.logCensus) census()
|
|
2298
2344
|
return ownTableProgram({
|
|
2299
2345
|
code: this.code, k: this.k, fns: this.fns, cc: this.cc,
|
|
2300
2346
|
fx: this.fx, disp: this.disp, dsp: this.dsp, rules: this.rules,
|
|
@@ -2315,6 +2361,8 @@ class Encoder {
|
|
|
2315
2361
|
}, undefined, {
|
|
2316
2362
|
choiceRollbackMasks: this.choiceRollbackMasks,
|
|
2317
2363
|
failureRollbackCleanSites: this.failureRollbackCleanSites,
|
|
2364
|
+
choiceLogRollbackMasks: this.choiceLogRollbackMasks,
|
|
2365
|
+
failureLogRollbackSites: this.failureLogRollbackSites,
|
|
2318
2366
|
nonCommittingChoiceSites: this.nonCommittingChoiceSites,
|
|
2319
2367
|
})
|
|
2320
2368
|
}
|
package/src/table/exec.ts
CHANGED
|
@@ -1558,8 +1558,10 @@ function makeDriver(
|
|
|
1558
1558
|
const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
1559
1559
|
const v = exec(code[ip + 1]!, input, pos, ctx)
|
|
1560
1560
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1561
|
+
// A lookahead contains its child's commitment (`not.ts` returns a fresh
|
|
1562
|
+
// result): a generic child's committed failure is cleared here.
|
|
1563
|
+
ctx._fc = savedFc
|
|
1561
1564
|
if (scalarChild) {
|
|
1562
|
-
ctx._fc = savedFc
|
|
1563
1565
|
ctx._fe = savedFe
|
|
1564
1566
|
ctx._fx = savedFx
|
|
1565
1567
|
}
|
|
@@ -1588,7 +1590,8 @@ function makeDriver(
|
|
|
1588
1590
|
const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
1589
1591
|
const v = exec(code[ip + 1]!, input, pos, ctx)
|
|
1590
1592
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1591
|
-
|
|
1593
|
+
// A lookahead contains its child's commitment, as under `not()`.
|
|
1594
|
+
ctx._fc = savedFc
|
|
1592
1595
|
if (ctx._probe !== undefined) ctx._probe.best = savedProbe ?? null
|
|
1593
1596
|
// `peek.ts:60` — the ASSERTION's own set, at the assertion's position.
|
|
1594
1597
|
if (v === FAIL) { ctx._fe = pos; ctx._fx = fx[code[ip + 2]!] as string[]; return FAIL }
|
package/src/table/program.ts
CHANGED
|
@@ -531,6 +531,8 @@ type RuntimeTableProgram = TableProgram & {
|
|
|
531
531
|
readonly choiceRollbackMasks?: ReadonlyMap<number, number>
|
|
532
532
|
readonly nonCommittingChoiceSites?: ReadonlySet<number>
|
|
533
533
|
readonly failureRollbackCleanSites?: ReadonlySet<number>
|
|
534
|
+
readonly choiceLogRollbackMasks?: ReadonlyMap<number, number>
|
|
535
|
+
readonly failureLogRollbackSites?: ReadonlySet<number>
|
|
534
536
|
}
|
|
535
537
|
}
|
|
536
538
|
|
|
@@ -542,6 +544,8 @@ export function ownTableProgram(
|
|
|
542
544
|
readonly choiceRollbackMasks?: ReadonlyMap<number, number>
|
|
543
545
|
readonly failureRollbackCleanSites?: ReadonlySet<number>
|
|
544
546
|
readonly nonCommittingChoiceSites?: ReadonlySet<number>
|
|
547
|
+
readonly choiceLogRollbackMasks?: ReadonlyMap<number, number>
|
|
548
|
+
readonly failureLogRollbackSites?: ReadonlySet<number>
|
|
545
549
|
} = {},
|
|
546
550
|
): TableProgram {
|
|
547
551
|
const runtime = (prog as RuntimeTableProgram)[TABLE_RUNTIME]
|
|
@@ -552,6 +556,8 @@ export function ownTableProgram(
|
|
|
552
556
|
choiceRollbackMasks: caches.choiceRollbackMasks ?? runtime?.choiceRollbackMasks,
|
|
553
557
|
nonCommittingChoiceSites: caches.nonCommittingChoiceSites ?? runtime?.nonCommittingChoiceSites,
|
|
554
558
|
failureRollbackCleanSites: caches.failureRollbackCleanSites ?? runtime?.failureRollbackCleanSites,
|
|
559
|
+
choiceLogRollbackMasks: caches.choiceLogRollbackMasks ?? runtime?.choiceLogRollbackMasks,
|
|
560
|
+
failureLogRollbackSites: caches.failureLogRollbackSites ?? runtime?.failureLogRollbackSites,
|
|
555
561
|
},
|
|
556
562
|
} as RuntimeTableProgram
|
|
557
563
|
}
|
|
@@ -575,6 +581,19 @@ export function failureRollbackClean(prog: TableProgram, ip: number): boolean {
|
|
|
575
581
|
return (prog as RuntimeTableProgram)[TABLE_RUNTIME]?.failureRollbackCleanSites?.has(ip) === true
|
|
576
582
|
}
|
|
577
583
|
|
|
584
|
+
/** The arms of the choice at `ip` the census counts as clean whose failure can
|
|
585
|
+
* still leave trivia-log rows: a node they reach fails after recording trivia
|
|
586
|
+
* (`encode.ts` `choiceLogRollbackMasks`). Zero without that authority. */
|
|
587
|
+
export function choiceLogRollbackMask(prog: TableProgram, ip: number): number {
|
|
588
|
+
return (prog as RuntimeTableProgram)[TABLE_RUNTIME]?.choiceLogRollbackMasks?.get(ip) ?? 0
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** An `optional()` or `attempt()` row `failureRollbackClean` admits whose failed
|
|
592
|
+
* child can still leave trivia-log rows, for the same reason. */
|
|
593
|
+
export function failureLogRollback(prog: TableProgram, ip: number): boolean {
|
|
594
|
+
return (prog as RuntimeTableProgram)[TABLE_RUNTIME]?.failureLogRollbackSites?.has(ip) === true
|
|
595
|
+
}
|
|
596
|
+
|
|
578
597
|
/** A compiled entry, shaped exactly like a codegen rule function. */
|
|
579
598
|
export type TableRule = (input: string, pos: number, ctx: ParseContext) => ParseResult<unknown>
|
|
580
599
|
|
package/src/version.ts
CHANGED