clarity-pattern-parser 11.0.30 → 11.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-pattern-parser",
3
- "version": "11.0.30",
3
+ "version": "11.1.0",
4
4
  "description": "Parsing Library for Typescript and Javascript.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -9,7 +9,7 @@ import { Repeat } from "../patterns/Repeat";
9
9
  import { Grammar } from "./Grammar";
10
10
  import { Optional } from "../patterns/Optional";
11
11
  import { Context } from "../patterns/Context";
12
- import { patterns } from "./patterns";
12
+ import { createPatternsTemplate, patterns } from "./patterns";
13
13
  import { Expression } from "../patterns/Expression";
14
14
 
15
15
  describe("Grammar", () => {
@@ -176,7 +176,7 @@ describe("Grammar", () => {
176
176
  const digits = new Optional("optional-digits", new Repeat("digits", digit, { min: 0 }));
177
177
 
178
178
  const expected = new Context("optional-digits", digits, [digit]);
179
- debugger;
179
+ debugger;
180
180
  expect(pattern.isEqual(expected)).toBeTruthy();
181
181
  });
182
182
 
@@ -596,4 +596,90 @@ debugger;
596
596
  expect(result).toBe(result);
597
597
  });
598
598
 
599
+ test("Decorators", () => {
600
+ const { spaces } = patterns`
601
+ @tokens([" "])
602
+ spaces = /\\s+/
603
+ `;
604
+
605
+ expect(spaces.getTokens()).toEqual([" "]);
606
+ });
607
+
608
+ test("Decorators No Args", () => {
609
+ const { spaces } = patterns`
610
+ @tokens()
611
+ spaces = /\\s+/
612
+ `;
613
+
614
+ expect(spaces.getTokens()).toEqual([]);
615
+ });
616
+
617
+ test("Decorators Bad Args", () => {
618
+ const { spaces } = patterns`
619
+ @tokens("Bad")
620
+ spaces = /\\s+/
621
+ `;
622
+
623
+ expect(spaces.getTokens()).toEqual([]);
624
+ });
625
+
626
+ test("Decorators Bad Decorator", () => {
627
+ const { spaces } = patterns`
628
+ @bad-decorator()
629
+ spaces = /\\s+/
630
+ `;
631
+
632
+ expect(spaces.getTokens()).toEqual([]);
633
+ });
634
+
635
+ test("Decorators On Multiple Patterns", () => {
636
+ const { spaces, digits } = patterns`
637
+ @tokens([" "])
638
+ spaces = /\\s+/
639
+
640
+ @tokens(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"])
641
+ digits = /\\d+/
642
+ `;
643
+
644
+ expect(spaces.getTokens()).toEqual([" "]);
645
+ expect(digits.getTokens()).toEqual(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
646
+ });
647
+
648
+ test("Decorators On Multiple Patterns And Comments", () => {
649
+ const { spaces, digits } = patterns`
650
+ #Comment
651
+ @tokens([" "])
652
+ spaces = /\\s+/
653
+
654
+ #Comment
655
+ @tokens(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"])
656
+ #Comment
657
+ digits = /\\d+/
658
+ `;
659
+
660
+ expect(spaces.getTokens()).toEqual([" "]);
661
+ expect(digits.getTokens()).toEqual(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
662
+ });
663
+
664
+ test("Custom Named Decorator", () => {
665
+ const allRecordedPatterns: string[] = [];
666
+ const patterns = createPatternsTemplate({
667
+ decorators: {
668
+ record: (pattern) => {
669
+ allRecordedPatterns.push(pattern.name);
670
+ }
671
+ }
672
+ });
673
+
674
+ patterns`
675
+ @record
676
+ spaces = /\\s+/
677
+
678
+ @record
679
+ digits = /\\d+/
680
+ `;
681
+
682
+ expect(allRecordedPatterns).toEqual(["spaces", "digits"]);
683
+ });
684
+
599
685
  });
@@ -13,9 +13,16 @@ import { Context } from "../patterns/Context";
13
13
  import { Expression } from "../patterns/Expression";
