tarsec 0.5.0 → 0.5.2
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/combinators.d.ts +87 -1
- package/dist/combinators.js +240 -15
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/parseState.d.ts +25 -0
- package/dist/parseState.js +21 -0
- package/dist/parsers/bash/index.d.ts +47 -50
- package/dist/parsers/bash/index.js +47 -51
- package/dist/parsers/bash/parsers.d.ts +95 -0
- package/dist/parsers/bash/parsers.js +306 -0
- package/dist/parsers/bash/types.d.ts +59 -110
- package/dist/parsers/bash/types.js +3 -1
- package/dist/parsers/within.js +5 -0
- package/dist/parsers.d.ts +13 -2
- package/dist/parsers.js +29 -3
- package/dist/position.d.ts +10 -0
- package/dist/position.js +38 -8
- package/dist/rightmostFailure.d.ts +10 -0
- package/dist/rightmostFailure.js +48 -19
- package/dist/runNested.d.ts +24 -0
- package/dist/runNested.js +28 -0
- package/dist/trace.js +10 -4
- package/dist/types.d.ts +13 -0
- package/dist/types.js +9 -0
- package/package.json +1 -1
- package/dist/parsers/bash/commands.d.ts +0 -11
- package/dist/parsers/bash/commands.js +0 -197
- package/dist/parsers/bash/lexemes.d.ts +0 -27
- package/dist/parsers/bash/lexemes.js +0 -44
- package/dist/parsers/bash/lists.d.ts +0 -18
- package/dist/parsers/bash/lists.js +0 -85
- package/dist/parsers/bash/words.d.ts +0 -22
- package/dist/parsers/bash/words.js +0 -112
|
@@ -1,136 +1,85 @@
|
|
|
1
1
|
/** AST types for the bash parser. See `index.ts` for the supported subset. */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export type
|
|
2
|
+
export type Word = LiteralWord | PathWord | FlagWord | SingleQuotedWord | DoubleQuotedWord | VariableWord | InterpolatedVariableWord;
|
|
3
|
+
export type ScriptName = LiteralWord | PathWord;
|
|
4
|
+
export type LiteralWord = {
|
|
5
5
|
tag: "literal";
|
|
6
6
|
text: string;
|
|
7
|
-
}
|
|
7
|
+
};
|
|
8
|
+
export type PathWord = {
|
|
9
|
+
tag: "path";
|
|
10
|
+
text: string;
|
|
11
|
+
};
|
|
12
|
+
export type FlagWord = {
|
|
13
|
+
tag: "flag";
|
|
14
|
+
flagName: string;
|
|
15
|
+
flagValue?: string;
|
|
16
|
+
};
|
|
17
|
+
export type SingleQuotedWord = {
|
|
8
18
|
tag: "singleQuoted";
|
|
9
19
|
text: string;
|
|
10
|
-
}
|
|
20
|
+
};
|
|
21
|
+
export type DoubleQuotedWord = {
|
|
11
22
|
tag: "doubleQuoted";
|
|
12
|
-
parts:
|
|
13
|
-
}
|
|
23
|
+
parts: Word[];
|
|
24
|
+
};
|
|
25
|
+
export type VariableWord = {
|
|
14
26
|
tag: "variable";
|
|
15
27
|
name: string;
|
|
16
|
-
} | {
|
|
17
|
-
tag: "paramExpansion";
|
|
18
|
-
expression: string;
|
|
19
|
-
} | {
|
|
20
|
-
tag: "commandSubstitution";
|
|
21
|
-
command: List;
|
|
22
|
-
} | {
|
|
23
|
-
tag: "arithmeticExpansion";
|
|
24
|
-
expression: string;
|
|
25
|
-
};
|
|
26
|
-
export type BashWord = {
|
|
27
|
-
tag: "word";
|
|
28
|
-
parts: WordPart[];
|
|
29
28
|
};
|
|
29
|
+
/** A word built from two or more adjacent parts: `$HOME.txt`, `"a"b`,
|
|
30
|
+
* `"$HOME"/x`. These are ONE word in bash; split into separate words they
|
|
31
|
+
* become separate arguments and the command means something else.
|
|
32
|
+
*
|
|
33
|
+
* Quoted parts belong here as much as variables do — quoting a variable
|
|
34
|
+
* and appending to it (`"$HOME"/bin`) is idiomatic shell. */
|
|
35
|
+
export type InterpolatedVariableWord = {
|
|
36
|
+
tag: "interpolatedVariable";
|
|
37
|
+
parts: (LiteralWord | VariableWord | SingleQuotedWord | DoubleQuotedWord)[];
|
|
38
|
+
};
|
|
39
|
+
export declare function literalWord(text: string): LiteralWord;
|
|
30
40
|
/** `name=value` (or `name=` with a null value) before a command. */
|
|
31
41
|
export type Assignment = {
|
|
32
42
|
tag: "assignment";
|
|
33
43
|
name: string;
|
|
34
|
-
value:
|
|
44
|
+
value: Word | null;
|
|
35
45
|
};
|
|
36
|
-
/** A redirect like `> out.txt`, `2
|
|
37
|
-
* explicit file descriptor (`2` in `2>`), or
|
|
46
|
+
/** A redirect like `> out.txt`, `>> log`, `2> err.txt` or `< in.txt`.
|
|
47
|
+
* `fd` is the explicit file descriptor (`2` in `2>`), or undefined for the
|
|
48
|
+
* default. Only `>`, `>>`, `<` and `&>` are recognized; `2>&1`, heredocs
|
|
49
|
+
* and here-strings are rejected rather than parsed. */
|
|
38
50
|
export type Redirect = {
|
|
39
51
|
tag: "redirect";
|
|
40
|
-
fd
|
|
52
|
+
fd?: number;
|
|
41
53
|
op: string;
|
|
42
|
-
target:
|
|
54
|
+
target: Word;
|
|
43
55
|
};
|
|
44
56
|
export type SimpleCommand = {
|
|
45
57
|
tag: "simpleCommand";
|
|
46
58
|
assignments: Assignment[];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
cond: List;
|
|
56
|
-
thenBody: List;
|
|
57
|
-
}[];
|
|
58
|
-
elseBody: List | null;
|
|
59
|
-
redirects: Redirect[];
|
|
60
|
-
};
|
|
61
|
-
export type LoopCommand = {
|
|
62
|
-
tag: "loop";
|
|
63
|
-
kind: "while" | "until";
|
|
64
|
-
cond: List;
|
|
65
|
-
body: List;
|
|
66
|
-
redirects: Redirect[];
|
|
67
|
-
};
|
|
68
|
-
export type ForCommand = {
|
|
69
|
-
tag: "for";
|
|
70
|
-
variable: string;
|
|
71
|
-
/** The `in word...` list, or null for the implicit `in "$@"`. */
|
|
72
|
-
words: BashWord[] | null;
|
|
73
|
-
body: List;
|
|
59
|
+
/** The command name, or null for an assignment-only line (`FOO=bar`).
|
|
60
|
+
* Bash requires at least one of a command name or an assignment. */
|
|
61
|
+
command: ScriptName | null;
|
|
62
|
+
/** Every word after the command name, in source order. There is no
|
|
63
|
+
* `subcommands` field: no syntactic rule separates `git status` from
|
|
64
|
+
* `echo status`, so splitting them would put a command's real
|
|
65
|
+
* arguments in whichever bucket the preceding word happened to pick. */
|
|
66
|
+
args: Word[];
|
|
74
67
|
redirects: Redirect[];
|
|
75
68
|
};
|
|
76
|
-
export type
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
82
|
-
export type CaseCommand = {
|
|
83
|
-
tag: "case";
|
|
84
|
-
subject: BashWord;
|
|
85
|
-
items: CaseItem[];
|
|
86
|
-
redirects: Redirect[];
|
|
87
|
-
};
|
|
88
|
-
export type Subshell = {
|
|
89
|
-
tag: "subshell";
|
|
90
|
-
body: List;
|
|
91
|
-
redirects: Redirect[];
|
|
92
|
-
};
|
|
93
|
-
export type Group = {
|
|
94
|
-
tag: "group";
|
|
95
|
-
body: List;
|
|
96
|
-
redirects: Redirect[];
|
|
97
|
-
};
|
|
98
|
-
/** `(( expression ))` as a command. The expression is kept as raw text. */
|
|
99
|
-
export type ArithmeticCommand = {
|
|
100
|
-
tag: "arithmeticCommand";
|
|
101
|
-
expression: string;
|
|
102
|
-
redirects: Redirect[];
|
|
103
|
-
};
|
|
104
|
-
export type FunctionDef = {
|
|
105
|
-
tag: "functionDef";
|
|
106
|
-
name: string;
|
|
107
|
-
body: Command;
|
|
108
|
-
};
|
|
109
|
-
export type Command = SimpleCommand | IfCommand | LoopCommand | ForCommand | CaseCommand | Subshell | Group | ArithmeticCommand | FunctionDef;
|
|
110
|
-
/** One or more commands joined by `|` (or `|&`), optionally negated. */
|
|
111
|
-
export type Pipeline = {
|
|
112
|
-
tag: "pipeline";
|
|
113
|
-
negated: boolean;
|
|
114
|
-
commands: Command[];
|
|
115
|
-
};
|
|
116
|
-
/** Pipelines joined by `&&` / `||`, left to right. */
|
|
117
|
-
export type AndOr = {
|
|
118
|
-
tag: "andOr";
|
|
119
|
-
first: Pipeline;
|
|
120
|
-
rest: {
|
|
121
|
-
op: "&&" | "||";
|
|
122
|
-
pipeline: Pipeline;
|
|
123
|
-
}[];
|
|
69
|
+
export type Command = SimpleCommand | And | Or | Parens;
|
|
70
|
+
export type And = {
|
|
71
|
+
tag: "and";
|
|
72
|
+
left: Command;
|
|
73
|
+
right: Command;
|
|
124
74
|
};
|
|
125
|
-
export type
|
|
126
|
-
tag: "
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
background: boolean;
|
|
75
|
+
export type Or = {
|
|
76
|
+
tag: "or";
|
|
77
|
+
left: Command;
|
|
78
|
+
right: Command;
|
|
130
79
|
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
tag: "list";
|
|
135
|
-
items: ListItem[];
|
|
80
|
+
export type Parens = {
|
|
81
|
+
tag: "parens";
|
|
82
|
+
command: Command;
|
|
136
83
|
};
|
|
84
|
+
export type BashNode = Command | SimpleCommand | Assignment | Redirect | Word | ScriptName;
|
|
85
|
+
export type BashAST = Command[];
|
package/dist/parsers/within.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { trace } from "../trace.js";
|
|
2
2
|
import { success } from "../types.js";
|
|
3
|
+
import { getParseState } from "../parseState.js";
|
|
3
4
|
/**
|
|
4
5
|
* `within` is a funny combinator. It finds zero or more instances of `parser` within the input.
|
|
5
6
|
* It always succeeds and returns an array of results, each one being a matched or unmatched string.
|
|
@@ -59,6 +60,9 @@ import { success } from "../types.js";
|
|
|
59
60
|
*/
|
|
60
61
|
export function within(parser) {
|
|
61
62
|
return trace("within", (input) => {
|
|
63
|
+
// A search is speculation: probes at every offset must not leak
|
|
64
|
+
// commits into the committedFailure slot.
|
|
65
|
+
const slotBefore = getParseState().committedFailure;
|
|
62
66
|
let start = 0;
|
|
63
67
|
let current = 0;
|
|
64
68
|
const results = [];
|
|
@@ -89,6 +93,7 @@ export function within(parser) {
|
|
|
89
93
|
value: input.slice(start, current),
|
|
90
94
|
});
|
|
91
95
|
}
|
|
96
|
+
getParseState().committedFailure = slotBefore;
|
|
92
97
|
return success(results, "");
|
|
93
98
|
});
|
|
94
99
|
}
|
package/dist/parsers.d.ts
CHANGED
|
@@ -138,8 +138,19 @@ export declare function takeWhile1(charsOrPred: string | CharPredicate, expected
|
|
|
138
138
|
export declare const anyChar: Parser<string>;
|
|
139
139
|
/**
|
|
140
140
|
* Wraps a parser with a human-readable label for error reporting.
|
|
141
|
-
*
|
|
142
|
-
*
|
|
141
|
+
*
|
|
142
|
+
* If the wrapped parser fails at the label's own position (it never got
|
|
143
|
+
* anywhere), its internal failure records are suppressed and only the
|
|
144
|
+
* label is recorded — producing clean messages like `expected a digit`
|
|
145
|
+
* instead of `expected one of "0123456789"`.
|
|
146
|
+
*
|
|
147
|
+
* If the wrapped parser fails STRICTLY INSIDE the labeled region (it
|
|
148
|
+
* consumed input first), its deeper, more specific record is kept and
|
|
149
|
+
* the label stays out of the way — so a grammar labeled at every level
|
|
150
|
+
* still surfaces the deepest thing the parser actually knew.
|
|
151
|
+
* (Comparing against the label's own position, rather than the previous
|
|
152
|
+
* record, is what keeps shallow labels clean while deep knowledge
|
|
153
|
+
* survives.)
|
|
143
154
|
*
|
|
144
155
|
* @param name - human-readable description of what the parser expects
|
|
145
156
|
* @param parser - the parser to wrap
|
package/dist/parsers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { many1WithJoin } from "./combinators.js";
|
|
2
|
-
import { trace } from "./trace.js";
|
|
2
|
+
import { trace, getInputStr } from "./trace.js";
|
|
3
3
|
import { recordFailure, saveRightmostFailure, restoreRightmostFailure } from "./rightmostFailure.js";
|
|
4
4
|
import { captureSuccess, failure, success, } from "./types.js";
|
|
5
5
|
import { escape } from "./utils.js";
|
|
@@ -241,8 +241,19 @@ export const anyChar = trace("anyChar", (input) => {
|
|
|
241
241
|
});
|
|
242
242
|
/**
|
|
243
243
|
* Wraps a parser with a human-readable label for error reporting.
|
|
244
|
-
*
|
|
245
|
-
*
|
|
244
|
+
*
|
|
245
|
+
* If the wrapped parser fails at the label's own position (it never got
|
|
246
|
+
* anywhere), its internal failure records are suppressed and only the
|
|
247
|
+
* label is recorded — producing clean messages like `expected a digit`
|
|
248
|
+
* instead of `expected one of "0123456789"`.
|
|
249
|
+
*
|
|
250
|
+
* If the wrapped parser fails STRICTLY INSIDE the labeled region (it
|
|
251
|
+
* consumed input first), its deeper, more specific record is kept and
|
|
252
|
+
* the label stays out of the way — so a grammar labeled at every level
|
|
253
|
+
* still surfaces the deepest thing the parser actually knew.
|
|
254
|
+
* (Comparing against the label's own position, rather than the previous
|
|
255
|
+
* record, is what keeps shallow labels clean while deep knowledge
|
|
256
|
+
* survives.)
|
|
246
257
|
*
|
|
247
258
|
* @param name - human-readable description of what the parser expects
|
|
248
259
|
* @param parser - the parser to wrap
|
|
@@ -252,6 +263,21 @@ export function label(name, parser) {
|
|
|
252
263
|
return (input) => {
|
|
253
264
|
const saved = saveRightmostFailure();
|
|
254
265
|
const result = parser(input);
|
|
266
|
+
if (!result.success && result.committed === true) {
|
|
267
|
+
// Committed failures carry their own story — no re-labeling,
|
|
268
|
+
// no restore games.
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
const afterChild = saveRightmostFailure();
|
|
272
|
+
const labelPos = getInputStr().length - input.length;
|
|
273
|
+
const childGotStrictlyDeeper = afterChild.pos > labelPos;
|
|
274
|
+
if (!result.success && childGotStrictlyDeeper) {
|
|
275
|
+
// The child FAILED somewhere specific past this label's start —
|
|
276
|
+
// keep its record; adding the label here would only blur it.
|
|
277
|
+
// On success the restore below still runs, scrubbing speculative
|
|
278
|
+
// records left by backtracked alternatives inside the region.
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
255
281
|
restoreRightmostFailure(saved);
|
|
256
282
|
if (!result.success)
|
|
257
283
|
recordFailure(input, name);
|
package/dist/position.d.ts
CHANGED
|
@@ -18,14 +18,24 @@ export declare function buildLineTable(source: string): number[];
|
|
|
18
18
|
* Both line and column are 0-based.
|
|
19
19
|
*/
|
|
20
20
|
export declare function offsetToPosition(lineTable: number[], offset: number): Position;
|
|
21
|
+
/**
|
|
22
|
+
* Compose an inner-parse position with a base position (the point in the
|
|
23
|
+
* enclosing input where the inner input begins). Offsets and lines add;
|
|
24
|
+
* the base column applies only while still on the base line (inner line 0).
|
|
25
|
+
*/
|
|
26
|
+
export declare function composePosition(base: Position, pos: Position): Position;
|
|
21
27
|
/**
|
|
22
28
|
* A zero-width parser that returns the current offset into the input string.
|
|
23
29
|
* Requires `setInputStr` to have been called with the full input.
|
|
30
|
+
* Inside `runNested`, the offset is reported in the enclosing parse's
|
|
31
|
+
* coordinates (the state's `basePosition` is added).
|
|
24
32
|
*/
|
|
25
33
|
export declare const getOffset: Parser<number>;
|
|
26
34
|
/**
|
|
27
35
|
* A zero-width parser that returns the current position (offset, line, column).
|
|
28
36
|
* Requires `setInputStr` to have been called with the full input.
|
|
37
|
+
* Inside `runNested`, the position is reported in the enclosing parse's
|
|
38
|
+
* coordinates (composed with the state's `basePosition`).
|
|
29
39
|
*/
|
|
30
40
|
export declare const getPosition: Parser<Position>;
|
|
31
41
|
/**
|
package/dist/position.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
// NOTE: this module deliberately imports nothing from trace.ts — it reads
|
|
2
|
+
// the input via getParseState().inputStr. trace.ts imports composePosition
|
|
3
|
+
// from here, so an import in the other direction would create a runtime
|
|
4
|
+
// module cycle (fragile TDZ hazard under refactoring/bundlers).
|
|
2
5
|
import { success } from "./types.js";
|
|
6
|
+
import { getParseState } from "./parseState.js";
|
|
3
7
|
/**
|
|
4
8
|
* Build a lookup table of line-start offsets for a given source string.
|
|
5
9
|
* This allows O(log n) offset-to-position conversion via binary search.
|
|
@@ -17,6 +21,9 @@ export function buildLineTable(source) {
|
|
|
17
21
|
// table on every invocation. Parses run sequentially with a single
|
|
18
22
|
// `setInputStr` source, so a most-recently-seen cache is sufficient and
|
|
19
23
|
// avoids O(num_calls * source.length) work during a parse.
|
|
24
|
+
// Deliberately NOT part of ParseState: keyed by source-string identity,
|
|
25
|
+
// so a nested parse's source misses and rebuilds — it can never serve a
|
|
26
|
+
// stale table across parse states.
|
|
20
27
|
let cachedSource = null;
|
|
21
28
|
let cachedLineTable = [0];
|
|
22
29
|
function getLineTable(source) {
|
|
@@ -49,23 +56,45 @@ export function offsetToPosition(lineTable, offset) {
|
|
|
49
56
|
column: offset - lineTable[lo],
|
|
50
57
|
};
|
|
51
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Compose an inner-parse position with a base position (the point in the
|
|
61
|
+
* enclosing input where the inner input begins). Offsets and lines add;
|
|
62
|
+
* the base column applies only while still on the base line (inner line 0).
|
|
63
|
+
*/
|
|
64
|
+
export function composePosition(base, pos) {
|
|
65
|
+
if (base.offset === 0 && base.line === 0 && base.column === 0) {
|
|
66
|
+
return pos;
|
|
67
|
+
}
|
|
68
|
+
const column = pos.line === 0 ? base.column + pos.column : pos.column;
|
|
69
|
+
return {
|
|
70
|
+
offset: base.offset + pos.offset,
|
|
71
|
+
line: base.line + pos.line,
|
|
72
|
+
column,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
52
75
|
/**
|
|
53
76
|
* A zero-width parser that returns the current offset into the input string.
|
|
54
77
|
* Requires `setInputStr` to have been called with the full input.
|
|
78
|
+
* Inside `runNested`, the offset is reported in the enclosing parse's
|
|
79
|
+
* coordinates (the state's `basePosition` is added).
|
|
55
80
|
*/
|
|
56
81
|
export const getOffset = (input) => {
|
|
57
|
-
const source =
|
|
58
|
-
|
|
82
|
+
const source = getParseState().inputStr;
|
|
83
|
+
const base = getParseState().basePosition;
|
|
84
|
+
return success(base.offset + (source.length - input.length), input);
|
|
59
85
|
};
|
|
60
86
|
/**
|
|
61
87
|
* A zero-width parser that returns the current position (offset, line, column).
|
|
62
88
|
* Requires `setInputStr` to have been called with the full input.
|
|
89
|
+
* Inside `runNested`, the position is reported in the enclosing parse's
|
|
90
|
+
* coordinates (composed with the state's `basePosition`).
|
|
63
91
|
*/
|
|
64
92
|
export const getPosition = (input) => {
|
|
65
|
-
const source =
|
|
93
|
+
const source = getParseState().inputStr;
|
|
66
94
|
const offset = source.length - input.length;
|
|
67
95
|
const lineTable = getLineTable(source);
|
|
68
|
-
|
|
96
|
+
const base = getParseState().basePosition;
|
|
97
|
+
return success(composePosition(base, offsetToPosition(lineTable, offset)), input);
|
|
69
98
|
};
|
|
70
99
|
/**
|
|
71
100
|
* Wraps a parser so that its result includes span information (start and end positions).
|
|
@@ -80,18 +109,19 @@ export const getPosition = (input) => {
|
|
|
80
109
|
*/
|
|
81
110
|
export function withSpan(parser) {
|
|
82
111
|
return (input) => {
|
|
83
|
-
const source =
|
|
112
|
+
const source = getParseState().inputStr;
|
|
84
113
|
const lineTable = getLineTable(source);
|
|
85
114
|
const startOffset = source.length - input.length;
|
|
86
115
|
const result = parser(input);
|
|
87
116
|
if (!result.success)
|
|
88
117
|
return result;
|
|
89
118
|
const endOffset = source.length - result.rest.length;
|
|
119
|
+
const base = getParseState().basePosition;
|
|
90
120
|
return success({
|
|
91
121
|
value: result.result,
|
|
92
122
|
span: {
|
|
93
|
-
start: offsetToPosition(lineTable, startOffset),
|
|
94
|
-
end: offsetToPosition(lineTable, endOffset),
|
|
123
|
+
start: composePosition(base, offsetToPosition(lineTable, startOffset)),
|
|
124
|
+
end: composePosition(base, offsetToPosition(lineTable, endOffset)),
|
|
95
125
|
},
|
|
96
126
|
}, result.rest);
|
|
97
127
|
};
|
|
@@ -25,6 +25,16 @@ export declare function saveRightmostFailure(): SavedRightmostFailure;
|
|
|
25
25
|
export declare function restoreRightmostFailure(saved: SavedRightmostFailure): void;
|
|
26
26
|
/**
|
|
27
27
|
* Formats the rightmost failure into a human-readable error message with line and column info.
|
|
28
|
+
* A committed failure (see the `committed` combinator) takes precedence
|
|
29
|
+
* over the rightmost record — the committed error wins reporting even
|
|
30
|
+
* when some fallback alternative failed deeper into the input.
|
|
31
|
+
*
|
|
32
|
+
* Scope: the preference is per parse state. A commit inside `runNested`
|
|
33
|
+
* lives in the inner state and surfaces through the returned result's
|
|
34
|
+
* `committed` flag, never through the enclosing parse's getErrorMessage.
|
|
35
|
+
* The position math assumes the committed failure's `rest` is a suffix
|
|
36
|
+
* of the CURRENT state's source — don't stash a `runNested` result
|
|
37
|
+
* (inner-coordinate `rest`) into an outer-state commit.
|
|
28
38
|
* Returns `null` if no failures have been recorded.
|
|
29
39
|
* Requires `setInputStr` to have been called.
|
|
30
40
|
*/
|
package/dist/rightmostFailure.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getInputStr } from "./trace.js";
|
|
2
|
-
import { buildLineTable, offsetToPosition } from "./position.js";
|
|
3
|
-
|
|
4
|
-
let rightmostFailureExpected = [];
|
|
2
|
+
import { buildLineTable, offsetToPosition, composePosition } from "./position.js";
|
|
3
|
+
import { getParseState } from "./parseState.js";
|
|
5
4
|
export function resetRightmostFailure() {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const state = getParseState();
|
|
6
|
+
state.rightmostFailurePos = -1;
|
|
7
|
+
state.rightmostFailureExpected = [];
|
|
8
|
+
state.committedFailure = null;
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* Record an expected alternative at the current failure position.
|
|
@@ -16,17 +17,18 @@ export function resetRightmostFailure() {
|
|
|
16
17
|
* @param expected - a human-readable description of what was expected
|
|
17
18
|
*/
|
|
18
19
|
export function recordFailure(input, expected) {
|
|
20
|
+
const state = getParseState();
|
|
19
21
|
const source = getInputStr();
|
|
20
22
|
if (source.length === 0)
|
|
21
23
|
return;
|
|
22
24
|
const pos = source.length - input.length;
|
|
23
|
-
if (pos > rightmostFailurePos) {
|
|
24
|
-
rightmostFailurePos = pos;
|
|
25
|
-
rightmostFailureExpected = [expected];
|
|
25
|
+
if (pos > state.rightmostFailurePos) {
|
|
26
|
+
state.rightmostFailurePos = pos;
|
|
27
|
+
state.rightmostFailureExpected = [expected];
|
|
26
28
|
}
|
|
27
|
-
else if (pos === rightmostFailurePos) {
|
|
28
|
-
if (!rightmostFailureExpected.includes(expected)) {
|
|
29
|
-
rightmostFailureExpected.push(expected);
|
|
29
|
+
else if (pos === state.rightmostFailurePos) {
|
|
30
|
+
if (!state.rightmostFailureExpected.includes(expected)) {
|
|
31
|
+
state.rightmostFailureExpected.push(expected);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -35,16 +37,25 @@ export function recordFailure(input, expected) {
|
|
|
35
37
|
* or `null` if no failures have been recorded.
|
|
36
38
|
*/
|
|
37
39
|
export function getRightmostFailure() {
|
|
38
|
-
|
|
40
|
+
const state = getParseState();
|
|
41
|
+
if (state.rightmostFailurePos < 0)
|
|
39
42
|
return null;
|
|
40
|
-
return {
|
|
43
|
+
return {
|
|
44
|
+
pos: state.rightmostFailurePos,
|
|
45
|
+
expected: [...state.rightmostFailureExpected],
|
|
46
|
+
};
|
|
41
47
|
}
|
|
42
48
|
export function saveRightmostFailure() {
|
|
43
|
-
|
|
49
|
+
const state = getParseState();
|
|
50
|
+
return {
|
|
51
|
+
pos: state.rightmostFailurePos,
|
|
52
|
+
expected: [...state.rightmostFailureExpected],
|
|
53
|
+
};
|
|
44
54
|
}
|
|
45
55
|
export function restoreRightmostFailure(saved) {
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
const state = getParseState();
|
|
57
|
+
state.rightmostFailurePos = saved.pos;
|
|
58
|
+
state.rightmostFailureExpected = [...saved.expected];
|
|
48
59
|
}
|
|
49
60
|
function formatExpected(expected) {
|
|
50
61
|
if (expected.length === 1)
|
|
@@ -55,16 +66,34 @@ function formatExpected(expected) {
|
|
|
55
66
|
}
|
|
56
67
|
/**
|
|
57
68
|
* Formats the rightmost failure into a human-readable error message with line and column info.
|
|
69
|
+
* A committed failure (see the `committed` combinator) takes precedence
|
|
70
|
+
* over the rightmost record — the committed error wins reporting even
|
|
71
|
+
* when some fallback alternative failed deeper into the input.
|
|
72
|
+
*
|
|
73
|
+
* Scope: the preference is per parse state. A commit inside `runNested`
|
|
74
|
+
* lives in the inner state and surfaces through the returned result's
|
|
75
|
+
* `committed` flag, never through the enclosing parse's getErrorMessage.
|
|
76
|
+
* The position math assumes the committed failure's `rest` is a suffix
|
|
77
|
+
* of the CURRENT state's source — don't stash a `runNested` result
|
|
78
|
+
* (inner-coordinate `rest`) into an outer-state commit.
|
|
58
79
|
* Returns `null` if no failures have been recorded.
|
|
59
80
|
* Requires `setInputStr` to have been called.
|
|
60
81
|
*/
|
|
61
82
|
export function getErrorMessage() {
|
|
62
|
-
|
|
83
|
+
const state = getParseState();
|
|
84
|
+
const committed = state.committedFailure;
|
|
85
|
+
if (committed !== null) {
|
|
86
|
+
const source = getInputStr();
|
|
87
|
+
const lineTable = buildLineTable(source);
|
|
88
|
+
const pos = composePosition(state.basePosition, offsetToPosition(lineTable, source.length - committed.rest.length));
|
|
89
|
+
return `Line ${pos.line + 1}, col ${pos.column + 1}: ${committed.message}`;
|
|
90
|
+
}
|
|
91
|
+
if (state.rightmostFailurePos < 0)
|
|
63
92
|
return null;
|
|
64
93
|
const source = getInputStr();
|
|
65
94
|
const lineTable = buildLineTable(source);
|
|
66
|
-
const pos = offsetToPosition(lineTable, rightmostFailurePos);
|
|
95
|
+
const pos = composePosition(state.basePosition, offsetToPosition(lineTable, state.rightmostFailurePos));
|
|
67
96
|
const line = pos.line + 1;
|
|
68
97
|
const column = pos.column + 1;
|
|
69
|
-
return `Line ${line}, col ${column}: expected ${formatExpected(rightmostFailureExpected)}`;
|
|
98
|
+
return `Line ${line}, col ${column}: expected ${formatExpected(state.rightmostFailureExpected)}`;
|
|
70
99
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Parser, ParserResult } from "./types.js";
|
|
2
|
+
import type { Position } from "./position.js";
|
|
3
|
+
export type NestedOptions = {
|
|
4
|
+
/** Positions in the sub-parse's results are offset by this, so spans
|
|
5
|
+
* and error messages come out in the ENCLOSING parse's coordinates.
|
|
6
|
+
* Defaults to zero (positions relative to `input`). */
|
|
7
|
+
basePosition?: Position;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Run a complete parse of `input` with its own input string, rightmost-
|
|
11
|
+
* failure record, and memo caches, restoring the enclosing parse's state
|
|
12
|
+
* on exit — success, failure, or throw. The one supported way to run a
|
|
13
|
+
* parse inside another parse.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // While parsing a file, parse an embedded snippet as its own program,
|
|
18
|
+
* // reporting positions relative to the enclosing file:
|
|
19
|
+
* const embedded = runNested(exprParser, snippetText, {
|
|
20
|
+
* basePosition: openingPosition, // where the snippet starts in the file
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function runNested<T>(parser: Parser<T>, input: string, opts?: NestedOptions): ParserResult<T>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createParseState, swapParseState } from "./parseState.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run a complete parse of `input` with its own input string, rightmost-
|
|
4
|
+
* failure record, and memo caches, restoring the enclosing parse's state
|
|
5
|
+
* on exit — success, failure, or throw. The one supported way to run a
|
|
6
|
+
* parse inside another parse.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // While parsing a file, parse an embedded snippet as its own program,
|
|
11
|
+
* // reporting positions relative to the enclosing file:
|
|
12
|
+
* const embedded = runNested(exprParser, snippetText, {
|
|
13
|
+
* basePosition: openingPosition, // where the snippet starts in the file
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function runNested(parser, input, opts) {
|
|
18
|
+
// Deliberately does NOT call setInputStr: createParseState pre-sets
|
|
19
|
+
// inputStr and a fresh rightmost record (replicating setInputStr's
|
|
20
|
+
// reset), and calling it here would clobber the fresh state's fields.
|
|
21
|
+
const savedState = swapParseState(createParseState(input, opts === null || opts === void 0 ? void 0 : opts.basePosition));
|
|
22
|
+
try {
|
|
23
|
+
return parser(input);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
swapParseState(savedState);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/trace.js
CHANGED
|
@@ -2,6 +2,8 @@ import { escape, round, shorten } from "./utils.js";
|
|
|
2
2
|
import process from "process";
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import { resetRightmostFailure } from "./rightmostFailure.js";
|
|
5
|
+
import { getParseState } from "./parseState.js";
|
|
6
|
+
import { composePosition } from "./position.js";
|
|
5
7
|
const isNode = typeof process !== "undefined" &&
|
|
6
8
|
process.versions != null &&
|
|
7
9
|
process.versions.node != null;
|
|
@@ -186,7 +188,6 @@ export function limitSteps(limit, callback) {
|
|
|
186
188
|
callback();
|
|
187
189
|
stepLimit = -1;
|
|
188
190
|
}
|
|
189
|
-
let inputStr = "";
|
|
190
191
|
/**
|
|
191
192
|
* Use this function in conjunction with the parseError combinator. Before running your parser,
|
|
192
193
|
* call this function, giving it the entire input. Then, if the parseError combinator throws an error,
|
|
@@ -194,11 +195,11 @@ let inputStr = "";
|
|
|
194
195
|
* @param s full string to parse
|
|
195
196
|
*/
|
|
196
197
|
export function setInputStr(s) {
|
|
197
|
-
inputStr = s;
|
|
198
|
+
getParseState().inputStr = s;
|
|
198
199
|
resetRightmostFailure();
|
|
199
200
|
}
|
|
200
201
|
export function getInputStr() {
|
|
201
|
-
return inputStr;
|
|
202
|
+
return getParseState().inputStr;
|
|
202
203
|
}
|
|
203
204
|
export function getDiagnostics(result, input, _message) {
|
|
204
205
|
const inputStr = getInputStr();
|
|
@@ -222,9 +223,14 @@ export function getDiagnostics(result, input, _message) {
|
|
|
222
223
|
}
|
|
223
224
|
const linesIndex = Math.max(0, i - 1);
|
|
224
225
|
const column = lines[linesIndex].length - (acc - index);
|
|
225
|
-
|
|
226
|
+
const composed = composePosition(getParseState().basePosition, {
|
|
227
|
+
offset: index,
|
|
226
228
|
line: i - 1,
|
|
227
229
|
column,
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
line: composed.line,
|
|
233
|
+
column: composed.column,
|
|
228
234
|
length: 1,
|
|
229
235
|
prettyMessage: messages.join("\n"),
|
|
230
236
|
message: message,
|