clarity-pattern-parser 11.3.13 → 11.4.1

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.
@@ -3,7 +3,7 @@ import { Match } from "./CursorHistory";
3
3
  import { ParseError } from "./ParseError";
4
4
  import { Pattern } from "./Pattern";
5
5
  export declare class Cursor {
6
- private _text;
6
+ private _chars;
7
7
  private _index;
8
8
  private _length;
9
9
  private _history;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-pattern-parser",
3
- "version": "11.3.13",
3
+ "version": "11.4.1",
4
4
  "description": "Parsing Library for Typescript and Javascript.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -779,4 +779,42 @@ describe("Grammar", () => {
779
779
  expect(result?.value).toBe("function(){}");
780
780
  });
781
781
 
782
+ test("Import Sync", () => {
783
+ function resolveImportSync(path: string, importer: string | null) {
784
+ return {
785
+ expression: pathMap[path],
786
+ resource: path,
787
+ }
788
+ }
789
+
790
+ const rootExpression = `
791
+ import { name } from "first-name.cpat"
792
+ full-name = name
793
+ `;
794
+
795
+ const firstNameExpression = `
796
+ import { last-name } from "last-name.cpat"
797
+ first-name = "John"
798
+ name = first-name + " " + last-name
799
+ `;
800
+
801
+ const lastNameExpression = `
802
+ last-name = "Doe"
803
+ `;
804
+
805
+ const pathMap: Record<string, string> = {
806
+ "first-name.cpat": firstNameExpression,
807
+ "last-name.cpat": lastNameExpression,
808
+ };
809
+
810
+ const patterns = Grammar.parseString(rootExpression, {
811
+ resolveImportSync: resolveImportSync,
812
+ originResource: "/root.cpat",
813
+ });
814
+
815
+ expect(patterns["full-name"]).not.toBeNull();
816
+ const result = patterns["full-name"].exec("John Doe");
817
+ expect(result?.ast?.value).toBe("John Doe");
818
+ });
819
+
782
820
  });
@@ -50,6 +50,10 @@ function defaultImportResolver(_path: string, _basePath: string | null): Promise
50
50
  throw new Error("No import resolver supplied.");
51
51
  }
52
52
 
