parseman 0.60.4 → 0.60.6

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.
Files changed (54) hide show
  1. package/dist/analysis/commitment.d.ts.map +1 -1
  2. package/dist/analysis/diagnostics.js +31 -5
  3. package/dist/analysis/diagnostics.js.map +1 -1
  4. package/dist/cli/index.js +639 -613
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/combinators/parser.d.ts +34 -3
  7. package/dist/combinators/parser.d.ts.map +1 -1
  8. package/dist/combinators/trivia-boundary.d.ts +5 -3
  9. package/dist/combinators/trivia-boundary.d.ts.map +1 -1
  10. package/dist/compiler/compile-linkable-table.d.ts.map +1 -1
  11. package/dist/compiler/linker.d.ts +11 -3
  12. package/dist/compiler/linker.d.ts.map +1 -1
  13. package/dist/compiler/token-capability.js +6 -0
  14. package/dist/compiler/token-capability.js.map +1 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +93 -25
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugin/evaluator.d.ts +3 -1
  20. package/dist/plugin/evaluator.d.ts.map +1 -1
  21. package/dist/plugin/index.d.ts.map +1 -1
  22. package/dist/plugin/index.js +116 -75
  23. package/dist/plugin/index.js.map +1 -1
  24. package/dist/spec/index.js +3 -1
  25. package/dist/spec/index.js.map +1 -1
  26. package/dist/spec/model.d.ts.map +1 -1
  27. package/dist/table/assemble.d.ts.map +1 -1
  28. package/dist/table/emit-assembly.d.ts.map +1 -1
  29. package/dist/table/encode.d.ts.map +1 -1
  30. package/dist/table/exec.d.ts.map +1 -1
  31. package/dist/table/index.js +31 -5
  32. package/dist/table/index.js.map +1 -1
  33. package/dist/table/ops.d.ts +6 -0
  34. package/dist/table/ops.d.ts.map +1 -1
  35. package/dist/version.d.ts +1 -1
  36. package/package.json +1 -1
  37. package/src/analysis/commitment.ts +3 -1
  38. package/src/combinators/grammar.ts +1 -1
  39. package/src/combinators/parser.ts +82 -9
  40. package/src/combinators/trivia-boundary.ts +11 -3
  41. package/src/compiler/compile-linkable-table.ts +4 -1
  42. package/src/compiler/ir-serialize.ts +10 -4
  43. package/src/compiler/linker.ts +22 -12
  44. package/src/index.ts +2 -1
  45. package/src/plugin/evaluator.ts +16 -5
  46. package/src/plugin/index.ts +27 -104
  47. package/src/spec/model.ts +3 -1
  48. package/src/table/assemble.ts +11 -1
  49. package/src/table/emit-assembly.ts +10 -1
  50. package/src/table/encode.ts +16 -9
  51. package/src/table/exec.ts +11 -9
  52. package/src/table/ops.ts +6 -0
  53. package/src/table/site-labels.ts +3 -3
  54. package/src/version.ts +1 -1
@@ -24,7 +24,7 @@ import { createUnplugin } from 'unplugin'
24
24
  import { parseSync } from 'oxc-parser'
25
25
  import { ResolverFactory } from 'oxc-resolver'
26
26
  import MagicString from 'magic-string'
27
- import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, externalAccesses, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, setBuilderImportResolver, propName, ModuleScope, type FreeNameResolution, type Scope, type ScopeEntry } from './evaluator.ts'
27
+ import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, externalAccesses, ruleMapRecipes, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, setBuilderImportResolver, propName, ModuleScope, type FreeNameResolution, type Scope, type ScopeEntry } from './evaluator.ts'
28
28
  import { classifyRuleMap } from '../analysis/commitment.ts'
29
29
  import { compile } from '../table/compile.ts'
30
30
  import { compileRuleMap, type CompiledRuleMapTable } from '../table/compile-rule-map.ts'
@@ -223,12 +223,6 @@ const DEFAULT_ASSEMBLY_BUDGET = 0.25
223
223
  /** A build failure the lowering must not soften into a warning and a fallback. */
224
224
  class LinkRefusal extends Error {}
