parseman 0.59.3 → 0.60.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 +874 -188
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +874 -188
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/dispatch.d.ts +13 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts +35 -2
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +11 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +1 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +2 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +65 -53
- package/dist/compiler/token-capability.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +894 -197
- package/dist/index.js.map +3 -3
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.js +901 -199
- package/dist/plugin/index.js.map +3 -3
- package/dist/regex/first-set.d.ts +19 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +14 -0
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/child-slots.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +8 -2
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts +4 -3
- 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/factor-choice.d.ts +27 -0
- package/dist/table/factor-choice.d.ts.map +1 -1
- package/dist/table/gap-gate.d.ts +11 -0
- package/dist/table/gap-gate.d.ts.map +1 -0
- package/dist/table/index.js +924 -199
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-program.d.ts.map +1 -1
- package/dist/table/ops.d.ts +34 -6
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +6 -1
- package/dist/table/program.d.ts.map +1 -1
- package/dist/types.d.ts +16 -4
- 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/combinators/attempt.ts +1 -1
- package/src/combinators/dispatch.ts +30 -6
- package/src/combinators/first-set.ts +55 -12
- package/src/combinators/node.ts +1 -1
- package/src/combinators/regex.ts +9 -4
- package/src/combinators/repeat.ts +6 -6
- package/src/combinators/sequence.ts +1 -1
- package/src/combinators/trivia-boundary.ts +18 -0
- package/src/combinators/trivia-skip.ts +1 -1
- package/src/compiler/ir-serialize.ts +1 -1
- package/src/compiler/token-alphabet.ts +3 -1
- package/src/coverage.ts +1 -1
- package/src/index.ts +1 -1
- package/src/plugin/evaluator.ts +8 -3
- package/src/regex/first-set.ts +37 -3
- package/src/table/assemble.ts +171 -25
- package/src/table/child-slots.ts +8 -3
- package/src/table/compile-rule-map.ts +6 -3
- package/src/table/emit-assembly.ts +182 -31
- package/src/table/emit.ts +6 -4
- package/src/table/encode.ts +468 -73
- package/src/table/exec.ts +38 -13
- package/src/table/factor-choice.ts +62 -0
- package/src/table/fail-clean.ts +2 -2
- package/src/table/gap-gate.ts +55 -0
- package/src/table/hold-gap.ts +2 -2
- package/src/table/lex-program.ts +15 -6
- package/src/table/ops.ts +37 -6
- package/src/table/program.ts +7 -2
- package/src/table/select-hot.ts +7 -2
- package/src/types.ts +12 -5
- package/src/version.ts +1 -1
package/src/table/exec.ts
CHANGED
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, 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
|
-
OP_COV,
|
|
25
|
-
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE,
|
|
24
|
+
OP_COV, OP_GAPGATE, OP_STRICT,
|
|
25
|
+
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_RGATE, OP_RGATE2,
|
|
26
26
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
27
27
|
} from './ops.ts'
|
|
28
28
|
import { adjacencyHolds, adjacencyMisuse } from '../combinators/adjacency.ts'
|
|
@@ -375,6 +375,11 @@ function makeDriver(
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
function exec(ip: number, input: string, pos: number, ctx: ParseContext): unknown {
|
|
378
|
+
// A row that only hands on to one child at the same position (a gate that
|
|
379
|
+
// passes, a rule trampoline, `OP_STRICT`) continues here rather than
|
|
380
|
+
// recursing, so the reference driver's stack grows with the grammar's
|
|
381
|
+
// nesting, not with its gate rows.
|
|
382
|
+
rows: for (;;) {
|
|
378
383
|
if (COUNT) { tableCounters.rows++; tableCounters.byOp[code[ip]!]!++ }
|
|
379
384
|
switch (code[ip]) {
|
|
380
385
|
case OP_LIT: {
|
|
@@ -492,7 +497,7 @@ function makeDriver(
|
|
|
492
497
|
ctx._fe = pos; ctx._fx = fx[code[ip + 3]!] as string[]
|
|
493
498
|
return FAIL
|
|
494
499
|
}
|
|
495
|
-
|
|
500
|
+
ip = code[ip + 2]!; continue rows
|
|
496
501
|
}
|
|
497
502
|
|
|
498
503
|
case OP_LABEL: {
|
|
@@ -502,7 +507,7 @@ function makeDriver(
|
|
|
502
507
|
return v
|
|
503
508
|
}
|
|
504
509
|
case OP_RULE:
|
|
505
|
-
|
|
510
|
+
ip = code[ip + 1]!; continue rows
|
|
506
511
|
|
|
507
512
|
/**
|
|
508
513
|
* A COUNTER ROW IS TRANSPARENT HERE, and that is the whole of its handling.
|
|
@@ -518,8 +523,14 @@ function makeDriver(
|
|
|
518
523
|
* a second implementation of it here would be a second place for the phase
|
|
519
524
|
* semantics (entry for a rule, success for an arm) to drift.
|
|
520
525
|
*/
|
|
526
|
+
// The reference runs a gap-gated item as written: the gate is the repeat's,
|
|
527
|
+
// in the shipped engines, and this driver is the leg it is compared with.
|
|
521
528
|
case OP_COV:
|
|
522
|
-
|
|
529
|
+
case OP_GAPGATE:
|
|
530
|
+
ip = code[ip + 1]!; continue rows
|
|
531
|
+
|
|
532
|
+
case OP_STRICT:
|
|
533
|
+
ip = code[ctx._tolerant || ctx._probe !== undefined ? ip + 2 : ip + 1]!; continue rows
|
|
523
534
|
|
|
524
535
|
case OP_LIVE: {
|
|
525
536
|
// A HAND-WRITTEN COMBINATOR, run through its own `.parse` — mirrors
|
|
@@ -601,6 +612,7 @@ function makeDriver(
|
|
|
601
612
|
|
|
602
613
|
let target: number
|
|
603
614
|
let usesRouted: boolean
|
|
615
|
+
let commits = true
|
|
604
616
|
if (arm === undefined) {
|
|
605
617
|
const other = code[ip + 3]!
|
|
606
618
|
if (other < 0) {
|
|
@@ -610,7 +622,8 @@ function makeDriver(
|
|
|
610
622
|
return FAIL
|
|
611
623
|
}
|
|
612
624
|
target = other
|
|
613
|
-
usesRouted = code[ip + 4]! === 1
|
|
625
|
+
usesRouted = (code[ip + 4]! & 1) === 1
|
|
626
|
+
commits = (code[ip + 4]! & 2) === 0
|
|
614
627
|
} else {
|
|
615
628
|
target = code[armBase + arm]!
|
|
616
629
|
usesRouted = spec.routed[arm] === 1
|
|
@@ -637,8 +650,8 @@ function makeDriver(
|
|
|
637
650
|
rollbackTrivia(ctx, mark)
|
|
638
651
|
// The interpreter marks a failed dispatch branch COMMITTED: the
|
|
639
652
|
// selector already matched, so an enclosing choice must not treat this
|
|
640
|
-
// as "try the next arm".
|
|
641
|
-
ctx._fc = true
|
|
653
|
+
// as "try the next arm". An uncommitted fallback keeps its own flag.
|
|
654
|
+
if (commits) ctx._fc = true
|
|
642
655
|
return FAIL
|
|
643
656
|
}
|
|
644
657
|
// The end cell already holds the branch's end — dispatch's span runs from `pos`
|
|
@@ -1029,6 +1042,12 @@ function makeDriver(
|
|
|
1029
1042
|
// its `g.X` arms were unresolved refs (choice.ts:35 — the same staleness
|
|
1030
1043
|
// `encode.ts` recomputes around), so they firstMatch all seven arms where
|
|
1031
1044
|
// this driver dispatches to one. The arm sets differ, not the rule.
|
|
1045
|
+
//
|
|
1046
|
+
// Under a completions probe the interpreter runs every arm on a miss
|
|
1047
|
+
// (`choice.ts`), so each opener reaches the probe; so does this.
|
|
1048
|
+
if (arm < 0 && ctx._probe !== undefined) {
|
|
1049
|
+
for (let i = 0; i < code[ip + 2]!; i++) exec(code[base + i]!, input, pos, ctx)
|
|
1050
|
+
}
|
|
1032
1051
|
return failChoice()
|
|
1033
1052
|
}
|
|
1034
1053
|
// THE PER-ARM GATE. Arms in source order, each skipped when its own
|
|
@@ -1063,7 +1082,9 @@ function makeDriver(
|
|
|
1063
1082
|
const armFx = base + n
|
|
1064
1083
|
for (let i = 0; i < n; i++) {
|
|
1065
1084
|
const cls = armCls[i]
|
|
1066
|
-
|
|
1085
|
+
// Under a completion probe every arm is entered, as the interpreter's
|
|
1086
|
+
// `firstMatch` enters it: its opener then reaches the probe (`failAt`).
|
|
1087
|
+
if (ctx._probe === undefined && cls !== undefined && cls !== null && !classHas(cls, c)) {
|
|
1067
1088
|
if (COUNT) tableCounters.armGateSkips++
|
|
1068
1089
|
// THE GATED ARM STILL REPORTS. The interpreter has no char gate: it
|
|
1069
1090
|
// enters the arm, the arm's opener fails, and its static set is what
|
|
@@ -1119,14 +1140,17 @@ function makeDriver(
|
|
|
1119
1140
|
return sup
|
|
1120
1141
|
}
|
|
1121
1142
|
|
|
1122
|
-
|
|
1143
|
+
// The reference tests the first character only: the second is the shipped
|
|
1144
|
+
// engines' (`OP_RGATE2`), and this driver is the leg they are compared with.
|
|
1145
|
+
case OP_RGATE:
|
|
1146
|
+
case OP_RGATE2: {
|
|
1123
1147
|
const item = ctx._routed
|
|
1124
1148
|
if (item !== undefined && item.span.start === pos && ctx._probe === undefined && !ctx._tolerant
|
|
1125
1149
|
&& !classHas(cc[code[ip + 1]!]!, lead(input, item.span.end))) {
|
|
1126
1150
|
ctx._fe = item.span.end; ctx._fx = fx[code[ip + 3]!] as string[]
|
|
1127
1151
|
return FAIL
|
|
1128
1152
|
}
|
|
1129
|
-
|
|
1153
|
+
ip = code[ip + 2]!; continue rows
|
|
1130
1154
|
}
|
|
1131
1155
|
|
|
1132
1156
|
case OP_ARMGATE: {
|
|
@@ -1141,7 +1165,7 @@ function makeDriver(
|
|
|
1141
1165
|
ctx._fx = fx[code[ip + 3]!] as string[]
|
|
1142
1166
|
return FAIL
|
|
1143
1167
|
}
|
|
1144
|
-
|
|
1168
|
+
ip = code[ip + 2]!; continue rows
|
|
1145
1169
|
}
|
|
1146
1170
|
|
|
1147
1171
|
case OP_REJECT: {
|
|
@@ -1225,7 +1249,7 @@ function makeDriver(
|
|
|
1225
1249
|
// own sync. The item's expected set is at ip + 6 and the separator's
|
|
1226
1250
|
// sentinel class at ip + 7, both laid down only for a recovery table.
|
|
1227
1251
|
const mySync = REC ? ctx._sync : undefined
|
|
1228
|
-
const recFx = REC ? fx[code[ip + 6]!] as string[]
|
|
1252
|
+
const recFx = !REC ? undefined : (code[ip + 5]! & 16) !== 0 ? [] : fx[code[ip + 6]!] as string[]
|
|
1229
1253
|
const sepSent = REC ? sentinelFor(code[ip + 7]!) : undefined
|
|
1230
1254
|
const itemClassIndex = sep < 0 ? code[ip + 7]! : -1
|
|
1231
1255
|
const itemClass = REC && itemClassIndex >= 0 ? cc[itemClassIndex]! : undefined
|
|
@@ -1644,6 +1668,7 @@ function makeDriver(
|
|
|
1644
1668
|
default:
|
|
1645
1669
|
throw new Error(`table driver: unknown opcode ${String(code[ip])} at ${ip}`)
|
|
1646
1670
|
}
|
|
1671
|
+
}
|
|
1647
1672
|
}
|
|
1648
1673
|
|
|
1649
1674
|
/**
|
|
@@ -35,8 +35,16 @@ import { sequence } from '../combinators/sequence.ts'
|
|
|
35
35
|
import { leaf, sourceLeaf } from '../combinators/token.ts'
|
|
36
36
|
import { noTrivia, parser } from '../combinators/grammar.ts'
|
|
37
37
|
import { attempt } from '../combinators/attempt.ts'
|
|
38
|
+
import { optional } from '../combinators/repeat.ts'
|
|
38
39
|
import { mayCommitFailure } from '../analysis/commitment.ts'
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The choices this module builds. The interpreter never runs them: it runs the
|
|
43
|
+
* authored choice, so a factored one's `disjoint` says nothing about which arms
|
|
44
|
+
* the interpreter lets in (`Encoder` classifies them by their derived sets).
|
|
45
|
+
*/
|
|
46
|
+
export const factoredChoices = new WeakSet<Combinator<unknown>>()
|
|
47
|
+
|
|
40
48
|
type ChoiceDef = Extract<ParserDef, { tag: 'choice' }>
|
|
41
49
|
|
|
42
50
|
/** Own keys every combinator may carry beside its definition; all derived from it. */
|
|
@@ -151,6 +159,7 @@ export function factorSharedEnds(d: ChoiceDef): Combinator<unknown> | undefined
|
|
|
151
159
|
// it walks everything the arms reach.
|
|
152
160
|
if (arms.some(a => mayCommitFailure(a))) return undefined
|
|
153
161
|
const middle = choice(...(shapes.map(s => s.terms[at]!) as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
162
|
+
factoredChoices.add(middle)
|
|
154
163
|
// The inner choice must keep arm order: a strategy that reorders (longest
|
|
155
164
|
// literal first) or classifies would pick a different arm. Nor may it add an
|
|
156
165
|
// automatic lookahead the arms as written do not have: `'start'` before
|
|
@@ -171,3 +180,56 @@ export function factorSharedEnds(d: ChoiceDef): Combinator<unknown> | undefined
|
|
|
171
180
|
// `attempt`. The sequence alone would report where its inner choice failed.
|
|
172
181
|
return out === undefined ? undefined : attempt(out)
|
|
173
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* AN ARM WHOSE NEXT SIBLING IS ITS OWN PREFIX — `choice(sequence(X, a), X)`.
|
|
186
|
+
*
|
|
187
|
+
* On input where `X` matches and `a` does not, the choice parses `X`, fails `a`,
|
|
188
|
+
* rolls back, and parses `X` again for the next arm. Where the choice's value is
|
|
189
|
+
* unused (its sequence arms are `valueUnused`, so only their captures reach the
|
|
190
|
+
* enclosing node), the first arm may be `sequence(X, optional(a))`:
|
|
191
|
+
*
|
|
192
|
+
* - `X` then `a` matches: the arm as it was.
|
|
193
|
+
* - `X` matches, `a` fails uncommitted: the optional matches empty, rolls `a`'s
|
|
194
|
+
* captures back and leaves the gap before it unrecorded, so the arm ends where
|
|
195
|
+
* `X` did with `X`'s captures, which is what the retried next arm produced.
|
|
196
|
+
* - `a` fails committed: the arm fails committed, as it did.
|
|
197
|
+
* - `X` fails: the arm fails as before, and the next arm, kept as it is, fails the
|
|
198
|
+
* same way, so the choice merges the same sets in the same order.
|
|
199
|
+
*
|
|
200
|
+
* `X` is the next arm's sequence of terms, or that arm itself as a single term,
|
|
201
|
+
* and "the same" is `sameComb`. Gated arms, `autoNot` and strategies that reorder
|
|
202
|
+
* are left alone.
|
|
203
|
+
*/
|
|
204
|
+
export function factorPrefixArms(d: ChoiceDef): Combinator<unknown> | undefined {
|
|
205
|
+
const arms = d.parsers
|
|
206
|
+
if (arms.length < 2) return undefined
|
|
207
|
+
if (d.strategy.tag !== 'firstMatch' && d.strategy.tag !== 'sharedPrefix') return undefined
|
|
208
|
+
if (d.gates.some(g => g !== null) || d.autoNot.some(a => a !== null)) return undefined
|
|
209
|
+
const out = [...arms]
|
|
210
|
+
let changed = false
|
|
211
|
+
for (let i = 0; i + 1 < arms.length; i++) {
|
|
212
|
+
const arm = arms[i]!
|
|
213
|
+
if (arm._def.tag !== 'sequence' || arm._def.valueUnused !== true) continue
|
|
214
|
+
const terms = arm._def.parsers
|
|
215
|
+
const next = arms[i + 1]!
|
|
216
|
+
const prefix = next._def.tag === 'sequence' && next._def.valueUnused === true ? next._def.parsers : [next]
|
|
217
|
+
if (prefix.length >= terms.length) continue
|
|
218
|
+
if (!prefix.every((t, j) => sameComb(t, terms[j]!, 0))) continue
|
|
219
|
+
const rest = terms.slice(prefix.length)
|
|
220
|
+
// A rest that cannot fail (already `optional`, or `many`) has nothing to factor.
|
|
221
|
+
if (rest.every(never)) continue
|
|
222
|
+
const tail = rest.length === 1 ? rest[0]! : sequence(...(rest as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
223
|
+
if (tail !== rest[0]) (tail._def as { valueUnused?: boolean }).valueUnused = true
|
|
224
|
+
const parts: Combinator<unknown>[] = [...terms.slice(0, prefix.length), optional(tail)]
|
|
225
|
+
const seq = sequence(...(parts as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
226
|
+
;(seq._def as { valueUnused?: boolean }).valueUnused = true
|
|
227
|
+
out[i] = seq
|
|
228
|
+
changed = true
|
|
229
|
+
}
|
|
230
|
+
if (!changed) return undefined
|
|
231
|
+
const rebuilt = choice(...(out as [Combinator<unknown>, ...Combinator<unknown>[]]))
|
|
232
|
+
factoredChoices.add(rebuilt)
|
|
233
|
+
const st = rebuilt._def.tag === 'choice' ? rebuilt._def.strategy : undefined
|
|
234
|
+
return st !== undefined && (st.tag === 'firstMatch' || st.tag === 'sharedPrefix') ? rebuilt : undefined
|
|
235
|
+
}
|
package/src/table/fail-clean.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
OP_ATTEMPT, OP_CHOICE, OP_COV, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE, OP_GUARD, OP_LABEL,
|
|
25
25
|
OP_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK,
|
|
26
26
|
OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX, OP_RX_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ,
|
|
27
|
-
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
27
|
+
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM, OP_GAPGATE,
|
|
28
28
|
} from './ops.ts'
|
|
29
29
|
|
|
30
30
|
export type FailClean = (ip: number) => boolean
|
|
@@ -35,7 +35,7 @@ export type FailClean = (ip: number) => boolean
|
|
|
35
35
|
*/
|
|
36
36
|
function passChild(code: ArrayLike<number>, ip: number): number {
|
|
37
37
|
switch (code[ip]) {
|
|
38
|
-
case OP_RULE: case OP_LABEL: case OP_COV: case OP_TOKEN:
|
|
38
|
+
case OP_RULE: case OP_LABEL: case OP_COV: case OP_TOKEN: case OP_GAPGATE:
|
|
39
39
|
return code[ip + 1]!
|
|
40
40
|
case OP_GATE: case OP_NODE: case OP_NODE_TRACK: case OP_XFORM: case OP_FIELD: case OP_SCOPE:
|
|
41
41
|
case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_LEAF: case OP_SOURCE_LEAF:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AN OPTIONAL REPEAT ITEM THAT OPENS WITH A GAP (`OP_GAPGATE`).
|
|
3
|
+
*
|
|
4
|
+
* SCSS writes its operators as `sequence(pad, '*', pad)` with `pad` a regex that
|
|
5
|
+
* may match empty, and a value chain has a repeat of them at every level. After
|
|
6
|
+
* a value followed by a space, each level entered its operator, scanned the
|
|
7
|
+
* space and failed on the character after it: the same gap read once per level,
|
|
8
|
+
* and an operator node's frame opened each time.
|
|
9
|
+
*
|
|
10
|
+
* The repeat now reads the gap itself, ONCE, with the item's own regex, and
|
|
11
|
+
* looks at the character after it: an item that opens with the gap must
|
|
12
|
+
* continue with a character of `cls` there (and an arm without the gap must
|
|
13
|
+
* start with one of `other` where the gap starts). Where neither holds the item
|
|
14
|
+
* cannot match, so the repeat ends, exactly as it ends on a strict item whose
|
|
15
|
+
* class excludes the lead. Otherwise it enters the item and leaves the gap's end
|
|
16
|
+
* in the GAP SLOT, from which the regex row takes it instead of scanning again.
|
|
17
|
+
*
|
|
18
|
+
* The slot is a memo of one pure function: the end of regex row `row` matched
|
|
19
|
+
* at `at` in `input`. A row reads it only when all three are its own, so a
|
|
20
|
+
* different row, position or input cannot take it, and a slot left set can
|
|
21
|
+
* only ever give a row the end it would have computed. Only rows the table
|
|
22
|
+
* names as a gap (`gapRows`) test it at all. It is set for the item and cleared
|
|
23
|
+
* when the item returns.
|
|
24
|
+
*
|
|
25
|
+
* Not under tolerant recovery or a completions probe, which observe an item's
|
|
26
|
+
* failure: there the repeat enters the item as written. The reference driver
|
|
27
|
+
* (`exec.ts`) always does, which is what makes it the ungated leg.
|
|
28
|
+
*/
|
|
29
|
+
import { childSlots } from './child-slots.ts'
|
|
30
|
+
import { OP_GAPGATE } from './ops.ts'
|
|
31
|
+
|
|
32
|
+
/** The gap slot one assembly shares between its closure and emitted pieces. */
|
|
33
|
+
export type GapSlot = { row: number; at: number; end: number; input: string | undefined }
|
|
34
|
+
|
|
35
|
+
export function newGapSlot(): GapSlot {
|
|
36
|
+
return { row: -1, at: -1, end: -1, input: undefined }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The regex rows some reachable `OP_GAPGATE` names as its gap. */
|
|
40
|
+
export function gapRows(code: ArrayLike<number>, roots: Iterable<number>): Set<number> {
|
|
41
|
+
const rows = new Set<number>()
|
|
42
|
+
const seen = new Set<number>()
|
|
43
|
+
const stack = [...roots]
|
|
44
|
+
const kids: number[] = []
|
|
45
|
+
while (stack.length > 0) {
|
|
46
|
+
const ip = stack.pop()!
|
|
47
|
+
if (seen.has(ip)) continue
|
|
48
|
+
seen.add(ip)
|
|
49
|
+
if (code[ip] === OP_GAPGATE) rows.add(code[ip + 2]!)
|
|
50
|
+
kids.length = 0
|
|
51
|
+
childSlots(code, ip, kids)
|
|
52
|
+
for (const c of kids) stack.push(c)
|
|
53
|
+
}
|
|
54
|
+
return rows
|
|
55
|
+
}
|
package/src/table/hold-gap.ts
CHANGED
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
import {
|
|
18
18
|
OP_ADJ, OP_ATTEMPT, OP_CHOICE, OP_COV, OP_EXPECT, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI,
|
|
19
19
|
OP_NODE, OP_NOT, OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ,
|
|
20
|
-
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
20
|
+
OP_SEQV, OP_SEQX, OP_SOURCE_LEAF, OP_TOKEN, OP_WITHCTX, OP_XFORM, OP_GAPGATE,
|
|
21
21
|
} from './ops.ts'
|
|
22
22
|
import { childSlots } from './child-slots.ts'
|
|
23
23
|
import { leadingTriviaFieldAt } from './field-spec.ts'
|
|
24
24
|
import { regexCanMatchEmpty } from '../regex/first-set.ts'
|
|
25
25
|
|
|
26
26
|
const WRAPPERS = new Set([OP_NODE, OP_XFORM, OP_LEAF, OP_SOURCE_LEAF, OP_TOKEN, OP_SCOPE, OP_SCOPE_CAP,
|
|
27
|
-
OP_SCOPE_PLAIN, OP_FIELD, OP_LABEL, OP_EXPECT, OP_WITHCTX, OP_RULE, OP_ATTEMPT, OP_COV])
|
|
27
|
+
OP_SCOPE_PLAIN, OP_FIELD, OP_LABEL, OP_EXPECT, OP_WITHCTX, OP_RULE, OP_ATTEMPT, OP_COV, OP_GAPGATE])
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Can the row at `ip` match empty? `true` wherever it cannot tell. A first-set
|
package/src/table/lex-program.ts
CHANGED
|
@@ -187,7 +187,7 @@ function sequence5(
|
|
|
187
187
|
|
|
188
188
|
type ChoiceTail = (
|
|
189
189
|
input: string, pos: number, ctx: ParseContext, lead: number,
|
|
190
|
-
acc: string[] | undefined, best: number,
|
|
190
|
+
acc: string[] | undefined, best: number, entered: boolean,
|
|
191
191
|
) => number
|
|
192
192
|
|
|
193
193
|
function appendExpected(acc: string[] | undefined, values: readonly string[] | undefined): string[] | undefined {
|
|
@@ -211,19 +211,22 @@ function choiceNode(
|
|
|
211
211
|
return end < 0 ? next(input, pos) : end
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
-
let tail: ChoiceTail = (
|
|
214
|
+
let tail: ChoiceTail = (input, pos, ctx, _lead, acc, _best, entered) => {
|
|
215
|
+
// No arm was entered. A completion probe still sees each one fail on its
|
|
216
|
+
// opener, as the interpreter's entered arms leave it.
|
|
217
|
+
if (!entered && ctx._probe !== undefined) for (const arm of arms) arm.run(input, pos, ctx)
|
|
215
218
|
ctx._fe = pos
|
|
216
219
|
ctx._fx = acc ?? choiceExpected as string[]
|
|
217
220
|
return -1
|
|
218
221
|
}
|
|
219
222
|
for (let i = arms.length - 1; i >= 0; i--) {
|
|
220
223
|
const armRun = arms[i]!.run, cls = classes[i], staticExpected = armExpected[i]!, next = tail
|
|
221
|
-
tail = (input, pos, ctx, lead, acc, best) => {
|
|
224
|
+
tail = (input, pos, ctx, lead, acc, best, entered) => {
|
|
222
225
|
if (cls !== undefined && !classHas(cls, lead)) {
|
|
223
226
|
return next(
|
|
224
227
|
input, pos, ctx, lead,
|
|
225
228
|
best === pos ? appendExpected(acc, staticExpected) : acc,
|
|
226
|
-
best,
|
|
229
|
+
best, entered,
|
|
227
230
|
)
|
|
228
231
|
}
|
|
229
232
|
ctx._fc = false
|
|
@@ -236,13 +239,13 @@ function choiceNode(
|
|
|
236
239
|
if (nextAcc !== undefined) ctx._fx = nextAcc
|
|
237
240
|
return -1
|
|
238
241
|
}
|
|
239
|
-
return next(input, pos, ctx, lead, nextAcc, best)
|
|
242
|
+
return next(input, pos, ctx, lead, nextAcc, best, true)
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
245
|
return {
|
|
243
246
|
match: matchTail,
|
|
244
247
|
run: (input, pos, ctx) => tail(
|
|
245
|
-
input, pos, ctx, pos < input.length ? input.codePointAt(pos)! : -1, undefined, pos,
|
|
248
|
+
input, pos, ctx, pos < input.length ? input.codePointAt(pos)! : -1, undefined, pos, false,
|
|
246
249
|
),
|
|
247
250
|
}
|
|
248
251
|
}
|
|
@@ -290,6 +293,9 @@ function exclusiveChoiceNode(
|
|
|
290
293
|
const failed = ctx._fx
|
|
291
294
|
if (failed !== undefined && failed.length > 0) { ctx._fe = pos; return -1 }
|
|
292
295
|
}
|
|
296
|
+
// A miss under a completions probe: each arm fails on its opener, as the
|
|
297
|
+
// interpreter's arms do when it runs them all (`choice.ts`).
|
|
298
|
+
if (arm < 0 && ctx._probe !== undefined) for (const a of arms) a.run(input, pos, ctx)
|
|
293
299
|
ctx._fe = pos
|
|
294
300
|
ctx._fx = choiceFx as string[]
|
|
295
301
|
return -1
|
|
@@ -547,6 +553,9 @@ export function buildLexProgramRunner(
|
|
|
547
553
|
if (!classHas(cls, lead)) {
|
|
548
554
|
ctx._fe = pos
|
|
549
555
|
ctx._fx = choiceFx.slice()
|
|
556
|
+
// Each arm fails here; under a probe their openers are recorded as the
|
|
557
|
+
// interpreter's entered arms record them.
|
|
558
|
+
if (ctx._probe !== undefined) failAt(ctx, [...x0, ...x1, ...x2, ...x3], pos)
|
|
550
559
|
return -1
|
|
551
560
|
}
|
|
552
561
|
let acc: string[] | undefined
|
package/src/table/ops.ts
CHANGED
|
@@ -38,11 +38,15 @@ export const OP_SEQV = 4
|
|
|
38
38
|
*/
|
|
39
39
|
export const OP_CHOICE = 5
|
|
40
40
|
/**
|
|
41
|
-
* `REP c min max sep flags fx sepClass` —
|
|
42
|
-
*
|
|
43
|
-
* for a separated list,
|
|
44
|
-
* (or −1). `flags
|
|
45
|
-
* `
|
|
41
|
+
* `REP c min max sep flags fx sepClass [sepGate]` — EIGHT words, or NINE with
|
|
42
|
+
* flag bit 3. `sep` is a child offset or −1; `max` −1 = ∞. `fx` is the item's
|
|
43
|
+
* expected set. `sepClass` is the separator sentinel for a separated list,
|
|
44
|
+
* otherwise the finite/non-nullable optional-item class (or −1). `flags`: bit 0
|
|
45
|
+
* (`&1`) trailing separator allowed; bit 1 (`&2`) `keepSeparators`; bit 2 (`&4`)
|
|
46
|
+
* item expected reporting; bit 3 (`&8`) a ninth word at `ip + 8`, the class of
|
|
47
|
+
* characters the separator can start, so a strict list past its minimum ends
|
|
48
|
+
* where it cannot (`sepGate`); bit 4 (`&16`) the item's derived set is empty, so
|
|
49
|
+
* `fx` holds the item's tag for the minimum check and a resync error carries [].
|
|
46
50
|
*/
|
|
47
51
|
export const OP_REP = 6
|
|
48
52
|
/** `REPV …` — `REP` with `valueUnused`. */
|
|
@@ -486,11 +490,35 @@ export const OP_SOURCE_LEAF = 44
|
|
|
486
490
|
* at this position, a completions probe, or tolerant recovery enters the arm.
|
|
487
491
|
*/
|
|
488
492
|
export const OP_RGATE = 45
|
|
493
|
+
/**
|
|
494
|
+
* `GAPGATE c p cls other` — an optional repeat ITEM that opens with a gap: the
|
|
495
|
+
* pure regex row `p`, which may match empty, and then a term whose first set is
|
|
496
|
+
* the class `cls` (`other`: the class of any choice arm that does not open with
|
|
497
|
+
* `p`, or −1). `c` is the item. The row is TRANSPARENT wherever it runs: only
|
|
498
|
+
* the repeat that holds it reads its operands (`gap-gate.ts`).
|
|
499
|
+
*/
|
|
500
|
+
export const OP_GAPGATE = 46
|
|
501
|
+
/**
|
|
502
|
+
* `RGATE2 cls child fx first second` — `OP_RGATE` with a SECOND character: where
|
|
503
|
+
* the character after the routed token is in `first`, the one after it must be
|
|
504
|
+
* in `second` too (the next term reads both, `regexFirstTwo`), or the arm fails
|
|
505
|
+
* there with `fx`, as entering it would.
|
|
506
|
+
*/
|
|
507
|
+
export const OP_RGATE2 = 47
|
|
508
|
+
/**
|
|
509
|
+
* `STRICT strict tolerant` — one construct encoded two ways: `strict` for a
|
|
510
|
+
* strict parse, `tolerant` for tolerant recovery and a completions probe, which
|
|
511
|
+
* must see the construct as written (`factor-choice.ts` `factorPrefixArms`). An
|
|
512
|
+
* assembly resolves it to one child; it has no body of its own.
|
|
513
|
+
*/
|
|
514
|
+
export const OP_STRICT = 48
|
|
489
515
|
/**
|
|
490
516
|
* `DISPATCH sel d other otherRouted n a1 … an` — `dispatch()`.
|
|
491
517
|
*
|
|
492
518
|
* `sel` is the selector's offset, `d` indexes a dispatch table in `prog.dsp`,
|
|
493
|
-
* `other` is the `otherwise()` offset (or −1), `otherRouted`
|
|
519
|
+
* `other` is the `otherwise()` offset (or −1), `otherRouted` bit 1 (`&2`) is set
|
|
520
|
+
* when the fallback's failure is NOT committed (`otherwise(p, { commit: false })`)
|
|
521
|
+
* and bit 0 when the
|
|
494
522
|
* fallback consumes the routed token, and `a1…an` are the arm offsets. Arm
|
|
495
523
|
* `usesRouted` bits live in the `dsp` entry beside the key maps.
|
|
496
524
|
*
|
|
@@ -520,4 +548,7 @@ export const OP_NAMES: Record<number, string> = {
|
|
|
520
548
|
[OP_SCOPE_PLAIN]: 'SCOPE_PLAIN',
|
|
521
549
|
[OP_LEX_BODY]: 'LEX_BODY',
|
|
522
550
|
[OP_LEX_PROGRAM]: 'LEX_PROGRAM',
|
|
551
|
+
[OP_GAPGATE]: 'GAPGATE',
|
|
552
|
+
[OP_RGATE2]: 'RGATE2',
|
|
553
|
+
[OP_STRICT]: 'STRICT',
|
|
523
554
|
}
|
package/src/table/program.ts
CHANGED
|
@@ -17,10 +17,15 @@ import { buildLexProgramRunner, type LexProgramRunner } from './lex-program.ts'
|
|
|
17
17
|
* `EMITTED_PARAMS` in order. `plan` rebuilds the three data pools the factory
|
|
18
18
|
* expects (see `PoolPlan`), and `reached` is the emitter's site set, which
|
|
19
19
|
* `Assembly.reached` publishes and `test/unit/table-assemble.test.ts`
|
|
20
|
-
* asserts on.
|
|
20
|
+
* asserts on. `arity` is the number of helpers the factory was printed against
|
|
21
|
+
* (`EMITTED_PARAMS.length` of the printing parseman); the runtime compares it with
|
|
22
|
+
* its own list before calling the factory. It is printed data, not
|
|
23
|
+
* `factory.length`, which a minifier that drops unused trailing parameters lowers.
|
|
24
|
+
* An assembly printed before 0.60.0 has none.
|
|
21
25
|
*/
|
|
22
26
|
export type PrecompiledAssembly = {
|
|
23
27
|
readonly key: number
|
|
28
|
+
readonly arity?: number
|
|
24
29
|
readonly factory: EmittedFactory
|
|
25
30
|
readonly plan: PoolPlan
|
|
26
31
|
readonly reached: readonly number[]
|
|
@@ -433,7 +438,7 @@ export function validateDispatchSpec(
|
|
|
433
438
|
return false
|
|
434
439
|
}
|
|
435
440
|
if (spec === undefined || !Number.isInteger(arity) || arity < 0
|
|
436
|
-
|| (fallbackRouted
|
|
441
|
+
|| !Number.isInteger(fallbackRouted) || fallbackRouted < 0 || fallbackRouted > 3
|
|
437
442
|
|| spec.routed.length !== arity
|
|
438
443
|
|| spec.routed.some(value => value !== 0 && value !== 1)
|
|
439
444
|
|| invalid(spec.byKey.values()) || invalid(spec.byFold.values())
|
package/src/table/select-hot.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* A build given a corpus (`assemblyProfile`) ranks by measured calls instead.
|
|
16
16
|
*/
|
|
17
17
|
import { childSlots } from './child-slots.ts'
|
|
18
|
-
import { OP_CHOICE, OP_DISPATCH, OP_REP, OP_REPV } from './ops.ts'
|
|
18
|
+
import { OP_CHOICE, OP_DISPATCH, OP_GAPGATE, OP_REP, OP_REPV } from './ops.ts'
|
|
19
19
|
|
|
20
20
|
const LOOP = 8
|
|
21
21
|
const ROUNDS = 60
|
|
@@ -62,7 +62,12 @@ function staticWeights(code: readonly number[], entries: readonly number[]): Map
|
|
|
62
62
|
const scale = op === OP_REP || op === OP_REPV ? LOOP
|
|
63
63
|
: op === OP_CHOICE || op === OP_DISPATCH ? 1 / Math.max(1, kids.length)
|
|
64
64
|
: 1
|
|
65
|
-
for (const
|
|
65
|
+
for (const k of kids) {
|
|
66
|
+
// A gap gate has no body: weigh its item as the repeat's own child, so
|
|
67
|
+
// adding the row moves no other site's estimate (`gap-gate.ts`).
|
|
68
|
+
const kid = code[k] === OP_GAPGATE ? code[k + 1]! : k
|
|
69
|
+
next.set(kid, Math.min(CAP, (next.get(kid) ?? 0) + w * scale))
|
|
70
|
+
}
|
|
66
71
|
}
|
|
67
72
|
weight = next
|
|
68
73
|
}
|
package/src/types.ts
CHANGED
|
@@ -58,14 +58,14 @@ export type ParserDef =
|
|
|
58
58
|
// force interpreter fallback). Absent under runtime compile() — the real gate
|
|
59
59
|
// closures live in `gates`.
|
|
60
60
|
| { tag: 'choice'; parsers: Combinator<unknown>[]; gates: (((state: unknown) => boolean) | null)[]; gateSrcs?: (string | null)[]; disjoint: boolean; strategy: ChoiceStrategy; autoNot: (AutoNotCheck[] | null)[] }
|
|
61
|
-
| { tag: 'dispatch'; selector: Combinator<string>; cases: readonly DispatchCase[]; matchers?: readonly DispatchMatcherCase[] | undefined; otherwise?: Combinator<unknown> | undefined; otherwiseUsesRouted?: boolean | undefined }
|
|
62
|
-
| { tag: 'attempt'; parser: Combinator<unknown>; /** `attempt(p, { contain: true })`: a committed failure inside is reported uncommitted. */ contain?: true }
|
|
61
|
+
| { tag: 'dispatch'; selector: Combinator<string>; cases: readonly DispatchCase[]; matchers?: readonly DispatchMatcherCase[] | undefined; otherwise?: Combinator<unknown> | undefined; otherwiseUsesRouted?: boolean | undefined; otherwiseCommit?: false | undefined }
|
|
62
|
+
| { tag: 'attempt'; parser: Combinator<unknown>; /** `attempt(p, { contain: true })`: a committed failure inside is reported uncommitted. */ contain?: true; /** The interpreter tests the child's live `_meta.firstSet` before entering it: its set was finite when this was built. */ guarded?: true }
|
|
63
63
|
// The TAG carries NULLABILITY (what every downstream switch keys on): `many` is
|
|
64
64
|
// the nullable min-0 repeat, `oneOrMore` the non-nullable min>=1 one. `min`/`max`
|
|
65
65
|
// carry the actual ITEM bounds (`many(x, { min: 3, max: 8 })` is a `oneOrMore`
|
|
66
66
|
// def with `min: 3, max: 8`). `max` absent = unbounded.
|
|
67
|
-
| { tag: 'many'; parser: Combinator<unknown>; min: 0; max?: number; valueUnused?: boolean }
|
|
68
|
-
| { tag: 'oneOrMore'; parser: Combinator<unknown>; min: number; max?: number; valueUnused?: boolean }
|
|
67
|
+
| { tag: 'many'; parser: Combinator<unknown>; min: 0; max?: number; valueUnused?: boolean; /** The interpreter tests the child's live `_meta.firstSet` before entering it: its set was finite when this was built. */ guarded?: true }
|
|
68
|
+
| { tag: 'oneOrMore'; parser: Combinator<unknown>; min: number; max?: number; valueUnused?: boolean; /** The interpreter tests the child's live `_meta.firstSet` before entering it: its set was finite when this was built. */ guarded?: true }
|
|
69
69
|
| { tag: 'optional'; parser: Combinator<unknown> }
|
|
70
70
|
// `min`/`max` count ITEMS, not separators. `min: 0` (the default) is
|
|
71
71
|
// `(item (sep item)*)?` — NULLABLE, matches the empty string. `min >= 1` is
|
|
@@ -92,7 +92,7 @@ export type ParserDef =
|
|
|
92
92
|
// it requires, so it carries `parser`'s first-set and a leading `peek()` gates
|
|
93
93
|
// its choice arm (see `isPositiveLookahead` / `sequenceFirstSet`).
|
|
94
94
|
| { tag: 'peek'; parser: Combinator<unknown> }
|
|
95
|
-
| { tag: 'node'; type?: string; parser: Combinator<unknown>; build?: ((children: ReadonlyArray<unknown>, fields: FieldMap | undefined, span: { start: number; end: number }, rawChildren: ReadonlyArray<unknown>, triviaLog: readonly number[], state: unknown) => unknown) | undefined; buildSrc?: string; /** The IDENTIFIER the type argument was written as at the `node()` call site, when it was a plain identifier rather than a literal — ANALYSIS ONLY, never emitted. Lets the inline-`mk` matcher recognise `node(t, …, (…) => mk(t, …))` from a factory, where the two `t`s are provably one binding. */ typeSrc?: string; /** Resolved source of a NAMED `buildSrc` — ANALYSIS ONLY, never emitted. See `buildAnalysisSrc`. */ buildSigSrc?: string; /** Macro analysis proved the fourth formal (`rawChildren`) unreferenced. Never inferred from an author-declared `buildArity`; carried through composed IR. */ buildRawUnused?: true; /** Declared positional arity of `build`: author-declared via `node(..., { buildArity })`, or resolved from the reducer's declaration. Wins over source inspection. */ buildArity?: number; /** Why arity could not be resolved, for the diagnostic. */ buildArityUnresolved?: string; buildStaticError?: readonly string[]; /** Import provenance for a direct builder's free names — each `{ local, source, imported }` lets a downstream compose() re-emit `import { imported as local } from source` so the inlined builder source can bind. Carried through the IR round-trip. */ buildImports?: ReadonlyArray<{ local: string; source: string; imported: string }>; unwrap?: boolean; collapse?: boolean; project?: number; captureTrivia?: boolean; trailingTrivia?: boolean; tags?: readonly string[] }
|
|
95
|
+
| { tag: 'node'; type?: string; parser: Combinator<unknown>; build?: ((children: ReadonlyArray<unknown>, fields: FieldMap | undefined, span: { start: number; end: number }, rawChildren: ReadonlyArray<unknown>, triviaLog: readonly number[], state: unknown) => unknown) | undefined; buildSrc?: string; /** The IDENTIFIER the type argument was written as at the `node()` call site, when it was a plain identifier rather than a literal — ANALYSIS ONLY, never emitted. Lets the inline-`mk` matcher recognise `node(t, …, (…) => mk(t, …))` from a factory, where the two `t`s are provably one binding. */ typeSrc?: string; /** Resolved source of a NAMED `buildSrc` — ANALYSIS ONLY, never emitted. See `buildAnalysisSrc`. */ buildSigSrc?: string; /** Macro analysis proved the fourth formal (`rawChildren`) unreferenced. Never inferred from an author-declared `buildArity`; carried through composed IR. */ buildRawUnused?: true; /** Declared positional arity of `build`: author-declared via `node(..., { buildArity })`, or resolved from the reducer's declaration. Wins over source inspection. */ buildArity?: number; /** Why arity could not be resolved, for the diagnostic. */ buildArityUnresolved?: string; buildStaticError?: readonly string[]; /** Import provenance for a direct builder's free names — each `{ local, source, imported }` lets a downstream compose() re-emit `import { imported as local } from source` so the inlined builder source can bind. Carried through the IR round-trip. */ buildImports?: ReadonlyArray<{ local: string; source: string; imported: string }>; unwrap?: boolean; collapse?: boolean; project?: number; captureTrivia?: boolean; trailingTrivia?: boolean; tags?: readonly string[]; /** The interpreter tests the child's live `_meta.firstSet` before entering it: its set was finite when this was built. */ guarded?: true }
|
|
96
96
|
// `predSrc`/`extraSrc` (set by the macro evaluator): SOURCE TEXT of the guard
|
|
97
97
|
// predicate / the withCtx `extra` value, so codegen inlines them into `_mf`
|
|
98
98
|
// rather than pushing a `null` source. Absent under runtime compile() (the real
|
|
@@ -461,6 +461,13 @@ export type ParseError = {
|
|
|
461
461
|
export type ParserMeta = {
|
|
462
462
|
/** Character codes / ranges that can start this parser (for choice dispatch) */
|
|
463
463
|
firstSet: FirstSet
|
|
464
|
+
/**
|
|
465
|
+
* Set only by `regex()` for a pattern that can match empty: the first chars
|
|
466
|
+
* of its NON-EMPTY matches. `firstSet` stays `any` for such a pattern, because
|
|
467
|
+
* it may match empty anywhere; a sequence, which tracks nullability itself,
|
|
468
|
+
* unions this set with the next term's instead (`sequenceSet`).
|
|
469
|
+
*/
|
|
470
|
+
consumingFirstSet?: FirstSet
|
|
464
471
|
/** Whether this parser can consume a newline character */
|
|
465
472
|
canMatchNewline: boolean
|
|
466
473
|
/** Whether this parser is marked as trivia (auto-skip) */
|
package/src/version.ts
CHANGED