parseman 0.47.0 → 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 +2955 -357
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +2946 -348
- 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 +19 -1
- 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 +2959 -348
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +7 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/index.js +2969 -358
- package/dist/plugin/index.js.map +3 -3
- package/dist/spec/index.js +366 -49
- 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 +13 -14
- 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 +4839 -2232
- 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 +47 -4
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +53 -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 +12 -11
- package/src/combinators/expect.ts +1 -0
- package/src/combinators/expected.ts +17 -0
- package/src/combinators/first-set.ts +82 -3
- 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 +35 -16
- package/src/combinators/trivia-skip.ts +75 -11
- 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 +1224 -222
- package/src/table/child-slots.ts +4 -3
- package/src/table/compile-rule-map.ts +21 -3
- package/src/table/emit-assembly.ts +363 -109
- package/src/table/emit.ts +26 -1
- package/src/table/encode.ts +246 -54
- package/src/table/exec-baseline.ts +3 -7
- package/src/table/exec.ts +140 -18
- package/src/table/lex-body.ts +30 -0
- package/src/table/lex-program.ts +422 -0
- package/src/table/ops.ts +50 -4
- package/src/table/program.ts +67 -2
- package/src/table/scalar-terminal.ts +51 -0
- package/src/table/site-labels.ts +13 -3
- package/src/table/stamp.ts +3 -3
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/analysis/diagnostics.cjs +0 -13736
- package/dist/analysis/diagnostics.cjs.map +0 -6
- package/dist/cli/index.cjs +0 -14837
- package/dist/cli/index.cjs.map +0 -6
- package/dist/index.cjs +0 -16303
- package/dist/index.cjs.map +0 -6
- package/dist/language-service/index.cjs +0 -1302
- 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 -16009
- 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 -6188
- package/dist/spec/index.cjs.map +0 -6
- package/dist/table/index.cjs +0 -11375
- package/dist/table/index.cjs.map +0 -6
|
@@ -115,8 +115,10 @@ export function parser<T>(opts: ParserOptions, root: Combinator<T>): ParsemanPar
|
|
|
115
115
|
const refuseUnclassified = scopeTrivia !== undefined
|
|
116
116
|
&& !scopeTrivia._meta?.rootTriviaClassified && !opaqueRootCapture
|
|
117
117
|
const forceCaptureTrivia = opts.captureTrivia === true
|
|
118
|
-
const
|
|
119
|
-
: opts.trackLines === false ?
|
|
118
|
+
const trackLinesPolicy = opts.trackLines === true ? 'on'
|
|
119
|
+
: opts.trackLines === false ? 'off' : 'inherit'
|
|
120
|
+
const trackLinesOf = trackLinesPolicy === 'on' ? TRACK_LINES_ON
|
|
121
|
+
: trackLinesPolicy === 'off' ? TRACK_LINES_OFF
|
|
120
122
|
: TRACK_LINES_INHERIT
|
|
121
123
|
/**
|
|
122
124
|
* The per-node capture mask, resolved as far as this scope can resolve it.
|
|
@@ -130,7 +132,7 @@ export function parser<T>(opts: ParserOptions, root: Combinator<T>): ParsemanPar
|
|
|
130
132
|
: scopeLabels !== undefined ? triviaKindMask(scopeLabels, captureKinds)
|
|
131
133
|
: undefined
|
|
132
134
|
const inheritCaptureMask = captureKinds !== undefined && scopeLabels === undefined
|
|
133
|
-
|
|
135
|
+
const grammar: ParsemanParser<T> = {
|
|
134
136
|
_tag: 'grammar',
|
|
135
137
|
_meta: {
|
|
136
138
|
...root._meta,
|
|
@@ -150,6 +152,8 @@ export function parser<T>(opts: ParserOptions, root: Combinator<T>): ParsemanPar
|
|
|
150
152
|
...(opaqueRootCapture ? { rootCapture: 'opaque' as const } : {}),
|
|
151
153
|
...(opts.captureTrivia ? { captureTrivia: true } : {}),
|
|
152
154
|
trackLines: opts.trackLines ?? false,
|
|
155
|
+
constructionTrackLines: trackLinesPolicy,
|
|
156
|
+
...(captureKinds === undefined ? {} : { constructionCaptureTriviaKinds: captureKinds }),
|
|
153
157
|
},
|
|
154
158
|
parse(input: string, pos?: number, _ctx?: ParseContext): ParseResult<T> {
|
|
155
159
|
if (refuseUnclassified) refuseUnclassifiedRootScope(_ctx?._rootTriviaStrictScopes)
|
|
@@ -197,6 +201,7 @@ export function parser<T>(opts: ParserOptions, root: Combinator<T>): ParsemanPar
|
|
|
197
201
|
return lineIndex ? annotateResultLines(result, normalizeLineIndex(lineIndex)) : result
|
|
198
202
|
},
|
|
199
203
|
} as ParsemanParser<T>
|
|
204
|
+
return grammar
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
/**
|
|
@@ -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,10 @@
|
|
|
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'
|
|
7
|
+
import { cstTlLen } from '../cst/capture-buffer.ts'
|
|
4
8
|
import { firstSetSentinel } from '../recovery/scan.ts'
|
|
5
9
|
import { adjacencyFail, adjacencyOf, holdsAdjacency, type AdjacencyDef } from './adjacency.ts'
|
|
6
10
|
|
|
@@ -80,19 +84,24 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
80
84
|
continue
|
|
81
85
|
}
|
|
82
86
|
if (ctx.trivia && i > 0) {
|
|
83
|
-
const
|
|
87
|
+
const mTlog = cstTlLen(ctx)
|
|
88
|
+
const mLog = ctx._triviaLog?.length ?? 0
|
|
89
|
+
const mRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
84
90
|
let scanEnd: number
|
|
85
91
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
86
|
-
|
|
87
|
-
scan.commit()
|
|
88
|
-
scanEnd = scan.end
|
|
92
|
+
scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
89
93
|
} else {
|
|
90
94
|
scanEnd = advanceTrivia(input, cur, ctx)
|
|
91
95
|
}
|
|
96
|
+
const scanTlog = cstTlLen(ctx)
|
|
97
|
+
const scanLog = ctx._triviaLog?.length ?? 0
|
|
98
|
+
const scanRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
92
99
|
const result = parsers[i]!.parse(input, scanEnd, ctx)
|
|
93
100
|
if (!result.ok) return result as ParseFail
|
|
94
101
|
if (result.span.end > scanEnd) cur = result.span.end
|
|
95
|
-
else
|
|
102
|
+
else rollbackScannedTriviaAt(
|
|
103
|
+
ctx, mTlog, scanTlog, mLog, scanLog, mRootLog, scanRootLog,
|
|
104
|
+
)
|
|
96
105
|
if (values !== undefined) values.push(result.value)
|
|
97
106
|
continue
|
|
98
107
|
}
|
|
@@ -131,18 +140,23 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
131
140
|
}
|
|
132
141
|
if (ctx.trivia && i > 0) {
|
|
133
142
|
let scanEnd: number
|
|
134
|
-
const
|
|
143
|
+
const mTlog = cstTlLen(ctx)
|
|
144
|
+
const mLog = ctx._triviaLog?.length ?? 0
|
|
145
|
+
const mRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
135
146
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
136
|
-
|
|
137
|
-
scan.commit()
|
|
138
|
-
scanEnd = scan.end
|
|
147
|
+
scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
139
148
|
} else {
|
|
140
149
|
scanEnd = advanceTrivia(input, cur, ctx)
|
|
141
150
|
}
|
|
151
|
+
const scanTlog = cstTlLen(ctx)
|
|
152
|
+
const scanLog = ctx._triviaLog?.length ?? 0
|
|
153
|
+
const scanRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
142
154
|
const result = parsers[i]!.parse(input, scanEnd, ctx)
|
|
143
155
|
if (!result.ok) return result as ParseFail
|
|
144
156
|
if (result.span.end > scanEnd) cur = result.span.end
|
|
145
|
-
else
|
|
157
|
+
else rollbackScannedTriviaAt(
|
|
158
|
+
ctx, mTlog, scanTlog, mLog, scanLog, mRootLog, scanRootLog,
|
|
159
|
+
)
|
|
146
160
|
if (values !== undefined) values.push(result.value)
|
|
147
161
|
continue
|
|
148
162
|
}
|
|
@@ -183,22 +197,27 @@ export function sequence<T extends [Combinator<unknown>, ...Combinator<unknown>[
|
|
|
183
197
|
// this term actually matches content past the trivia. A term that matches
|
|
184
198
|
// empty (optional/many/lookahead) leaves the trivia for the enclosing rule.
|
|
185
199
|
let scanEnd: number
|
|
186
|
-
|
|
200
|
+
const mTlog = cstTlLen(ctx)
|
|
201
|
+
const mLog = ctx._triviaLog?.length ?? 0
|
|
202
|
+
const mRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
187
203
|
|
|
188
204
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
189
|
-
|
|
190
|
-
scan.commit()
|
|
191
|
-
scanEnd = scan.end
|
|
205
|
+
scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
192
206
|
} else {
|
|
193
207
|
scanEnd = advanceTrivia(input, cur, ctx)
|
|
194
208
|
}
|
|
209
|
+
const scanTlog = cstTlLen(ctx)
|
|
210
|
+
const scanLog = ctx._triviaLog?.length ?? 0
|
|
211
|
+
const scanRootLog = ctx._rootTriviaLog?.length ?? 0
|
|
195
212
|
|
|
196
213
|
const result = parsers[i]!.parse(input, scanEnd, ctx)
|
|
197
214
|
if (!result.ok) return result as ParseFail
|
|
198
215
|
if (result.span.end > scanEnd) {
|
|
199
216
|
cur = result.span.end
|
|
200
217
|
} else {
|
|
201
|
-
|
|
218
|
+
rollbackScannedTriviaAt(
|
|
219
|
+
ctx, mTlog, scanTlog, mLog, scanLog, mRootLog, scanRootLog,
|
|
220
|
+
)
|
|
202
221
|
}
|
|
203
222
|
if (values !== undefined) values.push(result.value)
|
|
204
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
|
|
@@ -95,6 +107,43 @@ export function rollbackTrivia(ctx: ParseContext, mark: TriviaRollbackMark): voi
|
|
|
95
107
|
if (ctx._rootTriviaLog && ctx._rootTriviaLog.length !== mark.rootLog) ctx._rootTriviaLog.length = mark.rootLog
|
|
96
108
|
}
|
|
97
109
|
|
|
110
|
+
function removeNumberRange(values: number[] | undefined, start: number, end: number): void {
|
|
111
|
+
if (values === undefined || end <= start) return
|
|
112
|
+
if (values.length > end) values.copyWithin(start, end)
|
|
113
|
+
values.length -= end - start
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
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.
|
|
125
|
+
*/
|
|
126
|
+
export function rollbackScannedTriviaAt(
|
|
127
|
+
ctx: ParseContext,
|
|
128
|
+
tlogStart: number,
|
|
129
|
+
tlogEnd: number,
|
|
130
|
+
logStart: number,
|
|
131
|
+
logEnd: number,
|
|
132
|
+
rootLogStart: number,
|
|
133
|
+
rootLogEnd: number,
|
|
134
|
+
): void {
|
|
135
|
+
const b = ctx._cstBuf
|
|
136
|
+
if (b !== undefined) {
|
|
137
|
+
const tlog = b.tl
|
|
138
|
+
removeNumberRange(tlog, tlogStart, tlogEnd)
|
|
139
|
+
if (tlog !== undefined && tlog.length === 0) b.tl = undefined
|
|
140
|
+
} else {
|
|
141
|
+
removeNumberRange(ctx._cstTriviaLog, tlogStart, tlogEnd)
|
|
142
|
+
}
|
|
143
|
+
removeNumberRange(ctx._triviaLog, logStart, logEnd)
|
|
144
|
+
removeNumberRange(ctx._rootTriviaLog, rootLogStart, rootLogEnd)
|
|
145
|
+
}
|
|
146
|
+
|
|
98
147
|
function parseTriviaNoCapture(
|
|
99
148
|
triviaP: Combinator<unknown>,
|
|
100
149
|
input: string,
|
|
@@ -138,10 +187,10 @@ export function rollbackLookahead(ctx: ParseContext, mark: LookaheadMark): void
|
|
|
138
187
|
if (ctx._probe) ctx._probe.best = mark.probeBest
|
|
139
188
|
}
|
|
140
189
|
|
|
141
|
-
function scanWithLabels(input: string, cur: number, ctx: ParseContext):
|
|
190
|
+
function scanWithLabels(input: string, cur: number, ctx: ParseContext): CompactTriviaScan {
|
|
142
191
|
const triviaP = ctx.trivia!
|
|
143
192
|
const spec = analyzeLabeledTrivia(triviaP)
|
|
144
|
-
if (!spec) return
|
|
193
|
+
if (!spec) return cur
|
|
145
194
|
|
|
146
195
|
const log = ctx._triviaLog
|
|
147
196
|
const rootLog = ctx._rootTriviaLog
|
|
@@ -162,7 +211,8 @@ function scanWithLabels(input: string, cur: number, ctx: ParseContext): TriviaSc
|
|
|
162
211
|
}
|
|
163
212
|
})
|
|
164
213
|
if (visitedEnd !== undefined) {
|
|
165
|
-
if (visitedEnd === cur) return
|
|
214
|
+
if (visitedEnd === cur) return cur
|
|
215
|
+
if (fullRows === undefined && rootRows === undefined && cstRows === undefined) return visitedEnd
|
|
166
216
|
return {
|
|
167
217
|
end: visitedEnd,
|
|
168
218
|
commit: () => {
|
|
@@ -182,7 +232,7 @@ function scanWithLabels(input: string, cur: number, ctx: ParseContext): TriviaSc
|
|
|
182
232
|
}
|
|
183
233
|
|
|
184
234
|
const { end, chunks } = scanLabeledTriviaChunks(input, cur, spec, ctx.state)
|
|
185
|
-
if (end === cur) return
|
|
235
|
+
if (end === cur) return cur
|
|
186
236
|
|
|
187
237
|
return {
|
|
188
238
|
end,
|
|
@@ -242,9 +292,11 @@ export function advanceTrivia(input: string, cur: number, ctx: ParseContext): nu
|
|
|
242
292
|
|
|
243
293
|
/**
|
|
244
294
|
* Scan trivia at `cur` using `ctx.trivia`, WITHOUT recording it. Returns the
|
|
245
|
-
* 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.
|
|
246
298
|
*/
|
|
247
|
-
export function
|
|
299
|
+
export function scanTriviaCompact(input: string, cur: number, ctx: ParseContext): CompactTriviaScan {
|
|
248
300
|
const triviaP = ctx.trivia
|
|
249
301
|
if (!triviaP) return { end: cur, commit: NOOP_COMMIT }
|
|
250
302
|
|
|
@@ -306,7 +358,7 @@ export function scanTrivia(input: string, cur: number, ctx: ParseContext): Trivi
|
|
|
306
358
|
|
|
307
359
|
if (ctx.triviaKindLabels && (log !== undefined || rootLog !== undefined || captureTl)) {
|
|
308
360
|
const scan = scanWithLabels(input, cur, ctx)
|
|
309
|
-
if (trackTriviaLines) recordLineRangeFromContext(ctx, input, cur, scan
|
|
361
|
+
if (trackTriviaLines) recordLineRangeFromContext(ctx, input, cur, triviaScanEnd(scan))
|
|
310
362
|
return scan
|
|
311
363
|
}
|
|
312
364
|
|
|
@@ -346,6 +398,12 @@ export function scanTrivia(input: string, cur: number, ctx: ParseContext): Trivi
|
|
|
346
398
|
return { end: tr.ok ? tr.span.end : cur, commit: NOOP_COMMIT }
|
|
347
399
|
}
|
|
348
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
|
+
|
|
349
407
|
/**
|
|
350
408
|
* Skip trivia at `cur` through an INSTALLED scanner, recording it immediately.
|
|
351
409
|
*
|
|
@@ -379,9 +437,7 @@ export function skipTriviaScanned(
|
|
|
379
437
|
*/
|
|
380
438
|
export function consumeTrivia(input: string, cur: number, ctx: ParseContext): number {
|
|
381
439
|
if (!needsDeferredTriviaCommit(ctx)) return advanceTrivia(input, cur, ctx)
|
|
382
|
-
|
|
383
|
-
scan.commit()
|
|
384
|
-
return scan.end
|
|
440
|
+
return commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
385
441
|
}
|
|
386
442
|
|
|
387
443
|
/**
|
|
@@ -405,7 +461,7 @@ export function probeTriviaEnd(input: string, cur: number, ctx: ParseContext): n
|
|
|
405
461
|
// actually need to find the end (`trivia`, `state`, the deferred-commit sinks)
|
|
406
462
|
// is carried through unchanged.
|
|
407
463
|
const probeCtx: ParseContext = { ...ctx, trackLines: false }
|
|
408
|
-
if (needsDeferredTriviaCommit(probeCtx)) return
|
|
464
|
+
if (needsDeferredTriviaCommit(probeCtx)) return triviaScanEnd(scanTriviaCompact(input, cur, probeCtx))
|
|
409
465
|
return advanceTrivia(input, cur, probeCtx)
|
|
410
466
|
}
|
|
411
467
|
|
|
@@ -449,11 +505,19 @@ function buildFastTriviaScanner(trivia: Combinator<unknown>): FastTriviaScanner
|
|
|
449
505
|
if (one) return loopScanner([one])
|
|
450
506
|
|
|
451
507
|
if (repeat._def.tag !== 'choice') return null
|
|
508
|
+
const fused = commonCssTriviaScanner(repeat._def.parsers.map(triviaRegexSource))
|
|
509
|
+
if (fused !== null) return fused
|
|
452
510
|
const arms = repeat._def.parsers.map(regexTriviaScanner)
|
|
453
511
|
if (arms.some(s => s === null)) return null
|
|
454
512
|
return loopScanner(arms as FastTriviaScanner[])
|
|
455
513
|
}
|
|
456
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
|
+
|
|
457
521
|
function loopScanner(arms: FastTriviaScanner[]): FastTriviaScanner {
|
|
458
522
|
return (input, cur) => {
|
|
459
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
|
+
}
|