parseman 0.51.2 → 0.53.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/commitment.d.ts.map +1 -1
- package/dist/analysis/diagnostics.js +411 -28
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +6162 -6372
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/attempt.d.ts +11 -1
- package/dist/combinators/attempt.d.ts.map +1 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +6 -2
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +34 -0
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts +0 -13
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/compiler/borrowed-rules.d.ts +30 -0
- package/dist/compiler/borrowed-rules.d.ts.map +1 -0
- package/dist/compiler/compile-linkable-table.d.ts +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts +10 -4
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +48 -91
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +4 -0
- package/dist/compiler/token-capability.js.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +470 -725
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/evaluator.d.ts +38 -2
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1127 -544
- package/dist/plugin/index.js.map +3 -3
- package/dist/plugin/ir-eval.d.ts +21 -0
- package/dist/plugin/ir-eval.d.ts.map +1 -0
- package/dist/plugin/reducer-resolver.d.ts +6 -0
- package/dist/plugin/reducer-resolver.d.ts.map +1 -1
- package/dist/spec/index.js +555 -6086
- package/dist/spec/index.js.map +3 -3
- package/dist/spec/railroad-lib.d.ts +7 -2
- package/dist/spec/railroad-lib.d.ts.map +1 -1
- package/dist/spec/railroad.d.ts.map +1 -1
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +11 -4
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/compile.d.ts +16 -0
- package/dist/table/compile.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/encode-baseline.d.ts.map +1 -1
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/index.js +272 -102
- package/dist/table/index.js.map +2 -2
- package/dist/table/ops.d.ts +7 -5
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/analysis/commitment.ts +3 -1
- package/src/analysis/rebuild.ts +1 -1
- package/src/combinators/attempt.ts +26 -3
- package/src/combinators/first-set.ts +5 -1
- package/src/combinators/map.ts +5 -1
- package/src/combinators/node.ts +12 -3
- package/src/combinators/parser.ts +133 -67
- package/src/combinators/ref.ts +10 -1
- package/src/compiler/borrowed-rules.ts +88 -0
- package/src/compiler/compile-linkable-table.ts +1 -1
- package/src/compiler/grammar-coverage-ids.ts +35 -6
- package/src/compiler/ir-serialize.ts +13 -119
- package/src/compiler/linker.ts +190 -666
- package/src/coverage.ts +7 -5
- package/src/cst/reflection.ts +6 -1
- package/src/cst/trivia-kinds.ts +6 -0
- package/src/index.ts +1 -0
- package/src/plugin/evaluator.ts +114 -14
- package/src/plugin/index.ts +588 -110
- package/src/plugin/ir-eval.ts +165 -0
- package/src/plugin/reducer-resolver.ts +20 -3
- package/src/spec/railroad-lib.ts +18 -8
- package/src/spec/railroad.ts +3 -4
- package/src/table/assemble.ts +22 -8
- package/src/table/compile-rule-map.ts +18 -8
- package/src/table/compile.ts +61 -0
- package/src/table/emit-assembly.ts +8 -3
- package/src/table/encode-baseline.ts +5 -1
- package/src/table/encode.ts +13 -13
- package/src/table/exec.ts +6 -2
- package/src/table/ops.ts +7 -5
- package/src/table/site-labels.ts +3 -1
- package/src/types.ts +8 -1
- package/src/version.ts +1 -1
package/src/coverage.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { scanTo } from './combinators/scanTo.ts'
|
|
|
18
18
|
import { sequence } from './combinators/sequence.ts'
|
|
19
19
|
import { token, leaf, sourceLeaf } from './combinators/token.ts'
|
|
20
20
|
import { withCtx } from './combinators/withCtx.ts'
|
|
21
|
-
import { composedCoverageRules } from './compiler/linker.ts'
|
|
21
|
+
import { composedCoverageRules, linkedWinnersOf } from './compiler/linker.ts'
|
|
22
22
|
import { buildGrammarPlan, type GrammarCoverageDefinition, type GrammarCoveragePlan } from './compiler/grammar-coverage-ids.ts'
|
|
23
23
|
|
|
24
24
|
export type { GrammarCoverageDefinition } from './compiler/grammar-coverage-ids.ts'
|
|
@@ -156,11 +156,11 @@ export function grammarCoverageDefinitions(entry: Combinator<unknown>, winners?:
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/** Definitions for a runtime `compose()` result, normalized through its final
|
|
159
|
-
*
|
|
159
|
+
* winner map. Anything else — a compiled table, a plain map — fails rather than
|
|
160
160
|
* reporting source-piece identities as though they were final grammar IDs. */
|
|
161
161
|
export function composedGrammarCoverageDefinitions(grammar: Record<string, unknown>, startRule: string): readonly GrammarCoverageDefinition[] {
|
|
162
162
|
const winners = composedCoverageRules(grammar)
|
|
163
|
-
if (!winners) throw new TypeError('semantic coverage needs
|
|
163
|
+
if (!winners) throw new TypeError('semantic coverage needs a runtime compose() result (a linked interpreter grammar); this is not one')
|
|
164
164
|
const entry = winners[startRule]
|
|
165
165
|
if (!entry) throw new TypeError(`semantic coverage start rule ${JSON.stringify(startRule)} is not a final winner`)
|
|
166
166
|
return grammarCoverageDefinitions(entry, winners)
|
|
@@ -327,7 +327,7 @@ function coverageEntry(entry: Combinator<unknown>, collector: GrammarCoverageCol
|
|
|
327
327
|
case 'oneOrMore': return oneOrMore(build(def.parser))
|
|
328
328
|
case 'optional': return optional(build(def.parser))
|
|
329
329
|
case 'attempt': {
|
|
330
|
-
const base = attempt(build(def.parser))
|
|
330
|
+
const base = attempt(build(def.parser), def.contain === true ? { contain: true } : undefined)
|
|
331
331
|
const id = maps.attempts.get(parser)
|
|
332
332
|
return id === undefined ? base : {
|
|
333
333
|
...base,
|
|
@@ -380,7 +380,9 @@ function coverageEntry(entry: Combinator<unknown>, collector: GrammarCoverageCol
|
|
|
380
380
|
|
|
381
381
|
export function runWithGrammarCoverage(entry: Runnable, input: string, options: RunOptions & { collector?: GrammarCoverageCollector; trace?: GrammarTraceSink } = {}): { result: RunResult; coverage: GrammarCoverageSnapshot } {
|
|
382
382
|
if (typeof entry === 'function') throw new TypeError('runWithGrammarCoverage currently requires an interpreter combinator entry')
|
|
383
|
-
|
|
383
|
+
// A runtime composition's rule is planned through its winner map, so its IDs are
|
|
384
|
+
// the ones `composedGrammarCoverageDefinitions` reports.
|
|
385
|
+
const plan = buildGrammarPlan(entry, linkedWinnersOf(entry))
|
|
384
386
|
const definitions = plan.definitions
|
|
385
387
|
const collector = options.collector ?? createGrammarCoverageCollector(definitions)
|
|
386
388
|
const { collector: _collector, trace, ...runOptions } = options
|
package/src/cst/reflection.ts
CHANGED
|
@@ -115,6 +115,11 @@ export function collectGrammarReflection(
|
|
|
115
115
|
): GrammarReflection {
|
|
116
116
|
const byType = new Map<string, string[]>()
|
|
117
117
|
const followLazy = opts?.followLazy !== false
|
|
118
|
+
// ONE walk over the whole map. A node already visited from an earlier rule had
|
|
119
|
+
// its whole subgraph visited then, and `addNode` is idempotent, so a fresh set
|
|
120
|
+
// per rule only re-walked shared structure — O(rules × graph), and every unbound
|
|
121
|
+
// cross-piece hole threw (a stack-capturing Error) once per rule reaching it.
|
|
122
|
+
const seen = new Set<Combinator<unknown>>()
|
|
118
123
|
for (const [, rule] of ruleMap) {
|
|
119
124
|
if (!followLazy && rule._def.tag === 'lazy') {
|
|
120
125
|
try {
|
|
@@ -123,7 +128,7 @@ export function collectGrammarReflection(
|
|
|
123
128
|
} catch {}
|
|
124
129
|
continue
|
|
125
130
|
}
|
|
126
|
-
collectFromCombinator(rule, byType,
|
|
131
|
+
collectFromCombinator(rule, byType, seen, followLazy)
|
|
127
132
|
}
|
|
128
133
|
return { nodes: [...byType].map(([type, tags]) => ({ type, tags })) }
|
|
129
134
|
}
|
package/src/cst/trivia-kinds.ts
CHANGED
|
@@ -52,6 +52,12 @@ export function peelLabel(p: Combinator<unknown>): { label: string; parser: Comb
|
|
|
52
52
|
* (or a single labeled arm), return the label table and matchers.
|
|
53
53
|
*/
|
|
54
54
|
export function analyzeLabeledTrivia(trivia: Combinator<unknown>): LabeledTriviaSpec | null {
|
|
55
|
+
// A `trivia()` combinator carries its own spec, built once at construction —
|
|
56
|
+
// the hot path. The four slots below serve every other caller.
|
|
57
|
+
if (trivia._def.tag === 'trivia') {
|
|
58
|
+
const own = trivia._meta.labeledTriviaSpec
|
|
59
|
+
if (own !== undefined) return own
|
|
60
|
+
}
|
|
55
61
|
if (trivia === labeledTrivia0) return labeledSpec0!
|
|
56
62
|
if (trivia === labeledTrivia1) return labeledSpec1!
|
|
57
63
|
if (trivia === labeledTrivia2) return labeledSpec2!
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { choice } from './combinators/choice.ts'
|
|
|
19
19
|
export { dispatch, endsWith, makeWhen, matches, otherwise, routed, startsWith, when } from './combinators/dispatch.ts'
|
|
20
20
|
export type { DispatchArm, DispatchOtherwise, DispatchStringMatcher, DispatchWhen, DispatchWhenFactory, DispatchWhenOptions, DispatchWhenMatcher } from './combinators/dispatch.ts'
|
|
21
21
|
export { attempt } from './combinators/attempt.ts'
|
|
22
|
+
export type { AttemptOptions } from './combinators/attempt.ts'
|
|
22
23
|
export { many, oneOrMore, optional, sepBy, oneOrMoreSep, keepSeparator } from './combinators/repeat.ts'
|
|
23
24
|
export type { RepeatOptions, SepByOptions, TrailingSeparator, KeptSeparator } from './combinators/repeat.ts'
|
|
24
25
|
export { rules } from './combinators/parser.ts'
|
package/src/plugin/evaluator.ts
CHANGED
|
@@ -154,6 +154,69 @@ export type ScopeEntry = {
|
|
|
154
154
|
}
|
|
155
155
|
export type Scope = Map<string, ScopeEntry>
|
|
156
156
|
|
|
157
|
+
/** How a {@link ModuleScope} answers a name it does not hold: a value, the reason a
|
|
158
|
+
* binding the module DOES have cannot be known at build time, or `null` — not a
|
|
159
|
+
* binding this scope can speak for (an ordinary miss). */
|
|
160
|
+
export type FreeNameResolution = { value: unknown } | { unresolved: string } | null
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A module's scope whose IMPORTED (and, for a foreign module, top-level) bindings
|
|
164
|
+
* resolve on first read instead of being absent.
|
|
165
|
+
*
|
|
166
|
+
* Absent was the defect. `anyValue` answered an imported name with `null`, so
|
|
167
|
+
* `rules({ scanSkip: [importedUnit] })` evaluated to nothing, the option quietly
|
|
168
|
+
* became `undefined`, and the build succeeded with no ambient skip — and a terminal
|
|
169
|
+
* imported into a rule body could not be lowered at all. Lazy, because a grammar
|
|
170
|
+
* module imports far more than combinators (AST classes, helpers) and resolving each
|
|
171
|
+
* one eagerly would read every one of those modules for nothing.
|
|
172
|
+
*
|
|
173
|
+
* A binding that exists but cannot be resolved is RECORDED in `unresolved` (shared by
|
|
174
|
+
* every scope of one transform) as "`name` <reason>", so the plugin can fail the build
|
|
175
|
+
* naming it rather than warn about an anonymous "isn't statically evaluable". Only a
|
|
176
|
+
* VALUE read records: `has()` is how the evaluator probes a name it may legitimately
|
|
177
|
+
* leave to runtime (a named reducer), so a probe that misses is not a failure.
|
|
178
|
+
*/
|
|
179
|
+
export class ModuleScope extends Map<string, unknown> {
|
|
180
|
+
private readonly tried = new Map<string, string | undefined>()
|
|
181
|
+
private readonly resolveFree: (name: string) => FreeNameResolution
|
|
182
|
+
readonly unresolved: Set<string>
|
|
183
|
+
constructor(resolveFree: (name: string) => FreeNameResolution, unresolved: Set<string>) {
|
|
184
|
+
super()
|
|
185
|
+
this.resolveFree = resolveFree
|
|
186
|
+
this.unresolved = unresolved
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
override has(name: string): boolean {
|
|
190
|
+
return super.has(name) || this.resolve(name)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
override get(name: string): unknown {
|
|
194
|
+
if (super.has(name) || this.resolve(name)) return super.get(name)
|
|
195
|
+
const why = this.tried.get(name)
|
|
196
|
+
if (why !== undefined) this.unresolved.add(`\`${name}\` ${why}`)
|
|
197
|
+
return undefined
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A child scope (a factory body) that keeps resolving this module's free names. */
|
|
201
|
+
fork(): ModuleScope {
|
|
202
|
+
const child = new ModuleScope(this.resolveFree, this.unresolved)
|
|
203
|
+
for (const [k, v] of super.entries()) child.set(k, v)
|
|
204
|
+
return child
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private resolve(name: string): boolean {
|
|
208
|
+
if (this.tried.has(name)) return false
|
|
209
|
+
const r = this.resolveFree(name)
|
|
210
|
+
if (r !== null && 'value' in r) {
|
|
211
|
+
super.set(name, r.value)
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
// A name nothing binds YET is not remembered: a later declaration may bind it.
|
|
215
|
+
if (r !== null) this.tried.set(name, r.unresolved)
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
157
220
|
// Internal XScope also holds non-Combinator values (g proxy objects etc.)
|
|
158
221
|
type XScopeVal = ScopeEntry | unknown
|
|
159
222
|
type XScope = Map<string, XScopeVal>
|
|
@@ -243,7 +306,7 @@ const SUPPORTED: Record<string, (...args: unknown[]) => Combinator<unknown>> = {
|
|
|
243
306
|
word: (...a) => parseman.word(a[0] as string, a[1] as string | undefined, a[2] as Omit<parseman.KeywordsOptions, 'boundary'> | undefined),
|
|
244
307
|
sequence: (...a) => (parseman.sequence as (...p: Combinator<unknown>[]) => Combinator<unknown[]>)(...(a as Combinator<unknown>[])),
|
|
245
308
|
choice: (...a) => (parseman.choice as (...p: Combinator<unknown>[]) => Combinator<unknown>)(...(a as Combinator<unknown>[])),
|
|
246
|
-
attempt: (...a) => parseman.attempt(a[0] as Combinator<unknown
|
|
309
|
+
attempt: (...a) => parseman.attempt(a[0] as Combinator<unknown>, a[1] as parseman.AttemptOptions | undefined),
|
|
247
310
|
optional: (...a) => parseman.optional(a[0] as Combinator<unknown>),
|
|
248
311
|
trivia: (...a) => parseman.trivia(a[0] as Combinator<unknown>),
|
|
249
312
|
classifiedTrivia: (...a) =>
|
|
@@ -556,6 +619,20 @@ function staticNodeOptions(expr: Expression, scope: XScope): StaticNodeOptions {
|
|
|
556
619
|
return opts.unwrap || opts.collapse || opts.project !== undefined || opts.captureTrivia || opts.trailingTrivia || opts.tags !== undefined || opts.buildArity !== undefined ? opts : undefined
|
|
557
620
|
}
|
|
558
621
|
|
|
622
|
+
/**
|
|
623
|
+
* `scanTo`/`balanced` options: `undefined` when absent, `UNKNOWN_OPTIONS` when present
|
|
624
|
+
* but not statically known. Passing the unknown value on as `null` is what built a
|
|
625
|
+
* region with NO skip units out of `balanced('(', ')', importedOpts)` — the options
|
|
626
|
+
* silently vanished.
|
|
627
|
+
*/
|
|
628
|
+
const UNKNOWN_OPTIONS = Symbol('parseman.unknownOptions')
|
|
629
|
+
function scanOptions(optsArg: Expression | { type: 'SpreadElement' } | undefined, scope: XScope, code?: string): unknown {
|
|
630
|
+
if (optsArg === undefined) return undefined
|
|
631
|
+
if (optsArg.type === 'SpreadElement') return UNKNOWN_OPTIONS
|
|
632
|
+
const opts = anyValue(optsArg as Expression, scope, code, [])
|
|
633
|
+
return isHole(optsArg as Expression, opts, scope) ? UNKNOWN_OPTIONS : opts
|
|
634
|
+
}
|
|
635
|
+
|
|
559
636
|
/**
|
|
560
637
|
* Evaluate a call expression to a Combinator.
|
|
561
638
|
* `mfs` accumulates mapFn source texts in depth-first order — must match
|
|
@@ -893,9 +970,8 @@ function exprToCombi(node: Expression, scope: XScope, code?: string, mfs?: strin
|
|
|
893
970
|
const open = anyValue(openArg as Expression, scope, code, [])
|
|
894
971
|
const close = anyValue(closeArg as Expression, scope, code, [])
|
|
895
972
|
if (typeof open !== 'string' || typeof close !== 'string') return null
|
|
896
|
-
const opts = optsArg
|
|
897
|
-
|
|
898
|
-
: undefined
|
|
973
|
+
const opts = scanOptions(optsArg, scope, code)
|
|
974
|
+
if (opts === UNKNOWN_OPTIONS) return null
|
|
899
975
|
try { return parseman.balanced(open, close, opts as parseman.ScanToOptions | undefined) } catch { return null }
|
|
900
976
|
}
|
|
901
977
|
|
|
@@ -906,9 +982,8 @@ function exprToCombi(node: Expression, scope: XScope, code?: string, mfs?: strin
|
|
|
906
982
|
if (!sentinelArg || sentinelArg.type === 'SpreadElement') return null
|
|
907
983
|
const sentinel = anyValue(sentinelArg as Expression, scope, code, [])
|
|
908
984
|
if (!isCombinator(sentinel)) return null
|
|
909
|
-
const opts = optsArg
|
|
910
|
-
|
|
911
|
-
: undefined
|
|
985
|
+
const opts = scanOptions(optsArg, scope, code)
|
|
986
|
+
if (opts === UNKNOWN_OPTIONS) return null
|
|
912
987
|
try { return parseman.scanTo(sentinel, opts as parseman.ScanToOptions | undefined) } catch { return null }
|
|
913
988
|
}
|
|
914
989
|
|
|
@@ -1039,6 +1114,22 @@ function exprToCombi(node: Expression, scope: XScope, code?: string, mfs?: strin
|
|
|
1039
1114
|
} catch { return null }
|
|
1040
1115
|
}
|
|
1041
1116
|
|
|
1117
|
+
/**
|
|
1118
|
+
* An element that evaluated to `null` without BEING `null` — an unresolved name, a
|
|
1119
|
+
* call the evaluator could not build. Kept as a `null` slot, it made the container
|
|
1120
|
+
* look evaluated: `{ skip: [unknownUnit] }` became `{ skip: [null] }`, and an options
|
|
1121
|
+
* object went missing piece by piece with no warning. So a hole fails the whole
|
|
1122
|
+
* container. A function literal is exempt: it evaluates to `null` by design, its
|
|
1123
|
+
* source being captured by the caller that needs it.
|
|
1124
|
+
*/
|
|
1125
|
+
function isHole(node: Expression, value: unknown, scope: XScope): boolean {
|
|
1126
|
+
if (value !== null) return false
|
|
1127
|
+
const n = unwrapStaticExpr(node) as { type: string; value?: unknown; name?: string }
|
|
1128
|
+
if (n.type === 'Identifier') return !scope.has(n.name!) // a bound nullish const is a value
|
|
1129
|
+
return !(n.type === 'Literal' && n.value === null)
|
|
1130
|
+
&& n.type !== 'ArrowFunctionExpression' && n.type !== 'FunctionExpression'
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1042
1133
|
/** Evaluate any expression to its JS value (not necessarily a Combinator). */
|
|
1043
1134
|
function anyValue(node: Expression, scope: XScope, code?: string, mfs?: string[]): unknown {
|
|
1044
1135
|
if (node.type === 'TSAsExpression'
|
|
@@ -1064,7 +1155,9 @@ function anyValue(node: Expression, scope: XScope, code?: string, mfs?: string[]
|
|
|
1064
1155
|
for (const el of arr.elements) {
|
|
1065
1156
|
if (el === null) { out.push(null); continue }
|
|
1066
1157
|
if ((el as { type: string }).type === 'SpreadElement') return null
|
|
1067
|
-
|
|
1158
|
+
const v = anyValue(el as Expression, scope, code, mfs)
|
|
1159
|
+
if (isHole(el as Expression, v, scope)) return null
|
|
1160
|
+
out.push(v)
|
|
1068
1161
|
}
|
|
1069
1162
|
return out
|
|
1070
1163
|
}
|
|
@@ -1074,7 +1167,10 @@ function anyValue(node: Expression, scope: XScope, code?: string, mfs?: string[]
|
|
|
1074
1167
|
for (const prop of node.properties) {
|
|
1075
1168
|
const key = propName(prop as never)
|
|
1076
1169
|
if (key === null) return null
|
|
1077
|
-
|
|
1170
|
+
const valueNode = (prop as unknown as ObjectProperty).value as Expression
|
|
1171
|
+
const v = anyValue(valueNode, scope, code, mfs)
|
|
1172
|
+
if (isHole(valueNode, v, scope)) return null
|
|
1173
|
+
obj[key] = v
|
|
1078
1174
|
}
|
|
1079
1175
|
return obj
|
|
1080
1176
|
}
|
|
@@ -1184,15 +1280,16 @@ export function evaluateExpr(
|
|
|
1184
1280
|
* Evaluate a `const X = [combinator, …]` array literal into an array of
|
|
1185
1281
|
* Combinators. Lets a shared option array (e.g. a `skip` set reused across
|
|
1186
1282
|
* `scanTo`/`balanced` calls) be referenced by name — `{ skip: X }` — instead of
|
|
1187
|
-
* inlining the array at every call site. Returns null when `node`
|
|
1188
|
-
*
|
|
1283
|
+
* inlining the array at every call site. Returns null when `node` doesn't evaluate
|
|
1284
|
+
* to an array of statically-resolvable combinators.
|
|
1189
1285
|
*/
|
|
1190
1286
|
export function evaluateCombinatorArray(
|
|
1191
1287
|
node: Expression,
|
|
1192
1288
|
scope: Scope,
|
|
1193
1289
|
code?: string,
|
|
1194
1290
|
): Combinator<unknown>[] | null {
|
|
1195
|
-
|
|
1291
|
+
// Any expression, not just an array literal: `rules({ scanSkip: sharedSkip })`
|
|
1292
|
+
// names the array, and requiring a literal here is what dropped it.
|
|
1196
1293
|
const val = anyValue(node, scope as XScope, code, [])
|
|
1197
1294
|
if (!Array.isArray(val) || val.length === 0) return null
|
|
1198
1295
|
if (!val.every(isCombinator)) return null
|
|
@@ -1453,7 +1550,7 @@ export function evaluateParserFactory(
|
|
|
1453
1550
|
built = rules(options ?? {}, (g: Record<string, Combinator<unknown>>) => {
|
|
1454
1551
|
// Outer ScopeEntry values carry their mfSrcs and are replayed by scopeGet()
|
|
1455
1552
|
// when body statements or return expressions reference them.
|
|
1456
|
-
const localScope: XScope = new Map(scope as XScope)
|
|
1553
|
+
const localScope: XScope = scope instanceof ModuleScope ? scope.fork() : new Map(scope as XScope)
|
|
1457
1554
|
localScope.set(proxyName, g)
|
|
1458
1555
|
|
|
1459
1556
|
// ── Phase 1: the factory's own body statements ────────────────────────
|
|
@@ -1564,7 +1661,10 @@ export function applyDefineStatement(
|
|
|
1564
1661
|
/** Check if an AST node references any name from the given scope or names set. */
|
|
1565
1662
|
export function referencesAny(node: Node, names: Set<string>, scope: Scope): boolean {
|
|
1566
1663
|
if (node.type === 'Identifier') {
|
|
1567
|
-
|
|
1664
|
+
// The names the module has BOUND so far, not every import it could resolve: this
|
|
1665
|
+
// decides whether a declaration is macro code at all, and an ordinary
|
|
1666
|
+
// `const x = importedThing` must not start reading modules to find out.
|
|
1667
|
+
return names.has(node.name) || Map.prototype.has.call(scope, node.name)
|
|
1568
1668
|
}
|
|
1569
1669
|
for (const key of Object.keys(node) as (keyof typeof node)[]) {
|
|
1570
1670
|
const child = node[key]
|