14
14
  import { RightAssociated } from "../patterns/RightAssociated";
15
15
  import { generateErrorMessage } from "../patterns/generate_error_message";
16
+ import { tokens } from "./decorators/tokens";
16
17
 
17
18
  let anonymousIndexId = 0;
18
19
 
20
+ export type Decorator = (pattern: Pattern, arg?: string | boolean | number | null | Record<string, any> | any[]) => void;
21
+
22
+ const defaultDecorators = {
23
+ tokens: tokens
24
+ };
25
+
19
26
  const patternNodes: Record<string, boolean> = {
20
27
  "literal": true,
21
28
  "regex-literal": true,
@@ -27,12 +34,14 @@ const patternNodes: Record<string, boolean> = {
27
34
  };
28
35
 
29
36
  class ParseContext {
30
- constructor(params: Pattern[]) {
31
- params.forEach(p => this.paramsByName.set(p.name, p));
32
- }
33
37
  patternsByName = new Map<string, Pattern>();
34
38
  importedPatternsByName = new Map<string, Pattern>();
35
39
  paramsByName = new Map<string, Pattern>();
40
+ decorators: Record<string, Decorator>;
41
+ constructor(params: Pattern[], decorators: Record<string, Decorator> = {}) {
42
+ params.forEach(p => this.paramsByName.set(p.name, p));
43
+ this.decorators = { ...decorators, ...defaultDecorators };
44
+ }
36
45
  }
37
46
 
38
47
  function defaultImportResolver(_path: string, _basePath: string | null): Promise<GrammarFile> {
@@ -48,6 +57,7 @@ export interface GrammarOptions {
48
57
  resolveImport?: (resource: string, originResource: string | null) => Promise<GrammarFile>;
49
58
  originResource?: string | null;
50
59
  params?: Pattern[];
60
+ decorators?: Record<string, Decorator>;
51
61
  }
52
62
 
53
63
  export class Grammar {
@@ -60,7 +70,7 @@ export class Grammar {
60
70
  this._params = options?.params == null ? [] : options.params;
61
71
  this._originResource = options?.originResource == null ? null : options.originResource;
62
72
  this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
63
- this._parseContext = new ParseContext(this._params);
73
+ this._parseContext = new ParseContext(this._params, options.decorators || {});
64
74
  }
65
75
 
66
76
  async import(path: string) {
@@ -68,14 +78,15 @@ export class Grammar {
68
78
  const grammar = new Grammar({
69
79
  resolveImport: this._resolveImport,
70
80
  originResource: grammarFile.resource,
71
- params: this._params
81
+ params: this._params,
82
+ decorators: this._parseContext.decorators
72
83
  });
73
84
 
74
85
  return grammar.parse(grammarFile.expression);
75
86
  }
76
87
 
77
88
  async parse(expression: string) {
78
- this._parseContext = new ParseContext(this._params);
89
+ this._parseContext = new ParseContext(this._params, this._parseContext.decorators);
79
90
  const ast = this._tryToParse(expression);
80
91
 
81
92
  await this._resolveImports(ast);
@@ -85,7 +96,7 @@ export class Grammar {
85
96
  }
86
97
 
87
98
  parseString(expression: string) {
88
- this._parseContext = new ParseContext(this._params);
99
+ this._parseContext = new ParseContext(this._params, this._parseContext.decorators);
89
100
  const ast = this._tryToParse(expression);
90
101
 
91
102
  if (this._hasImports(ast)) {
@@ -191,6 +202,7 @@ export class Grammar {
191
202
  const name = nameNode.value;
192
203
  const literal = this._buildLiteral(name, literalNode);
193
204
 
205
+ this._applyDecorators(statementNode, literal);
194
206
  this._parseContext.patternsByName.set(name, literal);
195
207
  }
196
208
 
@@ -217,6 +229,7 @@ export class Grammar {
217
229
  const name = nameNode.value;
218
230
  const regex = this._buildRegex(name, regexNode);
219
231
 
232
+ this._applyDecorators(statementNode, regex);
220
233
  this._parseContext.patternsByName.set(name, regex);
221
234
  }
222
235
 
@@ -231,6 +244,7 @@ export class Grammar {
231
244
  const optionsNode = statementNode.find(n => n.name === "options-literal") as Node;
232
245
  const options = this._buildOptions(name, optionsNode);
233
246
 
247
+ this._applyDecorators(statementNode, options);
234
248
  this._parseContext.patternsByName.set(name, options);
235
249
  }
236
250
 
@@ -322,6 +336,7 @@ export class Grammar {
322
336
  const sequenceNode = statementNode.find(n => n.name === "sequence-literal") as Node;
323
337
  const sequence = this._buildSequence(name, sequenceNode);
324
338
 
339
+ this._applyDecorators(statementNode, sequence);
325
340
  this._parseContext.patternsByName.set(name, sequence);
326
341
  }
327
342
 
@@ -351,6 +366,7 @@ export class Grammar {
351
366
  const repeatNode = statementNode.find(n => n.name === "repeat-literal") as Node;
352
367
  const repeat = this._buildRepeat(name, repeatNode);
353
368
 
369
+ this._applyDecorators(statementNode, repeat);
354
370
  this._parseContext.patternsByName.set(name, repeat);
355
371
  }
356
372
 
@@ -413,6 +429,8 @@ export class Grammar {
413
429
  const isOptional = node.children[1] != null;
414
430
 
415
431
  const anonymous = isOptional ? new Optional(name, this._buildPattern(anonymousNode.children[0])) : this._buildPattern(anonymousNode.children[0]);
432
+
433
+ this._applyDecorators(node, anonymous);
416
434
  this._parseContext.patternsByName.set(name, anonymous);
417
435
  }
418
436
 
@@ -433,7 +451,8 @@ export class Grammar {
433
451
  const grammar = new Grammar({
434
452
  resolveImport: this._resolveImport,
435
453
  originResource: grammarFile.resource,
436
- params
454
+ params,
455
+ decorators: this._parseContext.decorators
437
456
  });
438
457
 
439
458
  try {
@@ -486,6 +505,59 @@ export class Grammar {
486
505
  }
487
506
  }
488
507
 
508
+ private _applyDecorators(statementNode: Node, pattern: Pattern) {
509
+ const decorators = this._parseContext.decorators;
510
+ const bodyLine = statementNode.parent;
511
+
512
+ if (bodyLine == null) {
513
+ return;
514
+ }
515
+
516
+ let prevSibling = bodyLine.previousSibling();
517
+ let decoratorNodes: Node[] = [];
518
+
519
+ while (prevSibling != null) {
520
+ if (prevSibling.find(n => n.name === "assign-statement")) {
521
+ break;
522
+ }
523
+ decoratorNodes.push(prevSibling);
524
+ prevSibling = prevSibling.previousSibling();
525
+ }
526
+
527
+ decoratorNodes = decoratorNodes.filter(n => n.find(n => n.name.includes("decorator")) != null);
528
+
529
+ decoratorNodes.forEach((d) => {
530
+ const nameNode = d.find(n => n.name === "decorator-name");
531
+
532
+ if (nameNode == null || decorators[nameNode.value] == null) {
533
+ return;
534
+ }
535
+
536
+ const nameDocorator = d.find(n => n.name === "name-decorator");
537
+
538
+ if (nameDocorator != null) {
539
+ decorators[nameNode.value](pattern);
540
+ return;
541
+ }
542
+
543
+ const methodDecorator = d.find(n => n.name === "method-decorator");
544
+
545
+ if (methodDecorator == null) {
546
+ return;
547
+ }
548
+
549
+ methodDecorator.findAll(n => n.name.includes("space")).forEach(n => n.remove());
550
+ const argsNode = methodDecorator.children[3];
551
+
552
+ if (argsNode == null || argsNode.name === "close-paren") {
553
+ decorators[nameNode.value](pattern);
554
+ } else {
555
+ decorators[nameNode.value](pattern, JSON.parse(argsNode.value));
556
+ }
557
+ });
558
+
559
+ }
560
+
489
561
  private _getParams(importStatement: Node) {
490
562
  let params: Pattern[] = [];
491
563
  const paramsStatement = importStatement.find(n => n.name === "with-params-statement");
@@ -504,7 +576,8 @@ export class Grammar {
504
576
  const grammar = new Grammar({
505
577
  params: [...importedValues, ...this._parseContext.paramsByName.values()],
506
578
  originResource: this._originResource,
507
- resolveImport: this._resolveImport
579
+ resolveImport: this._resolveImport,
580
+ decorators: this._parseContext.decorators
508
581
  });
509
582
 
510
583
  const patterns = grammar.parseString(expression);
@@ -539,13 +612,15 @@ export class Grammar {
539
612
  const aliasName = aliasNode.value;
540
613
  const name = nameNode.value;
541
614
  const aliasPattern = this._getPattern(aliasName);
542
-
615
+
543
616
  // This solves the problem for an alias pointing to a reference.
544
- if (aliasPattern.type === "reference"){
617
+ if (aliasPattern.type === "reference") {
545
618
  const reference = new Reference(name, aliasName);
619
+ this._applyDecorators(statementNode, reference);
546
620
  this._parseContext.patternsByName.set(name, reference);
547
621
  } else {
548
622
  const alias = aliasPattern.clone(name);
623
+ this._applyDecorators(statementNode, alias);
549
624
  this._parseContext.patternsByName.set(name, alias);
550
625
  }
551
626
 
@@ -0,0 +1,18 @@
1
+ import { Pattern } from "../../patterns/Pattern";
2
+ import { Regex } from "../../patterns/Regex";
3
+ import { Decorator } from "../Grammar";
4
+
5
+ export const tokens: Decorator = (pattern: Pattern, arg: any) => {
6
+ if (pattern.type === "regex" && Array.isArray(arg)) {
7
+ const regex = pattern as Regex;
8
+ const tokens: string[] = [];
9
+
10
+ arg.forEach(token => {
11
+ if (typeof token === "string") {
12
+ tokens.push(token);
13
+ }
14
+ });
15
+
16
+ regex.setTokens(tokens);
17
+ }
18
+ };
@@ -0,0 +1,85 @@
1
+ import { Literal } from "../../patterns/Literal";
2
+ import { Optional } from "../../patterns/Optional";
3
+ import { Options } from "../../patterns/Options";
4
+ import { Reference } from "../../patterns/Reference";
5
+ import { Regex } from "../../patterns/Regex";
6
+ import { Repeat } from "../../patterns/Repeat";
7
+ import { Sequence } from "../../patterns/Sequence";
8
+ import { name } from "./name";
9
+ import { allSpaces } from "./spaces";
10
+
11
+ const colon = new Literal("colon", ":");
12
+ const comma = new Regex("comma", "\\s*,\\s*");
13
+ const openBracket = new Literal("open-bracket", "{");
14
+ const closeBracket = new Literal("close-bracket", "}");
15
+ const openSquareBracket = new Literal("open-square-bracket", "[");
16
+ const closeSquareBracket = new Literal("close-square-bracket", "]");
17
+ const optionalAllSpaces = new Optional("optional-all-spaces", allSpaces);
18
+
19
+ const stringLiteral = new Regex("string-literal", '"(?:\\\\.|[^"\\\\])*"');
20
+ const numberLiteral = new Regex("number-literal", '[+-]?\\d+(\\\\.\\d+)?([eE][+-]?\\d+)?');
21
+ const nullLiteral = new Literal("null-literal", "null");
22
+ const trueLiteral = new Literal("true-literal", "true");
23
+ const falseLiteral = new Literal("false-literal", "false");
24
+ const booleanLiteral = new Options("", [trueLiteral, falseLiteral]);
25
+
26
+ const objectKey = name.clone("object-key");
27
+ const objectProperty = new Sequence("object-property", [
28
+ objectKey,
29
+ optionalAllSpaces,
30
+ colon,
31
+ optionalAllSpaces,
32
+ new Reference("literal"),
33
+ ]);
34
+ const objectProperies = new Repeat("object-properties", objectProperty, { divider: comma });
35
+ const objectLiteral = new Sequence("object-literal", [
36
+ openBracket,
37
+ optionalAllSpaces,
38
+ new Optional("optional-object-properties", objectProperies),
39
+ optionalAllSpaces,
40
+ closeBracket
41
+ ]);
42
+
43
+ const arrayItems = new Repeat("array-items", new Reference("literal"), { divider: comma });
44
+ const arrayLiteral = new Sequence("array-literal", [
45
+ openSquareBracket,
46
+ optionalAllSpaces,
47
+ arrayItems,
48
+ optionalAllSpaces,
49
+ closeSquareBracket,
50
+ ]);
51
+
52
+ const literal = new Options("literal", [
53
+ objectLiteral,
54
+ arrayLiteral,
55
+ stringLiteral,
56
+ booleanLiteral,
57
+ nullLiteral,
58
+ numberLiteral,
59
+ ]);
60
+
61
+ const decoratorPrefix = new Literal("decorator-prefix", "@");
62
+ const decoratorName = name.clone("decorator-name");
63
+ const openParen = new Literal("open-paren", "(");
64
+ const closeParen = new Literal("close-paren", ")");
65
+
66
+ const methodDecoration = new Sequence("method-decorator", [
67
+ decoratorPrefix,
68
+ decoratorName,
69
+ optionalAllSpaces,
70
+ openParen,
71
+ optionalAllSpaces,
72
+ new Optional("optional-args", literal),
73
+ optionalAllSpaces,
74
+ closeParen
75
+ ]);
76
+
77
+ const nameDecoration = new Sequence("name-decorator", [
78
+ decoratorPrefix,
79
+ decoratorName,
80
+ ]);
81
+
82
+ export const decoratorStatement = new Options("decorator-statement", [
83
+ methodDecoration,
84
+ nameDecoration,
85
+ ]);
@@ -5,6 +5,7 @@ import { name } from "./name";
5
5
  import { spaces } from "./spaces";
6
6
  import { pattern } from "./pattern";
7
7
  import { Optional } from "../../patterns/Optional";
8
+ import {decoratorStatement} from './decoratorStatement';
8
9
 
9
10
  const optionalSpaces = new Optional("optional-spaces", spaces);
10
11
  const assignOperator = new Literal("assign-operator", "=");
@@ -18,4 +19,4 @@ const assignStatement = new Sequence("assign-statement", [
18
19
  pattern,
19
20
  ]);
20
21
 
21
- export const statement = new Options("statement", [assignStatement, name.clone("export-name")]);
22
+ export const statement = new Options("statement", [decoratorStatement, assignStatement, name.clone("export-name")]);
@@ -1,5 +1,5 @@
1
1
  import { Pattern } from "../patterns/Pattern";
2
- import { Grammar } from "./Grammar";
2
+ import { Grammar, GrammarOptions } from "./Grammar";
3
3
 
4
4
  const kebabRegex = /-([a-z])/g; // Define the regex once
5
5
 
@@ -21,4 +21,22 @@ export function patterns(strings: TemplateStringsArray, ...values: any) {
21
21
  });
22
22
 
23
23
  return result;
24
+ }
25
+
26
+ export function createPatternsTemplate(options: GrammarOptions){
27
+ return function patterns(strings: TemplateStringsArray, ...values: any) {
28
+ const combinedString = strings.reduce(
29
+ (result, str, i) => result + str + (values[i] || ''),
30
+ ''
31
+ );
32
+
33
+ const result: Record<string, Pattern> = {};
34
+ const patterns = Grammar.parseString(combinedString, options);
35
+
36
+ Object.keys(patterns).forEach(k => {
37
+ result[kebabToCamelCase(k)] = patterns[k];
38
+ });
39
+
40
+ return result;
41
+ }
24
42
  }