clarity-pattern-parser 11.1.4 → 11.2.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.
@@ -6,6 +6,7 @@ export declare class Context implements Pattern {
6
6
  private _id;
7
7
  private _type;
8
8
  private _name;
9
+ private _referencePatternName;
9
10
  private _parent;
10
11
  private _children;
11
12
  private _pattern;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-pattern-parser",
3
- "version": "11.1.4",
3
+ "version": "11.2.0",
4
4
  "description": "Parsing Library for Typescript and Javascript.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -11,6 +11,7 @@ import { Optional } from "../patterns/Optional";
11
11
  import { Context } from "../patterns/Context";
12
12
  import { createPatternsTemplate, patterns } from "./patterns";
13
13
  import { Expression } from "../patterns/Expression";
14
+ import { Cursor } from "../patterns/Cursor";
14
15
 
15
16
  describe("Grammar", () => {
16
17
  test("Literal", () => {
@@ -496,11 +497,83 @@ describe("Grammar", () => {
496
497
  }
497
498
  const patterns = await Grammar.parse(expression, { resolveImport });
498
499
  const pattern = patterns["name"] as Literal;
500
+ const result = pattern.exec("Value");
501
+ expect(result.ast?.value).toBe("Value");
502
+ });
503
+
504
+ test("Default Params Resolves to Default Value", async () => {
505
+ const expression = `
506
+ use params {
507
+ value = default-value
508
+ }
509
+
510
+ default-value = "DefaultValue"
511
+ alias = value
512
+ `;
513
+
514
+ function resolveImport(_: string) {
515
+ return Promise.reject(new Error("No Import"));
516
+ }
517
+
518
+ const patterns = await Grammar.parse(expression, { resolveImport });
519
+ const pattern = patterns["alias"] as Literal;
520
+
521
+ const result = pattern.exec("DefaultValue");
522
+ expect(result.ast?.value).toBe("DefaultValue");
523
+ });
524
+
525
+ test("Default Params Resolves to params imported", async () => {
526
+ const expression = `
527
+ use params {
528
+ value = default-value
529
+ }
530
+
531
+ default-value = "DefaultValue"
532
+ alias = value
533
+ `;
534
+
535
+ function resolveImport(_: string) {
536
+ return Promise.reject(new Error("No Import"));
537
+ }
538
+
539
+ const patterns = await Grammar.parse(expression, { resolveImport, params: [new Literal("value", "Value")] });
540
+ const pattern = patterns["alias"] as Literal;
499
541
 
500
542
  const result = pattern.exec("Value");
501
543
  expect(result.ast?.value).toBe("Value");
502
544
  });
503
545
 
546
+ test("Default Params Resolves to imported default value", async () => {
547
+ const expression = `
548
+ import { my-value as default-value } from "resource1"
549
+ use params {
550
+ value = default-value
551
+ }
552
+
553
+ default-value = "DefaultValue"
554
+ alias = value
555
+ `;
556
+
557
+ const resource1 = `
558
+ my-value = "MyValue"
559
+ `;
560
+
561
+
562
+ const pathMap: Record<string, string> = {
563
+ "resource1": resource1,
564
+ };
565
+
566
+ function resolveImport(resource: string) {
567
+ return Promise.resolve({ expression: pathMap[resource], resource });
568
+ }
569
+
570
+ const patterns = await Grammar.parse(expression, { resolveImport });
571
+ const pattern = patterns["alias"] as Literal;
572
+
573
+ const result = pattern.exec("MyValue");
574
+ expect(result.ast?.value).toBe("MyValue");
575
+ });
576
+
504
577
  test("Anonymous Patterns", () => {
505
578
  const expression = `
506
579
  complex-expression = !"NOT_THIS" + "Text"? + /regex/ + ("Text" <|> /regex/ <|> (pattern)+) + (pattern | pattern)
@@ -696,4 +769,13 @@ describe("Grammar", () => {
696
769
  `;
697
770
  });
698
771
 
772
+ test("Take Until", () => {
773
+ const { scriptText } = patterns`
774
+ script-text = ?->| "</script"
775
+ `;
776
+ const result = scriptText.parse(new Cursor("function(){}</script"));
777
+
778
+ expect(result?.value).toBe("function(){}");
779
+ });
780
+
699
781
  });
@@ -11,6 +11,7 @@ import { Repeat, RepeatOptions } from "../patterns/Repeat";
11
11
  import { Optional } from "../patterns/Optional";
12
12
  import { Context } from "../patterns/Context";
13
13
  import { Expression } from "../patterns/Expression";
14
+ import { TakeUntil } from "../patterns/TakeUntil";
14
15
  import { RightAssociated } from "../patterns/RightAssociated";
15
16
  import { generateErrorMessage } from "../patterns/generate_error_message";
16
17
  import { tokens } from "./decorators/tokens";
@@ -30,6 +31,7 @@ const patternNodes: Record<string, boolean> = {
30
31
  "sequence-literal": true,
31
32
  "repeat-literal": true,
32
33
  "alias-literal": true,
34
+ "take-until-literal": true,
33
35
  "configurable-anonymous-pattern": true
34
36
  };
35
37
 
@@ -180,6 +182,10 @@ export class Grammar {
180
182
  this._saveAlias(n);
181
183
  break;
182
184
  }
185
+ case "take-until-literal": {
186
+ this._saveTakeUntil(n);
187
+ break;
188
+ }
183
189
  case "configurable-anonymous-pattern": {
184
190
  this._saveConfigurableAnonymous(n);
185
191
  break;
@@ -196,6 +202,7 @@ export class Grammar {
196
202
  });
197
203
  }
198
204
 
205
+
199
206
  private _saveLiteral(statementNode: Node) {
200
207
  const nameNode = statementNode.find(n => n.name === "name") as Node;
201
208
  const literalNode = statementNode.find(n => n.name === "literal") as Node;
@@ -322,6 +329,9 @@ export class Grammar {
322
329
  case "sequence-literal": {
323
330
  return this._buildSequence(name, node);
324
331
  }
332
+ case "take-until-literal": {
333
+ return this._buildTakeUntil(name, node);
334
+ }
325
335
  case "complex-anonymous-pattern": {
326
336
  return this._buildComplexAnonymousPattern(node);
327
337
  }
@@ -422,6 +432,24 @@ export class Grammar {
422
432
  return isOptional ? new Optional(name, new Repeat(`inner-optional-${name}`, pattern, options)) : new Repeat(name, pattern, options);
423
433
  }
424
434
 
435
+ private _saveTakeUntil(statementNode: Node) {
436
+ const nameNode = statementNode.find(n => n.name === "name") as Node;
437
+ const name = nameNode.value;
438
+ const takeUntilNode = statementNode.find(n => n.name === "take-until-literal") as Node;
439
+ const takeUntil = this._buildTakeUntil(name, takeUntilNode);
440
+
441
+ this._applyDecorators(statementNode, takeUntil);
442
+ this._parseContext.patternsByName.set(name, takeUntil);
443
+ }
444
+
445
+
446
+ private _buildTakeUntil(name: string, takeUntilNode: Node) {
447
+ const patternNode = takeUntilNode.children[takeUntilNode.children.length - 1];
448
+ const untilPattern = this._buildPattern(patternNode);
449
+
450
+ return new TakeUntil(name, untilPattern);
451
+ }
452
+
425
453
  private _saveConfigurableAnonymous(node: Node) {
426
454
  const nameNode = node.find(n => n.name === "name") as Node;
427
455
  const name = nameNode.value;
@@ -440,69 +468,106 @@ export class Grammar {
440
468
  }
441
469
 
442
470
  private async _resolveImports(ast: Node) {
471
+ const importStatements = ast.findAll(n => {
472
+ return n.name === "import-from" || n.name === "param-name-with-default-value";
473
+ });
474
+
475
+ for (const statement of importStatements) {
476
+ if (statement.name === "import-from") {
477
+ await this.processImport(statement);
478
+ } else {
479
+ this.processUseParams(statement);
480
+ }
481
+ }
482
+ }
483
+
484
+ private async processImport(importStatement: Node) {
443
485
  const parseContext = this._parseContext;
444
- const importStatements = ast.findAll(n => n.name === "import-from");
445
-
446
- for (const importStatement of importStatements) {
447
- const resourceNode = importStatement.find(n => n.name === "resource") as Node;
448
- const params = this._getParams(importStatement);
449
- const resource = resourceNode.value.slice(1, -1);
450
- const grammarFile = await this._resolveImport(resource, this._originResource || null);
451
- const grammar = new Grammar({
452
- resolveImport: this._resolveImport,
453
- originResource: grammarFile.resource,
454
- params,
455
- decorators: this._parseContext.decorators
456
- });
486
+ const resourceNode = importStatement.find(n => n.name === "resource") as Node;
487
+ const params = this._getParams(importStatement);
488
+ const resource = resourceNode.value.slice(1, -1);
489
+ const grammarFile = await this._resolveImport(resource, this._originResource || null);
490
+ const grammar = new Grammar({
491
+ resolveImport: this._resolveImport,
492
+ originResource: grammarFile.resource,
493
+ params,
494
+ decorators: this._parseContext.decorators
495
+ });
457
496
 
458
- try {
459
- const patterns = await grammar.parse(grammarFile.expression);
460
- const importStatements = importStatement.findAll(n => n.name === "import-name" || n.name === "import-alias");
497
+ try {
498
+ const patterns = await grammar.parse(grammarFile.expression);
499
+ const importStatements = importStatement.findAll(n => n.name === "import-name" || n.name === "import-alias");
500
+
501
+ importStatements.forEach((node) => {
502
+ if (node.name === "import-name" && node.parent?.name === "import-alias") {
503
+ return;
504
+ }
505
+
506
+ if (node.name === "import-name" && node.parent?.name !== "import-alias") {
507
+ const importName = node.value;
461
508
 
462
- importStatements.forEach((node) => {
463
- if (node.name === "import-name" && node.parent?.name === "import-alias") {
464
- return;
509
+ if (parseContext.importedPatternsByName.has(importName)) {
510
+ throw new Error(`'${importName}' was already used within another import.`);
465
511
  }
466
512
 
467
- if (node.name === "import-name" && node.parent?.name !== "import-alias") {
468
- const importName = node.value;
513
+ const pattern = patterns[importName];
514
+ if (pattern == null) {
515
+ throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
516
+ }
469
517
 
470
- if (parseContext.importedPatternsByName.has(importName)) {
471
- throw new Error(`'${importName}' was already used within another import.`);
472
- }
518
+ parseContext.importedPatternsByName.set(importName, pattern);
519
+ } else {
520
+ const importNameNode = node.find(n => n.name === "import-name") as Node;
521
+ const importName = importNameNode.value;
522
+ const aliasNode = node.find(n => n.name === "import-name-alias") as Node;
523
+ const alias = aliasNode.value;
473
524
 
474
- const pattern = patterns[importName];
475
- if (pattern == null) {
476
- throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
477
- }
525
+ if (parseContext.importedPatternsByName.has(alias)) {
526
+ throw new Error(`'${alias}' was already used within another import.`);
527
+ }
478
528
 
479
- parseContext.importedPatternsByName.set(importName, pattern);
480
- } else {
481
- const importNameNode = node.find(n => n.name === "import-name") as Node;
482
- const importName = importNameNode.value;
483
- const aliasNode = node.find(n => n.name === "import-name-alias") as Node;
484
- const alias = aliasNode.value;
529
+ const pattern = patterns[importName];
530
+ if (pattern == null) {
531
+ throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
532
+ }
485
533
 
486
- if (parseContext.importedPatternsByName.has(alias)) {
487
- throw new Error(`'${alias}' was already used within another import.`);
488
- }
534
+ parseContext.importedPatternsByName.set(alias, pattern.clone(alias));
535
+ }
536
+ });
489
537
 
490
- const pattern = patterns[importName];
491
- if (pattern == null) {
492
- throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${resource}.`);
493
- }
538
+ } catch (e: any) {
539
+ throw new Error(`Failed loading expression from: "${resource}". Error details: "${e.message}"`);
540
+ }
494
541
 
495
- parseContext.importedPatternsByName.set(alias, pattern.clone(alias));
496
- }
497
- });
542
+ }
498
543
 
544
+ private processUseParams(paramName: Node) {
545
+ const defaultValueNode = paramName.find(n => n.name === "param-default");
546
+ if (defaultValueNode === null) {
547
+ return;
548
+ }
499
549
 
550
+ const nameNode = paramName.find(n => n.name === "param-name");
551
+ const defaultNameNode = defaultValueNode.find(n => n.name === "default-param-name");
500
552
 
501
- } catch (e: any) {
502
- throw new Error(`Failed loading expression from: "${resource}". Error details: "${e.message}"`);
503
- }
553
+ if (nameNode == null || defaultNameNode == null) {
554
+ return;
555
+ }
556
+
557
+ const name = nameNode.value;
558
+ const defaultName = defaultNameNode.value;
504
559
 
560
+ if (this._parseContext.paramsByName.has(name)) {
561
+ return;
562
+ }
563
+
564
+ let pattern = this._parseContext.importedPatternsByName.get(defaultName);
565
+
566
+ if (pattern == null) {
567
+ pattern = new Reference(defaultName);
505
568
  }
569
+
570
+ this._parseContext.importedPatternsByName.set(name, pattern);
506
571
  }
507
572
 
508
573
  private _applyDecorators(statementNode: Node, pattern: Pattern) {
@@ -615,7 +680,7 @@ export class Grammar {
615
680
 
616
681
  // This solves the problem for an alias pointing to a reference.
617
682
  if (aliasPattern.type === "reference") {
618
- const reference = new Reference(name, aliasName);
683
+ const reference = aliasPattern.clone(name);
619
684
  this._applyDecorators(statementNode, reference);
620
685
  this._parseContext.patternsByName.set(name, reference);
621
686
  } else {
@@ -23,12 +23,24 @@ const asKeyword = new Literal("as", "as");
23
23
  const fromKeyword = new Literal("from", "from");
24
24
  const openBracket = new Literal("open-bracket", "{");
25
25
  const closeBracket = new Literal("close-bracket", "}");
26
+ const equal = new Literal("equal", "=");
26
27
 
27
28
  const importNameAlias = name.clone("import-name-alias");
28
29
  const importAlias = new Sequence("import-alias", [name, lineSpaces, asKeyword, lineSpaces, importNameAlias]);
29
30
  const importedNames = new Repeat("imported-names", new Options("import-names", [importAlias, name]), { divider: importNameDivider });
30
31
  const paramName = name.clone("param-name");
31
- const paramNames = new Repeat("param-names", paramName, { divider: importNameDivider });
32
+ const defaultParamName = name.clone("default-param-name");
33
+
34
+ const paramNameWithDefault = new Sequence("param-name-with-default-value", [
35
+ paramName,
36
+ new Optional("optional-param-default", new Sequence("param-default", [
37
+ optionalLineSpaces,
38
+ equal,
39
+ optionalLineSpaces,
40
+ defaultParamName,
41
+ ])),
42
+ ]);
43
+ const paramNames = new Repeat("param-names", paramNameWithDefault, { divider: importNameDivider });
32
44
  const resource = literal.clone("resource");
33
45
 
34
46
  const useParams = new Sequence("import-params", [
@@ -14,6 +14,7 @@ export const anonymousLiterals = new Options("anonymous-literals", [
14
14
  ]);
15
15
 
16
16
  export const anonymousWrappedLiterals = new Options("anonymous-wrapped-literals", [
17
+ new Reference("take-until-literal"),
17
18
  new Reference("options-literal"),
18
19
  new Reference("sequence-literal"),
19
20
  new Reference("complex-anonymous-pattern")
@@ -5,6 +5,7 @@ import { repeatLiteral } from "./repeatLiteral";
5
5
  import { sequenceLiteral } from "./sequenceLiteral";
6
6
  import { optionsLiteral } from "./optionsLiteral";
7
7
  import { anonymousPattern } from "./anonymousPattern";
8
+ import { takeUntilLiteral } from "./takeUtilLiteral";
8
9
  import { Sequence } from "../../patterns/Sequence";
9
10
  import { Literal } from "../../patterns/Literal";
10
11
  import { name } from "./name";
@@ -20,8 +21,9 @@ export const pattern = new Options("pattern", [
20
21
  literal,
21
22
  regexLiteral,
22
23
  repeatLiteral,
24
+ takeUntilLiteral,
23
25
  aliasLiteral,
24
26
  optionsLiteral,
25
27
  sequenceLiteral,
26
28
  configurableAnonymousPattern,
27
- ], true);
29
+ ], true);
@@ -0,0 +1,21 @@
1
+ import { Literal } from "../../patterns/Literal"
2
+ import { Optional } from "../../patterns/Optional";
3
+ import { Sequence } from "../../patterns/Sequence";
4
+ import { lineSpaces } from "./spaces";
5
+ import { name } from "./name";
6
+ import { Reference } from "../../patterns/Reference";
7
+
8
+ const anyChar = new Literal("any-char", "?");
9
+ const upTo = new Literal("up-to", "->");
10
+ const wall = new Literal("wall", "|");
11
+ const optionalLineSpaces = new Optional("optional-line-spaces", lineSpaces);
12
+
13
+ export const takeUntilLiteral = new Sequence("take-until-literal", [
14
+ anyChar,
15
+ optionalLineSpaces,
16
+ upTo,
17
+ optionalLineSpaces,
18
+ wall,
19
+ optionalLineSpaces,
20
+ new Reference("pattern")
21
+ ]);
@@ -1,8 +1,8 @@
1
1
  import { patterns } from "./patterns";
2
2
 
3
- describe("Patterns String Template Literal", ()=>{
4
- test("Baseline", ()=>{
5
- const {fullName} = patterns`
3
+ describe("Patterns String Template Literal", () => {
4
+ test("Baseline", () => {
5
+ const { fullName } = patterns`
6
6
  first-name = "John"
7
7
  last-name = "Doe"
8
8
  space = /\\s+/
@@ -13,8 +13,8 @@ describe("Patterns String Template Literal", ()=>{
13
13
  expect(result?.ast?.value).toBe("John Doe");
14
14
  });
15
15
 
16
- test("Simple Markup", ()=>{
17
- const {body} = patterns`
16
+ test("Simple Markup", () => {
17
+ const { body } = patterns`
18
18
  tag-name = /[a-zA-Z_-]+[a-zA-Z0-9_-]*/
19
19
  space = /\\s+/
20
20
  opening-tag = "<" + tag-name + space? + ">"
@@ -32,7 +32,7 @@ describe("Patterns String Template Literal", ()=>{
32
32
  <div></div>
33
33
  </div>
34
34
  `, true);
35
- result && result.ast && result.ast.findAll(n=>n.name.includes("space")).forEach(n=>n.remove());
35
+ result && result.ast && result.ast.findAll(n => n.name.includes("space")).forEach(n => n.remove());
36
36
  expect(result?.ast?.value).toBe("<div><div></div><div></div></div>");
37
37
  });
38
38
  });
@@ -38,5 +38,5 @@ export function createPatternsTemplate(options: GrammarOptions){
38
38
  });
39
39
 
40
40
  return result;
41
- }
41
+ };
42
42
  }
@@ -9,6 +9,7 @@ export class Context implements Pattern {
9
9
  private _id: string;
10
10
  private _type: string;
11
11
  private _name: string;
12
+ private _referencePatternName: string;
12
13
  private _parent: Pattern | null;
13
14
  private _children: Pattern[];
14
15
  private _pattern: Pattern;
@@ -43,6 +44,10 @@ export class Context implements Pattern {
43
44
  }
44
45
 
45
46
  getPatternWithinContext(name: string): Pattern | null {
47
+ if (this._name === name || this._referencePatternName === name){
48
+ return this;
49
+ }
50
+
46
51
  return this._patterns[name] || null;
47
52
  }
48
53
 
@@ -56,6 +61,7 @@ export class Context implements Pattern {
56
61
  this._name = name;
57
62
  this._parent = null;
58
63
  this._patterns = {};
64
+ this._referencePatternName = name;
59
65
 
60
66
  const clonedPattern = pattern.clone();
61
67
  context.forEach(p => this._patterns[p.name] = p);
@@ -79,6 +85,7 @@ export class Context implements Pattern {
79
85
 
80
86
  clone(name = this._name): Pattern {
81
87
  const clone = new Context(name, this._pattern.clone(name), Object.values(this._patterns));
88
+ clone._referencePatternName = this._referencePatternName;
82
89
  clone._id = this._id;
83
90
  return clone;
84
91
  }
@@ -0,0 +1,62 @@
1
+ import { Cursor } from "./Cursor";
2
+ import { Literal } from "./Literal";
3
+ import { Options } from "./Options";
4
+ import { TakeUntil } from "./TakeUntil";
5
+
6
+ describe("TakeUntil", () => {
7
+ test("Take With No End", () => {
8
+ const takeUntilScript = new TakeUntil(
9
+ "script-content",
10
+ new Literal("close-script-tag", "</script")
11
+ );
12
+
13
+ const result = takeUntilScript.exec("function(){}");
14
+
15
+ expect(result.ast?.value).toBe("function(){}");
16
+ });
17
+
18
+ test("Take Until Terminating Match", () => {
19
+ const takeUntilScript = new TakeUntil(
20
+ "script-content",
21
+ new Literal("close-script-tag", "</script")
22
+ );
23
+ const cursor = new Cursor("function(){}function(){}</script");
24
+ const result = takeUntilScript.parse(cursor);
25
+
26
+ expect(result?.value).toBe("function(){}function(){}");
27
+ expect(cursor.index).toBe(23);
28
+ });
29
+
30
+ test("Take Until Terminating Complex Match", () => {
31
+ const takeUntilScript = new TakeUntil(
32
+ "script-content",
33
+ new Options("end-tags", [
34
+ new Literal("close-script-tag", "</script"),
35
+ new Literal("close-style-tag", "</style")
36
+ ])
37
+ );
38
+ let cursor = new Cursor("function(){}function(){}</script");
39
+ let result = takeUntilScript.parse(cursor);
40
+
41
+ expect(result?.value).toBe("function(){}function(){}");
42
+ expect(cursor.index).toBe(23);
43
+
44
+ cursor = new Cursor("function(){}function(){}</style");
45
+ result = takeUntilScript.parse(cursor);
46
+
47
+ expect(result?.value).toBe("function(){}function(){}");
48
+ expect(cursor.index).toBe(23);
49
+ });
50
+
51
+ test("Error", () => {
52
+ const takeUntilScript = new TakeUntil(
53
+ "script-content",
54
+ new Literal("close-script-tag", "</script")
55
+ );
56
+ const cursor = new Cursor("</script");
57
+ const result = takeUntilScript.parse(cursor);
58
+
59
+ expect(result).toBeNull();
60
+ expect(cursor.index).toBe(0);
61
+ });
62
+ });