225
225
 
226
- /** Whether a rule-map entry has a definition (a `g.X` merely read does not). */
227
- function isResolved(rule: Combinator<unknown>): boolean {
228
- if (rule._def.tag !== 'lazy') return true
229
- try { rule._def.thunk(); return true } catch { return false }
230
- }
231
-
232
226
  function reasonSuffix(runtimeOnly: readonly string[] | undefined): string {
233
227
  return runtimeOnly === undefined || runtimeOnly.length === 0
234
228
  ? ''
@@ -2061,7 +2055,7 @@ function transformMacroImpl(
2061
2055
  /**
2062
2056
  * Evaluate a carried list back to combinators, one entry per piece: its rule map
2063
2057
  * and the `rules()` recipe it was built from. `compose()` links the recipes into
2064
- * one namespace (`mergedCarriedRules`); `composeLeaf()` merges the maps by name.
2058
+ * one namespace (`mergedCarriedRules`), and `composeLeaf()` does the same.
2065
2059
  * Either way the merged grammar is encoded ONCE by `compileRuleMap` — no relocation
2066
2060
  * of code offsets and no merging of const / fn / expected pools between two
2067
2061
  * already-encoded programs.
@@ -2097,38 +2091,6 @@ function transformMacroImpl(
2097
2091
  return { pieces, trackLines }
2098
2092
  }
2099
2093
 
2100
- /**
2101
- * Fold ordered rule maps into the composed map: a later piece's name WINS, and
2102
- * `Map` keeps each name at its first-sighting position so the encoded rule order
2103
- * tracks the source lowering's rather than drifting per compose.
2104
- *
2105
- * A REFERENCE IS NOT A DEFINITION. A `rules(g => …)` cache also holds every `g.X`
2106
- * that was merely ACCESSED, so a delta that calls `g.Pair` carries a `Pair` entry
2107
- * that is an unresolved named lazy. Merged in name order that entry lands LAST and
2108
- * shadows the base piece which actually defines `Pair` — the encoder then finds a
2109
- * hole where the winner should be and refuses the whole grammar with "ref() used
2110
- * before .define()". The same filter guards `compileLinkableTable`'s `isLocal`
2111
- * (`compile-linkable-table.ts`) and `recoverComposedRules`; this site needs it
2112
- * for the same reason, and skipping it is why cross-piece composition looked blocked.
2113
- *
2114
- * The reference itself is not lost: it stays inside the delta's own rule bodies and
2115
- * `enc.winners` binds it BY NAME to whichever piece supplies the definition.
2116
- */
2117
- const mergeRuleMaps = (
2118
- maps: ReadonlyArray<ReadonlyArray<readonly [string, Combinator<unknown>]>>,
2119
- ): Array<[string, Combinator<unknown>]> => {
2120
- const winners = new Map<string, Combinator<unknown>>()
2121
- for (const map of maps) {
2122
- for (const [name, rule] of map) {
2123
- if (rule._def.tag === 'lazy') {
2124
- try { rule._def.thunk() } catch { continue }
2125
- }
2126
- winners.set(name, rule)
2127
- }
2128
- }
2129
- return [...winners]
2130
- }
2131
-
2132
2094
  /**
2133
2095
  * LINK a carried list into the composed map, exactly as runtime `compose()` does:
2134
2096
  * every piece's carried `rules()` recipe runs again against ONE namespace
@@ -2156,25 +2118,6 @@ function transformMacroImpl(
2156
2118
  return { rules: Object.entries(linked), trackLines: carriedMaps.trackLines, dangling }
2157
2119
  }
2158
2120
 
2159
- /**
2160
- * COMPOSING-WINS for `composeLeaf()`'s carried pieces, as an OVERRIDE rather than
2161
- * a gap-fill: `compileRuleMap`'s `applyAmbient` only fills a rule that carries no
2162
- * trivia of its own. (`compose()` gets the same override from `linkRules`.)
2163
- *
2164
- * Safe to mutate: every rule here came from `evalRuleMapIR`, which constructs FRESH
2165
- * combinators per piece per compose, so this cannot leak into another compose of the
2166
- * same base grammar.
2167
- */
2168
- const applyComposingTrivia = (
2169
- ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
2170
- composing: Combinator<unknown>,
2171
- ): void => {
2172
- for (const [, rule] of ruleMap) {
2173
- if (rule._meta.isTrivia) continue
2174
- ;(rule._meta as { grammarTrivia?: Combinator<unknown> }).grammarTrivia = composing
2175
- }
2176
- }
2177
-
2178
2121
  /** Resolve one `compose([...])` argument to its RE-LOWERABLE carried items (IR
2179
2122
  * pieces / import spreads / baked-pieces fallback). The composing grammar's trivia
2180
2123
  * is applied later, uniformly, in materializeCarried — never per-piece here. */
@@ -2340,10 +2283,17 @@ function transformMacroImpl(
2340
2283
  // does — one encode, one `tableRules(…)` expression, no linker. `carried` is
2341
2284
  // unchanged either way: it is the re-lowerable IR list, not a lowering artifact,
2342
2285
  // so a downstream re-compose behaves identically whichever engine emitted here.
2343
- const merged = mergedCarriedRules(carried, {
2344
- trivia: composing,
2345
- ...(cHostMode ? { hostMode: cHostMode as HostMode } : {}),
2346
- })
2286
+ let merged: ReturnType<typeof mergedCarriedRules>
2287
+ try {
2288
+ merged = mergedCarriedRules(carried, {
2289
+ trivia: composing,
2290
+ ...(cHostMode ? { hostMode: cHostMode as HostMode } : {}),
2291
+ })
2292
+ } catch (e) {
2293
+ // Linking refuses the composition (a `prior.X` no earlier piece defines): a
2294
+ // runtime `compose()` would refuse it the same way, so the build fails, here.
2295
+ throw new Error(`${id}:${lineOf(init.start)} — ${(e as Error).message}`, { cause: e })
2296
+ }
2347
2297
  if (merged?.dangling !== undefined) throw new Error(`${id}:${lineOf(init.start)} — compose: ${merged.dangling}`)
2348
2298
  if (merged !== null) {
2349
2299
  // Harvest the direct-builder import provenance carried on the merged graph so
@@ -2475,22 +2425,15 @@ function transformMacroImpl(
2475
2425
  return null
2476
2426
  }
2477
2427
  const localArg = elements[elements.length - 1]!
2478
- let localRules: Iterable<readonly [string, unknown]> | null = null
2479
- // The LOCAL leaf grammar's own ambient scanSkip (from its `rules({ scanSkip })`)
2480
- // governs the scanTo/balanced sites IT defines — unlike trivia (composing-wins),
2481
- // opaque units are dialect-specific, so the local declaration is threaded to the
2482
- // local piece's compile.
2483
- let localScanSkip: Combinator<unknown>[] | undefined
2428
+ let localRules: ReadonlyMap<string, Combinator<unknown>> | null = null
2484
2429
  const importedFactories: string[] = []
2485
2430
  if (isRulesCall(localArg)) {
2486
2431
  const evaluated = evaluateRulesFactory(localArg, `composeLeaf${init.start}`)
2487
2432
  localRules = evaluated?.ruleMap ?? null
2488
- localScanSkip = evaluated?.scanSkip
2489
2433
  if (evaluated?.importedFactory) importedFactories.push(evaluated.importedFactory)
2490
2434
  } else if (localArg.type === 'Identifier') {
2491
2435
  const name = (localArg as unknown as { name: string }).name
2492
2436
  localRules = localRuleMaps.get(name) ?? null
2493
- localScanSkip = localGrammarScanSkip.get(name)
2494
2437
  }
2495
2438
  if (!localRules) {
2496
2439
  // Distinguish "the argument is the wrong SHAPE" from "the argument is the right
@@ -2525,13 +2468,6 @@ function transformMacroImpl(
2525
2468
  // may place one beneath a direct node(). Re-lower their terminals with
2526
2469
  // capture enabled so that node receives the imported token values in its
2527
2470
  // normal child collector; the pieces still contain no semantic callback.
2528
- const localNs = nsFor(`composeLeaf${init.start}`)
2529
- const localEntries = [...localRules] as Array<[string, Combinator<unknown>]>
2530
-
2531
- // TABLE FIRST — same merge as `compose()`, with the local leaf map appended LAST
2532
- // so it wins every name and binds the imported shapes' holes (`enc.winners`
2533
- // resolves those by name; see `mergedCarriedRules`).
2534
- //
2535
2471
  // The recognition-only gate is preserved EXACTLY, and is the reason this path
2536
2472
  // needs no `compileLinkable`: `hasDirectBuilders` / `isRecognitionOnly` are
2537
2473
  // predicates over the combinator graph, not products of lowering it, so
@@ -2554,36 +2490,23 @@ function transformMacroImpl(
2554
2490
  warn(init.start, 'composeLeaf(): every pre-final grammar must explicitly prove recognition-only')
2555
2491
  return null
2556
2492
  }
2557
- // The local grammar's OWN ambient scanSkip is stamped onto the LOCAL entries
2558
- // only. Passing it as a merged-map option instead would let `applyAmbient`
2559
- // hand it to every imported rule that happens to carry no stamp of its own —
2560
- // opaque units are dialect-specific, and that is precisely the leak the
2561
- // per-piece threading in the source path exists to avoid.
2562
- if (localScanSkip) {
2563
- for (const [, rule] of localEntries) {
2564
- if (rule._meta.isTrivia) continue
2565
- const meta = rule._meta as { grammarScanSkip?: Combinator<unknown>[] }
2566
- if (meta.grammarScanSkip === undefined) meta.grammarScanSkip = localScanSkip
2567
- }
2493
+ // The same link as `compose()` and runtime `composeLeaf()`: the leaf is the last
2494
+ // recipe, so it wins every name, binds the imported shapes' holes and fills its
2495
+ // `prior.X`. Each recipe keeps its own scanSkip; the composing trivia governs all.
2496
+ let linked: Record<string, Combinator<unknown>>
2497
+ try {
2498
+ linked = linkRules(
2499
+ [...carriedMaps.pieces.flatMap(p => p.recipes), ...ruleMapRecipes.get(localRules)!],
2500
+ { trivia: composing },
2501
+ )
2502
+ } catch (e) {
2503
+ throw new LinkRefusal(`${id}:${lineOf(init.start)} — composeLeaf: ${(e as Error).message.replace(/^compose: /, '')}`, { cause: e })
2568
2504
  }
2569
- const leafMerged = mergeRuleMaps([...carriedMaps.pieces.map(p => p.rules), localEntries])
2570
- // The same refusal as `compose()`, over a merge by name: a name some piece only
2571
- // reads (an unresolved entry of a carried map, or a local factory's external
2572
- // access) that no piece defines.
2573
- const accessed = [
2574
- ...carriedMaps.pieces.flatMap(p => p.rules).filter(([, rule]) => !isResolved(rule)).map(([name]) => name),
2575
- ...(localRules instanceof Map ? externalAccesses.get(localRules) ?? [] : []),
2576
- ]
2577
- const dangling = danglingReference(Object.fromEntries(leafMerged), accessed)
2505
+ const dangling = danglingReference(linked)
2578
2506
  if (dangling !== undefined) throw new LinkRefusal(`${id}:${lineOf(init.start)} — composeLeaf: ${dangling}`)
2579
- // Composing-wins governs the leaf fuse too. The local entries are NOT freshly
2580
- // evaluated (they come from this module's own `rules()` factory), so they are
2581
- // excluded from the override — their declared trivia IS the composing candidate,
2582
- // and rewriting `_meta` on them would mutate the module-level grammar object.
2583
- if (composing) applyComposingTrivia(carriedMaps.pieces.flatMap(p => p.rules), composing)
2584
2507
  const refusals: string[] = []
2585
2508
  const compiled = compileRuleMap(
2586
- leafMerged,
2509
+ Object.entries(linked),
2587
2510
  {
2588
2511
  ...(composing ? { trivia: composing } : {}),
2589
2512
  ...(carriedMaps.trackLines ? { trackLines: true } : {}),
package/src/spec/model.ts CHANGED
@@ -507,7 +507,9 @@ class Builder {
507
507
 
508
508
  case 'lazy': {
509
509
  const inner = resolveLazy(self)
510
- return inner ? this.walk(inner) : { kind: 'annotation', text: '?' }
510
+ if (inner) return this.walk(inner)
511
+ const prior = (self as { _priorOf?: string })._priorOf
512
+ return { kind: 'annotation', text: prior === undefined ? '?' : `prior.${prior}` }
511
513
  }
512
514
 
513
515
  case 'unknown':
@@ -88,7 +88,7 @@ import { PlainBuf, PlainField, PlainLeaf, PlainNode, PlainSpan } from '../cst/pl
88
88
  import {
89
89
  OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
90
90
  OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
91
- OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
91
+ OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, SCOPE_INHERIT, OP_EXPECT, OP_SEQX, OP_SCAN,
92
92
  OP_LIVE,
93
93
  OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD, OP_ATTEMPT, OP_LABEL,
94
94
  OP_COV, OP_GAPGATE, OP_STRICT,
@@ -2109,6 +2109,16 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2109
2109
  case OP_SCOPE_CAP:
2110
2110
  case OP_SCOPE_PLAIN: {
2111
2111
  const ki = code[ip + 1]!
2112
+ if (ki === SCOPE_INHERIT) {
2113
+ const body = link(code[ip + 2]!)
2114
+ return (input, pos, ctx) => {
2115
+ const savedCap = ctx.captureTrivia
2116
+ ctx.captureTrivia = true
2117
+ const v = body(input, pos, ctx)
2118
+ ctx.captureTrivia = savedCap
2119
+ return v
2120
+ }
2121
+ }
2112
2122
  const scopeTrivia = ki < 0 ? undefined : (trivia[ki] as ParseContext['trivia'])
2113
2123
  const scopeLabels = scopeTrivia?._meta.triviaKindLabels
2114
2124
  // THE SWAP, RESOLVED AT ASSEMBLY. `exec.ts` computed
@@ -50,7 +50,7 @@ import {
50
50
  OP_ADJ, OP_ARMGATE, OP_RGATE, OP_RGATE2, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
51
51
  OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
52
52
  OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX,
53
- OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
53
+ OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, SCOPE_INHERIT, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
54
54
  OP_LEX_BODY, OP_LEX_PROGRAM, OP_GAPGATE, OP_STRICT,
55
55
  } from './ops.ts'
56
56
  import {
@@ -1625,6 +1625,15 @@ return r.value
1625
1625
  const cap = op === OP_SCOPE_CAP
1626
1626
  const flags = op === OP_SCOPE_PLAIN ? 0 : code[ip + 3]!
1627
1627
  const child = link(code[ip + 2]!)
1628
+ if (ki === SCOPE_INHERIT) {
1629
+ return `${head}
1630
+ const sC=ctx.captureTrivia
1631
+ ctx.captureTrivia=true
1632
+ const v=${child}(input,pos,ctx)
1633
+ ctx.captureTrivia=sC
1634
+ return v
1635
+ }`
1636
+ }
1628
1637
  // THE SWAP, RESOLVED AT EMIT. `swapLegal` is `!trackLines`, an option;
1629
1638
  // the other two are table data. All three are known here, so the body
1630
1639
  // holds the scanner it installs as a hoisted const.
@@ -15,7 +15,7 @@ import { asciiFoldKey, branchUsesRouted, parserUsesRouted } from '../combinators
15
15
  import {
16
16
  OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
17
17
  OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
18
- OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
18
+ OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, SCOPE_INHERIT, OP_EXPECT, OP_SEQX, OP_SCAN,
19
19
  OP_LIVE, OP_ATTEMPT, OP_LABEL,
20
20
  OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD,
21
21
  OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE, OP_COV, OP_GAPGATE, OP_RGATE2, OP_STRICT,
@@ -2182,13 +2182,19 @@ class Encoder {
2182
2182
  // this scope's trivia is opaque to root capture, which `run()` consumes.
2183
2183
  // Nothing in the parse changes, so there is nothing for the table to
2184
2184
  // lower — accepting it is not a gap, it is the correct lowering.
2185
- // ONLY `true` IS MEANINGFUL HERE. `grammar.ts:94` stores
2186
- // `opts.trackLines ?? false`, so an UNSET scope and an explicitly-false
2187
- // one are the same value on the def — the distinction is lost. At parse
2188
- // time `:103` reads `opts.trackLines ?? _ctx?.trackLines ?? false`, i.e.
2189
- // unset INHERITS. So stored-false cannot be read as "force off", and
2190
- // comparing it against the setting refused every inner scope of a
2191
- // trackLines:true grammar — all four `*PositionsGrammar`s.
2185
+ // ONLY `true` IS MEANINGFUL HERE. `def.trackLines` is `opts.trackLines ??
2186
+ // false`, so an unset scope and an explicitly-false one read the same on that
2187
+ // field (`def.constructionTrackLines` keeps the difference, and the encoder
2188
+ // does not read it). An unset scope inherits, so stored-false cannot be read
2189
+ // as "force off", and comparing it against the setting refused every inner
2190
+ // scope of a trackLines:true grammar — all four `*PositionsGrammar`s.
2191
+ //
2192
+ // THIS IS NOT THE INTERPRETER'S SCOPING, AND A REDUCER CAN SEE IT (#258). A
2193
+ // table has one line setting. A node outside a `trackLines: true` scope gets
2194
+ // line fields from the table and none from the interpreter; a
2195
+ // `trackLines: false` scope under a tracked grammar keeps them in the table
2196
+ // and drops them in the interpreter; and a `trackLines: true` scope in a rule
2197
+ // the entry cannot reach still turns them on for `encodeTable`.
2192
2198
  //
2193
2199
  // A SCOPE THAT ASKS FOR TRACKING NO LONGER REFUSES. `encodeTable` folds
2194
2200
  // every such scope into the table's own `track` before encoding begins
@@ -2237,8 +2243,9 @@ class Encoder {
2237
2243
  this.activeTrivia = savedTrivia
2238
2244
  this.triviaKnown = savedKnown
2239
2245
  if (d.clearTrivia === true) return this.emit(op, -1, bodyIp, flags)
2246
+ // No trivia named: `flags` is zero (`rootCapture: 'opaque'` requires one).
2240
2247
  if (d.triviaParser === undefined) {
2241
- return cap || flags !== 0 ? this.emit(op, -1, bodyIp, flags) : bodyIp
2248
+ return cap ? this.emit(op, SCOPE_INHERIT, bodyIp, flags) : bodyIp
2242
2249
  }
2243
2250
  return this.emit(op, this.triviaSlot(d.triviaParser), bodyIp, flags)
2244
2251
  }
package/src/table/exec.ts CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  import {
19
19
  OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
20
20
  OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
21
- OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
21
+ OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, SCOPE_INHERIT, OP_EXPECT, OP_SEQX, OP_SCAN,
22
22
  OP_LIVE,
23
23
  OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD, OP_ATTEMPT, OP_LABEL,
24
24
  OP_COV, OP_GAPGATE, OP_STRICT,
@@ -779,14 +779,16 @@ function makeDriver(
779
779
  // labelled trivia is excluded here rather than tested later: `scanTrivia`
780
780
  // suppresses its own fast path when labels are present, so a swap there
781
781
  // would drop the labelled records silently.
782
- SCAN = FAST && ki >= 0 && !triviaLabelled[ki]! ? triviaScan[ki]! : null
783
- ctx.trivia = scopeTrivia
784
- // A scope installs its trivia's KIND LABELS too. Root-trivia rows are
785
- // only ever written on the labelled scan path (trivia-skip.ts:212) — the
786
- // unlabelled fast scanner returns before any root logging and does not
787
- // even test `_rootTriviaLog` — so a scope that sets trivia without its
788
- // labels captures NOTHING at the root, silently.
789
- ctx.triviaKindLabels = scopeTrivia?._meta.triviaKindLabels
782
+ if (ki !== SCOPE_INHERIT) {
783
+ SCAN = FAST && ki >= 0 && !triviaLabelled[ki]! ? triviaScan[ki]! : null
784
+ ctx.trivia = scopeTrivia
785
+ // A scope installs its trivia's KIND LABELS too. Root-trivia rows are
786
+ // only ever written on the labelled scan path (trivia-skip.ts:212) — the
787
+ // unlabelled fast scanner returns before any root logging and does not
788
+ // even test `_rootTriviaLog` — so a scope that sets trivia without its
789
+ // labels captures NOTHING at the root, silently.
790
+ ctx.triviaKindLabels = scopeTrivia?._meta.triviaKindLabels
791
+ }
790
792
  // SCOPE_CAP additionally turns capture ON for the child. It is an OR with
791
793
  // whatever the enclosing context already asked for (`grammar.ts:129`), so
792
794
  // the RESTORE puts the saved value back rather than writing `false` — an
package/src/table/ops.ts CHANGED
@@ -211,6 +211,12 @@ export const OP_TOKEN = 29
211
211
  * is why this restores the saved value rather than writing `false`.
212
212
  */
213
213
  export const OP_SCOPE_CAP = 30
214
+ /**
215
+ * `k` of a `SCOPE_CAP` row whose `parser({ captureTrivia: true })` names no trivia.
216
+ * It raises capture and leaves the trivia the scope was entered with in force,
217
+ * where `k = -1` clears it (`trivia: null`).
218
+ */
219
+ export const SCOPE_INHERIT = -2
214
220
  /**
215
221
  * `WITHCTX k c` — `withCtx(extra, c)`. `k >= 0` is `extra` in the const pool
216
222
  * (the replace form); `k < 0` is the derive form, `~k` indexing the author's
@@ -62,7 +62,7 @@
62
62
  import {
63
63
  OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EXPECT, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF, OP_LIT, OP_SOURCE_LEAF,
64
64
  OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE,
65
- OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
65
+ OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, SCOPE_INHERIT, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
66
66
  } from './ops.ts'
67
67
  import { childSlots } from './child-slots.ts'
68
68
  import { leadRanges } from '../lead-ranges.ts'
@@ -206,7 +206,7 @@ function transfer(code: Int32Array, ip: number, at: SiteLabel, hostCst: boolean)
206
206
  const op = code[ip]
207
207
  if (op === OP_SCOPE || op === OP_SCOPE_CAP || op === OP_SCOPE_PLAIN) {
208
208
  const ki = code[ip + 1]!
209
- const tri = ki < 0 ? TRI_NONE : ki
209
+ const tri = ki === SCOPE_INHERIT ? at.tri : ki < 0 ? TRI_NONE : ki
210
210
  // `OP_SCOPE_CAP` is an OR with the inherited context, never a switch-off
211
211
  // (`encode.ts:1153-1158`), so it can only ever raise `cap` to `CAP_ON`.
212
212
  const cap = op === OP_SCOPE_CAP ? CAP_ON : at.cap
@@ -358,7 +358,7 @@ export function scanHandoffNeeded(code: Int32Array, ip: number, labels: SiteLabe
358
358
  const op = code[at]
359
359
  // A scan runs interpreted combinators that reach pieces through `prog.scans`.
360
360
  if (op === OP_SCAN) return true
361
- if (op === OP_TOKEN || op === OP_SCOPE_CAP) continue
361
+ if (op === OP_TOKEN || (op === OP_SCOPE_CAP && code[at + 1] !== SCOPE_INHERIT)) continue
362
362
  if ((op === OP_SCOPE_PLAIN || op === OP_SCOPE) && !(code[at + 1]! >= 0 && l.tri === code[at + 1]!
363
363
  && (op === OP_SCOPE_PLAIN || code[at + 3]! === 0))) continue
364
364
  kids.length = 0
package/src/version.ts CHANGED
@@ -13,4 +13,4 @@
13
13
  * Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
14
14
  * the build if this constant drifts from the published version.
15
15
  */
16
- export const PARSEMAN_VERSION = '0.60.4'
16
+ export const PARSEMAN_VERSION = '0.60.6'