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.
Files changed (38) hide show
  1. package/architecture.md +300 -0
  2. package/dist/grammar/Grammar.d.ts +24 -25
  3. package/dist/grammar/patterns/grammar.d.ts +99 -0
  4. package/dist/grammar/patterns/grammar.test.d.ts +1 -0
  5. package/dist/index.browser.js +2314 -2592
  6. package/dist/index.browser.js.map +1 -1
  7. package/dist/index.esm.js +2314 -2592
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/index.js +2314 -2592
  10. package/dist/index.js.map +1 -1
  11. package/grammar-guide.md +836 -0
  12. package/package.json +1 -1
  13. package/src/grammar/Grammar.test.ts +418 -0
  14. package/src/grammar/Grammar.ts +483 -515
  15. package/src/grammar/patterns/grammar.test.ts +276 -0
  16. package/src/grammar/patterns/grammar.ts +113 -37
  17. package/src/grammar/patterns.ts +6 -6
  18. package/src/grammar/patterns/anonymousPattern.ts +0 -23
  19. package/src/grammar/patterns/body.ts +0 -22
  20. package/src/grammar/patterns/comment.ts +0 -4
  21. package/src/grammar/patterns/decoratorStatement.ts +0 -85
  22. package/src/grammar/patterns/import.ts +0 -88
  23. package/src/grammar/patterns/literal.ts +0 -4
  24. package/src/grammar/patterns/literals.ts +0 -21
  25. package/src/grammar/patterns/name.ts +0 -3
  26. package/src/grammar/patterns/optionsLiteral.ts +0 -25
  27. package/src/grammar/patterns/pattern.ts +0 -29
  28. package/src/grammar/patterns/regexLiteral.ts +0 -5
  29. package/src/grammar/patterns/repeatLiteral.ts +0 -71
  30. package/src/grammar/patterns/sequenceLiteral.ts +0 -24
  31. package/src/grammar/patterns/spaces.ts +0 -16
  32. package/src/grammar/patterns/statement.ts +0 -22
  33. package/src/grammar/patterns/takeUtilLiteral.ts +0 -20
  34. package/src/grammar/spec.md +0 -331
  35. package/src/grammar_v2/patterns/Grammar.ts +0 -170
  36. package/src/grammar_v2/patterns/patterns/cpat.cpat +0 -91
  37. package/src/grammar_v2/patterns/patterns/grammar.test.ts +0 -218
  38. package/src/grammar_v2/patterns/patterns/grammar.ts +0 -103
