parseman 0.53.0 → 0.55.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 +295 -119
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +295 -119
- 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/repeat.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts +22 -7
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +2 -0
- package/dist/compiler/fields.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 +319 -130
- package/dist/index.js.map +3 -3
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.js +324 -132
- package/dist/plugin/index.js.map +3 -3
- package/dist/table/assemble.d.ts +9 -0
- package/dist/table/assemble.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 +262 -113
- package/dist/table/index.js.map +3 -3
- package/dist/table/ops.d.ts +3 -1
- package/dist/table/ops.d.ts.map +1 -1
- 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/repeat.ts +6 -1
- package/src/combinators/sequence.ts +9 -1
- package/src/combinators/withCtx.ts +61 -28
- package/src/compiler/fields.ts +5 -2
- package/src/compiler/ir-serialize.ts +7 -1
- package/src/coverage.ts +1 -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 +13 -3
- package/src/table/assemble.ts +137 -23
- package/src/table/emit-assembly.ts +38 -18
- package/src/table/emit.ts +15 -4
- package/src/table/encode.ts +23 -2
- package/src/table/exec.ts +17 -2
- package/src/table/field-spec.ts +17 -0
- package/src/table/ops.ts +3 -1
- package/src/table/scalar-terminal.ts +25 -0
- package/src/types.ts +14 -2
- package/src/version.ts +1 -1
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.
|
|
@@ -1576,6 +1592,11 @@ class Encoder {
|
|
|
1576
1592
|
this.expected([adjacencyExpected(d)]),
|
|
1577
1593
|
)
|
|
1578
1594
|
case 'withCtx': {
|
|
1595
|
+
// The DERIVE form is an author callback, parked in `fns` like a gate
|
|
1596
|
+
// predicate and told apart from a const-pool index by its sign.
|
|
1597
|
+
if (typeof d.extra === 'function') {
|
|
1598
|
+
return this.emit(OP_WITHCTX, ~this.fn(d.extra, d.extraSrc ?? null), this.node(d.parser).ip)
|
|
1599
|
+
}
|
|
1579
1600
|
// `extra` is arbitrary user data in the const pool. `emitConst` takes
|
|
1580
1601
|
// scalars, arrays of scalars, and plain objects of those — anything
|
|
1581
1602
|
// richer (a class instance, a function value) RUNS fine but cannot be
|
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
|
|
|
@@ -791,7 +803,8 @@ function makeDriver(
|
|
|
791
803
|
|
|
792
804
|
case OP_WITHCTX: {
|
|
793
805
|
const saved = ctx.state
|
|
794
|
-
|
|
806
|
+
const ki = code[ip + 1]!
|
|
807
|
+
ctx.state = ki >= 0 ? k[ki] : (fns[~ki] as (s: unknown) => unknown)(saved)
|
|
795
808
|
try { return exec(code[ip + 2]!, input, pos, ctx) }
|
|
796
809
|
finally { ctx.state = saved }
|
|
797
810
|
}
|
|
@@ -885,6 +898,7 @@ function makeDriver(
|
|
|
885
898
|
const scanTl = need ? cstTlLen(ctx) : 0
|
|
886
899
|
const scanLog = need ? ctx._triviaLog?.length ?? 0 : 0
|
|
887
900
|
const scanRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
|
|
901
|
+
if (leadingTriviaFieldAt(code, k, child)) LEAD.at = cur
|
|
888
902
|
const v = exec(child, input, scanEnd, ctx)
|
|
889
903
|
if (v === FAIL) { if (REC) ctx._sync = inheritedSync; return FAIL }
|
|
890
904
|
if (EC.e > scanEnd) cur = EC.e
|
|
@@ -1256,6 +1270,7 @@ function makeDriver(
|
|
|
1256
1270
|
let sp = cur
|
|
1257
1271
|
if (hasTrivia) sp = skipTrivia(input, sp, ctx)
|
|
1258
1272
|
ctx._fc = false
|
|
1273
|
+
if (leadingTriviaFieldAt(code, k, sep)) LEAD.at = cur
|
|
1259
1274
|
const sv = exec(sep, input, sp, ctx)
|
|
1260
1275
|
if (sv === FAIL) {
|
|
1261
1276
|
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
|
+
}
|
package/src/table/ops.ts
CHANGED
|
@@ -217,7 +217,9 @@ export const OP_TOKEN = 29
|
|
|
217
217
|
*/
|
|
218
218
|
export const OP_SCOPE_CAP = 30
|
|
219
219
|
/**
|
|
220
|
-
* `WITHCTX k c` — `withCtx(extra, c)`. `k` is `extra` in the const pool
|
|
220
|
+
* `WITHCTX k c` — `withCtx(extra, c)`. `k >= 0` is `extra` in the const pool
|
|
221
|
+
* (the replace form); `k < 0` is the derive form, `~k` indexing the author's
|
|
222
|
+
* `(state) => state` in `prog.fns`, called once per entry with the outer state.
|
|
221
223
|
*
|
|
222
224
|
* SAVE / RESTORE, matching `withCtx.ts`. Both used to CLONE the context, which
|
|
223
225
|
* scoped far more than the state: every scalar the child wrote on `ctx` landed
|
|
@@ -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> }
|
|
@@ -102,6 +102,8 @@ export type ParserDef =
|
|
|
102
102
|
// asserts that something did. `kinds` (notAdjacent only) narrows the assertion to
|
|
103
103
|
// trivia CATEGORIES from `classifiedTrivia({...})`. See combinators/adjacency.ts.
|
|
104
104
|
| { tag: 'adjacency'; polarity: 'adjacent' | 'notAdjacent'; kinds?: readonly string[] }
|
|
105
|
+
// `extra` is the subtree's state (replace form) or, when it is a function, the
|
|
106
|
+
// `(state) => state` that derives it from the state in force at entry.
|
|
105
107
|
| { tag: 'withCtx'; extra: unknown; parser: Combinator<unknown>; extraSrc?: string }
|
|
106
108
|
| { tag: 'recover'; parser: Combinator<unknown>; sentinel: Combinator<unknown> }
|
|
107
109
|
| { tag: 'expect'; parser: Combinator<unknown>; label: string | undefined; expected: string[] }
|
|
@@ -203,6 +205,16 @@ export type BuildHost = ((
|
|
|
203
205
|
export type FieldCapture<T = unknown> = {
|
|
204
206
|
value: T
|
|
205
207
|
span: Span
|
|
208
|
+
/**
|
|
209
|
+
* `field(…, { leadingTrivia: true })` only: the source text of the trivia the
|
|
210
|
+
* enclosing sequence or separated list skipped right before this field, or `''`.
|
|
211
|
+
*/
|
|
212
|
+
leading?: string
|
|
213
|
+
/**
|
|
214
|
+
* `field(…, { trailingTrivia: true })` only: the source text of the trivia the
|
|
215
|
+
* field consumed after its match, or `''`.
|
|
216
|
+
*/
|
|
217
|
+
trailing?: string
|
|
206
218
|
}
|
|
207
219
|
|
|
208
220
|
export type FieldMap = Record<string, FieldCapture | FieldCapture[]>
|
|
@@ -382,7 +394,7 @@ export type ParseContext = {
|
|
|
382
394
|
*/
|
|
383
395
|
_cstTriviaLog?: number[] | undefined
|
|
384
396
|
/** Framework-internal: active node() field captures, enabled only when needed. */
|
|
385
|
-
_fields?: Array<{ name: string; value: unknown; span: Span }> | undefined
|
|
397
|
+
_fields?: Array<{ name: string; value: unknown; span: Span; leading?: string; trailing?: string }> | undefined
|
|
386
398
|
/** Framework-internal: lazy capture buffer for active node() parse. */
|
|
387
399
|
_cstBuf?: CstCaptureBuf | undefined
|
|
388
400
|
/** Framework-internal: value/span already consumed by an enclosing dispatch(). */
|
package/src/version.ts
CHANGED