parseman 0.52.0 → 0.54.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 +636 -126
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +6191 -6274
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/field-trivia.d.ts +40 -0
- package/dist/combinators/field-trivia.d.ts.map +1 -0
- package/dist/combinators/map.d.ts +19 -2
- package/dist/combinators/map.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/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/sequence.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/fields.d.ts +2 -0
- package/dist/compiler/fields.d.ts.map +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/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-charscan.d.ts +11 -3
- package/dist/cst/trivia-charscan.d.ts.map +1 -1
- package/dist/cst/trivia-kinds.d.ts +11 -0
- 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 +764 -888
- 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 +1443 -729
- 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 -6109
- 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 +9 -0
- 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 +1 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts +6 -2
- package/dist/table/emit.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/field-spec.d.ts +13 -0
- package/dist/table/field-spec.d.ts.map +1 -0
- package/dist/table/index.js +480 -199
- package/dist/table/index.js.map +2 -2
- package/dist/table/scalar-terminal.d.ts +11 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/analysis/duplication.ts +1 -1
- package/src/analysis/rebuild.ts +1 -1
- package/src/combinators/field-trivia.ts +44 -0
- package/src/combinators/map.ts +48 -6
- package/src/combinators/parser.ts +133 -67
- package/src/combinators/ref.ts +10 -1
- package/src/combinators/repeat.ts +6 -1
- package/src/combinators/sequence.ts +9 -1
- package/src/compiler/borrowed-rules.ts +88 -0
- package/src/compiler/compile-linkable-table.ts +1 -1
- package/src/compiler/fields.ts +5 -2
- package/src/compiler/grammar-coverage-ids.ts +35 -6
- package/src/compiler/ir-serialize.ts +19 -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-charscan.ts +17 -56
- package/src/cst/trivia-kinds.ts +24 -8
- 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 +126 -22
- package/src/table/compile-rule-map.ts +18 -8
- package/src/table/compile.ts +61 -0
- package/src/table/emit-assembly.ts +38 -18
- package/src/table/emit.ts +15 -4
- package/src/table/encode.ts +18 -2
- package/src/table/exec.ts +15 -1
- package/src/table/field-spec.ts +17 -0
- package/src/table/scalar-terminal.ts +25 -0
- package/src/types.ts +12 -2
- package/src/version.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Combinator } from '../types.ts'
|
|
2
2
|
import { hasDirectBuildDef } from '../analysis/commitment.ts'
|
|
3
3
|
import type { HostMode } from '../cst/host-mode.ts'
|
|
4
|
-
import { collectGrammarReflection, type GrammarReflection } from '../cst/reflection.ts'
|
|
4
|
+
import { collectGrammarReflection, GRAMMAR_REFLECTION, type GrammarReflection } from '../cst/reflection.ts'
|
|
5
5
|
import { encodeTableProgram, type TableSettings } from './encode.ts'
|
|
6
6
|
import { defaultAssemblyCfgs, emitTableExpression } from './emit.ts'
|
|
7
7
|
import { tableRules } from './assemble.ts'
|
|
@@ -54,6 +54,12 @@ const DEFAULT_PRECOMPILE_MIN_WORDS = 1_024
|
|
|
54
54
|
export type TableRuleMapOptions = {
|
|
55
55
|
/** Grammar-level ambient trivia, as `rules({ trivia }, …)` declares it. */
|
|
56
56
|
readonly trivia?: Combinator<unknown>
|
|
57
|
+
/**
|
|
58
|
+
* `compileRuleMapRunnable` only: specialise the runnable table once with generated
|
|
59
|
+
* code (falling back to closures under CSP). `compile()` alone passes it — no other
|
|
60
|
+
* runtime path may construct code.
|
|
61
|
+
*/
|
|
62
|
+
readonly specialise?: boolean
|
|
57
63
|
/** Grammar-level ambient scan-skip, as `rules({ scanSkip }, …)` declares it. */
|
|
58
64
|
readonly scanSkip?: Combinator<unknown>[]
|
|
59
65
|
readonly recovery?: boolean
|
|
@@ -120,10 +126,11 @@ export type CompiledRuleMapTable = {
|
|
|
120
126
|
* hands back text and nothing else, because its artifact only exists once the
|
|
121
127
|
* emitted source is evaluated. A table exists as data before it is printed, so
|
|
122
128
|
* the same call that produces the replacement can also hand back the parser.
|
|
123
|
-
* It
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
129
|
+
* It runs the CLOSURE artifact — the engine printed output runs — so building
|
|
130
|
+
* it never constructs code: only `compile()` specialises (`specialise`), per
|
|
131
|
+
* docs/design/runtime-and-size-contract.md, rule 1. That is what makes a
|
|
132
|
+
* differential against the interpreter possible without `eval`, and it is why the
|
|
133
|
+
* table-vs-interpreter test can compare `expected` sets rather than only accept/reject.
|
|
127
134
|
*/
|
|
128
135
|
rules: Record<string, TableRule>
|
|
129
136
|
/** The closure-stamped wire program, for a caller that wants to fold or inspect it. */
|
|
@@ -232,13 +239,16 @@ export function compileRuleMapRunnable(
|
|
|
232
239
|
if (encoded === null) return null
|
|
233
240
|
const { prog, hostMode, plan } = encoded
|
|
234
241
|
const artifact = closureArtifact(prog)
|
|
242
|
+
const reflection = collectGrammarReflection(ruleMap)
|
|
235
243
|
return {
|
|
236
244
|
keys: ruleMap.map(([key]) => key),
|
|
237
245
|
hostMode,
|
|
238
246
|
hostBranchElided: hostMode === 'ast' && ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
239
|
-
reflection
|
|
247
|
+
reflection,
|
|
240
248
|
...(plan === undefined ? {} : { coverageDefinitions: plan.definitions }),
|
|
241
|
-
|
|
249
|
+
// Reflection rides on the map's metadata prototype from birth. Only `compile()`
|
|
250
|
+
// asks to specialise; every other caller runs the closure artifact.
|
|
251
|
+
rules: tableRules(opts.specialise === true ? prog : artifact, { [GRAMMAR_REFLECTION]: reflection }),
|
|
242
252
|
prog: artifact,
|
|
243
253
|
}
|
|
244
254
|
}
|
|
@@ -349,7 +359,7 @@ export function compileRuleMap(
|
|
|
349
359
|
hostBranchElided: hostMode === 'ast' && ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
350
360
|
reflection: collectGrammarReflection(ruleMap),
|
|
351
361
|
...(plan === undefined ? {} : { coverageDefinitions: plan.definitions }),
|
|
352
|
-
rules: tableRules(
|
|
362
|
+
rules: tableRules(artifact),
|
|
353
363
|
prog: artifact,
|
|
354
364
|
}
|
|
355
365
|
}
|
package/src/table/compile.ts
CHANGED
|
@@ -8,6 +8,9 @@ import { closureArtifact } from './program.ts'
|
|
|
8
8
|
import { buildGrammarPlan } from '../compiler/grammar-coverage-ids.ts'
|
|
9
9
|
import { runDuplicationDiagnostic, type DuplicationOption } from './duplication-hook.ts'
|
|
10
10
|
import { beginCompileDegradationDrain } from '../compiler/degradation.ts'
|
|
11
|
+
import { compileRuleMapRunnable } from './compile-rule-map.ts'
|
|
12
|
+
import { borrowedRuleMessage, borrowedRuleReference } from '../compiler/borrowed-rules.ts'
|
|
13
|
+
import type { TableRule } from './program.ts'
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* `compile()` FOR THE TABLE LOWERING — same contract, different artifact.
|
|
@@ -110,7 +113,65 @@ export type TableCompileOptions = {
|
|
|
110
113
|
readonly fnSources?: readonly string[]
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Compile a whole RULE MAP — a `rules()` grammar or a runtime `compose()` result —
|
|
118
|
+
* into a map of runnable rules that `run()` / `parseDoc()` take like any other.
|
|
119
|
+
*
|
|
120
|
+
* This is how a caller opts a runtime composition into speed: `compose()` only
|
|
121
|
+
* links an interpreter grammar, and `compile()` encodes it to one table and
|
|
122
|
+
* specialises that table once. Where the environment forbids generated code (a
|
|
123
|
+
* Content-Security-Policy without `'unsafe-eval'`) the specialisation throws
|
|
124
|
+
* `EvalError` and the same table runs on the closure assembler instead.
|
|
125
|
+
*/
|
|
126
|
+
export function compile<T>(combinator: Combinator<T>, mapFnSources?: readonly string[], opts?: TableCompileOptions): CompiledParser<T>
|
|
127
|
+
export function compile(grammar: Record<string, unknown> & { readonly _def?: never }, opts?: { readonly hostMode?: HostMode }): Record<string, TableRule>
|
|
113
128
|
export function compile<T>(
|
|
129
|
+
target: Combinator<T> | Record<string, unknown>,
|
|
130
|
+
second?: readonly string[] | { readonly hostMode?: HostMode },
|
|
131
|
+
third?: TableCompileOptions,
|
|
132
|
+
): CompiledParser<T> | Record<string, TableRule> {
|
|
133
|
+
if (!isCombinator(target)) return compileMap(target, second as { readonly hostMode?: HostMode } | undefined)
|
|
134
|
+
return compileRoot(target as Combinator<T>, second as readonly string[] | undefined, third)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isCombinator(x: unknown): x is Combinator<unknown> {
|
|
138
|
+
return typeof x === 'object' && x !== null
|
|
139
|
+
&& typeof (x as { parse?: unknown }).parse === 'function' && '_def' in x
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function compileMap(grammar: Record<string, unknown>, opts: { readonly hostMode?: HostMode } = {}): Record<string, TableRule> {
|
|
143
|
+
const entries: Array<[string, Combinator<unknown>]> = []
|
|
144
|
+
for (const [key, value] of Object.entries(grammar)) {
|
|
145
|
+
if (!isCombinator(value)) throw new TypeError(`compile: grammar entry "${key}" is not a parser`)
|
|
146
|
+
// A referenced-but-undefined `g.X` is a hole, not a rule: it has nothing to encode.
|
|
147
|
+
if (value._def.tag === 'lazy') {
|
|
148
|
+
try { value._def.thunk() } catch { continue }
|
|
149
|
+
}
|
|
150
|
+
entries.push([key, value])
|
|
151
|
+
}
|
|
152
|
+
// A runtime composition was already checked when it linked; a plain map is checked
|
|
153
|
+
// here, because this encoder binds references by name.
|
|
154
|
+
const borrowed = borrowedRuleReference(Object.fromEntries(entries))
|
|
155
|
+
if (borrowed !== undefined) throw new Error(borrowedRuleMessage('compile', borrowed))
|
|
156
|
+
// One aggregated degradation block per compile, exactly as `compileRoot` drains it.
|
|
157
|
+
const drain = beginCompileDegradationDrain()
|
|
158
|
+
let done = false
|
|
159
|
+
try {
|
|
160
|
+
const refusals: string[] = []
|
|
161
|
+
const compiled = compileRuleMapRunnable(entries, {
|
|
162
|
+
...(opts.hostMode ? { hostMode: opts.hostMode } : {}),
|
|
163
|
+
refusals,
|
|
164
|
+
specialise: true,
|
|
165
|
+
})
|
|
166
|
+
if (compiled === null) {
|
|
167
|
+
throw new Error(`compile: this grammar could not be encoded to a table${refusals.length ? ` — ${refusals.join('; ')}` : ''}`)
|
|
168
|
+
}
|
|
169
|
+
done = true
|
|
170
|
+
return compiled.rules
|
|
171
|
+
} finally { drain(done) }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function compileRoot<T>(
|
|
114
175
|
combinator: Combinator<T>,
|
|
115
176
|
mapFnSources?: readonly string[],
|
|
116
177
|
opts: TableCompileOptions = {},
|
|
@@ -63,10 +63,12 @@ import {
|
|
|
63
63
|
computeSiteLabels, reachableSites, type SiteLabel,
|
|
64
64
|
} from './site-labels.ts'
|
|
65
65
|
import {
|
|
66
|
-
|
|
66
|
+
largeChoiceLiteralPretest, leadingScalarSequence, leadingScalarTerminal,
|
|
67
67
|
scalarTerminalNodeChild, scalarTerminalNotChild, type LeadingScalarSequence,
|
|
68
68
|
} from './scalar-terminal.ts'
|
|
69
69
|
import { childSlots } from './child-slots.ts'
|
|
70
|
+
import { fieldSpec, leadingTriviaFieldAt } from './field-spec.ts'
|
|
71
|
+
import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA } from '../combinators/field-trivia.ts'
|
|
70
72
|
|
|
71
73
|
/** What the compiled factory hands back — the emitted twin of `Assembly`. */
|
|
72
74
|
export type EmittedPiece = (input: string, pos: number, ctx: ParseContext) => unknown
|
|
@@ -122,6 +124,9 @@ export const EMITTED_PARAMS = [
|
|
|
122
124
|
'adjacencyHolds',
|
|
123
125
|
// Appended for selected composite lexical programs; old factories ignore it.
|
|
124
126
|
'LEXPROG',
|
|
127
|
+
// Appended for trivia-attributing fields (`field(…, { leadingTrivia })`);
|
|
128
|
+
// old factories ignore them.
|
|
129
|
+
'LEAD', 'triviaFieldCapture',
|
|
125
130
|
] as const
|
|
126
131
|
|
|
127
132
|
/**
|
|
@@ -607,20 +612,12 @@ export function emitAssemblySource(
|
|
|
607
612
|
if (child >= 0) scalarSpecs.add(code[child + 1]!)
|
|
608
613
|
continue
|
|
609
614
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
continue
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
const family = leadingLiteralFamily(code, k, armIp)
|
|
618
|
-
if (family !== undefined && family.length >= 2 && family.every(terminal => {
|
|
619
|
-
const value = k[code[terminal + 1]!]
|
|
620
|
-
return typeof value === 'string' && value.length >= 2
|
|
621
|
-
})) {
|
|
622
|
-
largeChoiceLiteralFamilies.set(armIp, family)
|
|
615
|
+
const literals = largeChoiceLiteralPretest(code, k, armIp)
|
|
616
|
+
if (literals !== undefined && literals.length === 1 && literals[0] === child) {
|
|
617
|
+
largeChoiceScalarSpecs.add(code[child + 1]!)
|
|
618
|
+
continue
|
|
623
619
|
}
|
|
620
|
+
if (literals !== undefined) largeChoiceLiteralFamilies.set(armIp, literals)
|
|
624
621
|
const sequence = leadingScalarSequence(code, armIp)
|
|
625
622
|
// A labelled trivia scanner is still recognition authority here. This
|
|
626
623
|
// pretest is pure: on a miss the ordinary arm's trivia log would have
|
|
@@ -1307,17 +1304,34 @@ return v
|
|
|
1307
1304
|
}
|
|
1308
1305
|
|
|
1309
1306
|
case OP_FIELD: {
|
|
1310
|
-
const
|
|
1307
|
+
const spec = fieldSpec(k[code[ip + 1]!])
|
|
1308
|
+
const name = q(spec.name)
|
|
1311
1309
|
const child = link(code[ip + 2]!)
|
|
1312
1310
|
// `ctx._fields?.push(…)` — conditional on a field-reading node being
|
|
1313
1311
|
// open, exactly as `map.ts` has it, so a field outside one costs a load
|
|
1314
1312
|
// and a branch. The ARRAY is what `OP_NODE` hands to `buildFieldMap`.
|
|
1315
|
-
|
|
1313
|
+
if (spec.bits === 0) {
|
|
1314
|
+
return `${head}
|
|
1316
1315
|
const v=${child}(input,pos,ctx)
|
|
1317
1316
|
if(v===FAIL)return FAIL
|
|
1318
1317
|
const f=ctx._fields
|
|
1319
1318
|
if(f!==undefined)f.push({name:${name},value:v,span:{start:pos,end:EC.e}})
|
|
1320
1319
|
return v
|
|
1320
|
+
}`
|
|
1321
|
+
}
|
|
1322
|
+
const leading = (spec.bits & FIELD_LEADING_TRIVIA) !== 0
|
|
1323
|
+
const trailing = (spec.bits & FIELD_TRAILING_TRIVIA) !== 0
|
|
1324
|
+
return `${head}
|
|
1325
|
+
const lf=${leading ? 'LEAD.at' : '-1'}
|
|
1326
|
+
LEAD.at=-1
|
|
1327
|
+
const v=${child}(input,pos,ctx)
|
|
1328
|
+
if(v===FAIL)return FAIL
|
|
1329
|
+
const e=EC.e
|
|
1330
|
+
const te=${trailing ? 'ctx.trivia!==undefined?consumeTrivia(input,e,ctx):e' : 'e'}
|
|
1331
|
+
const f=ctx._fields
|
|
1332
|
+
if(f!==undefined)f.push(triviaFieldCapture(${name},v,input,pos,e,lf,te))
|
|
1333
|
+
EC.e=te
|
|
1334
|
+
return v
|
|
1321
1335
|
}`
|
|
1322
1336
|
}
|
|
1323
1337
|
|
|
@@ -1687,7 +1701,13 @@ return v
|
|
|
1687
1701
|
`if(!adjacencyHolds(input,cur,ctx,${negated ? 'true' : 'false'},${kinds})){ctx._fe=cur;ctx._fx=${expected};return FAIL}`,
|
|
1688
1702
|
`${vn}=null`,
|
|
1689
1703
|
)
|
|
1690
|
-
} else
|
|
1704
|
+
} else {
|
|
1705
|
+
// A leading-trivia field reads the cursor from before the term's scan.
|
|
1706
|
+
// The callee expression hands it over AFTER the scan statement, right
|
|
1707
|
+
// before the call, so a field inside the trivia grammar cannot take it.
|
|
1708
|
+
const callee = leadingTriviaFieldAt(code, k, childIp) ? `(LEAD.at=cur,${kids[i]!})` : kids[i]!
|
|
1709
|
+
parts.push(emitTerm(callee, vn, tmp(), L, skipFor(L)))
|
|
1710
|
+
}
|
|
1691
1711
|
names.push(vn)
|
|
1692
1712
|
}
|
|
1693
1713
|
parts.push('EC.e=cur')
|
|
@@ -2232,7 +2252,7 @@ ${sep !== undefined ? `if(count>0){
|
|
|
2232
2252
|
const lb=cstLeavesLen(ctx)
|
|
2233
2253
|
let sp=cur
|
|
2234
2254
|
${hasTrivia === 'false' ? '' : hasTrivia === 'true' ? `sp=${skip}(input,sp,ctx)\n` : `if(hasTrivia)sp=${skip}(input,sp,ctx)\n`}ctx._fc=false
|
|
2235
|
-
const sv=${sep}(input,sp,ctx)
|
|
2255
|
+
${sepIp >= 0 && leadingTriviaFieldAt(code, k, sepIp) ? 'LEAD.at=cur\n' : ''}const sv=${sep}(input,sp,ctx)
|
|
2236
2256
|
if(sv===FAIL){
|
|
2237
2257
|
${rb}
|
|
2238
2258
|
if(ctx._fc===true)return FAIL
|
package/src/table/emit.ts
CHANGED
|
@@ -352,12 +352,16 @@ export type ExpressionEmitOptions = EmitOptions & {
|
|
|
352
352
|
*
|
|
353
353
|
* `null` is what a `rules()` call site needs: its initialiser evaluates to the
|
|
354
354
|
* `{ name: fn, … }` object, exactly what `tableRules(…)` already returns, so
|
|
355
|
-
* the map form is the expression WITHOUT the
|
|
355
|
+
* the map form is the expression WITHOUT the entry argument — not a second
|
|
356
356
|
* emitter. `compileRuleMap`'s replacement has the same shape and the same
|
|
357
357
|
* splice point, which is what makes the two lowerings interchangeable there.
|
|
358
358
|
*/
|
|
359
359
|
readonly entry?: string | null
|
|
360
|
-
/**
|
|
360
|
+
/**
|
|
361
|
+
* Static metadata object passed to `tableRules` as its second argument. Only
|
|
362
|
+
* the rule-map form (`entry: null`) takes it; with an entry, that argument is
|
|
363
|
+
* the entry's name.
|
|
364
|
+
*/
|
|
361
365
|
readonly metadataSource?: string
|
|
362
366
|
}
|
|
363
367
|
|
|
@@ -379,11 +383,18 @@ export function emitTableExpression(prog: TableProgram, opts: ExpressionEmitOpti
|
|
|
379
383
|
const entry = opts.entry === undefined ? 'grammar' : opts.entry
|
|
380
384
|
const ref = opts.runtimeRef ?? 'tableRules'
|
|
381
385
|
const fns = opts.fnSources ?? prog.fns.map(() => '() => {}')
|
|
382
|
-
|
|
386
|
+
// The entry is an ARGUMENT, never a trailing `[entry]`: `@__PURE__` covers the
|
|
387
|
+
// call and not a property read after it, so `tableRules({…})["Entry"]` left an
|
|
388
|
+
// unreferenced terminal's whole table in every bundle.
|
|
389
|
+
const meta = opts.metadataSource
|
|
390
|
+
if (entry !== null && meta !== undefined) {
|
|
391
|
+
throw new Error('emitTableExpression: metadataSource is for the rule-map form (entry: null); a single entry carries none')
|
|
392
|
+
}
|
|
393
|
+
const tail = entry !== null ? `,${jsString(entry)}` : meta === undefined ? '' : `,${meta}`
|
|
383
394
|
return [
|
|
384
395
|
`/* @__PURE__ */ ${ref}({`,
|
|
385
396
|
...programFields(prog, fns, opts),
|
|
386
|
-
|
|
397
|
+
`}${tail})`,
|
|
387
398
|
].join('\n')
|
|
388
399
|
}
|
|
389
400
|
|
package/src/table/encode.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { deriveExpected } from '../combinators/expect.ts'
|
|
|
7
7
|
import { assertionFailureExpected, directTerminalFailureExpected } from '../combinators/expected.ts'
|
|
8
8
|
import { buildReadsRaw, buildReadsState, buildReadsTrivia } from '../compiler/build-arity.ts'
|
|
9
9
|
import { buildReadsFields, parserHasOwnFields } from '../compiler/fields.ts'
|
|
10
|
+
import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA } from '../combinators/field-trivia.ts'
|
|
10
11
|
import { asciiFoldKey, branchUsesRouted, parserUsesRouted } from '../combinators/dispatch.ts'
|
|
11
12
|
import {
|
|
12
13
|
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
@@ -555,6 +556,17 @@ class Encoder {
|
|
|
555
556
|
return i
|
|
556
557
|
}
|
|
557
558
|
|
|
559
|
+
private fieldSpecIndex = new Map<string, number>()
|
|
560
|
+
private fieldSpecSlot(name: string, bits: number): number {
|
|
561
|
+
const key = JSON.stringify([name, bits])
|
|
562
|
+
const hit = this.fieldSpecIndex.get(key)
|
|
563
|
+
if (hit !== undefined) return hit
|
|
564
|
+
const i = this.k.length
|
|
565
|
+
this.k.push([name, bits])
|
|
566
|
+
this.fieldSpecIndex.set(key, i)
|
|
567
|
+
return i
|
|
568
|
+
}
|
|
569
|
+
|
|
558
570
|
/**
|
|
559
571
|
* A `notAdjacent({ kinds })` filter, pooled by its CONTENTS.
|
|
560
572
|
*
|
|
@@ -1518,8 +1530,12 @@ class Encoder {
|
|
|
1518
1530
|
for (let i = 0; i < arms.length; i++) this.code[head + 6 + i] = arms[i]!
|
|
1519
1531
|
return head
|
|
1520
1532
|
}
|
|
1521
|
-
case 'field':
|
|
1522
|
-
|
|
1533
|
+
case 'field': {
|
|
1534
|
+
// A trivia-attributing field carries `[name, bits]` in the name slot
|
|
1535
|
+
// (`FIELD_LEADING_TRIVIA`/`FIELD_TRAILING_TRIVIA`); a plain one the name.
|
|
1536
|
+
const bits = (d.leadingTrivia === true ? FIELD_LEADING_TRIVIA : 0) | (d.trailingTrivia === true ? FIELD_TRAILING_TRIVIA : 0)
|
|
1537
|
+
return this.emit(OP_FIELD, bits === 0 ? this.constant(d.name) : this.fieldSpecSlot(d.name, bits), this.node(d.parser).ip)
|
|
1538
|
+
}
|
|
1523
1539
|
case 'lazy': {
|
|
1524
1540
|
// A named reference is not a hop: it resolves to the target's row.
|
|
1525
1541
|
// Emitting a trampoline here cost one dispatch per reference for nothing.
|
package/src/table/exec.ts
CHANGED
|
@@ -72,6 +72,8 @@ import { scalarTerminalNotChild } from './scalar-terminal.ts'
|
|
|
72
72
|
* a successful match to the other engine.
|
|
73
73
|
*/
|
|
74
74
|
import { FAIL, newEndCell, type EndCell } from './cell.ts'
|
|
75
|
+
import { fieldSpec, leadingTriviaFieldAt } from './field-spec.ts'
|
|
76
|
+
import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA, LEAD, triviaFieldCapture } from '../combinators/field-trivia.ts'
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
79
|
* DIAGNOSTIC ROW COUNTER — off unless `PM_TABLE_COUNT=1` at process start.
|
|
@@ -645,11 +647,21 @@ function makeDriver(
|
|
|
645
647
|
}
|
|
646
648
|
|
|
647
649
|
case OP_FIELD: {
|
|
650
|
+
const spec = fieldSpec(k[code[ip + 1]!])
|
|
651
|
+
const leadFrom = LEAD.at
|
|
652
|
+
if (spec.bits !== 0) LEAD.at = -1
|
|
648
653
|
const v = exec(code[ip + 2]!, input, pos, ctx)
|
|
649
654
|
if (v === FAIL) return FAIL
|
|
650
655
|
// Conditional on a live sink, exactly as src/combinators/map.ts has it:
|
|
651
656
|
// a `field()` outside any field-reading node costs nothing.
|
|
652
|
-
|
|
657
|
+
if (spec.bits === 0) {
|
|
658
|
+
ctx._fields?.push({ name: spec.name, value: v, span: { start: pos, end: EC.e } })
|
|
659
|
+
return v
|
|
660
|
+
}
|
|
661
|
+
const end = EC.e
|
|
662
|
+
const trailEnd = (spec.bits & FIELD_TRAILING_TRIVIA) !== 0 && ctx.trivia !== undefined ? consumeTrivia(input, end, ctx) : end
|
|
663
|
+
ctx._fields?.push(triviaFieldCapture(spec.name, v, input, pos, end, (spec.bits & FIELD_LEADING_TRIVIA) !== 0 ? leadFrom : -1, trailEnd))
|
|
664
|
+
EC.e = trailEnd
|
|
653
665
|
return v
|
|
654
666
|
}
|
|
655
667
|
|
|
@@ -885,6 +897,7 @@ function makeDriver(
|
|
|
885
897
|
const scanTl = need ? cstTlLen(ctx) : 0
|
|
886
898
|
const scanLog = need ? ctx._triviaLog?.length ?? 0 : 0
|
|
887
899
|
const scanRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
900
|
+
if (leadingTriviaFieldAt(code, k, child)) LEAD.at = cur
|
|
888
901
|
const v = exec(child, input, scanEnd, ctx)
|
|
889
902
|
if (v === FAIL) { if (REC) ctx._sync = inheritedSync; return FAIL }
|
|
890
903
|
if (EC.e > scanEnd) cur = EC.e
|
|
@@ -1256,6 +1269,7 @@ function makeDriver(
|
|
|
1256
1269
|
let sp = cur
|
|
1257
1270
|
if (hasTrivia) sp = skipTrivia(input, sp, ctx)
|
|
1258
1271
|
ctx._fc = false
|
|
1272
|
+
if (leadingTriviaFieldAt(code, k, sep)) LEAD.at = cur
|
|
1259
1273
|
const sv = exec(sep, input, sp, ctx)
|
|
1260
1274
|
if (sv === FAIL) {
|
|
1261
1275
|
if (needMark) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OP_FIELD } from './ops.ts'
|
|
2
|
+
import { FIELD_LEADING_TRIVIA } from '../combinators/field-trivia.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An `OP_FIELD` name slot: the name, or `[name, bits]` for a field that
|
|
6
|
+
* attributes trivia (`FIELD_LEADING_TRIVIA` / `FIELD_TRAILING_TRIVIA`). A
|
|
7
|
+
* program printed before those options existed carries only names, and reads
|
|
8
|
+
* as bits 0.
|
|
9
|
+
*/
|
|
10
|
+
export function fieldSpec(slot: unknown): { readonly name: string; readonly bits: number } {
|
|
11
|
+
return Array.isArray(slot) ? { name: slot[0] as string, bits: slot[1] as number } : { name: slot as string, bits: 0 }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Is the instruction at `ip` a `field(…, { leadingTrivia: true })`? */
|
|
15
|
+
export function leadingTriviaFieldAt(code: ArrayLike<number>, k: readonly unknown[], ip: number): boolean {
|
|
16
|
+
return code[ip] === OP_FIELD && (fieldSpec(k[code[ip + 1]!]).bits & FIELD_LEADING_TRIVIA) !== 0
|
|
17
|
+
}
|
|
@@ -261,3 +261,28 @@ export function leadingLiteralFamily(
|
|
|
261
261
|
|
|
262
262
|
return walk(ip, 0)
|
|
263
263
|
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The literal terminals an arm of a LARGE ordered choice (more than three arms)
|
|
267
|
+
* may be pretested on before it is entered, or `undefined` when it has none.
|
|
268
|
+
*
|
|
269
|
+
* A pretest runs again when the arm then matches, so a large choice only takes
|
|
270
|
+
* literals of two or more characters: one `startsWith` is the price of skipping an
|
|
271
|
+
* arm that first-char dispatch could not exclude (`@{` and `@@` beside `@name`).
|
|
272
|
+
* A one-character literal is exactly what the dispatch mask already decided.
|
|
273
|
+
* The emitted and the closure engines both select through this function.
|
|
274
|
+
*/
|
|
275
|
+
export function largeChoiceLiteralPretest(
|
|
276
|
+
code: ArrayLike<number>, constants: readonly unknown[], armIp: number,
|
|
277
|
+
): readonly number[] | undefined {
|
|
278
|
+
const child = leadingScalarTerminal(code, armIp, 2, true, true)
|
|
279
|
+
if (child >= 0) {
|
|
280
|
+
const spec = constants[code[child + 1]!]
|
|
281
|
+
if (code[child] === OP_LIT && typeof spec === 'string' && spec.length >= 2) return [child]
|
|
282
|
+
}
|
|
283
|
+
const family = leadingLiteralFamily(code, constants, armIp)
|
|
284
|
+
return family !== undefined && family.length >= 2 && family.every(terminal => {
|
|
285
|
+
const value = constants[code[terminal + 1]!]
|
|
286
|
+
return typeof value === 'string' && value.length >= 2
|
|
287
|
+
}) ? family : undefined
|
|
288
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -83,7 +83,7 @@ export type ParserDef =
|
|
|
83
83
|
| { tag: 'routed'; fallback?: Combinator<unknown> }
|
|
84
84
|
| { tag: 'leaf'; parser: Combinator<unknown>; fn: (v: unknown, span: { start: number; end: number }) => unknown; fnSrc?: string; cstValue?: 'source' }
|
|
85
85
|
| { tag: 'label'; label: string; parser: Combinator<unknown> }
|
|
86
|
-
| { tag: 'field'; name: string; parser: Combinator<unknown
|
|
86
|
+
| { tag: 'field'; name: string; parser: Combinator<unknown>; leadingTrivia?: true; trailingTrivia?: true }
|
|
87
87
|
| { tag: 'grammar'; parser: Combinator<unknown>; triviaParser: Combinator<unknown> | undefined; clearTrivia?: boolean; captureTrivia?: boolean; rootCapture?: 'opaque'; trackLines: boolean; constructionTrackLines?: 'on' | 'off' | 'inherit'; constructionCaptureTriviaKinds?: readonly string[] }
|
|
88
88
|
| { tag: 'lazy'; thunk: () => Combinator<unknown> }
|
|
89
89
|
| { tag: 'not'; parser: Combinator<unknown> }
|
|
@@ -203,6 +203,16 @@ export type BuildHost = ((
|
|
|
203
203
|
export type FieldCapture<T = unknown> = {
|
|
204
204
|
value: T
|
|
205
205
|
span: Span
|
|
206
|
+
/**
|
|
207
|
+
* `field(…, { leadingTrivia: true })` only: the source text of the trivia the
|
|
208
|
+
* enclosing sequence or separated list skipped right before this field, or `''`.
|
|
209
|
+
*/
|
|
210
|
+
leading?: string
|
|
211
|
+
/**
|
|
212
|
+
* `field(…, { trailingTrivia: true })` only: the source text of the trivia the
|
|
213
|
+
* field consumed after its match, or `''`.
|
|
214
|
+
*/
|
|
215
|
+
trailing?: string
|
|
206
216
|
}
|
|
207
217
|
|
|
208
218
|
export type FieldMap = Record<string, FieldCapture | FieldCapture[]>
|
|
@@ -382,7 +392,7 @@ export type ParseContext = {
|
|
|
382
392
|
*/
|
|
383
393
|
_cstTriviaLog?: number[] | undefined
|
|
384
394
|
/** Framework-internal: active node() field captures, enabled only when needed. */
|
|
385
|
-
_fields?: Array<{ name: string; value: unknown; span: Span }> | undefined
|
|
395
|
+
_fields?: Array<{ name: string; value: unknown; span: Span; leading?: string; trailing?: string }> | undefined
|
|
386
396
|
/** Framework-internal: lazy capture buffer for active node() parse. */
|
|
387
397
|
_cstBuf?: CstCaptureBuf | undefined
|
|
388
398
|
/** Framework-internal: value/span already consumed by an enclosing dispatch(). */
|
package/src/version.ts
CHANGED