clarity-pattern-parser 11.5.4 → 11.6.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/architecture.md +300 -0
- package/dist/grammar/Grammar.d.ts +24 -25
- package/dist/grammar/patterns/grammar.d.ts +99 -0
- package/dist/grammar/patterns/grammar.test.d.ts +1 -0
- package/dist/index.browser.js +2314 -2592
- package/dist/index.browser.js.map +1 -1
- package/dist/index.esm.js +2314 -2592
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2314 -2592
- package/dist/index.js.map +1 -1
- package/grammar-guide.md +836 -0
- package/package.json +1 -1
- package/src/grammar/Grammar.test.ts +418 -0
- package/src/grammar/Grammar.ts +483 -515
- package/src/grammar/patterns/grammar.test.ts +276 -0
- package/src/grammar/patterns/grammar.ts +113 -37
- package/src/grammar/patterns.ts +6 -6
- package/src/grammar/patterns/anonymousPattern.ts +0 -23
- package/src/grammar/patterns/body.ts +0 -22
- package/src/grammar/patterns/comment.ts +0 -4
- package/src/grammar/patterns/decoratorStatement.ts +0 -85
- package/src/grammar/patterns/import.ts +0 -88
- package/src/grammar/patterns/literal.ts +0 -4
- package/src/grammar/patterns/literals.ts +0 -21
- package/src/grammar/patterns/name.ts +0 -3
- package/src/grammar/patterns/optionsLiteral.ts +0 -25
- package/src/grammar/patterns/pattern.ts +0 -29
- package/src/grammar/patterns/regexLiteral.ts +0 -5
- package/src/grammar/patterns/repeatLiteral.ts +0 -71
- package/src/grammar/patterns/sequenceLiteral.ts +0 -24
- package/src/grammar/patterns/spaces.ts +0 -16
- package/src/grammar/patterns/statement.ts +0 -22
- package/src/grammar/patterns/takeUtilLiteral.ts +0 -20
- package/src/grammar/spec.md +0 -331
- package/src/grammar_v2/patterns/Grammar.ts +0 -170
- package/src/grammar_v2/patterns/patterns/cpat.cpat +0 -91
- package/src/grammar_v2/patterns/patterns/grammar.test.ts +0 -218
- package/src/grammar_v2/patterns/patterns/grammar.ts +0 -103
package/src/grammar/Grammar.ts
CHANGED
|
@@ -1,85 +1,96 @@
|
|
|
1
1
|
import { Node } from "../ast/Node";
|
|
2
|
+
import { tokens } from "./decorators/tokens";
|
|
3
|
+
import { generateErrorMessage } from "../patterns/generate_error_message";
|
|
4
|
+
|
|
5
|
+
export interface GrammarFile {
|
|
6
|
+
resource: string;
|
|
7
|
+
expression: string;
|
|
8
|
+
}
|
|
9
|
+
import { Context } from "../patterns/Context";
|
|
10
|
+
import { Expression } from "../patterns/Expression";
|
|
2
11
|
import { Literal } from "../patterns/Literal";
|
|
12
|
+
import { Not } from "../patterns/Not";
|
|
13
|
+
import { Optional } from "../patterns/Optional";
|
|
14
|
+
import { Options } from "../patterns/Options";
|
|
3
15
|
import { Pattern } from "../patterns/Pattern";
|
|
4
|
-
import { Regex } from "../patterns/Regex";
|
|
5
16
|
import { Reference } from "../patterns/Reference";
|
|
6
|
-
import {
|
|
7
|
-
import { Options } from "../patterns/Options";
|
|
8
|
-
import { Not } from "../patterns/Not";
|
|
9
|
-
import { Sequence } from "../patterns/Sequence";
|
|
17
|
+
import { Regex } from "../patterns/Regex";
|
|
10
18
|
import { Repeat, RepeatOptions } from "../patterns/Repeat";
|
|
11
|
-
import { Optional } from "../patterns/Optional";
|
|
12
|
-
import { Context } from "../patterns/Context";
|
|
13
|
-
import { Expression } from "../patterns/Expression";
|
|
14
|
-
import { TakeUntil } from "../patterns/TakeUntil";
|
|
15
19
|
import { RightAssociated } from "../patterns/RightAssociated";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
20
|
+
import { Sequence } from "../patterns/Sequence";
|
|
21
|
+
import { TakeUntil } from "../patterns/TakeUntil";
|
|
22
|
+
import { grammar } from "./patterns/grammar"
|
|
18
23
|
|
|
19
24
|
let anonymousIndexId = 0;
|
|
20
25
|
|
|
26
|
+
function defaultImportResolver(_path: string, _basePath: string | null): Promise<GrammarFile> {
|
|
27
|
+
throw new Error("No import resolver supplied.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function defaultImportResolverSync(_path: string, _basePath: string | null): GrammarFile {
|
|
31
|
+
throw new Error("No import resolver supplied.");
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
export type Decorator = (pattern: Pattern, arg?: string | boolean | number | null | Record<string, any> | any[]) => void;
|
|
22
35
|
|
|
23
|
-
const defaultDecorators = {
|
|
36
|
+
const defaultDecorators: Record<string, Decorator> = {
|
|
24
37
|
tokens: tokens
|
|
25
38
|
};
|
|
26
39
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
40
|
+
// Node names that are operators/whitespace and should be filtered during building
|
|
41
|
+
const skipNodes: Record<string, boolean> = {
|
|
42
|
+
"+": true,
|
|
43
|
+
"|": true,
|
|
44
|
+
"<|>": true,
|
|
45
|
+
"optionalWS": true,
|
|
46
|
+
"optionalLS": true,
|
|
47
|
+
"ws": true,
|
|
48
|
+
"ls": true,
|
|
36
49
|
};
|
|
37
50
|
|
|
51
|
+
export interface GrammarOptions {
|
|
52
|
+
resolveImport?: (resource: string, originResource: string | null) => Promise<GrammarFile>;
|
|
53
|
+
resolveImportSync?: (resource: string, originResource: string | null) => GrammarFile;
|
|
54
|
+
originResource?: string | null;
|
|
55
|
+
params?: Pattern[];
|
|
56
|
+
decorators?: Record<string, Decorator>;
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
class ParseContext {
|
|
39
60
|
patternsByName = new Map<string, Pattern>();
|
|
40
61
|
importedPatternsByName = new Map<string, Pattern>();
|
|
41
62
|
paramsByName = new Map<string, Pattern>();
|
|
42
63
|
decorators: Record<string, Decorator>;
|
|
64
|
+
|
|
43
65
|
constructor(params: Pattern[], decorators: Record<string, Decorator> = {}) {
|
|
44
66
|
params.forEach(p => this.paramsByName.set(p.name, p));
|
|
45
67
|
this.decorators = { ...decorators, ...defaultDecorators };
|
|
46
68
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function defaultImportResolver(_path: string, _basePath: string | null): Promise<GrammarFile> {
|
|
50
|
-
throw new Error("No import resolver supplied.");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function defaultImportResolverSync(_path: string, _basePath: string | null): GrammarFile {
|
|
54
|
-
throw new Error("No import resolver supplied.");
|
|
55
|
-
}
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
70
|
+
getImportedPatterns() {
|
|
71
|
+
return Array.from(this.importedPatternsByName.values());
|
|
72
|
+
}
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
originResource?: string | null;
|
|
66
|
-
params?: Pattern[];
|
|
67
|
-
decorators?: Record<string, Decorator>;
|
|
74
|
+
getParams() {
|
|
75
|
+
return Array.from(this.paramsByName.values());
|
|
76
|
+
}
|
|
68
77
|
}
|
|
69
78
|
|
|
70
79
|
export class Grammar {
|
|
80
|
+
private _options: GrammarOptions;
|
|
81
|
+
private _parseContext: ParseContext;
|
|
71
82
|
private _params: Pattern[];
|
|
72
83
|
private _originResource?: string | null;
|
|
73
84
|
private _resolveImport: (resource: string, originResource: string | null) => Promise<GrammarFile>;
|
|
74
85
|
private _resolveImportSync: (resource: string, originResource: string | null) => GrammarFile;
|
|
75
|
-
private _parseContext: ParseContext;
|
|
76
86
|
|
|
77
87
|
constructor(options: GrammarOptions = {}) {
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
88
|
+
this._options = options;
|
|
89
|
+
this._params = options.params || [];
|
|
90
|
+
this._originResource = options.originResource || null;
|
|
91
|
+
this._parseContext = new ParseContext(this._params, options.decorators || {});
|
|
80
92
|
this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
|
|
81
93
|
this._resolveImportSync = options.resolveImportSync == null ? defaultImportResolverSync : options.resolveImportSync;
|
|
82
|
-
this._parseContext = new ParseContext(this._params, options.decorators || {});
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
async import(path: string) {
|
|
@@ -114,19 +125,27 @@ export class Grammar {
|
|
|
114
125
|
return this._buildPatternRecord();
|
|
115
126
|
}
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
const patterns: Record<string, Pattern> = {};
|
|
119
|
-
const allPatterns = Array.from(this._parseContext.patternsByName.values());
|
|
128
|
+
// --- Static convenience methods ---
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
static parse(expression: string, options?: GrammarOptions) {
|
|
131
|
+
const g = new Grammar(options);
|
|
132
|
+
return g.parse(expression);
|
|
133
|
+
}
|
|
124
134
|
|
|
125
|
-
|
|
135
|
+
static import(path: string, options?: GrammarOptions) {
|
|
136
|
+
const g = new Grammar(options);
|
|
137
|
+
return g.import(path);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static parseString(expression: string, options?: GrammarOptions) {
|
|
141
|
+
const g = new Grammar(options);
|
|
142
|
+
return g.parseString(expression);
|
|
126
143
|
}
|
|
127
144
|
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
// --- Parsing ---
|
|
146
|
+
|
|
147
|
+
private _tryToParse(cpat: string): Node {
|
|
148
|
+
const { ast, cursor } = grammar.exec(cpat, true);
|
|
130
149
|
|
|
131
150
|
if (ast == null) {
|
|
132
151
|
const message = generateErrorMessage(grammar, cursor);
|
|
@@ -136,112 +155,137 @@ export class Grammar {
|
|
|
136
155
|
return ast;
|
|
137
156
|
}
|
|
138
157
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
// --- Expression Flattening (Phase 2) ---
|
|
159
|
+
|
|
160
|
+
private _flattenExpressionsRecursive(node: Node) {
|
|
161
|
+
// Process children first (bottom-up)
|
|
162
|
+
for (const child of node.children) {
|
|
163
|
+
this._flattenExpressionsRecursive(child);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
switch (node.name) {
|
|
167
|
+
case "sequenceExpr":
|
|
168
|
+
case "optionsExpr":
|
|
169
|
+
case "greedyOptionsExpr":
|
|
170
|
+
this._unwrapNode(node.name, node);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private _unwrapNode(type: string, node: Node) {
|
|
176
|
+
for (let x = 0; x < node.children.length; x++) {
|
|
177
|
+
const child = node.children[x];
|
|
178
|
+
|
|
179
|
+
if (child.name === type) {
|
|
180
|
+
node.spliceChildren(x, 1, ...child.children);
|
|
181
|
+
x--;
|
|
182
|
+
}
|
|
143
183
|
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- Pattern Record ---
|
|
187
|
+
|
|
188
|
+
private _buildPatternRecord() {
|
|
189
|
+
const patterns: Record<string, Pattern> = {};
|
|
190
|
+
const allPatterns = Array.from(this._parseContext.patternsByName.values());
|
|
144
191
|
|
|
145
|
-
|
|
192
|
+
this._parseContext.patternsByName.forEach((p, name) => {
|
|
193
|
+
patterns[name] = new Context(name, p, allPatterns.filter(o => o !== p));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return patterns;
|
|
146
197
|
}
|
|
147
198
|
|
|
199
|
+
// --- Pattern Building (Phase 3) ---
|
|
200
|
+
|
|
148
201
|
private _buildPatterns(ast: Node) {
|
|
149
|
-
const
|
|
202
|
+
const statementsNode = ast.find(n => n.name === "statements");
|
|
150
203
|
|
|
151
|
-
if (
|
|
204
|
+
if (statementsNode == null) {
|
|
152
205
|
return;
|
|
153
206
|
}
|
|
154
207
|
|
|
155
|
-
const
|
|
208
|
+
const allStatements = statementsNode.children;
|
|
156
209
|
|
|
157
|
-
|
|
158
|
-
|
|
210
|
+
// First pass: build pattern assignments
|
|
211
|
+
for (let i = 0; i < allStatements.length; i++) {
|
|
212
|
+
const statementNode = allStatements[i];
|
|
159
213
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
214
|
+
if (statementNode.name === "patternAssignment") {
|
|
215
|
+
const nameNode = statementNode.find(n => n.name === "patternName") as Node;
|
|
216
|
+
const name = nameNode.value;
|
|
163
217
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
case "options-literal": {
|
|
174
|
-
this._saveOptions(n);
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
case "sequence-literal": {
|
|
178
|
-
this._saveSequence(n);
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
case "repeat-literal": {
|
|
182
|
-
this._saveRepeat(n);
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
case "alias-literal": {
|
|
186
|
-
this._saveAlias(n);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
case "take-until-literal": {
|
|
190
|
-
this._saveTakeUntil(n);
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
case "configurable-anonymous-pattern": {
|
|
194
|
-
this._saveConfigurableAnonymous(n);
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
default: {
|
|
198
|
-
break;
|
|
218
|
+
// Find the pattern expression (the RHS of the assignment)
|
|
219
|
+
const patternExprNode = statementNode.children.find(n =>
|
|
220
|
+
n.name !== "patternName" && !skipNodes[n.name] && n.name !== "assign"
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (patternExprNode == null) {
|
|
224
|
+
continue;
|
|
199
225
|
}
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
226
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this._parseContext.patternsByName.set(n.value, pattern);
|
|
206
|
-
});
|
|
207
|
-
}
|
|
227
|
+
// Flatten nested binary expressions
|
|
228
|
+
this._flattenExpressionsRecursive(patternExprNode);
|
|
208
229
|
|
|
230
|
+
let pattern = this._buildPattern(name, patternExprNode);
|
|
209
231
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const literal = this._buildLiteral(name, literalNode);
|
|
232
|
+
// For alias assignments (RHS is just a patternIdentifier), rename to the assignment name
|
|
233
|
+
if (patternExprNode.name === "patternIdentifier" && pattern.name !== name) {
|
|
234
|
+
pattern = pattern.clone(name);
|
|
235
|
+
}
|
|
215
236
|
|
|
216
|
-
|
|
217
|
-
|
|
237
|
+
this._applyDecorators(i, allStatements, pattern);
|
|
238
|
+
this._parseContext.patternsByName.set(name, pattern);
|
|
239
|
+
} else if (statementNode.name === "exportPattern") {
|
|
240
|
+
const exportName = statementNode.value;
|
|
241
|
+
const pattern = this._getPattern(exportName).clone();
|
|
242
|
+
this._parseContext.patternsByName.set(exportName, pattern);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
218
245
|
}
|
|
219
246
|
|
|
220
|
-
private
|
|
221
|
-
|
|
222
|
-
|
|
247
|
+
private _buildPattern(name: string, node: Node): Pattern {
|
|
248
|
+
switch (node.name) {
|
|
249
|
+
case "literal": {
|
|
250
|
+
// Use the literal's content as the name when in anonymous position
|
|
251
|
+
const litName = name.startsWith("anonymous-pattern-") ? this._resolveStringValue(node.value) : name;
|
|
252
|
+
return this._buildLiteral(litName, node);
|
|
253
|
+
}
|
|
254
|
+
case "regex": {
|
|
255
|
+
// Use the regex's content as the name when in anonymous position
|
|
256
|
+
const regName = name.startsWith("anonymous-pattern-") ? node.value.slice(1, -1) : name;
|
|
257
|
+
return this._buildRegex(regName, node);
|
|
258
|
+
}
|
|
259
|
+
case "sequenceExpr":
|
|
260
|
+
return this._buildSequence(name, node);
|
|
261
|
+
case "optionsExpr":
|
|
262
|
+
return this._buildOptions(name, node);
|
|
263
|
+
case "greedyOptionsExpr":
|
|
264
|
+
return this._buildGreedyOptions(name, node);
|
|
265
|
+
case "repeatExpr":
|
|
266
|
+
return this._buildRepeat(name, node);
|
|
267
|
+
case "patternGroupExpr":
|
|
268
|
+
return this._buildPatternGroup(name, node);
|
|
269
|
+
case "notExpr":
|
|
270
|
+
return this._buildNot(name, node);
|
|
271
|
+
case "optionalExpr":
|
|
272
|
+
return this._buildOptional(name, node);
|
|
273
|
+
case "rightAssociationExpr":
|
|
274
|
+
return this._buildRightAssociation(node);
|
|
275
|
+
case "patternIdentifier": {
|
|
276
|
+
// Use the pattern's own name for references (important for Expression recursion detection)
|
|
277
|
+
// The caller (_buildPatterns) renames for alias assignments
|
|
278
|
+
return this._getPattern(node.value).clone();
|
|
279
|
+
}
|
|
280
|
+
case "takeUntilExpr":
|
|
281
|
+
return this._buildTakeUntil(name, node);
|
|
282
|
+
}
|
|
223
283
|
|
|
224
|
-
|
|
225
|
-
return value.replace(/\\n/g, '\n')
|
|
226
|
-
.replace(/\\r/g, '\r')
|
|
227
|
-
.replace(/\\t/g, '\t')
|
|
228
|
-
.replace(/\\b/g, '\b')
|
|
229
|
-
.replace(/\\f/g, '\f')
|
|
230
|
-
.replace(/\\v/g, '\v')
|
|
231
|
-
.replace(/\\0/g, '\0')
|
|
232
|
-
.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
233
|
-
.replace(/\\u([0-9A-Fa-f]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
234
|
-
.replace(/\\(.)/g, '$1').slice(1, -1);
|
|
284
|
+
throw new Error(`Couldn't build node: ${node.name}.`);
|
|
235
285
|
}
|
|
236
286
|
|
|
237
|
-
private
|
|
238
|
-
|
|
239
|
-
const regexNode = statementNode.find(n => n.name === "regex-literal") as Node;
|
|
240
|
-
const name = nameNode.value;
|
|
241
|
-
const regex = this._buildRegex(name, regexNode);
|
|
242
|
-
|
|
243
|
-
this._applyDecorators(statementNode, regex);
|
|
244
|
-
this._parseContext.patternsByName.set(name, regex);
|
|
287
|
+
private _buildLiteral(name: string, node: Node) {
|
|
288
|
+
return new Literal(name, this._resolveStringValue(node.value));
|
|
245
289
|
}
|
|
246
290
|
|
|
247
291
|
private _buildRegex(name: string, node: Node) {
|
|
@@ -249,237 +293,288 @@ export class Grammar {
|
|
|
249
293
|
return new Regex(name, value);
|
|
250
294
|
}
|
|
251
295
|
|
|
252
|
-
private
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
const options = this._buildOptions(name, optionsNode);
|
|
257
|
-
|
|
258
|
-
this._applyDecorators(statementNode, options);
|
|
259
|
-
this._parseContext.patternsByName.set(name, options);
|
|
296
|
+
private _buildSequence(name: string, node: Node) {
|
|
297
|
+
const patternChildren = node.children.filter(n => !skipNodes[n.name]);
|
|
298
|
+
const patterns = patternChildren.map(n => this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, n));
|
|
299
|
+
return new Sequence(name, patterns);
|
|
260
300
|
}
|
|
261
301
|
|
|
262
302
|
private _buildOptions(name: string, node: Node) {
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const rightAssociated = n.find(n => n.name === "right-associated");
|
|
267
|
-
if (rightAssociated != null) {
|
|
268
|
-
return new RightAssociated(this._buildPattern(n.children[0]));
|
|
269
|
-
} else {
|
|
270
|
-
return this._buildPattern(n.children[0]);
|
|
271
|
-
}
|
|
272
|
-
|
|
303
|
+
const patternChildren = node.children.filter(n => !skipNodes[n.name]);
|
|
304
|
+
const patterns = patternChildren.map(n => {
|
|
305
|
+
return this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, n);
|
|
273
306
|
});
|
|
274
307
|
|
|
275
308
|
const hasRecursivePattern = patterns.some(p => this._isRecursive(name, p));
|
|
276
|
-
if (hasRecursivePattern
|
|
309
|
+
if (hasRecursivePattern) {
|
|
277
310
|
try {
|
|
278
|
-
|
|
279
|
-
return expression;
|
|
311
|
+
return new Expression(name, patterns);
|
|
280
312
|
} catch { }
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
|
|
284
|
-
return options;
|
|
315
|
+
return new Options(name, patterns);
|
|
285
316
|
}
|
|
286
317
|
|
|
287
|
-
private
|
|
288
|
-
|
|
289
|
-
|
|
318
|
+
private _buildGreedyOptions(name: string, node: Node) {
|
|
319
|
+
const patternChildren = node.children.filter(n => !skipNodes[n.name]);
|
|
320
|
+
const patterns = patternChildren.map(n => this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, n));
|
|
321
|
+
return new Options(name, patterns, true);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private _buildRepeat(name: string, node: Node) {
|
|
325
|
+
let isOptional = false;
|
|
326
|
+
|
|
327
|
+
// Find the main pattern (first non-structural child inside parens)
|
|
328
|
+
const patternNode = node.children.find(n =>
|
|
329
|
+
!skipNodes[n.name] && n.name !== "(" && n.name !== ")" &&
|
|
330
|
+
n.name !== "repeatOptions" && n.name !== "optionalDelimiter" &&
|
|
331
|
+
n.name !== "delimiter" && n.name !== "oneOrMore" &&
|
|
332
|
+
n.name !== "zeroOrMore" && n.name !== "repeatBounds"
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (patternNode == null) {
|
|
336
|
+
throw new Error(`Repeat pattern missing inner pattern.`);
|
|
290
337
|
}
|
|
291
338
|
|
|
292
|
-
|
|
293
|
-
}
|
|
339
|
+
const pattern = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, patternNode);
|
|
294
340
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
341
|
+
const options: RepeatOptions = {
|
|
342
|
+
min: 1,
|
|
343
|
+
max: Infinity
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// Handle delimiter
|
|
347
|
+
const delimiterNode = node.find(n => n.name === "delimiter");
|
|
348
|
+
if (delimiterNode != null) {
|
|
349
|
+
const delimPatternNode = delimiterNode.children.find(n =>
|
|
350
|
+
!skipNodes[n.name] && n.name !== "," && n.name !== "optionalTrim" && n.name !== "trim"
|
|
351
|
+
);
|
|
352
|
+
if (delimPatternNode != null) {
|
|
353
|
+
options.divider = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, delimPatternNode);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const trimFlag = delimiterNode.find(n => n.name === "trim");
|
|
357
|
+
if (trimFlag != null) {
|
|
358
|
+
options.trimDivider = true;
|
|
359
|
+
}
|
|
300
360
|
}
|
|
301
361
|
|
|
302
|
-
|
|
303
|
-
|
|
362
|
+
// Handle quantifier / bounds
|
|
363
|
+
const repeatOptionsNode = node.find(n => n.name === "oneOrMore" || n.name === "zeroOrMore" || n.name === "repeatBounds");
|
|
364
|
+
|
|
365
|
+
if (repeatOptionsNode != null) {
|
|
366
|
+
if (repeatOptionsNode.name === "oneOrMore") {
|
|
367
|
+
options.min = 1;
|
|
368
|
+
options.max = Infinity;
|
|
369
|
+
} else if (repeatOptionsNode.name === "zeroOrMore") {
|
|
370
|
+
isOptional = true;
|
|
371
|
+
options.min = 0;
|
|
372
|
+
options.max = Infinity;
|
|
373
|
+
} else if (repeatOptionsNode.name === "repeatBounds") {
|
|
374
|
+
const integers = repeatOptionsNode.findAll(n => n.name === "integer");
|
|
375
|
+
const hasComma = repeatOptionsNode.find(n => n.name === ",") != null;
|
|
376
|
+
|
|
377
|
+
if (integers.length === 2) {
|
|
378
|
+
// {min, max}
|
|
379
|
+
options.min = Number(integers[0].value);
|
|
380
|
+
options.max = Number(integers[1].value);
|
|
381
|
+
} else if (integers.length === 1 && hasComma) {
|
|
382
|
+
// Check position to determine {min,} or {,max}
|
|
383
|
+
const commaNode = repeatOptionsNode.find(n => n.name === ",") as Node;
|
|
384
|
+
const intNode = integers[0];
|
|
385
|
+
if (intNode.startIndex < commaNode.startIndex) {
|
|
386
|
+
// {min,}
|
|
387
|
+
options.min = Number(intNode.value);
|
|
388
|
+
options.max = Infinity;
|
|
389
|
+
} else {
|
|
390
|
+
// {,max}
|
|
391
|
+
options.min = 0;
|
|
392
|
+
options.max = Number(intNode.value);
|
|
393
|
+
}
|
|
394
|
+
} else if (integers.length === 1 && !hasComma) {
|
|
395
|
+
// {exact}
|
|
396
|
+
const count = Number(integers[0].value);
|
|
397
|
+
options.min = count;
|
|
398
|
+
options.max = count;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
304
401
|
}
|
|
305
402
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
403
|
+
if (isOptional) {
|
|
404
|
+
return new Optional(name, new Repeat(`inner-optional-${name}`, pattern, options));
|
|
405
|
+
}
|
|
309
406
|
|
|
310
|
-
return
|
|
311
|
-
(firstChild.name === name ||
|
|
312
|
-
lastChild.name === name);
|
|
407
|
+
return new Repeat(name, pattern, options);
|
|
313
408
|
}
|
|
314
409
|
|
|
315
|
-
private
|
|
316
|
-
const
|
|
317
|
-
|
|
410
|
+
private _buildPatternGroup(_name: string, node: Node) {
|
|
411
|
+
const innerNode = node.children.find(n =>
|
|
412
|
+
!skipNodes[n.name] && n.name !== "(" && n.name !== ")"
|
|
413
|
+
);
|
|
318
414
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return this._getPattern(node.value).clone();
|
|
322
|
-
}
|
|
323
|
-
case "literal": {
|
|
324
|
-
return this._buildLiteral(node.value.slice(1, -1), node);
|
|
325
|
-
}
|
|
326
|
-
case "regex-literal": {
|
|
327
|
-
return this._buildRegex(node.value.slice(1, -1), node);
|
|
328
|
-
}
|
|
329
|
-
case "repeat-literal": {
|
|
330
|
-
return this._buildRepeat(name, node);
|
|
331
|
-
}
|
|
332
|
-
case "options-literal": {
|
|
333
|
-
return this._buildOptions(name, node);
|
|
334
|
-
}
|
|
335
|
-
case "sequence-literal": {
|
|
336
|
-
return this._buildSequence(name, node);
|
|
337
|
-
}
|
|
338
|
-
case "take-until-literal": {
|
|
339
|
-
return this._buildTakeUntil(name, node);
|
|
340
|
-
}
|
|
341
|
-
case "complex-anonymous-pattern": {
|
|
342
|
-
return this._buildComplexAnonymousPattern(node);
|
|
343
|
-
}
|
|
415
|
+
if (innerNode == null) {
|
|
416
|
+
throw new Error("Empty pattern group.");
|
|
344
417
|
}
|
|
345
418
|
|
|
346
|
-
|
|
419
|
+
return this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, innerNode);
|
|
347
420
|
}
|
|
348
421
|
|
|
349
|
-
private
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
422
|
+
private _buildNot(_name: string, node: Node) {
|
|
423
|
+
const innerNode = node.children.find(n => !skipNodes[n.name] && n.name !== "not");
|
|
424
|
+
|
|
425
|
+
if (innerNode == null) {
|
|
426
|
+
throw new Error("Not pattern missing inner pattern.");
|
|
427
|
+
}
|
|
354
428
|
|
|
355
|
-
this.
|
|
356
|
-
|
|
429
|
+
const inner = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, innerNode);
|
|
430
|
+
return new Not(`not-${inner.name}`, inner);
|
|
357
431
|
}
|
|
358
432
|
|
|
359
|
-
private
|
|
360
|
-
const
|
|
433
|
+
private _buildOptional(_name: string, node: Node) {
|
|
434
|
+
const innerNode = node.children.find(n => !skipNodes[n.name] && n.name !== "?");
|
|
361
435
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const isOptional = n.find(n => n.name === "is-optional");
|
|
366
|
-
const pattern = this._buildPattern(patternNode);
|
|
367
|
-
const finalPattern = isOptional ? new Optional(`optional-${pattern.name}`, pattern) : pattern;
|
|
436
|
+
if (innerNode == null) {
|
|
437
|
+
throw new Error("Optional pattern missing inner pattern.");
|
|
438
|
+
}
|
|
368
439
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
440
|
+
const inner = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, innerNode);
|
|
441
|
+
return new Optional(`optional-${inner.name}`, inner);
|
|
442
|
+
}
|
|
372
443
|
|
|
373
|
-
|
|
374
|
-
|
|
444
|
+
private _buildRightAssociation(node: Node) {
|
|
445
|
+
const innerNode = node.children.find(n => !skipNodes[n.name] && n.name !== "right");
|
|
375
446
|
|
|
376
|
-
|
|
447
|
+
if (innerNode == null) {
|
|
448
|
+
throw new Error("RightAssociation pattern missing inner pattern.");
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return new RightAssociated(this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, innerNode));
|
|
377
452
|
}
|
|
378
453
|
|
|
379
|
-
private
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
454
|
+
private _buildTakeUntil(name: string, node: Node) {
|
|
455
|
+
// The last meaningful child is the until pattern
|
|
456
|
+
const patternChildren = node.children.filter(n =>
|
|
457
|
+
!skipNodes[n.name] && n.name !== "anyChar" && n.name !== "upTo" && n.name !== "wall"
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const untilPatternNode = patternChildren[patternChildren.length - 1];
|
|
384
461
|
|
|
385
|
-
|
|
386
|
-
|
|
462
|
+
if (untilPatternNode == null) {
|
|
463
|
+
throw new Error("TakeUntil pattern missing terminator pattern.");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const untilPattern = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, untilPatternNode);
|
|
467
|
+
return new TakeUntil(name, untilPattern);
|
|
387
468
|
}
|
|
388
469
|
|
|
389
|
-
|
|
390
|
-
let isOptional = false;
|
|
391
|
-
const bounds = repeatNode.find(n => n.name === "bounds");
|
|
392
|
-
const exactCount = repeatNode.find(n => n.name === "exact-count");
|
|
393
|
-
const quantifier = repeatNode.find(n => n.name === "quantifier-shorthand");
|
|
394
|
-
const trimDivider = repeatNode.find(n => n.name === "trim-flag") != null;
|
|
395
|
-
const patterNode = repeatNode.children[1].type === "spaces" ? repeatNode.children[2] : repeatNode.children[1];
|
|
396
|
-
const pattern = this._buildPattern(patterNode);
|
|
397
|
-
const dividerSectionNode = repeatNode.find(n => n.name === "repeat-divider-section");
|
|
470
|
+
// --- Helpers ---
|
|
398
471
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
472
|
+
private _resolveStringValue(value: string) {
|
|
473
|
+
return value.replace(/\\n/g, '\n')
|
|
474
|
+
.replace(/\\r/g, '\r')
|
|
475
|
+
.replace(/\\t/g, '\t')
|
|
476
|
+
.replace(/\\b/g, '\b')
|
|
477
|
+
.replace(/\\f/g, '\f')
|
|
478
|
+
.replace(/\\v/g, '\v')
|
|
479
|
+
.replace(/\\0/g, '\0')
|
|
480
|
+
.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))
|
|
481
|
+
.replace(/\\u([0-9A-Fa-f]{4})/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))
|
|
482
|
+
.replace(/\\(.)/g, '$1').slice(1, -1);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private _getPattern(name: string) {
|
|
486
|
+
let pattern = this._parseContext.patternsByName.get(name);
|
|
403
487
|
|
|
404
|
-
if (
|
|
405
|
-
|
|
488
|
+
if (pattern == null) {
|
|
489
|
+
pattern = this._parseContext.importedPatternsByName.get(name);
|
|
406
490
|
}
|
|
407
491
|
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
options.divider = this._buildPattern(dividerNode);
|
|
492
|
+
if (pattern == null) {
|
|
493
|
+
pattern = this._parseContext.paramsByName.get(name);
|
|
411
494
|
}
|
|
412
495
|
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
|
|
496
|
+
if (pattern == null) {
|
|
497
|
+
return new Reference(name);
|
|
498
|
+
}
|
|
416
499
|
|
|
417
|
-
|
|
418
|
-
|
|
500
|
+
return pattern;
|
|
501
|
+
}
|
|
419
502
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
503
|
+
private _isRecursive(name: string, pattern: Pattern) {
|
|
504
|
+
if (pattern.type === "right-associated") {
|
|
505
|
+
pattern = pattern.children[0];
|
|
506
|
+
}
|
|
507
|
+
return this._isRecursivePattern(name, pattern);
|
|
508
|
+
}
|
|
425
509
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const type = quantifier.value;
|
|
430
|
-
if (type === "+") {
|
|
431
|
-
options.min = 1;
|
|
432
|
-
options.max = Infinity;
|
|
433
|
-
} else {
|
|
434
|
-
isOptional = true;
|
|
435
|
-
}
|
|
510
|
+
private _isRecursivePattern(name: string, pattern: Pattern) {
|
|
511
|
+
if (pattern.type === "reference") {
|
|
512
|
+
return true;
|
|
436
513
|
}
|
|
437
514
|
|
|
438
|
-
|
|
439
|
-
|
|
515
|
+
if (pattern.children.length === 0) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
440
518
|
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
const takeUntilNode = statementNode.find(n => n.name === "take-until-literal") as Node;
|
|
445
|
-
const takeUntil = this._buildTakeUntil(name, takeUntilNode);
|
|
519
|
+
const firstChild = pattern.children[0];
|
|
520
|
+
const lastChild = pattern.children[pattern.children.length - 1];
|
|
521
|
+
const isLongEnough = pattern.children.length >= 2;
|
|
446
522
|
|
|
447
|
-
|
|
448
|
-
|
|
523
|
+
return pattern.type === "sequence" && isLongEnough &&
|
|
524
|
+
(firstChild.name === name || lastChild.name === name);
|
|
449
525
|
}
|
|
450
526
|
|
|
527
|
+
// --- Decorators ---
|
|
451
528
|
|
|
452
|
-
private
|
|
453
|
-
const
|
|
454
|
-
|
|
529
|
+
private _applyDecorators(statementIndex: number, allStatements: readonly Node[], pattern: Pattern) {
|
|
530
|
+
const decorators = this._parseContext.decorators;
|
|
531
|
+
let decoratorNodes: Node[] = [];
|
|
455
532
|
|
|
456
|
-
|
|
457
|
-
|
|
533
|
+
// Walk backwards from the statement to find preceding decorator statements
|
|
534
|
+
// Note: statements Repeat includes newLine divider nodes between statements
|
|
535
|
+
for (let i = statementIndex - 1; i >= 0; i--) {
|
|
536
|
+
const prev = allStatements[i];
|
|
537
|
+
if (prev.name === "decorationStatement" || prev.name === "methodDecorationStatement" || prev.name === "nameDecorationStatement") {
|
|
538
|
+
decoratorNodes.push(prev);
|
|
539
|
+
} else if (prev.name === "comment" || prev.name === "newLine") {
|
|
540
|
+
// Comments and newline dividers can appear between decorators and the statement
|
|
541
|
+
continue;
|
|
542
|
+
} else {
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
458
546
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const anonymousNode = node.find(n => n.name === "configurable-anonymous-pattern") as Node;
|
|
463
|
-
const isOptional = node.children[1] != null;
|
|
547
|
+
for (const d of decoratorNodes) {
|
|
548
|
+
const actualDecorator = d.name === "decorationStatement" ? d.children[0] : d;
|
|
549
|
+
const nameNode = actualDecorator.find(n => n.name === "decorationName");
|
|
464
550
|
|
|
465
|
-
|
|
551
|
+
if (nameNode == null || decorators[nameNode.value] == null) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
466
554
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
555
|
+
if (actualDecorator.name === "nameDecorationStatement") {
|
|
556
|
+
decorators[nameNode.value](pattern);
|
|
557
|
+
} else if (actualDecorator.name === "methodDecorationStatement") {
|
|
558
|
+
const jsonValueNode = actualDecorator.find(n => n.name === "jsonArray" || n.name === "jsonObject" || n.name === "jsonString" || n.name === "jsonNumber" || n.name === "jsonBoolean" || n.name === "jsonNull");
|
|
470
559
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
560
|
+
if (jsonValueNode == null) {
|
|
561
|
+
decorators[nameNode.value](pattern);
|
|
562
|
+
} else {
|
|
563
|
+
decorators[nameNode.value](pattern, JSON.parse(jsonValueNode.value));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
474
567
|
}
|
|
475
568
|
|
|
569
|
+
// --- Import Handling ---
|
|
570
|
+
|
|
476
571
|
private async _resolveImports(ast: Node) {
|
|
477
572
|
const importStatements = ast.findAll(n => {
|
|
478
|
-
return n.name === "
|
|
573
|
+
return n.name === "importStatement" || n.name === "useParamsStatement";
|
|
479
574
|
});
|
|
480
575
|
|
|
481
576
|
for (const statement of importStatements) {
|
|
482
|
-
if (statement.name === "
|
|
577
|
+
if (statement.name === "importStatement") {
|
|
483
578
|
await this._processImport(statement);
|
|
484
579
|
} else {
|
|
485
580
|
this._processUseParams(statement);
|
|
@@ -489,11 +584,11 @@ export class Grammar {
|
|
|
489
584
|
|
|
490
585
|
private _resolveImportsSync(ast: Node) {
|
|
491
586
|
const importStatements = ast.findAll(n => {
|
|
492
|
-
return n.name === "
|
|
587
|
+
return n.name === "importStatement" || n.name === "useParamsStatement";
|
|
493
588
|
});
|
|
494
589
|
|
|
495
590
|
for (const statement of importStatements) {
|
|
496
|
-
if (statement.name === "
|
|
591
|
+
if (statement.name === "importStatement") {
|
|
497
592
|
this._processImportSync(statement);
|
|
498
593
|
} else {
|
|
499
594
|
this._processUseParams(statement);
|
|
@@ -504,10 +599,15 @@ export class Grammar {
|
|
|
504
599
|
private _processImportSync(importStatement: Node) {
|
|
505
600
|
const parseContext = this._parseContext;
|
|
506
601
|
const resourceNode = importStatement.find(n => n.name === "resource") as Node;
|
|
507
|
-
|
|
602
|
+
|
|
603
|
+
if (resourceNode == null) {
|
|
604
|
+
throw new Error("Invalid import statement: resource is required");
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const params = this._getWithParams(importStatement);
|
|
508
608
|
const resource = resourceNode.value.slice(1, -1);
|
|
509
609
|
const grammarFile = this._resolveImportSync(resource, this._originResource || null);
|
|
510
|
-
const
|
|
610
|
+
const g = new Grammar({
|
|
511
611
|
resolveImport: this._resolveImport,
|
|
512
612
|
resolveImportSync: this._resolveImportSync,
|
|
513
613
|
originResource: grammarFile.resource,
|
|
@@ -516,59 +616,25 @@ export class Grammar {
|
|
|
516
616
|
});
|
|
517
617
|
|
|
518
618
|
try {
|
|
519
|
-
const patterns =
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
importStatements.forEach((node) => {
|
|
523
|
-
if (node.name === "import-name" && node.parent?.name === "import-alias") {
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
if (node.name === "import-name" && node.parent?.name !== "import-alias") {
|
|
528
|
-
const importName = node.value;
|
|
529
|
-
|
|
530
|
-
if (parseContext.importedPatternsByName.has(importName)) {
|
|
531
|
-
throw new Error(`'${importName}' was already used within another import.`);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const pattern = patterns[importName];
|
|
535
|
-
if (pattern == null) {
|
|
536
|
-
throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
parseContext.importedPatternsByName.set(importName, pattern);
|
|
540
|
-
} else {
|
|
541
|
-
const importNameNode = node.find(n => n.name === "import-name") as Node;
|
|
542
|
-
const importName = importNameNode.value;
|
|
543
|
-
const aliasNode = node.find(n => n.name === "import-name-alias") as Node;
|
|
544
|
-
const alias = aliasNode.value;
|
|
545
|
-
|
|
546
|
-
if (parseContext.importedPatternsByName.has(alias)) {
|
|
547
|
-
throw new Error(`'${alias}' was already used within another import.`);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const pattern = patterns[importName];
|
|
551
|
-
if (pattern == null) {
|
|
552
|
-
throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
parseContext.importedPatternsByName.set(alias, pattern.clone(alias));
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
|
|
619
|
+
const patterns = g.parseString(grammarFile.expression);
|
|
620
|
+
this._processImportNames(importStatement, patterns, parseContext, resource);
|
|
559
621
|
} catch (e: any) {
|
|
560
622
|
throw new Error(`Failed loading expression from: "${resource}". Error details: "${e.message}"`);
|
|
561
623
|
}
|
|
562
|
-
|
|
563
624
|
}
|
|
564
625
|
|
|
565
626
|
private async _processImport(importStatement: Node) {
|
|
566
627
|
const parseContext = this._parseContext;
|
|
567
628
|
const resourceNode = importStatement.find(n => n.name === "resource") as Node;
|
|
568
|
-
|
|
629
|
+
|
|
630
|
+
if (resourceNode == null) {
|
|
631
|
+
throw new Error("Invalid import statement: resource is required");
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const params = this._getWithParams(importStatement);
|
|
569
635
|
const resource = resourceNode.value.slice(1, -1);
|
|
570
636
|
const grammarFile = await this._resolveImport(resource, this._originResource || null);
|
|
571
|
-
const
|
|
637
|
+
const g = new Grammar({
|
|
572
638
|
resolveImport: this._resolveImport,
|
|
573
639
|
originResource: grammarFile.resource,
|
|
574
640
|
params,
|
|
@@ -576,157 +642,76 @@ export class Grammar {
|
|
|
576
642
|
});
|
|
577
643
|
|
|
578
644
|
try {
|
|
579
|
-
const patterns = await
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
importStatements.forEach((node) => {
|
|
583
|
-
if (node.name === "import-name" && node.parent?.name === "import-alias") {
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (node.name === "import-name" && node.parent?.name !== "import-alias") {
|
|
588
|
-
const importName = node.value;
|
|
589
|
-
|
|
590
|
-
if (parseContext.importedPatternsByName.has(importName)) {
|
|
591
|
-
throw new Error(`'${importName}' was already used within another import.`);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
const pattern = patterns[importName];
|
|
595
|
-
if (pattern == null) {
|
|
596
|
-
throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
parseContext.importedPatternsByName.set(importName, pattern);
|
|
600
|
-
} else {
|
|
601
|
-
const importNameNode = node.find(n => n.name === "import-name") as Node;
|
|
602
|
-
const importName = importNameNode.value;
|
|
603
|
-
const aliasNode = node.find(n => n.name === "import-name-alias") as Node;
|
|
604
|
-
const alias = aliasNode.value;
|
|
605
|
-
|
|
606
|
-
if (parseContext.importedPatternsByName.has(alias)) {
|
|
607
|
-
throw new Error(`'${alias}' was already used within another import.`);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const pattern = patterns[importName];
|
|
611
|
-
if (pattern == null) {
|
|
612
|
-
throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
parseContext.importedPatternsByName.set(alias, pattern.clone(alias));
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
|
|
645
|
+
const patterns = await g.parse(grammarFile.expression);
|
|
646
|
+
this._processImportNames(importStatement, patterns, parseContext, resource);
|
|
619
647
|
} catch (e: any) {
|
|
620
648
|
throw new Error(`Failed loading expression from: "${resource}". Error details: "${e.message}"`);
|
|
621
649
|
}
|
|
622
|
-
|
|
623
650
|
}
|
|
624
651
|
|
|
625
|
-
private
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const nameNode = paramName.find(n => n.name === "param-name");
|
|
632
|
-
const defaultNameNode = defaultValueNode.find(n => n.name === "default-param-name");
|
|
633
|
-
|
|
634
|
-
if (nameNode == null || defaultNameNode == null) {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const name = nameNode.value;
|
|
639
|
-
const defaultName = defaultNameNode.value;
|
|
652
|
+
private _processImportNames(importStatement: Node, patterns: Record<string, Pattern>, parseContext: ParseContext, resource: string) {
|
|
653
|
+
// Find all imported names (could be aliases or plain names)
|
|
654
|
+
const importedPatternsNode = importStatement.find(n => n.name === "importedPatterns");
|
|
655
|
+
if (importedPatternsNode == null) return;
|
|
640
656
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
657
|
+
const patternNamesNode = importedPatternsNode.find(n => n.name === "patternNames");
|
|
658
|
+
if (patternNamesNode == null) return;
|
|
644
659
|
|
|
645
|
-
|
|
660
|
+
for (const child of patternNamesNode.children) {
|
|
661
|
+
if (child.name === "importAlias") {
|
|
662
|
+
const nameNode = child.find(n => n.name === "patternName") as Node;
|
|
663
|
+
const aliasNode = child.find(n => n.name === "importNameAlias") as Node;
|
|
664
|
+
const importName = nameNode.value;
|
|
665
|
+
const alias = aliasNode.value;
|
|
646
666
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
667
|
+
if (parseContext.importedPatternsByName.has(alias)) {
|
|
668
|
+
throw new Error(`'${alias}' was already used within another import.`);
|
|
669
|
+
}
|
|
650
670
|
|
|
651
|
-
|
|
652
|
-
|
|
671
|
+
const pattern = patterns[importName];
|
|
672
|
+
if (pattern == null) {
|
|
673
|
+
throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
|
|
674
|
+
}
|
|
653
675
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
676
|
+
parseContext.importedPatternsByName.set(alias, pattern.clone(alias));
|
|
677
|
+
} else if (child.name === "patternName") {
|
|
678
|
+
const importName = child.value;
|
|
657
679
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
680
|
+
if (parseContext.importedPatternsByName.has(importName)) {
|
|
681
|
+
throw new Error(`'${importName}' was already used within another import.`);
|
|
682
|
+
}
|
|
661
683
|
|
|
662
|
-
|
|
663
|
-
|
|
684
|
+
const pattern = patterns[importName];
|
|
685
|
+
if (pattern == null) {
|
|
686
|
+
throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
|
|
687
|
+
}
|
|
664
688
|
|
|
665
|
-
|
|
666
|
-
if (prevSibling.find(n => n.name === "assign-statement")) {
|
|
667
|
-
break;
|
|
689
|
+
parseContext.importedPatternsByName.set(importName, pattern);
|
|
668
690
|
}
|
|
669
|
-
|
|
670
|
-
prevSibling = prevSibling.previousSibling();
|
|
691
|
+
// Skip comma divider nodes
|
|
671
692
|
}
|
|
672
|
-
|
|
673
|
-
decoratorNodes = decoratorNodes.filter(n => n.find(n => n.name.includes("decorator")) != null);
|
|
674
|
-
|
|
675
|
-
decoratorNodes.forEach((d) => {
|
|
676
|
-
const nameNode = d.find(n => n.name === "decorator-name");
|
|
677
|
-
|
|
678
|
-
if (nameNode == null || decorators[nameNode.value] == null) {
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
const nameDocorator = d.find(n => n.name === "name-decorator");
|
|
683
|
-
|
|
684
|
-
if (nameDocorator != null) {
|
|
685
|
-
decorators[nameNode.value](pattern);
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const methodDecorator = d.find(n => n.name === "method-decorator");
|
|
690
|
-
|
|
691
|
-
if (methodDecorator == null) {
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
methodDecorator.findAll(n => n.name.includes("space")).forEach(n => n.remove());
|
|
696
|
-
const argsNode = methodDecorator.children[3];
|
|
697
|
-
|
|
698
|
-
if (argsNode == null || argsNode.name === "close-paren") {
|
|
699
|
-
decorators[nameNode.value](pattern);
|
|
700
|
-
} else {
|
|
701
|
-
decorators[nameNode.value](pattern, JSON.parse(argsNode.value));
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
|
|
705
693
|
}
|
|
706
694
|
|
|
707
|
-
private
|
|
695
|
+
private _getWithParams(importStatement: Node) {
|
|
708
696
|
let params: Pattern[] = [];
|
|
709
|
-
const
|
|
697
|
+
const withParamsNode = importStatement.find(n => n.name === "withParamsExpr");
|
|
710
698
|
|
|
711
|
-
if (
|
|
712
|
-
const
|
|
699
|
+
if (withParamsNode != null) {
|
|
700
|
+
const statementsNode = withParamsNode.find(n => n.name === "withParamStatements");
|
|
713
701
|
|
|
714
|
-
if (
|
|
715
|
-
const expression =
|
|
716
|
-
const importedValues =
|
|
717
|
-
._parseContext
|
|
718
|
-
.importedPatternsByName
|
|
719
|
-
.values()
|
|
720
|
-
);
|
|
702
|
+
if (statementsNode != null) {
|
|
703
|
+
const expression = statementsNode.value;
|
|
704
|
+
const importedValues = this._parseContext.getImportedPatterns();
|
|
721
705
|
|
|
722
|
-
const
|
|
706
|
+
const g = new Grammar({
|
|
723
707
|
params: [...importedValues, ...this._parseContext.paramsByName.values()],
|
|
724
708
|
originResource: this._originResource,
|
|
725
709
|
resolveImport: this._resolveImport,
|
|
710
|
+
resolveImportSync: this._resolveImportSync,
|
|
726
711
|
decorators: this._parseContext.decorators
|
|
727
712
|
});
|
|
728
713
|
|
|
729
|
-
const patterns =
|
|
714
|
+
const patterns = g.parseString(expression);
|
|
730
715
|
params = Array.from(Object.values(patterns));
|
|
731
716
|
}
|
|
732
717
|
}
|
|
@@ -734,57 +719,40 @@ export class Grammar {
|
|
|
734
719
|
return params;
|
|
735
720
|
}
|
|
736
721
|
|
|
737
|
-
private
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (pattern == null) {
|
|
741
|
-
pattern = this._parseContext.importedPatternsByName.get(name);
|
|
742
|
-
}
|
|
722
|
+
private _processUseParams(useParamsStatement: Node) {
|
|
723
|
+
const patternsNode = useParamsStatement.find(n => n.name === "useParamPatterns");
|
|
743
724
|
|
|
744
|
-
if (
|
|
745
|
-
|
|
725
|
+
if (patternsNode == null) {
|
|
726
|
+
return;
|
|
746
727
|
}
|
|
747
728
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
729
|
+
// Each child is a paramNameWithDefault: patternName + optionalParamDefault
|
|
730
|
+
const paramNodes = patternsNode.findAll(n => n.name === "paramNameWithDefault");
|
|
751
731
|
|
|
752
|
-
|
|
753
|
-
|
|
732
|
+
for (const paramNode of paramNodes) {
|
|
733
|
+
const nameNode = paramNode.find(n => n.name === "patternName");
|
|
734
|
+
if (nameNode == null) continue;
|
|
754
735
|
|
|
755
|
-
|
|
756
|
-
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
757
|
-
const aliasNode = statementNode.find(n => n.name === "alias-literal") as Node;
|
|
758
|
-
const aliasName = aliasNode.value;
|
|
759
|
-
const name = nameNode.value;
|
|
760
|
-
const aliasPattern = this._getPattern(aliasName);
|
|
736
|
+
const name = nameNode.value;
|
|
761
737
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
this._parseContext.patternsByName.set(name, reference);
|
|
767
|
-
} else {
|
|
768
|
-
const alias = aliasPattern.clone(name);
|
|
769
|
-
this._applyDecorators(statementNode, alias);
|
|
770
|
-
this._parseContext.patternsByName.set(name, alias);
|
|
771
|
-
}
|
|
738
|
+
// If already provided via params option, skip
|
|
739
|
+
if (this._parseContext.paramsByName.has(name)) {
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
772
742
|
|
|
773
|
-
|
|
743
|
+
// Check for default value: use params { value = default-value }
|
|
744
|
+
const defaultNode = paramNode.find(n => n.name === "defaultParamName");
|
|
774
745
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
746
|
+
if (defaultNode != null) {
|
|
747
|
+
const defaultName = defaultNode.value;
|
|
748
|
+
let pattern = this._parseContext.importedPatternsByName.get(defaultName);
|
|
779
749
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}
|
|
750
|
+
if (pattern == null) {
|
|
751
|
+
pattern = new Reference(defaultName);
|
|
752
|
+
}
|
|
784
753
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
754
|
+
this._parseContext.importedPatternsByName.set(name, pattern);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
788
757
|
}
|
|
789
|
-
|
|
790
|
-
}
|
|
758
|
+
}
|