53
+ function defaultImportResolverSync(_path: string, _basePath: string | null): GrammarFile {
54
+ throw new Error("No import resolver supplied.");
55
+ }
56
+
53
57
  export interface GrammarFile {
54
58
  resource: string;
55
59
  expression: string;
@@ -57,6 +61,7 @@ export interface GrammarFile {
57
61
 
58
62
  export interface GrammarOptions {
59
63
  resolveImport?: (resource: string, originResource: string | null) => Promise<GrammarFile>;
64
+ resolveImportSync?: (resource: string, originResource: string | null) => GrammarFile;
60
65
  originResource?: string | null;
61
66
  params?: Pattern[];
62
67
  decorators?: Record<string, Decorator>;
@@ -66,12 +71,14 @@ export class Grammar {
66
71
  private _params: Pattern[];
67
72
  private _originResource?: string | null;
68
73
  private _resolveImport: (resource: string, originResource: string | null) => Promise<GrammarFile>;
74
+ private _resolveImportSync: (resource: string, originResource: string | null) => GrammarFile;
69
75
  private _parseContext: ParseContext;
70
76
 
71
77
  constructor(options: GrammarOptions = {}) {
72
78
  this._params = options?.params == null ? [] : options.params;
73
79
  this._originResource = options?.originResource == null ? null : options.originResource;
74
80
  this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
81
+ this._resolveImportSync = options.resolveImportSync == null ? defaultImportResolverSync : options.resolveImportSync;
75
82
  this._parseContext = new ParseContext(this._params, options.decorators || {});
76
83
  }
77
84
 
@@ -101,10 +108,7 @@ export class Grammar {
101
108
  this._parseContext = new ParseContext(this._params, this._parseContext.decorators);
102
109
  const ast = this._tryToParse(expression);
103
110
 
104
- if (this._hasImports(ast)) {
105
- throw new Error("Cannot use imports on parseString, use parse instead.");
106
- }
107
-
111
+ this._resolveImportsSync(ast);
108
112
  this._buildPatterns(ast);
109
113
 
110
114
  return this._buildPatternRecord();
@@ -476,14 +480,89 @@ export class Grammar {
476
480
 
477
481
  for (const statement of importStatements) {
478
482
  if (statement.name === "import-from") {
479
- await this.processImport(statement);
483
+ await this._processImport(statement);
480
484
  } else {
481
- this.processUseParams(statement);
485
+ this._processUseParams(statement);
482
486
  }
483
487
  }
484
488
  }
485
489
 
486
- private async processImport(importStatement: Node) {
490
+ private _resolveImportsSync(ast: Node) {
491
+ const importStatements = ast.findAll(n => {
492
+ return n.name === "import-from" || n.name === "param-name-with-default-value";
493
+ });
494
+
495
+ for (const statement of importStatements) {
496
+ if (statement.name === "import-from") {
497
+ this._processImportSync(statement);
498
+ } else {
499
+ this._processUseParams(statement);
500
+ }
501
+ }
502
+ }
503
+
504
+ private _processImportSync(importStatement: Node) {
505
+ const parseContext = this._parseContext;
506
+ const resourceNode = importStatement.find(n => n.name === "resource") as Node;
507
+ const params = this._getParams(importStatement);
508
+ const resource = resourceNode.value.slice(1, -1);
509
+ const grammarFile = this._resolveImportSync(resource, this._originResource || null);
510
+ const grammar = new Grammar({
511
+ resolveImport: this._resolveImport,
512
+ resolveImportSync: this._resolveImportSync,
513
+ originResource: grammarFile.resource,
514
+ params,
515
+ decorators: this._parseContext.decorators
516
+ });
517
+
518
+ 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
+
559
+ } catch (e: any) {
560
+ throw new Error(`Failed loading expression from: "${resource}". Error details: "${e.message}"`);
561
+ }
562
+
563
+ }
564
+
565
+ private async _processImport(importStatement: Node) {
487
566
  const parseContext = this._parseContext;
488
567
  const resourceNode = importStatement.find(n => n.name === "resource") as Node;
489
568
  const params = this._getParams(importStatement);
@@ -543,7 +622,7 @@ export class Grammar {
543
622
 
544
623
  }
545
624
 
546
- private processUseParams(paramName: Node) {
625
+ private _processUseParams(paramName: Node) {
547
626
  const defaultValueNode = paramName.find(n => n.name === "param-default");
548
627
  if (defaultValueNode === null) {
549
628
  return;
@@ -0,0 +1,170 @@
1
+ import { Node } from "../../ast/Node";
2
+ import { tokens } from "../../grammar/decorators/tokens";
3
+ import { GrammarFile } from "../../grammar/Grammar";
4
+ import { generateErrorMessage } from "../../patterns/generate_error_message";
5
+ import { Pattern } from "../../patterns/Pattern";
6
+ import { grammar } from "./patterns/grammar"
7
+
8
+ let anonymousIndexId = 0;
9
+
10
+ function defaultImportResolver(_path: string, _basePath: string | null): Promise<GrammarFile> {
11
+ throw new Error("No import resolver supplied.");
12
+ }
13
+
14
+ function defaultImportResolverSync(_path: string, _basePath: string | null): GrammarFile {
15
+ throw new Error("No import resolver supplied.");
16
+ }
17
+
18
+ export type Decorator = (pattern: Pattern, arg?: string | boolean | number | null | Record<string, any> | any[]) => void;
19
+
20
+ const defaultDecorators = {
21
+ tokens: tokens
22
+ };
23
+
24
+ export interface GrammarOptions {
25
+ resolveImport?: (resource: string, originResource: string | null) => Promise<GrammarFile>;
26
+ resolveImportSync?: (resource: string, originResource: string | null) => GrammarFile;
27
+ originResource?: string | null;
28
+ params?: Pattern[];
29
+ decorators?: Record<string, Decorator>;
30
+ }
31
+
32
+ class ParseContext {
33
+ patternsByName = new Map<string, Pattern>();
34
+ importedPatternsByName = new Map<string, Pattern>();
35
+ paramsByName = new Map<string, Pattern>();
36
+ decorators: Record<string, Decorator>;
37
+
38
+ constructor(params: Pattern[], decorators: Record<string, Decorator> = {}) {
39
+ params.forEach(p => this.paramsByName.set(p.name, p));
40
+ this.decorators = { ...decorators, ...defaultDecorators };
41
+ }
42
+
43
+ getImportedPatterns() {
44
+ return Array.from(this.importedPatternsByName.values());
45
+ }
46
+
47
+ getParams() {
48
+ return Array.from(this.paramsByName.values());
49
+ }
50
+
51
+ }
52
+
53
+ export class Grammar {
54
+ private _options: GrammarOptions;
55
+ private _parseContext: ParseContext;
56
+ private _resolveImportSync: (resource: string, originResource: string | null) => GrammarFile;
57
+
58
+ constructor(options: GrammarOptions) {
59
+ this._options = options;
60
+ this._parseContext = new ParseContext(options.params || [], options.decorators || {});
61
+ this._resolveImportSync = options.resolveImportSync == null ? defaultImportResolverSync : options.resolveImportSync;
62
+ }
63
+
64
+ // parse(cpat: string): Promise<Pattern> { }
65
+ // import(path: string): Promise<Pattern> { }
66
+
67
+ // parseString(cpat: string): Pattern {
68
+ // const ast = this._tryToParse(cpat);
69
+
70
+ // this._flattenExpressions(ast);
71
+
72
+ // }
73
+
74
+ private _resolveImportsSync(ast: Node) {
75
+ const importStatements = ast.findAll(n => {
76
+ return n.name === "importStatement" || n.name === "useParamsStatement";
77
+ });
78
+
79
+ for (const importStatement of importStatements) {
80
+ if (importStatement.name === "importStatement") {
81
+ this._processImportSync(importStatement);
82
+ } else {
83
+ this._processUseParams(importStatement);
84
+ }
85
+ }
86
+ }
87
+
88
+ private _processImportSync(importStatement: Node) {
89
+ const parseContext = this._parseContext;
90
+ const resourceNode = importStatement.find(n => n.name === "resource") as Node;
91
+ const params = this._getWithParams(importStatement);
92
+ const resource = resourceNode.value.slice(1, -1);
93
+ const grammarFile = this._resolveImportSync(resource, this._options.originResource || null);
94
+
95
+
96
+ if (resourceNode == null) {
97
+ throw new Error("Invalid import statement: resource is required");
98
+ }
99
+
100
+
101
+ }
102
+
103
+ private _getWithParams(importStatement: Node) {
104
+ const withParamsStatements = importStatement.find(n => n.name === "withParamsStatements");
105
+
106
+ if (withParamsStatements == null) {
107
+ return [];
108
+ }
109
+
110
+ const expression = withParamsStatements.toString();
111
+ const importedValues = this._parseContext.getImportedPatterns();
112
+
113
+ const grammar = new Grammar({
114
+ params: [...importedValues, ...this._parseContext.paramsByName.values()],
115
+ originResource: this._options.originResource,
116
+ resolveImport: this._options.resolveImport,
117
+ decorators: this._parseContext.decorators
118
+ });
119
+
120
+ // const patterns = grammar.parseString(expression);
121
+ // return Array.from(Object.values(patterns));
122
+ return[]
123
+ }
124
+
125
+
126
+ private _processUseParams(useParamsStatement: Node) {
127
+ }
128
+
129
+ private _tryToParse(cpat: string): Node {
130
+ const { ast, cursor } = grammar.exec(cpat, true);
131
+
132
+ if (ast == null) {
133
+ const message = generateErrorMessage(grammar, cursor);
134
+ throw new Error(`[Invalid Grammar] ${message}`);
135
+ }
136
+
137
+ return ast;
138
+ }
139
+
140
+
141
+ private _flattenExpressions(node: Node) {
142
+ switch (node.name) {
143
+ case "sequenceExpr":
144
+ return this._unwrapNode("sequenceExpr", node);
145
+ case "optionsExpr":
146
+ return this._unwrapNode("optionsExpr", node);
147
+ case "greedyOptionsExpr":
148
+ return this._unwrapNode("greedyOptionsExpr", node);
149
+ default:
150
+ return node;
151
+ }
152
+ }
153
+
154
+ private _unwrapNode(type: string, node: Node) {
155
+ if (node.name !== type) {
156
+ return node;
157
+ }
158
+
159
+ for (let x = 0; x < node.children.length; x++) {
160
+ const child = node.children[x];
161
+
162
+ if (child.name === type) {
163
+ node.spliceChildren(x, 1, ...child.children);
164
+ x--;
165
+ }
166
+ }
167
+
168
+ return node;
169
+ }
170
+ }
@@ -0,0 +1,91 @@
1
+ # syntax
2
+ syntax = "syntax"
3
+ import = "import"
4
+ useParams = "use params"
5
+ withParams = "with params"
6
+ from = "from"
7
+ right = "right"
8
+ ws = /\s+/
9
+ ls = /[ \t]+/
10
+ assign = "="
11
+ bar = "|"
12
+ concat = "+"
13
+ colon = ":"
14
+ openParen = "("
15
+ closeParen = ")"
16
+ openSquareBracket = "["
17
+ closeSquareBracket = "]"
18
+ openBracket = "{"
19
+ closeBracket = "}"
20
+ comma = /\s*[,]\s*/
21
+ trim = "trim"
22
+ not = "!"
23
+ optional = "?"
24
+ newLine = /\s*(\r\n|\r|\n)\s*/
25
+ syntaxVersion = /\S*/
26
+ at = "@"
27
+
28
+ literal = /"(?:\\.|[^"\\])*"/
29
+ regexLiteral = /\/(\\/|[^\n\r])*\//
30
+
31
+ integer = /[1-9][0-9]*|[0]/
32
+ name = /[a-zA-Z_-]+[a-zA-Z0-9_-]*/
33
+ patternName = name
34
+ patternIdentifier = name
35
+ resource = literal
36
+
37
+ comment = /#[^\n\r]*/
38
+
39
+ # JSON
40
+ jsonString = literal
41
+ jsonNumber = /-?\d+(\.\d+)?/
42
+ jsonBoolean = "true" | "false"
43
+ jsonNull = "null"
44
+ jsonValue = jsonString | jsonNumber | jsonBoolean | jsonNull | jsonArray | jsonObject
45
+ jsonArrayItems = (jsonValue, comma trim)+
46
+ jsonArray = openSquareBracket +ws? + jsonArrayItems? + ws? + closeSquareBracket
47
+ jsonObjectPropertyName = literal
48
+ jsonObjectProperty = jsonObjectPropertyName + ws? + colon + ws? + jsonValue
49
+ jsonObjectProperties = (jsonObjectProperty, comma trim)+
50
+ jsonObject = openBracket + ws? + jsonObjectProperties? + ws? + closeBracket
51
+
52
+ syntaxStatement = syntax + ws? + syntaxVersion
53
+
54
+ decorationName = name
55
+ decorationStatement = at + ws? + decorationName + ws? + openParen + ws? + jsonValue? + ws? + closeParen
56
+
57
+ useParamPatterns = (patternName, comma trim)+
58
+ useParamsStatement = useParams + ls? + openBracket + ws? + useParamPatterns + ws? + closeBracket
59
+
60
+ withParamStatements = (patternAssignment, newLine trim)+
61
+ withParamsExpr = withParams + ls? + openBracket + ws? + withParamStatements + ws? + closeBracket
62
+
63
+ patternNames = (patternName, comma trim)+
64
+ importedPatterns = openBracket + ws? + patternNames + ws? + closeBracket
65
+ importStatement = import + ls? + importedPatterns + ls? + from + ls? + resource + ls? + withParamsExpr?
66
+
67
+ notExpr = not + ls? + unaryPatternExpr
68
+ optionalExpr = unaryPatternExpr + ls? + optional
69
+ rightAssociationExpr = unaryPatternExpr + ls? + right
70
+
71
+ unaryPatternExpr = notExpr | optionalExpr | rightAssociationExpr | patternIdentifier
72
+
73
+ repeatBounds = openBracket + integer? + comma? + integer? + closeBracket
74
+ oneOrMore = "+"
75
+ zeroOrMore = "*"
76
+ repeatOptions = oneOrMore | zeroOrMore | repeatBounds
77
+ delimiter = comma + patternExpr + ws? + trim?
78
+ repeatExpr = openParen + ws? + patternExpr + ws? + delimiter? + ws? + closeParen + repeatOptions
79
+
80
+ sequenceExpr = patternExpr + ws? + "+" + ws? + patternExpr
81
+ optionsExpr = patternExpr + ws? + "|" + ws? + patternExpr
82
+ greedyOptionsExpr = patternExpr + ws? + "<|>" + ws? + patternExpr
83
+ patternGroupExpr = openParen + ws? + patternExpr + ws? + closeParen
84
+
85
+ exportPattern = patternName
86
+ patternExpr = sequenceExpr | optionsExpr | greedyOptionsExpr | repeatExpr | patternGroupExpr | literal | regexLiteral | unaryPatternExpr
87
+ patternAssignment = patternName + ws? + assign + ws? + patternExpr
88
+ statement = useParamsStatement | importStatement | patternAssignment | decorationStatement | exportPattern | comment
89
+ statements = (statement, newLine)+
90
+ cpat = ws? + syntaxStatement? + ws? + statements?
91
+