parseman 0.47.1 → 0.48.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/README.md +27 -21
- package/dist/analysis/diagnostics.js +2863 -353
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +2854 -344
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/expect.d.ts +1 -0
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/expected.d.ts +8 -0
- package/dist/combinators/expected.d.ts.map +1 -0
- package/dist/combinators/first-set.d.ts +8 -0
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/keywords.d.ts +4 -0
- package/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/not.d.ts.map +1 -1
- package/dist/combinators/peek.d.ts.map +1 -1
- package/dist/combinators/regex.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/trivia-skip.d.ts +16 -3
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/direct-projection.d.ts +12 -0
- package/dist/compiler/direct-projection.d.ts.map +1 -0
- package/dist/compiler/token-alphabet.d.ts +845 -5
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.d.ts +3 -0
- package/dist/compiler/token-capability.d.ts.map +1 -0
- package/dist/compiler/token-capability.js +3192 -0
- package/dist/compiler/token-capability.js.map +6 -0
- package/dist/cst/trivia-charscan.d.ts +3 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -1
- package/dist/cst/trivia-css-scanner.d.ts +5 -0
- package/dist/cst/trivia-css-scanner.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.js +2862 -339
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +5 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/index.js +2870 -347
- package/dist/plugin/index.js.map +3 -3
- package/dist/spec/index.js +315 -46
- package/dist/spec/index.js.map +2 -2
- 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 +3 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +12 -13
- package/dist/table/emit-assembly.d.ts.map +1 -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/index.js +2819 -300
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-body.d.ts +9 -0
- package/dist/table/lex-body.d.ts.map +1 -0
- package/dist/table/lex-program.d.ts +25 -0
- package/dist/table/lex-program.d.ts.map +1 -0
- package/dist/table/ops.d.ts +43 -7
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +50 -2
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +13 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +11 -11
- package/src/combinators/expect.ts +1 -0
- package/src/combinators/expected.ts +17 -0
- package/src/combinators/first-set.ts +58 -5
- package/src/combinators/grammar.ts +8 -3
- package/src/combinators/keywords.ts +22 -6
- package/src/combinators/literal.ts +2 -1
- package/src/combinators/not.ts +5 -1
- package/src/combinators/peek.ts +5 -1
- package/src/combinators/regex.ts +5 -3
- package/src/combinators/repeat.ts +4 -10
- package/src/combinators/sequence.ts +17 -14
- package/src/combinators/trivia-skip.ts +46 -13
- package/src/compiler/direct-projection.ts +43 -0
- package/src/compiler/token-alphabet.ts +4316 -12
- package/src/compiler/token-capability.ts +6 -0
- package/src/cst/trivia-charscan.ts +34 -0
- package/src/cst/trivia-css-scanner.ts +142 -0
- package/src/cst/trivia-kinds.ts +3 -1
- package/src/plugin/index.ts +4 -4
- package/src/table/assemble.ts +1211 -221
- package/src/table/child-slots.ts +2 -1
- package/src/table/compile-rule-map.ts +21 -3
- package/src/table/emit-assembly.ts +328 -85
- package/src/table/emit.ts +16 -2
- package/src/table/encode.ts +242 -50
- package/src/table/exec-baseline.ts +3 -7
- package/src/table/exec.ts +121 -7
- package/src/table/lex-body.ts +30 -0
- package/src/table/lex-program.ts +422 -0
- package/src/table/ops.ts +45 -7
- package/src/table/program.ts +46 -2
- package/src/table/scalar-terminal.ts +51 -0
- package/src/table/site-labels.ts +11 -1
- package/src/table/stamp.ts +1 -1
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/analysis/diagnostics.cjs +0 -13824
- package/dist/analysis/diagnostics.cjs.map +0 -6
- package/dist/cli/index.cjs +0 -14925
- package/dist/cli/index.cjs.map +0 -6
- package/dist/index.cjs +0 -16391
- package/dist/index.cjs.map +0 -6
- package/dist/language-service/index.cjs +0 -1304
- package/dist/language-service/index.cjs.map +0 -6
- package/dist/oracle/index.cjs +0 -254
- package/dist/oracle/index.cjs.map +0 -6
- package/dist/plugin/index.cjs +0 -16097
- package/dist/plugin/index.cjs.map +0 -6
- package/dist/run/index.cjs +0 -577
- package/dist/run/index.cjs.map +0 -6
- package/dist/spec/index.cjs +0 -6236
- package/dist/spec/index.cjs.map +0 -6
- package/dist/table/index.cjs +0 -11463
- package/dist/table/index.cjs.map +0 -6
|
@@ -3,6 +3,7 @@ import { fromChar, union, empty } from './first-set.ts'
|
|
|
3
3
|
import { caseFoldVariants } from './case-fold.ts'
|
|
4
4
|
import { failAt } from './probe.ts'
|
|
5
5
|
import { pushCstLeaf, cstCaptureActive } from '../cst/capture-buffer.ts'
|
|
6
|
+
import { directTerminalFailureExpected } from './expected.ts'
|
|
6
7
|
|
|
7
8
|
export type KeywordsOptions = {
|
|
8
9
|
/** Match case-insensitively. */
|
|
@@ -70,6 +71,19 @@ function escapeRe(s: string): string {
|
|
|
70
71
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/** The terminal's one canonical recognizer spelling. Table-selected lexical
|
|
75
|
+
* bodies call this at construction time so the authored parser, CHARACTER row,
|
|
76
|
+
* and TOKEN replacement cannot drift on alternation order, boundary, or flags. */
|
|
77
|
+
export function keywordsRegExp(
|
|
78
|
+
words: readonly string[],
|
|
79
|
+
boundary: string | undefined,
|
|
80
|
+
caseInsensitive: boolean,
|
|
81
|
+
): RegExp {
|
|
82
|
+
const alt = words.map(escapeRe).join('|')
|
|
83
|
+
const boundarySource = boundary ? `(?![${boundary}])` : ''
|
|
84
|
+
return new RegExp(`(?:${alt})${boundarySource}`, caseInsensitive ? 'iy' : 'uy')
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
/**
|
|
74
88
|
* Match any one of a set of fixed keywords, longest-first (so `border` wins over
|
|
75
89
|
* `bord`), with an optional trailing word-boundary guard. Compiles to a single
|
|
@@ -84,8 +98,6 @@ export function keywords(words: readonly string[], opts: KeywordsOptions = {}):
|
|
|
84
98
|
// Longest-first keeps the alternation greedy-correct (regex alternation is
|
|
85
99
|
// first-match, not longest-match).
|
|
86
100
|
const sorted = [...new Set(words)].sort((a, b) => b.length - a.length)
|
|
87
|
-
const alt = sorted.map(escapeRe).join('|')
|
|
88
|
-
const boundary = opts.boundary ? `(?![${opts.boundary}])` : ''
|
|
89
101
|
// Case-INSENSITIVE drops `u` deliberately, so that MATCHING and the first-set
|
|
90
102
|
// below fold the SAME set of characters — the invariant `regex()` established in
|
|
91
103
|
// 0.32.0 (see the flag-aware widening in `src/combinators/regex.ts`):
|
|
@@ -102,8 +114,7 @@ export function keywords(words: readonly string[], opts: KeywordsOptions = {}):
|
|
|
102
114
|
// not entering Unicode mode at all, which keeps the gate.
|
|
103
115
|
// Every keyword is fully escaped by `escapeRe`, so `u` mode changes nothing about
|
|
104
116
|
// what a case-SENSITIVE set matches; that path keeps `u` unchanged.
|
|
105
|
-
const
|
|
106
|
-
const re = new RegExp(`(?:${alt})${boundary}`, flags)
|
|
117
|
+
const re = keywordsRegExp(sorted, opts.boundary, opts.caseInsensitive ?? false)
|
|
107
118
|
|
|
108
119
|
// First-set: the set of first code points across all keywords (and their
|
|
109
120
|
// case-folded variants when case-insensitive), for choice() dispatch.
|
|
@@ -125,16 +136,21 @@ export function keywords(words: readonly string[], opts: KeywordsOptions = {}):
|
|
|
125
136
|
}
|
|
126
137
|
|
|
127
138
|
const meta: ParserMeta = { firstSet, canMatchNewline: false, isTrivia: false }
|
|
139
|
+
const def = {
|
|
140
|
+
tag: 'keywords', words: sorted,
|
|
141
|
+
caseInsensitive: opts.caseInsensitive ?? false,
|
|
142
|
+
boundary: opts.boundary,
|
|
143
|
+
} as const
|
|
128
144
|
|
|
129
145
|
return {
|
|
130
146
|
_tag: 'keywords',
|
|
131
147
|
_meta: meta,
|
|
132
|
-
_def:
|
|
148
|
+
_def: def,
|
|
133
149
|
parse(input: string, pos: number, ctx: ParseContext): ParseResult<string> {
|
|
134
150
|
re.lastIndex = pos
|
|
135
151
|
const m = re.exec(input)
|
|
136
152
|
if (m === null || m.index !== pos) {
|
|
137
|
-
return failAt(ctx,
|
|
153
|
+
return failAt(ctx, directTerminalFailureExpected(def), pos)
|
|
138
154
|
}
|
|
139
155
|
const value = m[0]!
|
|
140
156
|
const span = { start: pos, end: pos + value.length }
|
|
@@ -3,6 +3,7 @@ import { fromChar, empty } from './first-set.ts'
|
|
|
3
3
|
import { failAt } from './probe.ts'
|
|
4
4
|
import { pushCstLeaf, cstCaptureActive } from '../cst/capture-buffer.ts'
|
|
5
5
|
import { recordLineRangeFromContext } from '../line-index.ts'
|
|
6
|
+
import { directTerminalFailureExpected } from './expected.ts'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* ASCII case-fold equality — the interpreter twin of the compiler's `foldEq`
|
|
@@ -59,7 +60,7 @@ export function literal(value: string, opts: LiteralOptions = {}): Combinator<st
|
|
|
59
60
|
: firstSet
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
const expected =
|
|
63
|
+
const expected = directTerminalFailureExpected({ tag: 'literal', value, caseInsensitive })
|
|
63
64
|
|
|
64
65
|
// Single-char case-sensitive literals are the bulk of punctuation-heavy
|
|
65
66
|
// grammars (GraphQL `{ } ( ) : $ @ [ ] ! =`, JSON, CSS). A bare `charCodeAt`
|
package/src/combinators/not.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Combinator, ParseContext, ParseResult, ParserMeta } from '../types.ts'
|
|
2
2
|
import { any } from './first-set.ts'
|
|
3
3
|
import { saveLookaheadMark, rollbackLookahead } from './trivia-skip.ts'
|
|
4
|
+
import { assertionFailureExpected } from './expected.ts'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Negative lookahead. Succeeds (consuming nothing) when `combinator` fails;
|
|
@@ -47,7 +48,10 @@ export function not(combinator: Combinator<unknown>): Combinator<null> {
|
|
|
47
48
|
const result = combinator.parse(input, pos, ctx)
|
|
48
49
|
rollbackLookahead(ctx, mark)
|
|
49
50
|
if (result.ok) {
|
|
50
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
ok: false, expected: assertionFailureExpected(false, combinator._tag),
|
|
53
|
+
span: { start: pos, end: pos },
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
return { ok: true, value: null, span: { start: pos, end: pos } }
|
|
53
57
|
},
|
package/src/combinators/peek.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Combinator, ParseContext, ParseResult, ParserMeta } from '../types.ts'
|
|
2
2
|
import { any, matchesEmpty } from './first-set.ts'
|
|
3
3
|
import { saveLookaheadMark, rollbackLookahead } from './trivia-skip.ts'
|
|
4
|
+
import { assertionFailureExpected } from './expected.ts'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Positive lookahead. Succeeds (consuming nothing) when `combinator` matches at
|
|
@@ -57,7 +58,10 @@ export function peek(combinator: Combinator<unknown>): Combinator<null> {
|
|
|
57
58
|
const result = combinator.parse(input, pos, ctx)
|
|
58
59
|
rollbackLookahead(ctx, mark)
|
|
59
60
|
if (result.ok) return { ok: true, value: null, span: { start: pos, end: pos } }
|
|
60
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
ok: false, expected: assertionFailureExpected(true, combinator._tag),
|
|
63
|
+
span: { start: pos, end: pos },
|
|
64
|
+
}
|
|
61
65
|
},
|
|
62
66
|
}
|
|
63
67
|
}
|
package/src/combinators/regex.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { pushCstLeaf, cstCaptureActive } from '../cst/capture-buffer.ts'
|
|
|
5
5
|
import { recordLineRangeFromContext } from '../line-index.ts'
|
|
6
6
|
import { shorthandRanges, parseClassRanges } from '../regex/classes.ts'
|
|
7
7
|
import { firstSetFromRegex } from '../regex/first-set.ts'
|
|
8
|
+
import { directTerminalFailureExpected } from './expected.ts'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* A regex terminal's first-set (for choice-dispatch fast paths) is derived by
|
|
@@ -141,15 +142,16 @@ export function regex(pattern: string | RegExp, flags = ''): Combinator<string>
|
|
|
141
142
|
: asciiCaseFold(raw.firstSet)
|
|
142
143
|
const canMatchNewline = raw.canMatchNewline
|
|
143
144
|
const meta: ParserMeta = { firstSet, canMatchNewline, isTrivia: false }
|
|
145
|
+
const def = { tag: 'regex', source, flags: resolvedFlags } as const
|
|
144
146
|
|
|
145
147
|
return {
|
|
146
148
|
_tag: 'regex',
|
|
147
149
|
_meta: meta,
|
|
148
|
-
_def:
|
|
150
|
+
_def: def,
|
|
149
151
|
parse(input: string, pos: number, ctx: ParseContext): ParseResult<string> {
|
|
150
152
|
const scanEnd = scan?.(input, pos)
|
|
151
153
|
if (scanEnd !== undefined) {
|
|
152
|
-
if (scanEnd === null) return failAt(ctx,
|
|
154
|
+
if (scanEnd === null) return failAt(ctx, directTerminalFailureExpected(def), pos)
|
|
153
155
|
const value = input.slice(pos, scanEnd)
|
|
154
156
|
const span = { start: pos, end: scanEnd }
|
|
155
157
|
if (canMatchNewline && ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, scanEnd)
|
|
@@ -159,7 +161,7 @@ export function regex(pattern: string | RegExp, flags = ''): Combinator<string>
|
|
|
159
161
|
anchored.lastIndex = pos
|
|
160
162
|
const m = anchored.exec(input)
|
|
161
163
|
if (m === null) {
|
|
162
|
-
return failAt(ctx,
|
|
164
|
+
return failAt(ctx, directTerminalFailureExpected(def), pos)
|
|
163
165
|
}
|
|
164
166
|
const span = { start: pos, end: pos + m[0]!.length }
|
|
165
167
|
if (canMatchNewline && ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, span.end)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Combinator, ParseContext, ParseResult, ParserMeta } from '../types.ts'
|
|
2
|
-
import { advanceTrivia, needsDeferredTriviaCommit, rollbackTrivia, saveTriviaMark,
|
|
2
|
+
import { advanceTrivia, commitTriviaScan, needsDeferredTriviaCommit, rollbackTrivia, saveTriviaMark, scanTriviaCompact } from './trivia-skip.ts'
|
|
3
3
|
import { matchesEmpty, startsFirstSet } from './first-set.ts'
|
|
4
4
|
import { deriveExpected } from './expect.ts'
|
|
5
5
|
import { matchesAt, orSentinel, recoverScan, captureError } from '../recovery/scan.ts'
|
|
@@ -36,9 +36,7 @@ function repItem<T>(
|
|
|
36
36
|
let pos = cur
|
|
37
37
|
if (ctx.trivia) {
|
|
38
38
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
39
|
-
|
|
40
|
-
scan.commit()
|
|
41
|
-
pos = scan.end
|
|
39
|
+
pos = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
42
40
|
} else {
|
|
43
41
|
pos = advanceTrivia(input, cur, ctx)
|
|
44
42
|
}
|
|
@@ -478,9 +476,7 @@ export function sepBy<T, S>(
|
|
|
478
476
|
let sepPos = cur
|
|
479
477
|
if (ctx.trivia) {
|
|
480
478
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
481
|
-
|
|
482
|
-
scan.commit()
|
|
483
|
-
sepPos = scan.end
|
|
479
|
+
sepPos = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
484
480
|
} else {
|
|
485
481
|
sepPos = advanceTrivia(input, cur, ctx)
|
|
486
482
|
}
|
|
@@ -503,9 +499,7 @@ export function sepBy<T, S>(
|
|
|
503
499
|
let nextPos = sep.span.end
|
|
504
500
|
if (ctx.trivia) {
|
|
505
501
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
506
|
-
|
|
507
|
-
scan.commit()
|
|
508
|
-
nextPos = scan.end
|
|
502
|
+
nextPos = commitTriviaScan(scanTriviaCompact(input, sep.span.end, ctx))
|
|
509
503
|
} else {
|
|
510
504
|
nextPos = advanceTrivia(input, sep.span.end, ctx)
|
|
511
505
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Combinator, ParseContext, ParseResult, ParserMeta, ParseFail } from '../types.ts'
|
|
2
2
|
import { sequenceFirstSet, firstSetOf, union } from './first-set.ts'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
advanceTrivia, commitTriviaScan, needsDeferredTriviaCommit, rollbackScannedTriviaAt,
|
|
5
|
+
scanTriviaCompact,
|
|
6
|
+
} from './trivia-skip.ts'
|
|
4
7
|
import { cstTlLen } from '../cst/capture-buffer.ts'
|
|
5
8
|
import { firstSetSentinel } from '../recovery/scan.ts'
|
|
6
9
|
import { adjacencyFail, adjacencyOf, holdsAdjacency, type AdjacencyDef } from './adjacency.ts'
|
|
@@ -86,9 +89,7 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
86
89
|
const mRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
87
90
|
let scanEnd: number
|
|
88
91
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
89
|
-
|
|
90
|
-
scan.commit()
|
|
91
|
-
scanEnd = scan.end
|
|
92
|
+
scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
92
93
|
} else {
|
|
93
94
|
scanEnd = advanceTrivia(input, cur, ctx)
|
|
94
95
|
}
|
|
@@ -98,7 +99,9 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
98
99
|
const result = parsers[i]!.parse(input, scanEnd, ctx)
|
|
99
100
|
if (!result.ok) return result as ParseFail
|
|
100
101
|
if (result.span.end > scanEnd) cur = result.span.end
|
|
101
|
-
else rollbackScannedTriviaAt(
|
|
102
|
+
else rollbackScannedTriviaAt(
|
|
103
|
+
ctx, mTlog, scanTlog, mLog, scanLog, mRootLog, scanRootLog,
|
|
104
|
+
)
|
|
102
105
|
if (values !== undefined) values.push(result.value)
|
|
103
106
|
continue
|
|
104
107
|
}
|
|
@@ -141,9 +144,7 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
141
144
|
const mLog = ctx._triviaLog?.length ?? 0
|
|
142
145
|
const mRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
143
146
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
144
|
-
|
|
145
|
-
scan.commit()
|
|
146
|
-
scanEnd = scan.end
|
|
147
|
+
scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
147
148
|
} else {
|
|
148
149
|
scanEnd = advanceTrivia(input, cur, ctx)
|
|
149
150
|
}
|
|
@@ -153,7 +154,9 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
153
154
|
const result = parsers[i]!.parse(input, scanEnd, ctx)
|
|
154
155
|
if (!result.ok) return result as ParseFail
|
|
155
156
|
if (result.span.end > scanEnd) cur = result.span.end
|
|
156
|
-
else rollbackScannedTriviaAt(
|
|
157
|
+
else rollbackScannedTriviaAt(
|
|
158
|
+
ctx, mTlog, scanTlog, mLog, scanLog, mRootLog, scanRootLog,
|
|
159
|
+
)
|
|
157
160
|
if (values !== undefined) values.push(result.value)
|
|
158
161
|
continue
|
|
159
162
|
}
|
|
@@ -199,22 +202,22 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
199
202
|
const mRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
200
203
|
|
|
201
204
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
202
|
-
|
|
203
|
-
scan.commit()
|
|
204
|
-
scanEnd = scan.end
|
|
205
|
+
scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
205
206
|
} else {
|
|
206
207
|
scanEnd = advanceTrivia(input, cur, ctx)
|
|
207
208
|
}
|
|
208
|
-
|
|
209
209
|
const scanTlog = cstTlLen(ctx)
|
|
210
210
|
const scanLog = ctx._triviaLog?.length ?? 0
|
|
211
211
|
const scanRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
212
|
+
|
|
212
213
|
const result = parsers[i]!.parse(input, scanEnd, ctx)
|
|
213
214
|
if (!result.ok) return result as ParseFail
|
|
214
215
|
if (result.span.end > scanEnd) {
|
|
215
216
|
cur = result.span.end
|
|
216
217
|
} else {
|
|
217
|
-
rollbackScannedTriviaAt(
|
|
218
|
+
rollbackScannedTriviaAt(
|
|
219
|
+
ctx, mTlog, scanTlog, mLog, scanLog, mRootLog, scanRootLog,
|
|
220
|
+
)
|
|
218
221
|
}
|
|
219
222
|
if (values !== undefined) values.push(result.value)
|
|
220
223
|
continue
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from '../cst/capture-buffer.ts'
|
|
17
17
|
import { recordLineRangeFromContext } from '../line-index.ts'
|
|
18
18
|
import { createDetachedParseContext } from '../parse-context.ts'
|
|
19
|
+
import { commonCssTriviaScanner } from '../cst/trivia-css-scanner.ts'
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Result of scanning trivia: the position after it, plus a `commit()` that
|
|
@@ -25,11 +26,22 @@ import { createDetachedParseContext } from '../parse-context.ts'
|
|
|
25
26
|
* trivia that doesn't actually sit between two accepted items.
|
|
26
27
|
*/
|
|
27
28
|
export type TriviaScan = { end: number; commit: () => void }
|
|
29
|
+
export type CompactTriviaScan = number | TriviaScan
|
|
28
30
|
|
|
29
31
|
/** Saved lengths for rolling back speculative trivia commits. */
|
|
30
32
|
export type TriviaRollbackMark = { raw: number; tlog: number; leaves: number; fields: number; errors: number; log: number; rootLog: number }
|
|
31
33
|
|
|
32
34
|
const NOOP_COMMIT = () => {}
|
|
35
|
+
|
|
36
|
+
export function triviaScanEnd(scan: CompactTriviaScan): number {
|
|
37
|
+
return typeof scan === 'number' ? scan : scan.end
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function commitTriviaScan(scan: CompactTriviaScan): number {
|
|
41
|
+
if (typeof scan === 'number') return scan
|
|
42
|
+
scan.commit()
|
|
43
|
+
return scan.end
|
|
44
|
+
}
|
|
33
45
|
export type FastTriviaScanner = (input: string, cur: number) => number
|
|
34
46
|
/**
|
|
35
47
|
* Bounded structural cache for the handful of trivia grammars a process actively
|
|
@@ -102,8 +114,14 @@ function removeNumberRange(values: number[] | undefined, start: number, end: num
|
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
/**
|
|
105
|
-
* Remove only the trivia rows appended by an ambient scan, preserving
|
|
106
|
-
*
|
|
117
|
+
* Remove only the trivia rows appended by an ambient scan, preserving rows a
|
|
118
|
+
* successful zero-width child appended after that scan.
|
|
119
|
+
*
|
|
120
|
+
* A sequence cannot use `rollbackTriviaAt` for this case: its pre-scan mark also
|
|
121
|
+
* precedes the child, so truncating to it erases the child's nodes, fields,
|
|
122
|
+
* recovery errors, and trivia. The scan's contribution is instead the half-open
|
|
123
|
+
* range between the pre-scan and post-scan lengths. Compact that range in place;
|
|
124
|
+
* this is the cold zero-width-success path and allocates no replacement arrays.
|
|
107
125
|
*/
|
|
108
126
|
export function rollbackScannedTriviaAt(
|
|
109
127
|
ctx: ParseContext,
|
|
@@ -169,10 +187,10 @@ export function rollbackLookahead(ctx: ParseContext, mark: LookaheadMark): void
|
|
|
169
187
|
if (ctx._probe) ctx._probe.best = mark.probeBest
|
|
170
188
|
}
|
|
171
189
|
|
|
172
|
-
function scanWithLabels(input: string, cur: number, ctx: ParseContext):
|
|
190
|
+
function scanWithLabels(input: string, cur: number, ctx: ParseContext): CompactTriviaScan {
|
|
173
191
|
const triviaP = ctx.trivia!
|
|
174
192
|
const spec = analyzeLabeledTrivia(triviaP)
|
|
175
|
-
if (!spec) return
|
|
193
|
+
if (!spec) return cur
|
|
176
194
|
|
|
177
195
|
const log = ctx._triviaLog
|
|
178
196
|
const rootLog = ctx._rootTriviaLog
|
|
@@ -193,7 +211,8 @@ function scanWithLabels(input: string, cur: number, ctx: ParseContext): TriviaSc
|
|
|
193
211
|
}
|
|
194
212
|
})
|
|
195
213
|
if (visitedEnd !== undefined) {
|
|
196
|
-
if (visitedEnd === cur) return
|
|
214
|
+
if (visitedEnd === cur) return cur
|
|
215
|
+
if (fullRows === undefined && rootRows === undefined && cstRows === undefined) return visitedEnd
|
|
197
216
|
return {
|
|
198
217
|
end: visitedEnd,
|
|
199
218
|
commit: () => {
|
|
@@ -213,7 +232,7 @@ function scanWithLabels(input: string, cur: number, ctx: ParseContext): TriviaSc
|
|
|
213
232
|
}
|
|
214
233
|
|
|
215
234
|
const { end, chunks } = scanLabeledTriviaChunks(input, cur, spec, ctx.state)
|
|
216
|
-
if (end === cur) return
|
|
235
|
+
if (end === cur) return cur
|
|
217
236
|
|
|
218
237
|
return {
|
|
219
238
|
end,
|
|
@@ -273,9 +292,11 @@ export function advanceTrivia(input: string, cur: number, ctx: ParseContext): nu
|
|
|
273
292
|
|
|
274
293
|
/**
|
|
275
294
|
* Scan trivia at `cur` using `ctx.trivia`, WITHOUT recording it. Returns the
|
|
276
|
-
* position
|
|
295
|
+
* position directly when a classified scan retained no rows; otherwise returns
|
|
296
|
+
* that position with a deferred `commit()` that records the retained rows.
|
|
297
|
+
* Use `triviaScanEnd()` / `commitTriviaScan()` rather than inspecting the shape.
|
|
277
298
|
*/
|
|
278
|
-
export function
|
|
299
|
+
export function scanTriviaCompact(input: string, cur: number, ctx: ParseContext): CompactTriviaScan {
|
|
279
300
|
const triviaP = ctx.trivia
|
|
280
301
|
if (!triviaP) return { end: cur, commit: NOOP_COMMIT }
|
|
281
302
|
|
|
@@ -337,7 +358,7 @@ export function scanTrivia(input: string, cur: number, ctx: ParseContext): Trivi
|
|
|
337
358
|
|
|
338
359
|
if (ctx.triviaKindLabels && (log !== undefined || rootLog !== undefined || captureTl)) {
|
|
339
360
|
const scan = scanWithLabels(input, cur, ctx)
|
|
340
|
-
if (trackTriviaLines) recordLineRangeFromContext(ctx, input, cur, scan
|
|
361
|
+
if (trackTriviaLines) recordLineRangeFromContext(ctx, input, cur, triviaScanEnd(scan))
|
|
341
362
|
return scan
|
|
342
363
|
}
|
|
343
364
|
|
|
@@ -377,6 +398,12 @@ export function scanTrivia(input: string, cur: number, ctx: ParseContext): Trivi
|
|
|
377
398
|
return { end: tr.ok ? tr.span.end : cur, commit: NOOP_COMMIT }
|
|
378
399
|
}
|
|
379
400
|
|
|
401
|
+
/** Legacy object-shaped contract used by previously emitted assembly factories. */
|
|
402
|
+
export function scanTrivia(input: string, cur: number, ctx: ParseContext): TriviaScan {
|
|
403
|
+
const scan = scanTriviaCompact(input, cur, ctx)
|
|
404
|
+
return typeof scan === 'number' ? { end: scan, commit: NOOP_COMMIT } : scan
|
|
405
|
+
}
|
|
406
|
+
|
|
380
407
|
/**
|
|
381
408
|
* Skip trivia at `cur` through an INSTALLED scanner, recording it immediately.
|
|
382
409
|
*
|
|
@@ -410,9 +437,7 @@ export function skipTriviaScanned(
|
|
|
410
437
|
*/
|
|
411
438
|
export function consumeTrivia(input: string, cur: number, ctx: ParseContext): number {
|
|
412
439
|
if (!needsDeferredTriviaCommit(ctx)) return advanceTrivia(input, cur, ctx)
|
|
413
|
-
|
|
414
|
-
scan.commit()
|
|
415
|
-
return scan.end
|
|
440
|
+
return commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
416
441
|
}
|
|
417
442
|
|
|
418
443
|
/**
|
|
@@ -436,7 +461,7 @@ export function probeTriviaEnd(input: string, cur: number, ctx: ParseContext): n
|
|
|
436
461
|
// actually need to find the end (`trivia`, `state`, the deferred-commit sinks)
|
|
437
462
|
// is carried through unchanged.
|
|
438
463
|
const probeCtx: ParseContext = { ...ctx, trackLines: false }
|
|
439
|
-
if (needsDeferredTriviaCommit(probeCtx)) return
|
|
464
|
+
if (needsDeferredTriviaCommit(probeCtx)) return triviaScanEnd(scanTriviaCompact(input, cur, probeCtx))
|
|
440
465
|
return advanceTrivia(input, cur, probeCtx)
|
|
441
466
|
}
|
|
442
467
|
|
|
@@ -480,11 +505,19 @@ function buildFastTriviaScanner(trivia: Combinator<unknown>): FastTriviaScanner
|
|
|
480
505
|
if (one) return loopScanner([one])
|
|
481
506
|
|
|
482
507
|
if (repeat._def.tag !== 'choice') return null
|
|
508
|
+
const fused = commonCssTriviaScanner(repeat._def.parsers.map(triviaRegexSource))
|
|
509
|
+
if (fused !== null) return fused
|
|
483
510
|
const arms = repeat._def.parsers.map(regexTriviaScanner)
|
|
484
511
|
if (arms.some(s => s === null)) return null
|
|
485
512
|
return loopScanner(arms as FastTriviaScanner[])
|
|
486
513
|
}
|
|
487
514
|
|
|
515
|
+
/** The regex source beneath a classified-trivia `label()`, when it is plain. */
|
|
516
|
+
function triviaRegexSource(parser: Combinator<unknown>): string | null {
|
|
517
|
+
const d = parser._def.tag === 'label' ? parser._def.parser._def : parser._def
|
|
518
|
+
return d.tag === 'regex' && d.flags === '' ? d.source : null
|
|
519
|
+
}
|
|
520
|
+
|
|
488
521
|
function loopScanner(arms: FastTriviaScanner[]): FastTriviaScanner {
|
|
489
522
|
return (input, cur) => {
|
|
490
523
|
let pos = cur
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognise the deliberately tiny transform shape whose result is already one
|
|
3
|
+
* of a sequence's values: `([, value]) => value`.
|
|
4
|
+
*
|
|
5
|
+
* This is a descriptor extraction, not callback source execution. Anything
|
|
6
|
+
* with defaults, rest, nesting, a block body, or an expression is left as an
|
|
7
|
+
* ordinary callback. `fnSrc` is preferred because serialized grammars carry
|
|
8
|
+
* the author's stable source; live combinators fall back to the function's own
|
|
9
|
+
* source only for the same exact syntax.
|
|
10
|
+
*/
|
|
11
|
+
export function directArrayProjection(
|
|
12
|
+
fn: (...args: never[]) => unknown,
|
|
13
|
+
fnSrc: string | undefined,
|
|
14
|
+
arity: number,
|
|
15
|
+
): number | null {
|
|
16
|
+
let source = fnSrc
|
|
17
|
+
if (source === undefined) {
|
|
18
|
+
source = Function.prototype.toString.call(fn)
|
|
19
|
+
if (source.includes('[native code]')) return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const match = /^\s*\(\s*\[([^\]]*)\]\s*\)\s*=>\s*([A-Za-z_$][\w$]*)\s*$/.exec(source)
|
|
23
|
+
if (match === null) return null
|
|
24
|
+
|
|
25
|
+
const slots = match[1]!.split(',').map(slot => slot.trim())
|
|
26
|
+
// Array destructuring may omit trailing elements (`([key]) => key`) while
|
|
27
|
+
// the sequence still has more children. Only an explicit slot can be the
|
|
28
|
+
// result, and an over-long pattern cannot describe this sequence.
|
|
29
|
+
if (slots.length > arity) return null
|
|
30
|
+
|
|
31
|
+
const result = match[2]!
|
|
32
|
+
let selected = -1
|
|
33
|
+
for (let i = 0; i < slots.length; i++) {
|
|
34
|
+
const slot = slots[i]!
|
|
35
|
+
if (slot === '') continue
|
|
36
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(slot)) return null
|
|
37
|
+
if (slot === result) {
|
|
38
|
+
if (selected >= 0) return null
|
|
39
|
+
selected = i
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return selected >= 0 ? selected : null
|
|
43
|
+
}
|