@@ -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 { grammar } from "./patterns/grammar";
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 { generateErrorMessage } from "../patterns/generate_error_message";
17
- import { tokens } from "./decorators/tokens";
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
- const patternNodes: Record<string, boolean> = {
28
- "literal": true,
29
- "regex-literal": true,
30
- "options-literal": true,
31
- "sequence-literal": true,
32
- "repeat-literal": true,
33
- "alias-literal": true,
34
- "take-until-literal": true,
35
- "configurable-anonymous-pattern": true
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
- export interface GrammarFile {
58
- resource: string;
59
- expression: string;
60
- }
70
+ getImportedPatterns() {
71
+ return Array.from(this.importedPatternsByName.values());
72
+ }
61
73
 
62
- export interface GrammarOptions {
63
- resolveImport?: (resource: string, originResource: string | null) => Promise<GrammarFile>;
64
- resolveImportSync?: (resource: string, originResource: string | null) => GrammarFile;
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._params = options?.params == null ? [] : options.params;
79
- this._originResource = options?.originResource == null ? null : options.originResource;
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
- private _buildPatternRecord() {
118
- const patterns: Record<string, Pattern> = {};
119
- const allPatterns = Array.from(this._parseContext.patternsByName.values());
128
+ // --- Static convenience methods ---
120
129
 
121
- allPatterns.forEach(p => {
122
- patterns[p.name] = new Context(p.name, p, allPatterns.filter(o => o !== p));
123
- });
130
+ static parse(expression: string, options?: GrammarOptions) {
131
+ const g = new Grammar(options);
132
+ return g.parse(expression);
133
+ }
124
134
 
125
- return patterns;
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
- private _tryToParse(expression: string): Node {
129
- const { ast, cursor } = grammar.exec(expression, true);
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
- private _hasImports(ast: Node) {
140
- const importBlock = ast.find(n => n.name === "import-block");
141
- if (importBlock == null) {
142
- return false;
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
- return importBlock && importBlock.children.length > 0;
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 body = ast.find(n => n.name === "body" && n.findAncestor(n => n.name === "head") == null);
202
+ const statementsNode = ast.find(n => n.name === "statements");
150
203
 
151
- if (body == null) {
204
+ if (statementsNode == null) {
152
205
  return;
153
206
  }
154
207
 
155
- const statements = body.findAll(n => n.name === "assign-statement");
208
+ const allStatements = statementsNode.children;
156
209
 
157
- statements.forEach((n) => {
158
- const patternNode = n.children.find(n => patternNodes[n.name] != null);
210
+ // First pass: build pattern assignments
211
+ for (let i = 0; i < allStatements.length; i++) {
212
+ const statementNode = allStatements[i];
159
213
 
160
- if (patternNode == null) {
161
- return;
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
- switch (patternNode.name) {
165
- case "literal": {
166
- this._saveLiteral(n);
167
- break;
168
- }
169
- case "regex-literal": {
170
- this._saveRegex(n);
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
- body.findAll(n => n.name === "export-name").forEach((n) => {
204
- const pattern = this._getPattern(n.value).clone();
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
- private _saveLiteral(statementNode: Node) {
211
- const nameNode = statementNode.find(n => n.name === "name") as Node;
212
- const literalNode = statementNode.find(n => n.name === "literal") as Node;
213
- const name = nameNode.value;
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
- this._applyDecorators(statementNode, literal);
217
- this._parseContext.patternsByName.set(name, literal);
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 _buildLiteral(name: string, node: Node) {
221
- return new Literal(name, this._resolveStringValue(node.value));
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
- private _resolveStringValue(value: string) {
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 _saveRegex(statementNode: Node) {
238
- const nameNode = statementNode.find(n => n.name === "name") as Node;
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 _saveOptions(statementNode: Node) {
253
- const nameNode = statementNode.find(n => n.name === "name") as Node;
254
- const name = nameNode.value;
255
- const optionsNode = statementNode.find(n => n.name === "options-literal") as Node;
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 patternNodes = node.children.filter(n => n.name !== "default-divider" && n.name !== "greedy-divider");
264
- const isGreedy = node.find(n => n.name === "greedy-divider") != null;
265
- const patterns = patternNodes.map(n => {
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 && !isGreedy) {
309
+ if (hasRecursivePattern) {
277
310
  try {
278
- const expression = new Expression(name, patterns);
279
- return expression;
311
+ return new Expression(name, patterns);
280
312
  } catch { }
281
313
  }
282
314
 
283
- const options = new Options(name, patterns, isGreedy);
284
- return options;
315
+ return new Options(name, patterns);
285
316
  }
286
317
 
287
- private _isRecursive(name: string, pattern: Pattern) {
288
- if (pattern.type === "right-associated") {
289
- pattern = pattern.children[0];
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
- return this._isRecursivePattern(name, pattern);
293
- }
339
+ const pattern = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, patternNode);
294
340
 
295
- private _isRecursivePattern(name: string, pattern: Pattern) {
296
- // Because we don't know if the pattern is a sequence with a reference we have to just assume it is.
297
- // The better solution here would be to not have options at all and just use expresssion pattern instead.
298
- if (pattern.type === "reference") {
299
- return true;
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
- if (pattern.children.length === 0) {
303
- return false;
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
- const firstChild = pattern.children[0];
307
- const lastChild = pattern.children[pattern.children.length - 1];
308
- const isLongEnough = pattern.children.length >= 2;
403
+ if (isOptional) {
404
+ return new Optional(name, new Repeat(`inner-optional-${name}`, pattern, options));
405
+ }
309
406
 
310
- return pattern.type === "sequence" && isLongEnough &&
311
- (firstChild.name === name ||
312
- lastChild.name === name);
407
+ return new Repeat(name, pattern, options);
313
408
  }
314
409
 
315
- private _buildPattern(node: Node): Pattern {
316
- const type = node.name;
317
- const name = `anonymous-pattern-${anonymousIndexId++}`;
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
- switch (type) {
320
- case "pattern-name": {
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
- throw new Error(`Couldn't build node: ${node.name}.`);
419
+ return this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, innerNode);
347
420
  }
348
421
 
349
- private _saveSequence(statementNode: Node) {
350
- const nameNode = statementNode.find(n => n.name === "name") as Node;
351
- const name = nameNode.value;
352
- const sequenceNode = statementNode.find(n => n.name === "sequence-literal") as Node;
353
- const sequence = this._buildSequence(name, sequenceNode);
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._applyDecorators(statementNode, sequence);
356
- this._parseContext.patternsByName.set(name, sequence);
429
+ const inner = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, innerNode);
430
+ return new Not(`not-${inner.name}`, inner);
357
431
  }
358
432
 
359
- private _buildSequence(name: string, node: Node) {
360
- const patternNodes = node.children.filter(n => n.name !== "sequence-divider");
433
+ private _buildOptional(_name: string, node: Node) {
434
+ const innerNode = node.children.find(n => !skipNodes[n.name] && n.name !== "?");
361
435
 
362
- const patterns = patternNodes.map(n => {
363
- const patternNode = n.children[0].name === "not" ? n.children[1] : n.children[0];
364
- const isNot = n.find(n => n.name === "not") != null;
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
- if (isNot) {
370
- return new Not(`not-${finalPattern.name}`, finalPattern);
371
- }
440
+ const inner = this._buildPattern(`anonymous-pattern-${anonymousIndexId++}`, innerNode);
441
+ return new Optional(`optional-${inner.name}`, inner);
442
+ }
372
443
 
373
- return finalPattern;
374
- });
444
+ private _buildRightAssociation(node: Node) {
445
+ const innerNode = node.children.find(n => !skipNodes[n.name] && n.name !== "right");
375
446
 
376
- return new Sequence(name, patterns);
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 _saveRepeat(statementNode: Node) {
380
- const nameNode = statementNode.find(n => n.name === "name") as Node;
381
- const name = nameNode.value;
382
- const repeatNode = statementNode.find(n => n.name === "repeat-literal") as Node;
383
- const repeat = this._buildRepeat(name, repeatNode);
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
- this._applyDecorators(statementNode, repeat);
386
- this._parseContext.patternsByName.set(name, repeat);
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
- private _buildRepeat(name: string, repeatNode: Node) {
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
- const options: RepeatOptions = {
400
- min: 1,
401
- max: Infinity
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 (trimDivider) {
405
- options.trimDivider = trimDivider;
488
+ if (pattern == null) {
489
+ pattern = this._parseContext.importedPatternsByName.get(name);
406
490
  }
407
491
 
408
- if (dividerSectionNode != null) {
409
- const dividerNode = dividerSectionNode.children[1];
410
- options.divider = this._buildPattern(dividerNode);
492
+ if (pattern == null) {
493
+ pattern = this._parseContext.paramsByName.get(name);
411
494
  }
412
495
 
413
- if (bounds != null) {
414
- const minNode = bounds.find(p => p.name === "min");
415
- const maxNode = bounds.find(p => p.name === "max");
496
+ if (pattern == null) {
497
+ return new Reference(name);
498
+ }
416
499
 
417
- const min = minNode == null ? 0 : Number(minNode.value);
418
- const max = maxNode == null ? Infinity : Number(maxNode.value);
500
+ return pattern;
501
+ }
419
502
 
420
- options.min = min;
421
- options.max = max;
422
- } else if (exactCount != null) {
423
- const integerNode = exactCount.find(p => p.name === "integer") as Node;
424
- const integer = Number(integerNode.value);
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
- options.min = integer;
427
- options.max = integer;
428
- } else if (quantifier != null) {
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
- return isOptional ? new Optional(name, new Repeat(`inner-optional-${name}`, pattern, options)) : new Repeat(name, pattern, options);
439
- }
515
+ if (pattern.children.length === 0) {
516
+ return false;
517
+ }
440
518
 
441
- private _saveTakeUntil(statementNode: Node) {
442
- const nameNode = statementNode.find(n => n.name === "name") as Node;
443
- const name = nameNode.value;
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
- this._applyDecorators(statementNode, takeUntil);
448
- this._parseContext.patternsByName.set(name, takeUntil);
523
+ return pattern.type === "sequence" && isLongEnough &&
524
+ (firstChild.name === name || lastChild.name === name);
449
525
  }
450
526
 
527
+ // --- Decorators ---
451
528
 
452
- private _buildTakeUntil(name: string, takeUntilNode: Node) {
453
- const patternNode = takeUntilNode.children[takeUntilNode.children.length - 1];
454
- const untilPattern = this._buildPattern(patternNode);
529
+ private _applyDecorators(statementIndex: number, allStatements: readonly Node[], pattern: Pattern) {
530
+ const decorators = this._parseContext.decorators;
531
+ let decoratorNodes: Node[] = [];
455
532
 
456
- return new TakeUntil(name, untilPattern);
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
- private _saveConfigurableAnonymous(node: Node) {
460
- const nameNode = node.find(n => n.name === "name") as Node;
461
- const name = nameNode.value;
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
- const anonymous = isOptional ? new Optional(name, this._buildPattern(anonymousNode.children[0])) : this._buildPattern(anonymousNode.children[0]);
551
+ if (nameNode == null || decorators[nameNode.value] == null) {
552
+ continue;
553
+ }
466
554
 
467
- this._applyDecorators(node, anonymous);
468
- this._parseContext.patternsByName.set(name, anonymous);
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
- private _buildComplexAnonymousPattern(node: Node) {
472
- const wrappedNode = node.children[1].name === "line-spaces" ? node.children[2] : node.children[1];
473
- return this._buildPattern(wrappedNode);
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 === "import-from" || n.name === "param-name-with-default-value";
573
+ return n.name === "importStatement" || n.name === "useParamsStatement";
479
574
  });
480
575
 
481
576
  for (const statement of importStatements) {
482
- if (statement.name === "import-from") {
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 === "import-from" || n.name === "param-name-with-default-value";
587
+ return n.name === "importStatement" || n.name === "useParamsStatement";
493
588
  });
494
589
 
495
590
  for (const statement of importStatements) {
496
- if (statement.name === "import-from") {
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
- const params = this._getParams(importStatement);
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 grammar = new Grammar({
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 = grammar.parseString(grammarFile.expression);
520
- const importStatements = importStatement.findAll(n => n.name === "import-name" || n.name === "import-alias");
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
- const params = this._getParams(importStatement);
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 grammar = new Grammar({
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 grammar.parse(grammarFile.expression);
580
- const importStatements = importStatement.findAll(n => n.name === "import-name" || n.name === "import-alias");
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 _processUseParams(paramName: Node) {
626
- const defaultValueNode = paramName.find(n => n.name === "param-default");
627
- if (defaultValueNode === null) {
628
- return;
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
- if (this._parseContext.paramsByName.has(name)) {
642
- return;
643
- }
657
+ const patternNamesNode = importedPatternsNode.find(n => n.name === "patternNames");
658
+ if (patternNamesNode == null) return;
644
659
 
645
- let pattern = this._parseContext.importedPatternsByName.get(defaultName);
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
- if (pattern == null) {
648
- pattern = new Reference(defaultName);
649
- }
667
+ if (parseContext.importedPatternsByName.has(alias)) {
668
+ throw new Error(`'${alias}' was already used within another import.`);
669
+ }
650
670
 
651
- this._parseContext.importedPatternsByName.set(name, pattern);
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
- private _applyDecorators(statementNode: Node, pattern: Pattern) {
655
- const decorators = this._parseContext.decorators;
656
- const bodyLine = statementNode.parent;
676
+ parseContext.importedPatternsByName.set(alias, pattern.clone(alias));
677
+ } else if (child.name === "patternName") {
678
+ const importName = child.value;
657
679
 
658
- if (bodyLine == null) {
659
- return;
660
- }
680
+ if (parseContext.importedPatternsByName.has(importName)) {
681
+ throw new Error(`'${importName}' was already used within another import.`);
682
+ }
661
683
 
662
- let prevSibling = bodyLine.previousSibling();
663
- let decoratorNodes: Node[] = [];
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
- while (prevSibling != null) {
666
- if (prevSibling.find(n => n.name === "assign-statement")) {
667
- break;
689
+ parseContext.importedPatternsByName.set(importName, pattern);
668
690
  }
669
- decoratorNodes.push(prevSibling);
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 _getParams(importStatement: Node) {
695
+ private _getWithParams(importStatement: Node) {
708
696
  let params: Pattern[] = [];
709
- const paramsStatement = importStatement.find(n => n.name === "with-params-statement");
697
+ const withParamsNode = importStatement.find(n => n.name === "withParamsExpr");
710
698
 
711
- if (paramsStatement != null) {
712
- const statements = paramsStatement.find(n => n.name === "body");
699
+ if (withParamsNode != null) {
700
+ const statementsNode = withParamsNode.find(n => n.name === "withParamStatements");
713
701
 
714
- if (statements != null) {
715
- const expression = statements.toString();
716
- const importedValues = Array.from(this
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 grammar = new Grammar({
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 = grammar.parseString(expression);
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 _getPattern(name: string) {
738
- let pattern = this._parseContext.patternsByName.get(name);
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 (pattern == null) {
745
- pattern = this._parseContext.paramsByName.get(name);
725
+ if (patternsNode == null) {
726
+ return;
746
727
  }
747
728
 
748
- if (pattern == null) {
749
- return new Reference(name);
750
- }
729
+ // Each child is a paramNameWithDefault: patternName + optionalParamDefault
730
+ const paramNodes = patternsNode.findAll(n => n.name === "paramNameWithDefault");
751
731
 
752
- return pattern;
753
- }
732
+ for (const paramNode of paramNodes) {
733
+ const nameNode = paramNode.find(n => n.name === "patternName");
734
+ if (nameNode == null) continue;
754
735
 
755
- private _saveAlias(statementNode: Node) {
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
- // This solves the problem for an alias pointing to a reference.
763
- if (aliasPattern.type === "reference") {
764
- const reference = aliasPattern.clone(name);
765
- this._applyDecorators(statementNode, reference);
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
- static parse(expression: string, options?: GrammarOptions) {
776
- const grammar = new Grammar(options);
777
- return grammar.parse(expression);
778
- }
746
+ if (defaultNode != null) {
747
+ const defaultName = defaultNode.value;
748
+ let pattern = this._parseContext.importedPatternsByName.get(defaultName);
779
749
 
780
- static import(path: string, options?: GrammarOptions) {
781
- const grammar = new Grammar(options);
782
- return grammar.import(path);
783
- }
750
+ if (pattern == null) {
751
+ pattern = new Reference(defaultName);
752
+ }
784
753
 
785
- static parseString(expression: string, options?: GrammarOptions) {
786
- const grammar = new Grammar(options);
787
- return grammar.parseString(expression);
754
+ this._parseContext.importedPatternsByName.set(name, pattern);
755
+ }
756
+ }
788
757
  }
789
-
790
- }
758
+ }