parseman 0.60.2 → 0.60.4
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/compiler/ir-serialize.d.ts +3 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +10 -0
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/index.js +155 -57
- package/dist/index.js.map +2 -2
- package/dist/plugin/evaluator.d.ts +8 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +218 -59
- 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/compiler/ir-serialize.ts +14 -3
- package/src/compiler/linker.ts +28 -12
- package/src/plugin/evaluator.ts +11 -0
- package/src/plugin/index.ts +29 -11
- 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
|
@@ -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