parseman 0.56.0 → 0.58.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 +2232 -1088
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +2247 -1103
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +1 -1
- package/dist/compiler/build-arity.d.ts +1 -1
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +11 -1
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +69 -16
- package/dist/compiler/token-capability.js.map +2 -2
- package/dist/cst/capture-buffer.d.ts +6 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/trivia-kinds.d.ts +9 -1
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.js +1446 -236
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +1 -0
- package/dist/language-service/index.js.map +1 -1
- package/dist/parse-context.d.ts.map +1 -1
- package/dist/plugin/entry-supercompiler.d.ts +1 -1
- package/dist/plugin/entry-supercompiler.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +51 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2126 -380
- package/dist/plugin/index.js.map +3 -3
- package/dist/plugin/ir-eval.d.ts +2 -0
- package/dist/plugin/ir-eval.d.ts.map +1 -1
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/run/index.js +1 -0
- package/dist/run/index.js.map +1 -1
- package/dist/table/assemble.d.ts +9 -0
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/child-slots.d.ts +4 -6
- package/dist/table/child-slots.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +14 -2
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +17 -2
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts +5 -1
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/exec-baseline.d.ts.map +1 -1
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/factor-choice.d.ts +42 -0
- package/dist/table/factor-choice.d.ts.map +1 -0
- package/dist/table/fail-clean.d.ts +3 -0
- package/dist/table/fail-clean.d.ts.map +1 -0
- package/dist/table/hold-gap.d.ts +8 -0
- package/dist/table/hold-gap.d.ts.map +1 -0
- package/dist/table/index.js +1798 -316
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-program.d.ts +7 -3
- package/dist/table/lex-program.d.ts.map +1 -1
- package/dist/table/ops.d.ts +17 -6
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +13 -0
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/run-support.d.ts +19 -0
- package/dist/table/run-support.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +2 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -1
- package/dist/table/select-hot.d.ts +6 -0
- package/dist/table/select-hot.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +66 -0
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/table/trivia-gap.d.ts +31 -0
- package/dist/table/trivia-gap.d.ts.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +4 -1
- package/src/combinators/dispatch.ts +12 -3
- package/src/combinators/node.ts +6 -13
- package/src/combinators/trivia-skip.ts +44 -1
- package/src/compiler/build-arity.ts +3 -3
- package/src/compiler/token-alphabet.ts +57 -17
- package/src/cst/capture-buffer.ts +25 -0
- package/src/cst/trivia-kinds.ts +12 -1
- package/src/parse-context.ts +1 -0
- package/src/plugin/entry-supercompiler.ts +8 -9
- package/src/plugin/index.ts +362 -42
- package/src/plugin/ir-eval.ts +7 -1
- package/src/regex/classes.ts +3 -0
- package/src/table/assemble.ts +371 -83
- package/src/table/child-slots.ts +6 -8
- package/src/table/compile-rule-map.ts +64 -15
- package/src/table/emit-assembly.ts +244 -42
- package/src/table/emit.ts +11 -8
- package/src/table/encode.ts +149 -35
- package/src/table/exec-baseline.ts +1 -3
- package/src/table/exec.ts +34 -14
- package/src/table/factor-choice.ts +173 -0
- package/src/table/fail-clean.ts +130 -0
- package/src/table/hold-gap.ts +68 -0
- package/src/table/lex-program.ts +197 -43
- package/src/table/ops.ts +18 -7
- package/src/table/program.ts +24 -0
- package/src/table/run-support.ts +35 -0
- package/src/table/scalar-terminal.ts +8 -0
- package/src/table/select-hot.ts +70 -0
- package/src/table/site-labels.ts +135 -4
- package/src/table/trivia-gap.ts +109 -0
- package/src/types.ts +18 -0
- package/src/version.ts +1 -1
|
@@ -570,11 +570,54 @@ function regexTriviaScanner(parser: Combinator<unknown>): FastTriviaScanner | nu
|
|
|
570
570
|
// and every trivia gap in every dialect went through the labelled char scanner.
|
|
571
571
|
const d = parser._def.tag === 'label' ? parser._def.parser._def : parser._def
|
|
572
572
|
if (d.tag !== 'regex' || d.flags) return null
|
|
573
|
-
|
|
573
|
+
return sourceScanner(d.source)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function sourceScanner(source: string): FastTriviaScanner | null {
|
|
574
577
|
return classRunSource(source)
|
|
575
578
|
?? altStarSource(source)
|
|
576
579
|
?? prefixRunSource(source)
|
|
577
580
|
?? delimitedSource(source)
|
|
581
|
+
?? sharedPrefixAltSource(source)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* `<literal>(?:<alt>|<alt>…)`: a literal every alternative shares, as in jess's
|
|
586
|
+
* one comment arm `\/(?:\*(?:[^*]|\*(?!\/))*\*\/|\/[^\n\r]*)` for both `/*…*\/`
|
|
587
|
+
* and `//…`. Each `<literal><alt>` is lowered on its own, and the scanner takes
|
|
588
|
+
* the first that matches, as the regex does. Without it that arm had no
|
|
589
|
+
* scanner, so neither did the trivia it sits in.
|
|
590
|
+
*/
|
|
591
|
+
function sharedPrefixAltSource(source: string): FastTriviaScanner | null {
|
|
592
|
+
const m = /^((?:\\.|[^\\[\]()*+?|^$.{}])+)\(\?:(.*)\)$/.exec(source)
|
|
593
|
+
if (!m) return null
|
|
594
|
+
const alts: string[] = []
|
|
595
|
+
const body = m[2]!
|
|
596
|
+
let depth = 0
|
|
597
|
+
let start = 0
|
|
598
|
+
for (let i = 0; i < body.length; i++) {
|
|
599
|
+
const c = body[i]
|
|
600
|
+
if (c === '\\') { i++; continue }
|
|
601
|
+
if (c === '[') { while (i < body.length && body[i] !== ']') i += body[i] === '\\' ? 2 : 1; continue }
|
|
602
|
+
if (c === '(') depth++
|
|
603
|
+
else if (c === ')' && --depth < 0) return null
|
|
604
|
+
else if (c === '|' && depth === 0) { alts.push(body.slice(start, i)); start = i + 1 }
|
|
605
|
+
}
|
|
606
|
+
if (depth !== 0 || alts.length === 0) return null
|
|
607
|
+
alts.push(body.slice(start))
|
|
608
|
+
const scanners: FastTriviaScanner[] = []
|
|
609
|
+
for (const alt of alts) {
|
|
610
|
+
const scanner = sourceScanner(m[1]! + alt)
|
|
611
|
+
if (scanner === null) return null
|
|
612
|
+
scanners.push(scanner)
|
|
613
|
+
}
|
|
614
|
+
return (input, cur) => {
|
|
615
|
+
for (const scanner of scanners) {
|
|
616
|
+
const end = scanner(input, cur)
|
|
617
|
+
if (end > cur) return end
|
|
618
|
+
}
|
|
619
|
+
return cur
|
|
620
|
+
}
|
|
578
621
|
}
|
|
579
622
|
|
|
580
623
|
/**
|
|
@@ -338,7 +338,7 @@ export function confirmedArityForDef(def: NodeDef): number | null {
|
|
|
338
338
|
where: `node("${def.type ?? '<inferred>'}")`,
|
|
339
339
|
subject,
|
|
340
340
|
fellBackTo: `could not confirm its declared arity — ${unconfirmableReason(def, src)} — `
|
|
341
|
-
+ 'so this node captures children, fields and raw children, logs trivia, and
|
|
341
|
+
+ 'so this node captures children, fields and raw children, logs trivia, and hands it `_ctx.state` on every match',
|
|
342
342
|
otherwise: 'only the tiers the reducer actually declares would be captured '
|
|
343
343
|
+ '(arity >= 1 children, >= 2 fields, >= 4 raw, >= 5 trivia, >= 6 state). '
|
|
344
344
|
+ 'Declare it with `node(..., { buildArity: n })` if it cannot be derived',
|
|
@@ -371,9 +371,9 @@ export function buildReadsTrivia(def: NodeDef): boolean {
|
|
|
371
371
|
return arity >= 5
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
-
/** Build reads the 6th (state) arg? Unknown/unparseable → true (
|
|
374
|
+
/** Build reads the 6th (state) arg? Unknown/unparseable → true (hand it the state). */
|
|
375
375
|
export function buildReadsState(def: NodeDef): boolean {
|
|
376
|
-
if (!def.build) return true // structural node: host may read state
|
|
376
|
+
if (!def.build) return true // structural node: host may read state
|
|
377
377
|
const arity = confirmedArityForDef(def)
|
|
378
378
|
if (arity === null) return true
|
|
379
379
|
return arity >= 6
|
|
@@ -37,6 +37,7 @@ import type { BalancedSpec } from '../combinators/scanTo.ts'
|
|
|
37
37
|
import { deriveExpected } from '../combinators/expect.ts'
|
|
38
38
|
import { assertionFailureExpected, directTerminalFailureExpected } from '../combinators/expected.ts'
|
|
39
39
|
import { keywordsRegExp } from '../combinators/keywords.ts'
|
|
40
|
+
import { hasOwnTriviaBoundary } from '../combinators/trivia-boundary.ts'
|
|
40
41
|
|
|
41
42
|
/** A terminal in the derived alphabet, with its globally assigned id. */
|
|
42
43
|
export type TokenTerminal =
|
|
@@ -135,12 +136,17 @@ export type ExecutableLexNode =
|
|
|
135
136
|
| {
|
|
136
137
|
readonly kind: 'choice'
|
|
137
138
|
readonly arms: readonly ExecutableLexNode[]
|
|
138
|
-
|
|
139
|
+
/** The authored arms: the encoder classifies them as the character choice would. */
|
|
140
|
+
readonly parsers: readonly Combinator<unknown>[]
|
|
141
|
+
/** Inside a no-trivia scope of the token, where the encoder's trivia is none. */
|
|
142
|
+
readonly cleared: boolean
|
|
139
143
|
readonly armExpected: readonly (readonly string[])[]
|
|
140
144
|
readonly expected: readonly string[]
|
|
141
145
|
}
|
|
142
146
|
| { readonly kind: 'not'; readonly body: ExecutableLexNode; readonly expected: readonly string[] }
|
|
143
147
|
| { readonly kind: 'optional'; readonly body: ExecutableLexNode }
|
|
148
|
+
/** A separator-less `many`/`oneOrMore`; `max` −1 is unbounded. */
|
|
149
|
+
| { readonly kind: 'repeat'; readonly body: ExecutableLexNode; readonly min: number; readonly max: number }
|
|
144
150
|
|
|
145
151
|
export type ExecutableFixedLexBody = {
|
|
146
152
|
readonly kind: 'fixed-tree'
|
|
@@ -204,9 +210,22 @@ export function selectOptionalSuffixLexBody(ir: LexicalIr): LexBodyCandidate {
|
|
|
204
210
|
/** Unwrap only scopes that token() has already installed identically. The
|
|
205
211
|
* returned parser is still the authored terminal/sequence; no effectful wrapper
|
|
206
212
|
* or final named winner is guessed from construction metadata. */
|
|
213
|
+
/**
|
|
214
|
+
* A rule reference made with no trivia in force reinstalls the rule's own
|
|
215
|
+
* ambient trivia when the rule has a position that reads it (`ref.ts`, the
|
|
216
|
+
* encoder's `scopedRef`), and inside a token none is in force. Such a rule's
|
|
217
|
+
* terms are then trivia-separated, which a lexical body does not model, so the
|
|
218
|
+
* reference is not lowered.
|
|
219
|
+
*/
|
|
220
|
+
function reinstallsRuleTrivia(reference: Combinator<unknown>, target: Combinator<unknown>): boolean {
|
|
221
|
+
return (reference._meta.grammarTrivia ?? target._meta.grammarTrivia) !== undefined
|
|
222
|
+
&& hasOwnTriviaBoundary(target)
|
|
223
|
+
}
|
|
224
|
+
|
|
207
225
|
function directTokenBodyParser(
|
|
208
226
|
parser: Combinator<unknown>,
|
|
209
227
|
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
228
|
+
scope?: { cleared: boolean },
|
|
210
229
|
): Combinator<unknown> | undefined {
|
|
211
230
|
const outer = parser._def
|
|
212
231
|
if (outer.tag !== 'token') return undefined
|
|
@@ -220,12 +239,13 @@ function directTokenBodyParser(
|
|
|
220
239
|
|| wrapper.captureTrivia === true || wrapper.rootCapture === 'opaque'
|
|
221
240
|
|| wrapper.constructionTrackLines !== 'inherit'
|
|
222
241
|
|| wrapper.constructionCaptureTriviaKinds !== undefined) return undefined
|
|
242
|
+
if (scope !== undefined) scope.cleared = true
|
|
223
243
|
body = wrapper.parser
|
|
224
244
|
continue
|
|
225
245
|
}
|
|
226
246
|
if (wrapper.tag === 'lazy') {
|
|
227
247
|
const target = resolvedLazyTarget(body, resolve)
|
|
228
|
-
if (target === undefined) return undefined
|
|
248
|
+
if (target === undefined || reinstallsRuleTrivia(body, target)) return undefined
|
|
229
249
|
body = target
|
|
230
250
|
continue
|
|
231
251
|
}
|
|
@@ -436,6 +456,7 @@ function directFixedLexNode(
|
|
|
436
456
|
resolve: ((name: string) => Combinator<unknown> | undefined) | undefined,
|
|
437
457
|
active: Set<Combinator<unknown>>,
|
|
438
458
|
pureOnly = false,
|
|
459
|
+
cleared = false,
|
|
439
460
|
): ExecutableLexNode | undefined {
|
|
440
461
|
let current = parser
|
|
441
462
|
const wrappers = new Set<Combinator<unknown>>()
|
|
@@ -444,7 +465,7 @@ function directFixedLexNode(
|
|
|
444
465
|
const def = current._def
|
|
445
466
|
if (def.tag === 'lazy') {
|
|
446
467
|
const target = resolvedLazyTarget(current, resolve)
|
|
447
|
-
if (target === undefined) return undefined
|
|
468
|
+
if (target === undefined || reinstallsRuleTrivia(current, target)) return undefined
|
|
448
469
|
current = target
|
|
449
470
|
continue
|
|
450
471
|
}
|
|
@@ -459,43 +480,57 @@ function directFixedLexNode(
|
|
|
459
480
|
if (terminal !== undefined) return { kind: 'terminal', terminal }
|
|
460
481
|
const def = current._def
|
|
461
482
|
if (def.tag === 'grammar') {
|
|
483
|
+
// A scope with no trivia of its own writes nothing under a token that is
|
|
484
|
+
// not already in force: a cleared one runs as its child in the table
|
|
485
|
+
// (`scopeIsIdentity`) and an inheriting one encodes no row. Its line
|
|
486
|
+
// policy is the table's own (`hasScopedTrackLines`). So it is its child
|
|
487
|
+
// at any position.
|
|
488
|
+
if (def.triviaParser === undefined && def.captureTrivia !== true
|
|
489
|
+
&& def.rootCapture !== 'opaque' && def.constructionCaptureTriviaKinds === undefined) {
|
|
490
|
+
return directFixedLexNode(def.parser, resolve, active, pureOnly, cleared || def.clearTrivia === true)
|
|
491
|
+
}
|
|
462
492
|
if (!pureOnly || (def.parser._def.tag !== 'token'
|
|
463
493
|
&& (def.clearTrivia !== true || def.triviaParser !== undefined
|
|
464
494
|
|| def.captureTrivia === true || def.rootCapture === 'opaque'))) return undefined
|
|
465
|
-
return directFixedLexNode(def.parser, resolve, active, true)
|
|
495
|
+
return directFixedLexNode(def.parser, resolve, active, true, cleared)
|
|
466
496
|
}
|
|
467
497
|
if (def.tag === 'sequence') {
|
|
468
498
|
if (def.parsers.length < 2 || def.parsers.length > 5) return undefined
|
|
469
|
-
const parts = def.parsers.map(part => directFixedLexNode(part, resolve, active, pureOnly))
|
|
499
|
+
const parts = def.parsers.map(part => directFixedLexNode(part, resolve, active, pureOnly, cleared))
|
|
470
500
|
if (parts.some(part => part === undefined)) return undefined
|
|
471
501
|
return { kind: 'sequence', parts: parts as ExecutableLexNode[] }
|
|
472
502
|
}
|
|
473
503
|
if (def.tag === 'choice') {
|
|
474
|
-
if (
|
|
504
|
+
if (def.parsers.length < 2
|
|
475
505
|
|| def.strategy.tag !== 'firstMatch'
|
|
476
506
|
|| def.gates.some(gate => gate !== null)
|
|
477
507
|
|| def.autoNot.some(check => check !== null)) return undefined
|
|
478
|
-
const arms = def.parsers.map(arm => directFixedLexNode(arm, resolve, active, pureOnly))
|
|
508
|
+
const arms = def.parsers.map(arm => directFixedLexNode(arm, resolve, active, pureOnly, cleared))
|
|
479
509
|
if (arms.some(arm => arm === undefined)) return undefined
|
|
480
|
-
const final = classifyFinalChoice(def.parsers, resolve)
|
|
481
510
|
return {
|
|
482
511
|
kind: 'choice', arms: arms as ExecutableLexNode[],
|
|
483
|
-
|
|
512
|
+
parsers: def.parsers, cleared,
|
|
484
513
|
armExpected: def.parsers.map(deriveExpected),
|
|
485
514
|
expected: def.parsers.flatMap(deriveExpected),
|
|
486
515
|
}
|
|
487
516
|
}
|
|
488
517
|
if (def.tag === 'not') {
|
|
489
|
-
const body = directFixedLexNode(def.parser, resolve, active, true)
|
|
518
|
+
const body = directFixedLexNode(def.parser, resolve, active, true, cleared)
|
|
490
519
|
return body === undefined ? undefined : {
|
|
491
520
|
kind: 'not', body,
|
|
492
521
|
expected: assertionFailureExpected(false, def.parser._tag),
|
|
493
522
|
}
|
|
494
523
|
}
|
|
495
524
|
if (def.tag === 'optional') {
|
|
496
|
-
const body = directFixedLexNode(def.parser, resolve, active, pureOnly)
|
|
525
|
+
const body = directFixedLexNode(def.parser, resolve, active, pureOnly, cleared)
|
|
497
526
|
return body === undefined ? undefined : { kind: 'optional', body }
|
|
498
527
|
}
|
|
528
|
+
if (def.tag === 'many' || def.tag === 'oneOrMore') {
|
|
529
|
+
const body = directFixedLexNode(def.parser, resolve, active, pureOnly, cleared)
|
|
530
|
+
return body === undefined ? undefined : {
|
|
531
|
+
kind: 'repeat', body, min: def.tag === 'many' ? 0 : def.min, max: def.max ?? -1,
|
|
532
|
+
}
|
|
533
|
+
}
|
|
499
534
|
return undefined
|
|
500
535
|
} finally {
|
|
501
536
|
active.delete(current)
|
|
@@ -506,9 +541,10 @@ export function directFixedTokenBody(
|
|
|
506
541
|
parser: Combinator<unknown>,
|
|
507
542
|
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
508
543
|
): ExecutableFixedLexBody | undefined {
|
|
509
|
-
const
|
|
544
|
+
const scope = { cleared: false }
|
|
545
|
+
const body = directTokenBodyParser(parser, resolve, scope)
|
|
510
546
|
if (body === undefined) return undefined
|
|
511
|
-
const root = directFixedLexNode(body, resolve, new Set())
|
|
547
|
+
const root = directFixedLexNode(body, resolve, new Set(), false, scope.cleared)
|
|
512
548
|
return root === undefined ? undefined : { kind: 'fixed-tree', root }
|
|
513
549
|
}
|
|
514
550
|
|
|
@@ -1556,8 +1592,11 @@ function normalizeLexical(
|
|
|
1556
1592
|
if (stack.has(parser)) return { refusal: 'recursive token body' }
|
|
1557
1593
|
stack.add(parser)
|
|
1558
1594
|
try {
|
|
1559
|
-
|
|
1560
|
-
|
|
1595
|
+
// A nested `balanced()` is its scanner machine, not the transform that
|
|
1596
|
+
// builds its value — `sequence('@', balanced('{', '}'))` as a skipper.
|
|
1597
|
+
return normalizeBalancedLexical(parser, resolve, session)
|
|
1598
|
+
?? withLexicalControl(session, parser._def.tag, () =>
|
|
1599
|
+
normalizeLexicalBody(parser, resolve, stack, session))
|
|
1561
1600
|
} finally {
|
|
1562
1601
|
stack.delete(parser)
|
|
1563
1602
|
}
|
|
@@ -1841,8 +1880,9 @@ function fixedBindingProjection(
|
|
|
1841
1880
|
// and the emitted adapter captures that site's named body after linking every
|
|
1842
1881
|
// scan root. Only ambient trivia/scanSkip remain live runtime callbacks; they
|
|
1843
1882
|
// are scanner POLICY, never binding authority for these authored own children.
|
|
1844
|
-
// Recovery
|
|
1845
|
-
|
|
1883
|
+
// Recovery still has no equivalent named template. `withCtx` has one: the
|
|
1884
|
+
// emitter's OP_WITHCTX body calls its child by the name `link()` minted.
|
|
1885
|
+
if (edge.parentTag === 'recover') readerMask = 0b011
|
|
1846
1886
|
// Sequence strict, adjacency, and recovery bodies now all use scalar block
|
|
1847
1887
|
// chains, so no supported variant is withheld here.
|
|
1848
1888
|
const words = [
|
|
@@ -111,6 +111,31 @@ export function pushCstChild(ctx: ParseContext, built: unknown, rawEntry: unknow
|
|
|
111
111
|
if (ctx._cstRawChildren) ctx._cstRawChildren.push(rawEntry)
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Undo the LAST `pushCstChild` into the active collector, whichever form it took:
|
|
116
|
+
* a child the lazy buffer's empty sentinel dropped (`undefined` first) leaves no
|
|
117
|
+
* semantic entry to remove, only its raw one.
|
|
118
|
+
*/
|
|
119
|
+
export function popCstChild(ctx: ParseContext): void {
|
|
120
|
+
const b = ctx._cstBuf
|
|
121
|
+
if (b) {
|
|
122
|
+
const ch = b.ch
|
|
123
|
+
if (b.rawOnly !== true && ch) {
|
|
124
|
+
ch.pop()
|
|
125
|
+
if (ch.length === 1) { b.single = ch[0]; b.ch = undefined }
|
|
126
|
+
}
|
|
127
|
+
if (b.noRaw === true) b.rawLen = (b.rawLen ?? 1) - 1
|
|
128
|
+
else if (b.raw) {
|
|
129
|
+
b.raw.pop()
|
|
130
|
+
if (b.raw.length === 1) { b.rawSingle = b.raw[0]; b.raw = undefined }
|
|
131
|
+
} else b.rawSingle = undefined
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
if (ctx._cstChildren) ctx._cstChildren.pop()
|
|
135
|
+
else if (ctx._cstLeaves) ctx._cstLeaves.pop()
|
|
136
|
+
if (ctx._cstRawChildren) ctx._cstRawChildren.pop()
|
|
137
|
+
}
|
|
138
|
+
|
|
114
139
|
export function cstRawLen(ctx: ParseContext): number {
|
|
115
140
|
const b = ctx._cstBuf
|
|
116
141
|
if (b) {
|
package/src/cst/trivia-kinds.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Combinator, ParseContext } from '../types.ts'
|
|
1
|
+
import type { CharRange, Combinator, ParseContext } from '../types.ts'
|
|
2
2
|
import { pushCstTriviaEntry, pushTriviaLogEntry } from './capture-buffer.ts'
|
|
3
3
|
import { startsFirstSet } from '../combinators/first-set.ts'
|
|
4
4
|
import { createDetachedParseContext } from '../parse-context.ts'
|
|
@@ -20,6 +20,14 @@ export type LabeledTriviaSpec = {
|
|
|
20
20
|
*/
|
|
21
21
|
readonly visitor: CssTriviaVisitor | null
|
|
22
22
|
readonly charArms: readonly TriviaArmMatcher[] | null
|
|
23
|
+
/**
|
|
24
|
+
* `table/trivia-gap.ts`'s root-selection fast path, for the selection it was
|
|
25
|
+
* last primed with: the selected arms' first characters, and the unlabelled
|
|
26
|
+
* scanner that answers a run free of them (`null`: no fast path).
|
|
27
|
+
*/
|
|
28
|
+
rootKinds: Readonly<Record<string, number>> | undefined
|
|
29
|
+
rootLeaders: readonly CharRange[]
|
|
30
|
+
rootScan: ((input: string, cur: number) => number) | null
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
/** See `trivia-skip.ts`: bounded scalar memoisation avoids a WeakMap in the
|
|
@@ -114,6 +122,9 @@ export function labeledTriviaSpec(arms: LabeledTriviaSpec['arms'], minRepeats: n
|
|
|
114
122
|
minRepeats,
|
|
115
123
|
visitor: commonLabeledTriviaVisitor({ arms, minRepeats }),
|
|
116
124
|
charArms: charArmsFor({ arms, minRepeats }),
|
|
125
|
+
rootKinds: undefined,
|
|
126
|
+
rootLeaders: [],
|
|
127
|
+
rootScan: null,
|
|
117
128
|
}
|
|
118
129
|
}
|
|
119
130
|
|
package/src/parse-context.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { parseSync } from 'oxc-parser'
|
|
|
11
11
|
import { OP_ATTEMPT, OP_NODE, OP_NODE_TRACK, OP_REPV, OP_SCAN } from '../table/ops.ts'
|
|
12
12
|
import { emitAssemblySource, type EmitResult } from '../table/emit-assembly.ts'
|
|
13
13
|
import { defaultAssemblyCfgs } from '../table/emit.ts'
|
|
14
|
-
import { resolveTable, type TableProgram } from '../table/program.ts'
|
|
14
|
+
import { assemblyExtraIps, resolveTable, type TableProgram } from '../table/program.ts'
|
|
15
15
|
|
|
16
16
|
type Ast = { type: string; start?: number; end?: number; [key: string]: unknown }
|
|
17
17
|
|
|
@@ -229,20 +229,19 @@ export function supercompileEntryAssembly(
|
|
|
229
229
|
* then finds the precompiled assembly through the table object's AST shape and
|
|
230
230
|
* replaces only that factory body. Any mismatch returns the canonical source.
|
|
231
231
|
*/
|
|
232
|
-
export function supercompileRuleMapReplacement(
|
|
232
|
+
export function supercompileRuleMapReplacement(
|
|
233
|
+
prog: TableProgram,
|
|
234
|
+
replacement: string,
|
|
235
|
+
select?: ReadonlySet<number>,
|
|
236
|
+
): string {
|
|
233
237
|
const cfg = defaultAssemblyCfgs(prog)[0]
|
|
234
238
|
if (cfg === undefined) return replacement
|
|
235
|
-
const extraIps
|
|
236
|
-
for (const scan of prog.scans ?? []) {
|
|
237
|
-
for (const ref of scan.skip) extraIps.push(ref[0])
|
|
238
|
-
if (scan.sentinel !== undefined) extraIps.push(scan.sentinel[0])
|
|
239
|
-
}
|
|
240
|
-
for (const set of prog.scanSkip ?? []) for (const ref of set) extraIps.push(ref[0])
|
|
239
|
+
const extraIps = assemblyExtraIps(prog)
|
|
241
240
|
|
|
242
241
|
let canonical: EmitResult
|
|
243
242
|
let candidate: EntrySupercompileResult
|
|
244
243
|
try {
|
|
245
|
-
canonical = emitAssemblySource(resolveTable(prog), prog, cfg, extraIps, true)
|
|
244
|
+
canonical = emitAssemblySource(resolveTable(prog), prog, cfg, extraIps, true, select)
|
|
246
245
|
candidate = supercompileEmittedAssembly(prog, cfg, extraIps, canonical)
|
|
247
246
|
} catch {
|
|
248
247
|
return replacement
|