clarity-pattern-parser 11.0.21 → 11.0.23

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.
@@ -24,7 +24,8 @@ export declare class Sequence implements Pattern {
24
24
  parse(cursor: Cursor): Node | null;
25
25
  private tryToParse;
26
26
  private getLastValidNode;
27
- private areRemainingPatternsOptional;
27
+ private _areAllPatternsOptional;
28
+ private _areRemainingPatternsOptional;
28
29
  private createNode;
29
30
  getTokens(): string[];
30
31
  getTokensAfter(childReference: Pattern): string[];
@@ -0,0 +1,3 @@
1
+ import { Cursor } from "./Cursor";
2
+ import { Pattern } from "./Pattern";
3
+ export declare function generateErrorMessage(pattern: Pattern, cursor: Cursor): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-pattern-parser",
3
- "version": "11.0.21",
3
+ "version": "11.0.23",
4
4
  "description": "Parsing Library for Typescript and Javascript.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -8,11 +8,11 @@ import { Options } from "../patterns/Options";
8
8
  import { Not } from "../patterns/Not";
9
9
  import { Sequence } from "../patterns/Sequence";
10
10
  import { Repeat, RepeatOptions } from "../patterns/Repeat";
11
- import { AutoComplete } from "../intellisense/AutoComplete";
12
11
  import { Optional } from "../patterns/Optional";
13
12
  import { Context } from "../patterns/Context";
14
13
  import { Expression } from "../patterns/Expression";
15
14
  import { RightAssociated } from "../patterns/RightAssociated";
15
+ import { generateErrorMessage } from "../patterns/generate_error_message";
16
16
 
17
17
  let anonymousIndexId = 0;
18
18
 
@@ -55,16 +55,12 @@ export class Grammar {
55
55
  private _originResource?: string | null;
56
56
  private _resolveImport: (resource: string, originResource: string | null) => Promise<GrammarFile>;
57
57
  private _parseContext: ParseContext;
58
- private _autoComplete: AutoComplete;
59
58
 
60
59
  constructor(options: GrammarOptions = {}) {
61
60
  this._params = options?.params == null ? [] : options.params;
62
61
  this._originResource = options?.originResource == null ? null : options.originResource;
63
62
  this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
64
63
  this._parseContext = new ParseContext(this._params);
65
- this._autoComplete = new AutoComplete(grammar, {
66
- greedyPatternNames: ["spaces", "optional-spaces", "whitespace", "new-line"],
67
- });
68
64
  }
69
65
 
70
66
  async import(path: string) {
@@ -113,22 +109,14 @@ export class Grammar {
113
109
  }
114
110
 
115
111
  private _tryToParse(expression: string): Node {
116
- const { ast, cursor, options, isComplete } = this._autoComplete.suggestFor(expression);
117
-
118
- if (!isComplete) {
119
- const text = cursor?.text || "";
120
- const index = options.reduce((num, o) => Math.max(o.startIndex, num), 0);
121
- const foundText = text.slice(Math.max(index - 10, 0), index + 10);
122
- const expectedTexts = "'" + options.map(o => {
123
- const startText = text.slice(Math.max(o.startIndex - 10), o.startIndex);
124
- return startText + o.text;
125
- }).join("' or '") + "'";
126
- const message = `[Parse Error] Found: '${foundText}', expected: ${expectedTexts}.`;
127
- throw new Error(message);
112
+ const { ast, cursor } = grammar.exec(expression, true);
113
+
114
+ if (ast == null) {
115
+ const message = generateErrorMessage(grammar, cursor);
116
+ throw new Error(`[Invalid Grammar] ${message}`);
128
117
  }
129
118
 
130
- // If it is complete it will always have a node. So we have to cast it.
131
- return ast as Node;
119
+ return ast;
132
120
  }
133
121
 
134
122
  private _hasImports(ast: Node) {
@@ -291,9 +279,10 @@ export class Grammar {
291
279
  const firstChild = pattern.children[0];
292
280
  const lastChild = pattern.children[pattern.children.length - 1];
293
281
  const isLongEnough = pattern.children.length >= 2;
294
- return (pattern.type === "sequence" && isLongEnough &&
295
- (firstChild.name === name) ||
296
- (lastChild.name === name));
282
+
283
+ return pattern.type === "sequence" && isLongEnough &&
284
+ (firstChild.name === name ||
285
+ lastChild.name === name);
297
286
  }
298
287
 
299
288
  private _buildPattern(node: Node): Pattern {
@@ -94,6 +94,10 @@ export class Literal implements Pattern {
94
94
  let passed = false;
95
95
  const literalRuneLength = this._runes.length;
96
96
 
97
+ if (!cursor.hasNext){
98
+ return false;
99
+ }
100
+
97
101
  for (let i = 0; i < literalRuneLength; i++) {
98
102
  const literalRune = this._runes[i];
99
103
  const cursorRune = cursor.currentChar;
@@ -86,6 +86,8 @@ export class Reference implements Pattern {
86
86
 
87
87
  private _cacheAncestors(id: string) {
88
88
  if (!this._cachedAncestors) {
89
+ this._cachedAncestors = true;
90
+
89
91
  let pattern: Pattern | null = this.parent;
90
92
 
91
93
  while (pattern != null) {
@@ -101,7 +103,7 @@ export class Reference implements Pattern {
101
103
  let depth = 0;
102
104
 
103
105
  for (let pattern of this._recursiveAncestors) {
104
- if (pattern._firstIndex === this._firstIndex) {
106
+ if (pattern.startedOnIndex === this.startedOnIndex) {
105
107
  depth++;
106
108
 
107
109
  if (depth > 0) {
@@ -323,4 +323,15 @@ describe("Sequence", () => {
323
323
  expect(nextPatterns.length).toBe(0);
324
324
  });
325
325
 
326
+ test("All Patterns are Optional", () => {
327
+ const sequence = new Sequence("sequence", [
328
+ new Optional("optional-a", new Literal("a", "A")),
329
+ new Optional("optional-b", new Literal("b", "B"))
330
+ ]);
331
+ const result = sequence.exec("");
332
+
333
+ expect(result.ast).toBe(null);
334
+ expect(result.cursor.hasError).toBeFalsy();
335
+ });
336
+
326
337
  });
@@ -83,8 +83,8 @@ export class Sequence implements Pattern {
83
83
  this._firstIndex = cursor.index;
84
84
  this._nodes = [];
85
85
  const passed = this.tryToParse(cursor);
86
-
87
86
  if (passed) {
87
+
88
88
  const node = this.createNode(cursor);
89
89
 
90
90
  if (node !== null) {
@@ -121,7 +121,7 @@ export class Sequence implements Pattern {
121
121
  } else {
122
122
  // We are at the end of the text, it may still be valid, if all the
123
123
  // following patterns are optional.
124
- if (this.areRemainingPatternsOptional(i)) {
124
+ if (this._areRemainingPatternsOptional(i)) {
125
125
  passed = true;
126
126
  break;
127
127
  }
@@ -139,7 +139,7 @@ export class Sequence implements Pattern {
139
139
  } else {
140
140
  // If we don't have any results from what we parsed then record error.
141
141
  const lastNode = this.getLastValidNode();
142
- if (lastNode === null) {
142
+ if (lastNode === null && !this._areAllPatternsOptional()) {
143
143
  cursor.recordErrorAt(this._firstIndex, cursor.index, this);
144
144
  break;
145
145
  }
@@ -168,7 +168,11 @@ export class Sequence implements Pattern {
168
168
  return nodes[nodes.length - 1];
169
169
  }
170
170
 
171
- private areRemainingPatternsOptional(fromIndex: number): boolean {
171
+ private _areAllPatternsOptional() {
172
+ return this._areRemainingPatternsOptional(-1);
173
+ }
174
+
175
+ private _areRemainingPatternsOptional(fromIndex: number): boolean {
172
176
  const startOnIndex = fromIndex + 1;
173
177
  const length = this._children.length;
174
178
 
@@ -185,6 +189,11 @@ export class Sequence implements Pattern {
185
189
  private createNode(cursor: Cursor): Node | null {
186
190
  const children = filterOutNull(this._nodes);
187
191
 
192
+ if (children.length === 0) {
193
+ cursor.moveTo(this._firstIndex);
194
+ return null;
195
+ }
196
+
188
197
  const lastIndex = children[children.length - 1].lastIndex;
189
198
 
190
199
  cursor.moveTo(lastIndex);
@@ -0,0 +1,33 @@
1
+ import { Cursor } from "./Cursor";
2
+ import { Pattern } from "./Pattern";
3
+
4
+ export function generateErrorMessage(pattern: Pattern, cursor: Cursor) {
5
+ const furthestMatch = cursor.leafMatch;
6
+
7
+ if (furthestMatch == null || furthestMatch.node == null || furthestMatch.pattern == null) {
8
+ const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
9
+ return `Error at line 1, column 1. Hint: ${suggestions}`;
10
+ }
11
+
12
+ const endIndex = furthestMatch.node.endIndex;
13
+
14
+ if (endIndex === 0) {
15
+ const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
16
+ return `Error at line 1, column 1. Hint: ${suggestions}`;
17
+ }
18
+
19
+ const lastPattern = furthestMatch.pattern as Pattern;
20
+ const suggestions = cleanSuggestions(lastPattern.getTokens());
21
+ const strUpToError = cursor.getChars(0, endIndex);
22
+ const lines = strUpToError.split("\n");
23
+ const lastLine = lines[lines.length - 1];
24
+ const line = lines.length;
25
+ const column = lastLine.length;
26
+
27
+ return `Error at line ${line}, column ${column}. Hint: ${suggestions}`;
28
+
29
+ }
30
+
31
+ function cleanSuggestions(suggestions: string[]) {
32
+ return suggestions.map(s => s.trim()).filter(s => s.length > 0);
33
+ }
@@ -0,0 +1,174 @@
1
+ import { Node } from "../ast/Node";
2
+ import { $ } from "./query";
3
+
4
+ function createNodeTree() {
5
+ const node = Node.createNode("node", "root", [
6
+ Node.createNode("node", "grand-parent", [
7
+ Node.createNode("node", "parent", [
8
+ Node.createValueNode("node", 'child', "1"),
9
+ Node.createValueNode("node", 'child', "2"),
10
+ Node.createValueNode("node", 'child', "3"),
11
+ Node.createValueNode("node", 'child', "4"),
12
+ ]),
13
+ Node.createNode("node", "parent", [
14
+ Node.createValueNode("node", 'child', "5"),
15
+ Node.createValueNode("node", 'child', "6"),
16
+ Node.createValueNode("node", 'child', "7"),
17
+ Node.createValueNode("node", 'child', "8"),
18
+ ]),
19
+ Node.createNode("node", "parent", [
20
+ Node.createValueNode("node", 'child', "9"),
21
+ Node.createValueNode("node", 'child', "10"),
22
+ Node.createValueNode("node", 'child', "11"),
23
+ Node.createValueNode("node", 'child', "12"),
24
+ ])
25
+ ]),
26
+ Node.createNode("node", "grand-parent", [
27
+ Node.createNode("node", "parent", [
28
+ Node.createValueNode("node", 'child', "13"),
29
+ Node.createValueNode("node", 'child', "14"),
30
+ Node.createValueNode("node", 'child', "15"),
31
+ Node.createValueNode("node", 'child', "16"),
32
+ ]),
33
+ Node.createNode("node", "parent", [
34
+ Node.createValueNode("node", 'child', "17"),
35
+ Node.createValueNode("node", 'child', "18"),
36
+ Node.createValueNode("node", 'child', "19"),
37
+ Node.createValueNode("node", 'child', "20"),
38
+ ]),
39
+ Node.createNode("node", "parent", [
40
+ Node.createValueNode("node", 'child', "21"),
41
+ Node.createValueNode("node", 'child', "22"),
42
+ Node.createValueNode("node", 'child', "23"),
43
+ Node.createValueNode("node", 'child', "24"),
44
+ ])
45
+ ])
46
+ ]);
47
+ node.normalize();
48
+ return node;
49
+ }
50
+
51
+ describe("Query", () => {
52
+ test("Construct", () => {
53
+ const root = createNodeTree();
54
+
55
+ const children = $(root, "child");
56
+ const result = children.toArray().map(n => n.value).join(",");
57
+
58
+ expect(result).toBe("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24");
59
+ });
60
+
61
+ test("First", () => {
62
+ const root = createNodeTree();
63
+
64
+ const children = $(root, "child").first();
65
+ const result = children.toArray().map(n => n.value).join(",");
66
+
67
+ expect(result).toBe("1");
68
+ });
69
+
70
+ test("Last", () => {
71
+ const root = createNodeTree();
72
+
73
+ const children = $(root, "child").last();
74
+ const result = children.toArray().map(n => n.value).join(",");
75
+
76
+ expect(result).toBe("24");
77
+ });
78
+
79
+ test("Parent", () => {
80
+ const root = createNodeTree();
81
+
82
+ const children = $(root, "child").parent();
83
+ const result = children.toArray();
84
+
85
+ expect(result.length).toBe(6);
86
+ expect(result.map(n => n.value).join(",")).toBe("1234,5678,9101112,13141516,17181920,21222324");
87
+ });
88
+
89
+ test("Parents", () => {
90
+ const root = createNodeTree();
91
+
92
+ const children = $(root, "child").parents("grand-parent parent[value='1234']");
93
+ const result = children.toArray();
94
+
95
+ expect(result.length).toBe(1);
96
+ expect(result.map(n => n.value).join(",")).toBe("1234");
97
+ });
98
+
99
+ test("Filter", () => {
100
+ const root = createNodeTree();
101
+
102
+ const children = $(root, "parent").filter("grand-parent parent[value='1234']");
103
+ const result = children.toArray();
104
+
105
+ expect(result.length).toBe(1);
106
+ expect(result.map(n => n.value).join(",")).toBe("1234");
107
+ });
108
+
109
+ test("Not", () => {
110
+ const root = createNodeTree();
111
+
112
+ const children = $(root, "parent").not("grand-parent parent[value='1234']");
113
+ const result = children.toArray();
114
+
115
+ expect(result.length).toBe(5);
116
+ expect(result.map(n => n.value).join(",")).toBe("5678,9101112,13141516,17181920,21222324");
117
+ });
118
+
119
+ test("First Before", () => {
120
+ const root = createNodeTree();
121
+
122
+ $(root, "child").first().before(() => Node.createValueNode("node", "adopted-child", "0"));
123
+
124
+ const adoptedChild = root.find(n => n.name === "adopted-child");
125
+ const firstChild = root.find(n => n.value === "1");
126
+ expect(adoptedChild?.nextSibling()).toBe(firstChild);
127
+ });
128
+
129
+ test("Last After", () => {
130
+ const root = createNodeTree();
131
+
132
+ $(root, "child").last().after(() => Node.createValueNode("node", "adopted-child", "25"));
133
+
134
+ const adoptedChild = root.find(n => n.name === "adopted-child");
135
+ const lastChild = root.find(n => n.value === "24");
136
+ expect(adoptedChild?.previousSibling()).toBe(lastChild);
137
+ });
138
+
139
+ test("Append", () => {
140
+ const root = createNodeTree();
141
+
142
+ const parents = $(root, "parent").append((n) => {
143
+ const lastChild = n.children[n.children.length - 1];
144
+ const value = Number(lastChild.value) + 1;
145
+ return Node.createValueNode("node", "adopted-child", String(value));
146
+ }).toArray();
147
+
148
+ const haveFiveChildren = parents.every(n => n.children.length === 5);
149
+ expect(haveFiveChildren).toBeTruthy();
150
+ expect(parents.map(n => n.toString()).join(",")).toBe("12345,56789,910111213,1314151617,1718192021,2122232425");
151
+ });
152
+
153
+ test("Prepend", () => {
154
+ const root = createNodeTree();
155
+
156
+ const parents = $(root, "parent").prepend((n) => {
157
+ const firstChild = n.children[0];
158
+ const value = Number(firstChild.value) - 1;
159
+ return Node.createValueNode("node", "adopted-child", String(value));
160
+ }).toArray();
161
+
162
+ const haveFiveChildren = parents.every(n => n.children.length === 5);
163
+ expect(haveFiveChildren).toBeTruthy();
164
+ expect(parents.map(n => n.toString()).join(",")).toBe("01234,45678,89101112,1213141516,1617181920,2021222324");
165
+ });
166
+
167
+ test("ReplaceWith", () => {
168
+ const root = createNodeTree();
169
+
170
+ $(root, "child").replaceWith(() => Node.createValueNode("node","child","b"));
171
+
172
+ expect(root.toString()).toBe("bbbbbbbbbbbbbbbbbbbbbbbb");
173
+ });
174
+ });
@@ -1,41 +1,168 @@
1
+ import { Node } from "../ast/Node";
2
+ import { Selector } from "./selector";
3
+
1
4
  export class Query {
2
5
  private _context: Node[];
3
6
  private _prevQuery: Query | null;
4
7
 
5
- constructor(context: Node[], selector?: string, prevQuery: Query | null = null) {
8
+ constructor(context: Node[], prevQuery: Query | null = null) {
6
9
  this._context = context;
7
10
  this._prevQuery = prevQuery;
11
+ }
8
12
 
9
- if (selector != null) {
10
- this.find(selector);
11
- }
13
+ toArray() {
14
+ return this._context.slice();
15
+ }
16
+
17
+ // Modifiers
18
+ append(visitor: (node: Node) => Node) {
19
+ this._context.forEach(n => {
20
+ const parent = n.parent;
21
+
22
+ if (parent == null) {
23
+ return;
24
+ }
25
+ const newNode = visitor(n);
26
+ n.appendChild(newNode);
27
+ });
28
+
29
+ return this;
30
+ }
31
+
32
+ prepend(visitor: (node: Node) => Node) {
33
+ this._context.forEach(n => {
34
+ const parent = n.parent;
35
+
36
+ if (parent == null) {
37
+ return;
38
+ }
39
+ const newNode = visitor(n);
40
+ n.insertBefore(newNode, n.children[0]);
41
+ });
42
+
43
+ return this;
44
+ }
45
+
46
+ after(visitor: (node: Node) => Node) {
47
+ this._context.forEach(n => {
48
+ const parent = n.parent;
49
+
50
+ if (parent == null) {
51
+ return;
52
+ }
53
+ const index = parent.findChildIndex(n);
54
+ const newNode = visitor(n);
55
+
56
+ parent.spliceChildren(index + 1, 0, newNode);
57
+ });
58
+
59
+ return this;
60
+ }
61
+
62
+ before(visitor: (node: Node) => Node) {
63
+ this._context.forEach(n => {
64
+ const parent = n.parent;
65
+
66
+ if (parent == null) {
67
+ return;
68
+ }
69
+ const index = parent.findChildIndex(n);
70
+ const newNode = visitor(n);
71
+
72
+ parent.spliceChildren(index, 0, newNode);
73
+ });
74
+
75
+ return this;
76
+ }
77
+
78
+ replaceWith(visitor: (node: Node) => Node) {
79
+ this._context.forEach(n => {
80
+ const newNode = visitor(n);
81
+ n.replaceWith(newNode);
82
+ });
83
+
84
+ return this;
85
+ }
86
+
87
+ compact() {
88
+ this._context.forEach(n => {
89
+ n.compact();
90
+ });
91
+
92
+ return this;
93
+ }
94
+
95
+ remove() {
96
+ this._context.forEach(n => {
97
+ n.remove();
98
+ });
99
+
100
+ return this;
12
101
  }
13
102
 
103
+ // Filters from the currently matched nodes
14
104
  slice(start: number, end?: number) {
15
105
  return new Query(this._context.slice(start, end));
16
106
  }
17
107
 
18
- // Modifiers
19
- append() { }
108
+ filter(selectorString: string) {
109
+ const selector = new Selector(selectorString);
110
+ const newContext = selector.filter(this._context);
111
+
112
+ return new Query(newContext, this);
113
+ }
20
114
 
21
- prepend() { }
115
+ // Selects out of all descedants of currently matched nodes
116
+ find(selectorString: string) {
117
+ const selector = new Selector(selectorString);
118
+ const newContext = selector.find(this._context);
22
119
 
23
- after() { }
120
+ return new Query(newContext, this);
121
+ }
24
122
 
25
- before() { }
123
+ // Remove nodes from the set of matched nodes.
124
+ not(selectorString: string) {
125
+ const selector = new Selector(selectorString);
126
+ const newContext = selector.not(this._context);
26
127
 
128
+ return new Query(newContext, this);
129
+ }
27
130
 
28
- // Refiners
29
- filter(selector: string) { }
131
+ // Select the parent of currently matched nodes
132
+ parent() {
133
+ const parents = this._context.map(n => n.parent);
134
+ const result = new Set<Node>();
135
+ parents.forEach((n) => {
136
+ if (n != null) {
137
+ result.add(n);
138
+ }
139
+ });
140
+
141
+ return new Query(Array.from(result), this);
142
+ }
30
143
 
31
- find(selector: string) { }
144
+ // Select the ancestors of currently matched nodes
145
+ parents(selectorString: string) {
146
+ const selector = new Selector(selectorString);
147
+ const newContext = selector.parents(this._context);
32
148
 
33
- not(selector: string) { }
149
+ const result = new Set<Node>();
150
+ newContext.forEach((n) => {
151
+ if (n != null) {
152
+ result.add(n);
153
+ }
154
+ });
34
155
 
35
- parent() { }
156
+ return new Query(Array.from(result), this);
157
+ }
36
158
 
37
- parents(selector: string) { }
159
+ first() {
160
+ return new Query(this._context.slice(0, 1), this);
161
+ }
38
162
 
163
+ last() {
164
+ return new Query(this._context.slice(-1), this);
165
+ }
39
166
 
40
167
  // Pop query stack
41
168
  end() {
@@ -45,4 +172,13 @@ export class Query {
45
172
  return this;
46
173
  }
47
174
 
48
- }
175
+ }
176
+
177
+ export function $(root: Node, selector?: string) {
178
+ if (selector == null) {
179
+ return new Query([root]);
180
+ } else {
181
+ return new Query([root]).find(selector);
182
+ }
183
+
184
+ }