parseman 0.48.0 → 0.49.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 +158 -51
- package/dist/analysis/diagnostics.js.map +2 -2
- package/dist/cli/index.js +160 -52
- package/dist/cli/index.js.map +2 -2
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/index.js +169 -55
- package/dist/index.js.map +2 -2
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/direct-builder-static.d.ts +20 -0
- package/dist/plugin/direct-builder-static.d.ts.map +1 -1
- package/dist/plugin/evaluator.d.ts +13 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +365 -71
- package/dist/plugin/index.js.map +2 -2
- package/dist/spec/index.js +12 -2
- package/dist/spec/index.js.map +2 -2
- package/dist/table/assemble.d.ts.map +1 -1
- 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 +158 -51
- package/dist/table/index.js.map +1 -1
- package/dist/table/lex-program.d.ts.map +1 -1
- package/dist/table/ops.d.ts +4 -4
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/combinators/choice.ts +10 -1
- package/src/compiler/ir-serialize.ts +15 -3
- package/src/compiler/linker.ts +14 -1
- package/src/plugin/direct-builder-static.ts +116 -12
- package/src/plugin/evaluator.ts +50 -2
- package/src/plugin/index.ts +140 -1
- package/src/table/assemble.ts +82 -59
- package/src/table/emit-assembly.ts +10 -5
- package/src/table/encode.ts +6 -10
- package/src/table/exec.ts +13 -18
- package/src/table/lex-program.ts +15 -6
- package/src/table/ops.ts +4 -4
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
package/src/plugin/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { createUnplugin } from 'unplugin'
|
|
|
23
23
|
import { parseSync } from 'oxc-parser'
|
|
24
24
|
import { ResolverFactory } from 'oxc-resolver'
|
|
25
25
|
import MagicString from 'magic-string'
|
|
26
|
-
import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, propName, type Scope, type ScopeEntry } from './evaluator.ts'
|
|
26
|
+
import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, setBuilderImportResolver, propName, type Scope, type ScopeEntry } from './evaluator.ts'
|
|
27
27
|
import { classifyRuleMap } from '../analysis/commitment.ts'
|
|
28
28
|
import { compile } from '../table/compile.ts'
|
|
29
29
|
import { compileRuleMap } from '../table/compile-rule-map.ts'
|
|
@@ -516,6 +516,7 @@ export function transformMacro(
|
|
|
516
516
|
return transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recovery, grammarCoverage)
|
|
517
517
|
} finally {
|
|
518
518
|
setReducerResolver(null)
|
|
519
|
+
setBuilderImportResolver(null)
|
|
519
520
|
// Both are idempotent: on the success path the body already released them and these
|
|
520
521
|
// are no-ops. On an aborted transform they are what stops the leak.
|
|
521
522
|
for (const d of unwindDegradationCapture(depth)) console.warn(formatDegradation(d))
|
|
@@ -666,6 +667,10 @@ function transformMacroImpl(
|
|
|
666
667
|
*/
|
|
667
668
|
const reducerResolver = createReducerResolver(id, body as unknown[], code)
|
|
668
669
|
setReducerResolver(reducerResolver, code)
|
|
670
|
+
/* A direct builder's free name is rescuable when THIS module imported it — the
|
|
671
|
+
* artifact carries `{ source, imported }` and a downstream compose() re-emits the
|
|
672
|
+
* import (see the re-lower pass below). Module-private consts stay refusals. */
|
|
673
|
+
setBuilderImportResolver(name => importBindings.get(name) ?? null)
|
|
669
674
|
|
|
670
675
|
const topLevelFunction = (moduleBody: AnyNode[], name: string): AnyNode | null => {
|
|
671
676
|
for (const st of moduleBody) {
|
|
@@ -797,6 +802,69 @@ function transformMacroImpl(
|
|
|
797
802
|
* at the end of this transform is what proves it was actually emitted.
|
|
798
803
|
*/
|
|
799
804
|
let usedTableRuntime = false
|
|
805
|
+
/**
|
|
806
|
+
* `source` -> (local -> imported) for direct-builder free names that rode in on a
|
|
807
|
+
* carried piece's IR (a composed base grammar whose reducers call imported AST
|
|
808
|
+
* factories). Re-emitted as real imports into THIS module so the inlined builder
|
|
809
|
+
* source has bindings to resolve. A name this module already imports is skipped.
|
|
810
|
+
*/
|
|
811
|
+
const pendingBuilderImports = new Map<string, Map<string, string>>()
|
|
812
|
+
/**
|
|
813
|
+
* Harvest direct-builder import provenance off a MERGED rule graph so the re-lower
|
|
814
|
+
* pass re-emits those imports. `buildImports` rides on a `node()`'s `_def`, but a
|
|
815
|
+
* rule that ANOTHER rule references (`g.X`) is materialized in the evaluated map as
|
|
816
|
+
* a `lazy` proxy whose definition node sits behind `_def.thunk()` — never an own
|
|
817
|
+
* property. A generic `Object.values` descent therefore skips it entirely, silently
|
|
818
|
+
* dropping the imports of every cross-referenced rule (the css base's `VarCall`,
|
|
819
|
+
* referenced by its selector/value parents, is the canonical case: its `funcCall` /
|
|
820
|
+
* `isValueSlotValue` reads were lost and the fused downstream module threw "bound by
|
|
821
|
+
* nothing" / left a runtime compose()).
|
|
822
|
+
*
|
|
823
|
+
* The fused table encodes exactly the WINNER of each rule name and binds every
|
|
824
|
+
* reference BY NAME to that winner (`enc.winners`), so the imports it inlines are
|
|
825
|
+
* exactly the union over the winning rule bodies. Harvest that set: walk each rule
|
|
826
|
+
* entry's own body (following the entry lazy's thunk), and STOP at a named `lazy`
|
|
827
|
+
* reference inside a body — it resolves to a winner already covered as its own
|
|
828
|
+
* entry, so descending it would reach an OVERRIDDEN rule's shadowed original and
|
|
829
|
+
* re-emit a dead import. An anonymous `lazy` is inline recursion and is followed.
|
|
830
|
+
* One `seen` set is shared across every rule so a cyclic grammar terminates.
|
|
831
|
+
*/
|
|
832
|
+
const collectBuilderImports = (ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>): void => {
|
|
833
|
+
const seen = new Set<unknown>()
|
|
834
|
+
const walk = (v: unknown): void => {
|
|
835
|
+
if (v === null || typeof v !== 'object' || seen.has(v)) return
|
|
836
|
+
seen.add(v)
|
|
837
|
+
const def = (v as { _def?: { tag?: string; thunk?: () => unknown; buildImports?: ReadonlyArray<{ local: string; source: string; imported: string }> } })._def
|
|
838
|
+
for (const bi of def?.buildImports ?? []) {
|
|
839
|
+
let m = pendingBuilderImports.get(bi.source)
|
|
840
|
+
if (!m) { m = new Map(); pendingBuilderImports.set(bi.source, m) }
|
|
841
|
+
m.set(bi.local, bi.imported)
|
|
842
|
+
}
|
|
843
|
+
// An ANONYMOUS lazy is inline recursion — follow the thunk. A NAMED lazy is a
|
|
844
|
+
// rule reference bound by name to a winner covered as its own entry; do not
|
|
845
|
+
// descend it (that path reaches an overridden rule's shadowed original).
|
|
846
|
+
if (def?.tag === 'lazy' && (v as { _ruleName?: string })._ruleName === undefined && typeof def.thunk === 'function') {
|
|
847
|
+
let target: unknown = null
|
|
848
|
+
try { target = def.thunk() } catch { /* external ref — no definition to walk */ }
|
|
849
|
+
walk(target)
|
|
850
|
+
}
|
|
851
|
+
for (const child of Object.values(v as Record<string, unknown>)) {
|
|
852
|
+
if (Array.isArray(child)) { for (const c of child) walk(c) } else walk(child)
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
for (const [, rule] of ruleMap) {
|
|
856
|
+
walk(rule)
|
|
857
|
+
// A referenced rule entry is a named lazy whose body node (with its builder
|
|
858
|
+
// imports) is behind the thunk; `walk` skips a named lazy's thunk, so follow
|
|
859
|
+
// the WINNER'S body explicitly here. A raw-node entry was already fully walked.
|
|
860
|
+
const def = rule._def as { tag?: string; thunk?: () => unknown }
|
|
861
|
+
if (def?.tag === 'lazy' && typeof def.thunk === 'function') {
|
|
862
|
+
let body: unknown = null
|
|
863
|
+
try { body = def.thunk() } catch { /* unresolved winner — nothing to walk */ }
|
|
864
|
+
walk(body)
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
800
868
|
/** Exported `rules()` factories, whose bodies are left verbatim. See the push site. */
|
|
801
869
|
const exportedFactories: Array<{ name: string; pos: number }> = []
|
|
802
870
|
let runtimeComposeFallback = false
|
|
@@ -1457,6 +1525,9 @@ function transformMacroImpl(
|
|
|
1457
1525
|
const merged = mergedCarriedRules(carried)
|
|
1458
1526
|
if (merged !== null) {
|
|
1459
1527
|
if (composing) applyComposingTrivia(merged.rules, composing)
|
|
1528
|
+
// Harvest the direct-builder import provenance carried on the merged graph so
|
|
1529
|
+
// the re-lower pass at the end of this transform re-emits those imports.
|
|
1530
|
+
collectBuilderImports(merged.rules)
|
|
1460
1531
|
const refusals: string[] = []
|
|
1461
1532
|
const compiled = compileRuleMap(merged.rules, {
|
|
1462
1533
|
...(composing ? { trivia: composing } : {}),
|
|
@@ -2186,6 +2257,73 @@ function transformMacroImpl(
|
|
|
2186
2257
|
if (usedTableRuntime && applied.length > 0) {
|
|
2187
2258
|
ms.prepend(`import { tableRules } from ${JSON.stringify(TABLE_RUNTIME_SPECIFIER)}\n`)
|
|
2188
2259
|
}
|
|
2260
|
+
/* Re-bind the direct-builder free names carried in on composed IR: a base
|
|
2261
|
+
* grammar's reducer calls `dimension` from '@jesscss/core/ast', that provenance
|
|
2262
|
+
* rode in on the carried piece, and the fused table inlines the reducer source
|
|
2263
|
+
* here — so this module must import the same binding.
|
|
2264
|
+
*
|
|
2265
|
+
* The inlined builder source spells the free name VERBATIM, so a re-emitted import
|
|
2266
|
+
* cannot be aliased away: the local name is load-bearing. Two things therefore make
|
|
2267
|
+
* a carried import unsatisfiable, and both are refused rather than silently mis-bound
|
|
2268
|
+
* or emitted as a duplicate declaration:
|
|
2269
|
+
*
|
|
2270
|
+
* - the name is ALREADY BOUND in this module to something else — a named import
|
|
2271
|
+
* from a DIFFERENT source/symbol (the inlined builder would call the wrong
|
|
2272
|
+
* binding), a default/namespace import, or a top-level const/function/class
|
|
2273
|
+
* (re-emitting the import would be a redeclaration `SyntaxError`). The
|
|
2274
|
+
* free-identifier net below cannot catch either: a wrong-binding read is bound,
|
|
2275
|
+
* and a redeclaration is the opposite of an unbound read.
|
|
2276
|
+
* - two DISTINCT carried sources need the SAME local name (the map is keyed by
|
|
2277
|
+
* source, so each would emit its own `import { X } from …`).
|
|
2278
|
+
*
|
|
2279
|
+
* A name this module already imports from the SAME source under the SAME symbol is
|
|
2280
|
+
* the benign case — the exact import the builder needs is already present, so it is
|
|
2281
|
+
* skipped (no duplicate). */
|
|
2282
|
+
if (applied.length > 0 && pendingBuilderImports.size > 0) {
|
|
2283
|
+
// Every top-level binding of THIS module, not just its named imports.
|
|
2284
|
+
const boundLocally = new Set<string>()
|
|
2285
|
+
for (const stmt of body as Statement[]) {
|
|
2286
|
+
if (stmt.type === 'ImportDeclaration') {
|
|
2287
|
+
for (const sp of (stmt as unknown as ImportDeclaration).specifiers) boundLocally.add(sp.local.name)
|
|
2288
|
+
continue
|
|
2289
|
+
}
|
|
2290
|
+
const decl = stmt.type === 'ExportNamedDeclaration'
|
|
2291
|
+
? ((stmt as unknown as ExportNamedDeclaration).declaration as unknown as AnyNode | undefined)
|
|
2292
|
+
: (stmt as unknown as AnyNode)
|
|
2293
|
+
if (decl?.type === 'FunctionDeclaration' || decl?.type === 'ClassDeclaration') {
|
|
2294
|
+
const n = (decl as unknown as { id?: { name?: string } }).id?.name
|
|
2295
|
+
if (n) boundLocally.add(n)
|
|
2296
|
+
continue
|
|
2297
|
+
}
|
|
2298
|
+
const vd = unwrapVd(stmt)
|
|
2299
|
+
for (const d of vd?.declarations ?? []) {
|
|
2300
|
+
const n = (d.id as unknown as { name?: string }).name
|
|
2301
|
+
if (n) boundLocally.add(n)
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
const emitted = new Set<string>()
|
|
2305
|
+
for (const [source, locals] of pendingBuilderImports) {
|
|
2306
|
+
const needed: Array<[string, string]> = []
|
|
2307
|
+
for (const [local, imported] of locals) {
|
|
2308
|
+
const own = importBindings.get(local)
|
|
2309
|
+
// Exact same import already present: the builder's need is satisfied. Skip.
|
|
2310
|
+
if (own && own.source === source && own.imported === imported) continue
|
|
2311
|
+
if (boundLocally.has(local) || emitted.has(local)) {
|
|
2312
|
+
throw new Error(
|
|
2313
|
+
`${id} — parseman will not emit this module: a fused builder needs \`${imported}\` from `
|
|
2314
|
+
+ `${JSON.stringify(source)} bound as \`${local}\`, but this module already binds \`${local}\` `
|
|
2315
|
+
+ `to something else. The inlined builder source names \`${local}\` verbatim, so the import `
|
|
2316
|
+
+ `cannot be aliased; rename the conflicting local binding so the builder's import can be re-emitted.`,
|
|
2317
|
+
)
|
|
2318
|
+
}
|
|
2319
|
+
emitted.add(local)
|
|
2320
|
+
needed.push([local, imported])
|
|
2321
|
+
}
|
|
2322
|
+
if (needed.length === 0) continue
|
|
2323
|
+
const specs = needed.map(([local, imported]) => imported === local ? local : `${imported} as ${local}`)
|
|
2324
|
+
ms.prepend(`import { ${specs.join(', ')} } from ${JSON.stringify(source)}\n`)
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2189
2327
|
if (applied.length > 0) {
|
|
2190
2328
|
ms.prepend(
|
|
2191
2329
|
`// Generated by parseman v${PARSEMAN_VERSION} — DO NOT EDIT.\n` +
|
|
@@ -2259,6 +2397,7 @@ function transformMacroImpl(
|
|
|
2259
2397
|
// caller's config does not break; it is now inert.
|
|
2260
2398
|
|
|
2261
2399
|
setReducerResolver(null)
|
|
2400
|
+
setBuilderImportResolver(null)
|
|
2262
2401
|
// Every place the compiler chose a correct-but-slower path for this module. Reported
|
|
2263
2402
|
// on the ordinary warning channel and greppable on `[parseman] degraded`, so a
|
|
2264
2403
|
// consumer's build gate can assert zero of them the way jess's `check:macro` already
|
package/src/table/assemble.ts
CHANGED
|
@@ -224,13 +224,13 @@ type MaskedChoiceBlock = (
|
|
|
224
224
|
input: string, pos: number, ctx: ParseContext, bits: number,
|
|
225
225
|
need: boolean, mRaw: number, mTl: number, mLv: number, mFl: number,
|
|
226
226
|
mEr: number, mLog: number, mRoot: number,
|
|
227
|
-
acc: string[] | undefined, prev: number,
|
|
227
|
+
acc: string[] | undefined, prev: number, best: number,
|
|
228
228
|
) => unknown
|
|
229
229
|
type GeneralChoiceBlock = (
|
|
230
230
|
input: string, pos: number, ctx: ParseContext, c: number,
|
|
231
231
|
need: boolean, mRaw: number, mTl: number, mLv: number, mFl: number,
|
|
232
232
|
mEr: number, mLog: number, mRoot: number,
|
|
233
|
-
acc: string[] | undefined, prev: number,
|
|
233
|
+
acc: string[] | undefined, prev: number, best: number,
|
|
234
234
|
) => unknown
|
|
235
235
|
type ExclusiveChoiceBlock = (arm: number, input: string, pos: number, ctx: ParseContext) => unknown
|
|
236
236
|
type DispatchMatcher = (key: string) => boolean
|
|
@@ -1008,19 +1008,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1008
1008
|
return c._fc === true
|
|
1009
1009
|
}
|
|
1010
1010
|
|
|
1011
|
-
/**
|
|
1012
|
-
* Accumulate one failed choice arm's expected set, in arm order.
|
|
1013
|
-
*
|
|
1014
|
-
* `choice.ts:167` is `expected.push(...result.expected)` per failed arm, and the
|
|
1015
|
-
* accumulation — not the static union of the arms' OPENERS — is what a failing
|
|
1016
|
-
* ordered choice reports. A left-factored or shared-prefix choice whose arms all
|
|
1017
|
-
* failed PAST their common opener reported that opener alone: `['/::?/']` where
|
|
1018
|
-
* the interpreter says `['"before"', '"hover"']`.
|
|
1019
|
-
*
|
|
1020
|
-
* Returns the accumulator so the common one-failing-arm case borrows the arm's
|
|
1021
|
-
* own array (`slice` once) instead of building one per choice execution the way
|
|
1022
|
-
* the interpreter's eager `const expected: string[] = []` does.
|
|
1023
|
-
*/
|
|
1011
|
+
/** Append an exact-depth failed arm set in source order. Callers own depth ranking. */
|
|
1024
1012
|
function accSet(ax: readonly string[] | undefined, acc: string[] | undefined): string[] | undefined {
|
|
1025
1013
|
if (ax === undefined || ax.length === 0) return acc
|
|
1026
1014
|
if (acc === undefined) return ax.slice()
|
|
@@ -1062,13 +1050,16 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1062
1050
|
total: number,
|
|
1063
1051
|
choiceFx: string[],
|
|
1064
1052
|
): MaskedChoiceBlock {
|
|
1065
|
-
return (input, pos, ctx, bits, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev) => {
|
|
1053
|
+
return (input, pos, ctx, bits, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev, best) => {
|
|
1066
1054
|
if ((bits & (1 << start)) !== 0) {
|
|
1067
1055
|
ctx._fc = false
|
|
1068
1056
|
const v = a0(input, pos, ctx)
|
|
1069
1057
|
if (v !== FAIL) return v
|
|
1070
|
-
acc = through(start, prev, acc)
|
|
1071
|
-
|
|
1058
|
+
if (best === pos) acc = through(start, prev, acc)
|
|
1059
|
+
prev = start + 1
|
|
1060
|
+
const at = ctx._fe ?? pos
|
|
1061
|
+
if (at > best) { best = at; acc = undefined }
|
|
1062
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1072
1063
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1073
1064
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1074
1065
|
}
|
|
@@ -1076,8 +1067,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1076
1067
|
ctx._fc = false
|
|
1077
1068
|
const v = a1(input, pos, ctx)
|
|
1078
1069
|
if (v !== FAIL) return v
|
|
1079
|
-
acc = through(start + 1, prev, acc)
|
|
1080
|
-
|
|
1070
|
+
if (best === pos) acc = through(start + 1, prev, acc)
|
|
1071
|
+
prev = start + 2
|
|
1072
|
+
const at = ctx._fe ?? pos
|
|
1073
|
+
if (at > best) { best = at; acc = undefined }
|
|
1074
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1081
1075
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1082
1076
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1083
1077
|
}
|
|
@@ -1085,8 +1079,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1085
1079
|
ctx._fc = false
|
|
1086
1080
|
const v = a2(input, pos, ctx)
|
|
1087
1081
|
if (v !== FAIL) return v
|
|
1088
|
-
acc = through(start + 2, prev, acc)
|
|
1089
|
-
|
|
1082
|
+
if (best === pos) acc = through(start + 2, prev, acc)
|
|
1083
|
+
prev = start + 3
|
|
1084
|
+
const at = ctx._fe ?? pos
|
|
1085
|
+
if (at > best) { best = at; acc = undefined }
|
|
1086
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1090
1087
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1091
1088
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1092
1089
|
}
|
|
@@ -1094,15 +1091,18 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1094
1091
|
ctx._fc = false
|
|
1095
1092
|
const v = a3(input, pos, ctx)
|
|
1096
1093
|
if (v !== FAIL) return v
|
|
1097
|
-
acc = through(start + 3, prev, acc)
|
|
1098
|
-
|
|
1094
|
+
if (best === pos) acc = through(start + 3, prev, acc)
|
|
1095
|
+
prev = start + 4
|
|
1096
|
+
const at = ctx._fe ?? pos
|
|
1097
|
+
if (at > best) { best = at; acc = undefined }
|
|
1098
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1099
1099
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1100
1100
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1101
1101
|
}
|
|
1102
1102
|
if (next !== undefined) {
|
|
1103
|
-
return next(input, pos, ctx, bits, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev)
|
|
1103
|
+
return next(input, pos, ctx, bits, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev, best)
|
|
1104
1104
|
}
|
|
1105
|
-
acc = through(total, prev, acc)
|
|
1105
|
+
if (best === pos) acc = through(total, prev, acc)
|
|
1106
1106
|
ctx._fe = pos; ctx._fx = acc ?? choiceFx
|
|
1107
1107
|
return FAIL
|
|
1108
1108
|
}
|
|
@@ -1119,13 +1119,16 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1119
1119
|
total: number,
|
|
1120
1120
|
choiceFx: string[],
|
|
1121
1121
|
): GeneralChoiceBlock {
|
|
1122
|
-
return (input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev) => {
|
|
1122
|
+
return (input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev, best) => {
|
|
1123
1123
|
if (g0 === null || classHas(g0, c)) {
|
|
1124
1124
|
ctx._fc = false
|
|
1125
1125
|
const v = a0(input, pos, ctx)
|
|
1126
1126
|
if (v !== FAIL) return v
|
|
1127
|
-
acc = through(start, prev, acc)
|
|
1128
|
-
|
|
1127
|
+
if (best === pos) acc = through(start, prev, acc)
|
|
1128
|
+
prev = start + 1
|
|
1129
|
+
const at = ctx._fe ?? pos
|
|
1130
|
+
if (at > best) { best = at; acc = undefined }
|
|
1131
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1129
1132
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1130
1133
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1131
1134
|
}
|
|
@@ -1133,8 +1136,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1133
1136
|
ctx._fc = false
|
|
1134
1137
|
const v = a1(input, pos, ctx)
|
|
1135
1138
|
if (v !== FAIL) return v
|
|
1136
|
-
acc = through(start + 1, prev, acc)
|
|
1137
|
-
|
|
1139
|
+
if (best === pos) acc = through(start + 1, prev, acc)
|
|
1140
|
+
prev = start + 2
|
|
1141
|
+
const at = ctx._fe ?? pos
|
|
1142
|
+
if (at > best) { best = at; acc = undefined }
|
|
1143
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1138
1144
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1139
1145
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1140
1146
|
}
|
|
@@ -1142,8 +1148,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1142
1148
|
ctx._fc = false
|
|
1143
1149
|
const v = a2(input, pos, ctx)
|
|
1144
1150
|
if (v !== FAIL) return v
|
|
1145
|
-
acc = through(start + 2, prev, acc)
|
|
1146
|
-
|
|
1151
|
+
if (best === pos) acc = through(start + 2, prev, acc)
|
|
1152
|
+
prev = start + 3
|
|
1153
|
+
const at = ctx._fe ?? pos
|
|
1154
|
+
if (at > best) { best = at; acc = undefined }
|
|
1155
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1147
1156
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1148
1157
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1149
1158
|
}
|
|
@@ -1151,15 +1160,18 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1151
1160
|
ctx._fc = false
|
|
1152
1161
|
const v = a3(input, pos, ctx)
|
|
1153
1162
|
if (v !== FAIL) return v
|
|
1154
|
-
acc = through(start + 3, prev, acc)
|
|
1155
|
-
|
|
1163
|
+
if (best === pos) acc = through(start + 3, prev, acc)
|
|
1164
|
+
prev = start + 4
|
|
1165
|
+
const at = ctx._fe ?? pos
|
|
1166
|
+
if (at > best) { best = at; acc = undefined }
|
|
1167
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
1156
1168
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
1157
1169
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1158
1170
|
}
|
|
1159
1171
|
if (next !== undefined) {
|
|
1160
|
-
return next(input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev)
|
|
1172
|
+
return next(input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, acc, prev, best)
|
|
1161
1173
|
}
|
|
1162
|
-
acc = through(total, prev, acc)
|
|
1174
|
+
if (best === pos) acc = through(total, prev, acc)
|
|
1163
1175
|
ctx._fe = pos; ctx._fx = acc ?? choiceFx
|
|
1164
1176
|
return FAIL
|
|
1165
1177
|
}
|
|
@@ -2690,10 +2702,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2690
2702
|
// THE CUT — a committed failure fails the whole choice.
|
|
2691
2703
|
if (committed(ctx)) return FAIL
|
|
2692
2704
|
// THE DISPATCHED ARM'S OWN SET IS THE ANSWER, at the choice's own
|
|
2693
|
-
// position
|
|
2694
|
-
//
|
|
2695
|
-
// declining to overwrite it. See `exec.ts` for why this is NOT
|
|
2696
|
-
// furthest-failure merging and what the residual JSON gap is.
|
|
2705
|
+
// position. `_fx` already holds it; reporting the sole selected arm
|
|
2706
|
+
// is declining to overwrite it.
|
|
2697
2707
|
const armFx = ctx._fx
|
|
2698
2708
|
if (armFx !== undefined && armFx.length > 0) { ctx._fe = pos; return FAIL }
|
|
2699
2709
|
}
|
|
@@ -2739,6 +2749,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2739
2749
|
const mLog = need ? ctx._triviaLog?.length ?? 0 : 0
|
|
2740
2750
|
const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
2741
2751
|
let acc: string[] | undefined
|
|
2752
|
+
let best = pos
|
|
2742
2753
|
if (c < 128) {
|
|
2743
2754
|
const bits = mask[c < 0 ? 128 : c]!
|
|
2744
2755
|
let prev = 0
|
|
@@ -2747,7 +2758,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2747
2758
|
const v = a0(input, pos, ctx)
|
|
2748
2759
|
if (v !== FAIL) return v
|
|
2749
2760
|
prev = 1
|
|
2750
|
-
|
|
2761
|
+
const at = ctx._fe ?? pos
|
|
2762
|
+
if (at > best) { best = at; acc = undefined }
|
|
2763
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
2751
2764
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
2752
2765
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2753
2766
|
}
|
|
@@ -2755,9 +2768,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2755
2768
|
ctx._fc = false
|
|
2756
2769
|
const v = a1(input, pos, ctx)
|
|
2757
2770
|
if (v !== FAIL) return v
|
|
2758
|
-
if (prev < 1) acc = accSet(e0, acc)
|
|
2771
|
+
if (best === pos && prev < 1) acc = accSet(e0, acc)
|
|
2759
2772
|
prev = 2
|
|
2760
|
-
|
|
2773
|
+
const at = ctx._fe ?? pos
|
|
2774
|
+
if (at > best) { best = at; acc = undefined }
|
|
2775
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
2761
2776
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
2762
2777
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2763
2778
|
}
|
|
@@ -2765,16 +2780,18 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2765
2780
|
ctx._fc = false
|
|
2766
2781
|
const v = a2!(input, pos, ctx)
|
|
2767
2782
|
if (v !== FAIL) return v
|
|
2768
|
-
if (prev < 1) acc = accSet(e0, acc)
|
|
2769
|
-
if (prev < 2) acc = accSet(e1, acc)
|
|
2783
|
+
if (best === pos && prev < 1) acc = accSet(e0, acc)
|
|
2784
|
+
if (best === pos && prev < 2) acc = accSet(e1, acc)
|
|
2770
2785
|
prev = 3
|
|
2771
|
-
|
|
2786
|
+
const at = ctx._fe ?? pos
|
|
2787
|
+
if (at > best) { best = at; acc = undefined }
|
|
2788
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
2772
2789
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
2773
2790
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2774
2791
|
}
|
|
2775
|
-
if (prev < 1) acc = accSet(e0, acc)
|
|
2776
|
-
if (prev < 2) acc = accSet(e1, acc)
|
|
2777
|
-
if (n === 3 && prev < 3) acc = accSet(e2, acc)
|
|
2792
|
+
if (best === pos && prev < 1) acc = accSet(e0, acc)
|
|
2793
|
+
if (best === pos && prev < 2) acc = accSet(e1, acc)
|
|
2794
|
+
if (best === pos && n === 3 && prev < 3) acc = accSet(e2, acc)
|
|
2778
2795
|
ctx._fe = pos; ctx._fx = acc ?? choiceFx
|
|
2779
2796
|
return FAIL
|
|
2780
2797
|
}
|
|
@@ -2783,27 +2800,33 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2783
2800
|
ctx._fc = false
|
|
2784
2801
|
const v = a0(input, pos, ctx)
|
|
2785
2802
|
if (v !== FAIL) return v
|
|
2786
|
-
|
|
2803
|
+
const at = ctx._fe ?? pos
|
|
2804
|
+
if (at > best) { best = at; acc = undefined }
|
|
2805
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
2787
2806
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
2788
2807
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2789
|
-
} else acc = accSet(e0, acc)
|
|
2808
|
+
} else if (best === pos) acc = accSet(e0, acc)
|
|
2790
2809
|
if (g1 === null || classHas(g1, c)) {
|
|
2791
2810
|
ctx._fc = false
|
|
2792
2811
|
const v = a1(input, pos, ctx)
|
|
2793
2812
|
if (v !== FAIL) return v
|
|
2794
|
-
|
|
2813
|
+
const at = ctx._fe ?? pos
|
|
2814
|
+
if (at > best) { best = at; acc = undefined }
|
|
2815
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
2795
2816
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
2796
2817
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2797
|
-
} else acc = accSet(e1, acc)
|
|
2818
|
+
} else if (best === pos) acc = accSet(e1, acc)
|
|
2798
2819
|
if (n === 3) {
|
|
2799
2820
|
if (g2 === null || classHas(g2, c)) {
|
|
2800
2821
|
ctx._fc = false
|
|
2801
2822
|
const v = a2!(input, pos, ctx)
|
|
2802
2823
|
if (v !== FAIL) return v
|
|
2803
|
-
|
|
2824
|
+
const at = ctx._fe ?? pos
|
|
2825
|
+
if (at > best) { best = at; acc = undefined }
|
|
2826
|
+
if (at === best) acc = accSet(ctx._fx, acc)
|
|
2804
2827
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|
|
2805
2828
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
2806
|
-
} else acc = accSet(e2, acc)
|
|
2829
|
+
} else if (best === pos) acc = accSet(e2, acc)
|
|
2807
2830
|
}
|
|
2808
2831
|
ctx._fe = pos; ctx._fx = acc ?? choiceFx
|
|
2809
2832
|
return FAIL
|
|
@@ -2869,11 +2892,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2869
2892
|
if (c < 128) {
|
|
2870
2893
|
return masked(
|
|
2871
2894
|
input, pos, ctx, mask[c < 0 ? 128 : c]!,
|
|
2872
|
-
need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, undefined, 0,
|
|
2895
|
+
need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, undefined, 0, pos,
|
|
2873
2896
|
)
|
|
2874
2897
|
}
|
|
2875
2898
|
return general(
|
|
2876
|
-
input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, undefined, 0,
|
|
2899
|
+
input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, undefined, 0, pos,
|
|
2877
2900
|
)
|
|
2878
2901
|
}
|
|
2879
2902
|
}
|
|
@@ -2889,7 +2912,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2889
2912
|
const mLog = need ? ctx._triviaLog?.length ?? 0 : 0
|
|
2890
2913
|
const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
2891
2914
|
return general(
|
|
2892
|
-
input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, undefined, 0,
|
|
2915
|
+
input, pos, ctx, c, need, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot, undefined, 0, pos,
|
|
2893
2916
|
)
|
|
2894
2917
|
}
|
|
2895
2918
|
}
|
|
@@ -1463,9 +1463,11 @@ return FAIL
|
|
|
1463
1463
|
ctx._fc=false
|
|
1464
1464
|
{const v=${arm}(input,pos,ctx)
|
|
1465
1465
|
if(v!==FAIL)return v}
|
|
1466
|
-
acc=${catchName}(${i},prev,acc)
|
|
1466
|
+
if(best===pos)acc=${catchName}(${i},prev,acc)
|
|
1467
1467
|
prev=${i + 1}
|
|
1468
|
-
|
|
1468
|
+
{const at=ctx._fe??pos
|
|
1469
|
+
if(at>best){best=at;acc=undefined}
|
|
1470
|
+
if(at===best)acc=_accSet(ctx._fx,acc)}
|
|
1469
1471
|
if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1470
1472
|
${emitRollback(p, L.buf, sinks)}
|
|
1471
1473
|
}`
|
|
@@ -1474,20 +1476,23 @@ ${emitRollback(p, L.buf, sinks)}
|
|
|
1474
1476
|
ctx._fc=false
|
|
1475
1477
|
{const v=${arm}(input,pos,ctx)
|
|
1476
1478
|
if(v!==FAIL)return v}
|
|
1477
|
-
|
|
1479
|
+
{const at=ctx._fe??pos
|
|
1480
|
+
if(at>best){best=at;acc=undefined}
|
|
1481
|
+
if(at===best)acc=_accSet(ctx._fx,acc)}
|
|
1478
1482
|
if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1479
1483
|
${emitRollback(p, L.buf, sinks)}
|
|
1480
|
-
}else acc=_accSet(${expected[i]},acc)`).join('\n')
|
|
1484
|
+
}else if(best===pos)acc=_accSet(${expected[i]},acc)`).join('\n')
|
|
1481
1485
|
|
|
1482
1486
|
return `${head}
|
|
1483
1487
|
const c=lead(input,pos)
|
|
1484
1488
|
${emitMark(p, L.buf, sinks)}
|
|
1485
1489
|
let acc
|
|
1490
|
+
let best=pos
|
|
1486
1491
|
${maskable ? `if(c<128){
|
|
1487
1492
|
const bits=${maskName}[c<0?128:c]
|
|
1488
1493
|
let prev=0
|
|
1489
1494
|
${maskArms}
|
|
1490
|
-
acc=${catchName}(${n},prev,acc)
|
|
1495
|
+
if(best===pos)acc=${catchName}(${n},prev,acc)
|
|
1491
1496
|
ctx._fe=pos;ctx._fx=acc??${choiceFx}
|
|
1492
1497
|
return FAIL
|
|
1493
1498
|
}
|
package/src/table/encode.ts
CHANGED
|
@@ -1078,20 +1078,16 @@ class Encoder {
|
|
|
1078
1078
|
? -1 : this.charClass(finalChoice.firstSets[i]!))
|
|
1079
1079
|
const dispIdx = this.disp.length
|
|
1080
1080
|
this.disp.push(classes)
|
|
1081
|
-
// PER-ARM EXPECTED SETS RIDE ALONG, after the arm offsets.
|
|
1082
|
-
//
|
|
1083
|
-
//
|
|
1084
|
-
//
|
|
1085
|
-
// opener set back. Without these words a partially-gated site reported
|
|
1086
|
-
// only the arms that happened to run.
|
|
1081
|
+
// PER-ARM EXPECTED SETS RIDE ALONG, after the arm offsets. While the best
|
|
1082
|
+
// failure remains at the choice position, an arm declined by the driver's
|
|
1083
|
+
// char gate contributes the static opener set the interpreter would get by
|
|
1084
|
+
// entering it. A deeper dynamic arm failure discards those shallower sets.
|
|
1087
1085
|
const head = this.emitHead(OP_CHOICE, 3 + 2 * kids.length)
|
|
1088
1086
|
this.code[head + 1] = dispIdx
|
|
1089
1087
|
this.code[head + 2] = kids.length
|
|
1090
1088
|
// THE CHOICE'S OWN SET IS THE ARMS' FLATMAP, NOT `deriveExpected(p)`.
|
|
1091
|
-
//
|
|
1092
|
-
//
|
|
1093
|
-
// choice whose two arms both open with `/::?/` reports it TWICE. The
|
|
1094
|
-
// deduped union silently collapsed that to one.
|
|
1089
|
+
// It is the pure-dispatch-miss and empty-dynamic fallback, where source
|
|
1090
|
+
// order and duplicates remain observable. `deriveExpected` would dedupe.
|
|
1095
1091
|
this.code[head + 3] = this.expected(arms.flatMap(a => deriveExpected(a)))
|
|
1096
1092
|
for (let i = 0; i < kids.length; i++) {
|
|
1097
1093
|
this.code[head + 4 + i] = kids[i]!
|
package/src/table/exec.ts
CHANGED
|
@@ -952,9 +952,8 @@ function makeDriver(
|
|
|
952
952
|
const d = code[ip + 1]!
|
|
953
953
|
const base = ip + 4
|
|
954
954
|
const choiceFx = fx[code[ip + 3]!] as string[]
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
// user an empty expected set.
|
|
955
|
+
// A pure dispatch miss has no attempted arm to rank. Report the static
|
|
956
|
+
// opener union at the choice's own position so the result is meaningful.
|
|
958
957
|
const failChoice = (): typeof FAIL => {
|
|
959
958
|
ctx._fe = pos
|
|
960
959
|
ctx._fx = choiceFx
|
|
@@ -1003,12 +1002,9 @@ function makeDriver(
|
|
|
1003
1002
|
// union IS that flatMap's answer, without running seven parsers to
|
|
1004
1003
|
// rediscover it.
|
|
1005
1004
|
//
|
|
1006
|
-
//
|
|
1007
|
-
//
|
|
1008
|
-
//
|
|
1009
|
-
// library, they are gated on `_ctx._probe` and they surface as
|
|
1010
|
-
// `RunResult.furthestFail` under `{recover:true}` — a different field.
|
|
1011
|
-
// Every `_fe`/`_fx` site in all three drivers is last-write-wins.
|
|
1005
|
+
// There is only one selected arm on this path, so ordered-arm depth
|
|
1006
|
+
// ranking does not apply. `furthestFail` under `{recover:true}` remains
|
|
1007
|
+
// a separate probe result.
|
|
1012
1008
|
//
|
|
1013
1009
|
// A residual JSON divergence survives this and is NOT a failure-reporting
|
|
1014
1010
|
// bug: on `{"a":]` this driver reports `["}"]` (the Obj arm's own
|
|
@@ -1034,18 +1030,15 @@ function makeDriver(
|
|
|
1034
1030
|
const mEr = need ? ctx._errors?.length ?? 0 : 0
|
|
1035
1031
|
const mLog = need ? ctx._triviaLog?.length ?? 0 : 0
|
|
1036
1032
|
const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
1037
|
-
//
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
// is a set of OPENERS, so a left-factored or shared-prefix choice whose
|
|
1041
|
-
// arms all failed PAST their common opener reported that opener instead of
|
|
1042
|
-
// the tokens the arms were actually waiting for: `['/::?/']` where the
|
|
1043
|
-
// interpreter says `['"before"', '"hover"']`.
|
|
1033
|
+
// The ordered path keeps only arms at the deepest failure offset. Exact
|
|
1034
|
+
// ties merge in source order. The final failure is still anchored at the
|
|
1035
|
+
// choice's own position; the deeper offset ranks diagnostics only.
|
|
1044
1036
|
//
|
|
1045
1037
|
// `choiceFx` is still the answer when nothing ran or every arm reported
|
|
1046
1038
|
// nothing — the char gate can skip arms the interpreter would have entered,
|
|
1047
1039
|
// and the union is what those arms' start-failures would have contributed.
|
|
1048
1040
|
let acc: string[] | undefined
|
|
1041
|
+
let best = pos
|
|
1049
1042
|
const push = (ax: readonly string[] | undefined): void => {
|
|
1050
1043
|
if (ax === undefined || ax.length === 0) return
|
|
1051
1044
|
if (acc === undefined) acc = ax.slice()
|
|
@@ -1060,7 +1053,7 @@ function makeDriver(
|
|
|
1060
1053
|
// enters the arm, the arm's opener fails, and its static set is what
|
|
1061
1054
|
// lands in the accumulation. Declining to enter must not also decline
|
|
1062
1055
|
// to say what the arm wanted.
|
|
1063
|
-
push(fx[code[armFx + i]!] as string[])
|
|
1056
|
+
if (best === pos) push(fx[code[armFx + i]!] as string[])
|
|
1064
1057
|
continue
|
|
1065
1058
|
}
|
|
1066
1059
|
ctx._fc = false
|
|
@@ -1069,7 +1062,9 @@ function makeDriver(
|
|
|
1069
1062
|
const v = exec(code[base + i]!, input, pos, ctx)
|
|
1070
1063
|
if (v !== FAIL) return v
|
|
1071
1064
|
if (COUNT) { tableCounters.ungatedFails++; tableCounters.ungatedFailRows += tableCounters.rows - rows0 }
|
|
1072
|
-
|
|
1065
|
+
const at = ctx._fe ?? pos
|
|
1066
|
+
if (at > best) { best = at; acc = undefined }
|
|
1067
|
+
if (at === best) push(ctx._fx)
|
|
1073
1068
|
// A committed arm cuts, and the interpreter reports the accumulation at
|
|
1074
1069
|
// the arm's OWN span (`choice.ts:170`), not at the choice's position.
|
|
1075
1070
|
if (committed(ctx)) { if (acc !== undefined) ctx._fx = acc; return FAIL }
|