clarity-pattern-parser 10.1.14 → 10.1.16

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.
@@ -10,6 +10,7 @@ export declare class Options implements Pattern {
10
10
  private _children;
11
11
  private _isGreedy;
12
12
  private _firstIndex;
13
+ private _recursiveDepth;
13
14
  get id(): string;
14
15
  get type(): string;
15
16
  get name(): string;
@@ -17,11 +18,13 @@ export declare class Options implements Pattern {
17
18
  set parent(pattern: Pattern | null);
18
19
  get children(): Pattern[];
19
20
  constructor(name: string, options: Pattern[], isGreedy?: boolean);
21
+ private _calculateRecursiveDepth;
20
22
  private _assignChildrenToParent;
21
23
  test(text: string): boolean;
22
24
  exec(text: string, record?: boolean): ParseResult;
23
25
  parse(cursor: Cursor): Node | null;
24
26
  private _tryToParse;
27
+ private _isBeyondRecursiveDepth;
25
28
  getTokens(): string[];
26
29
  getTokensAfter(_childReference: Pattern): string[];
27
30
  getNextTokens(): string[];
@@ -24,6 +24,7 @@ export declare class Sequence implements Pattern {
24
24
  };
25
25
  parse(cursor: Cursor): Node | null;
26
26
  private tryToParse;
27
+ private _isBeyondRecursiveDepth;
27
28
  private getLastValidNode;
28
29
  private areRemainingPatternsOptional;
29
30
  private createNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-pattern-parser",
3
- "version": "10.1.14",
3
+ "version": "10.1.16",
4
4
  "description": "Parsing Library for Typescript and Javascript.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",
@@ -0,0 +1,16 @@
1
+ import { patterns } from "./patterns";
2
+
3
+ describe("Complex Grammar Tests", () => {
4
+ test("Nested", () => {
5
+ const { expression } = patterns`
6
+ integer = /[0-9]+/
7
+ variable = /[A-Za-z][A-Za-z0-9]*/
8
+ space = /\\s+/
9
+ or-expression = expression + space? + "||" + space? + expression
10
+ mult-expression = expression + space? + "*" + space? + expression
11
+ expression = or-expression | mult-expression | integer | variable
12
+ `;
13
+ const result = expression.exec("a * b || d * e");
14
+ expect(result.ast?.toString()).toBe("a * b || d * e");
15
+ });
16
+ });
@@ -9,6 +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 "..";
12
13
 
13
14
  describe("Grammar", () => {
14
15
  test("Literal", () => {
@@ -569,4 +570,6 @@ describe("Grammar", () => {
569
570
  const result = fullname.exec("John Doe");
570
571
  expect(result?.ast?.value).toBe("John Doe");
571
572
  });
573
+
574
+
572
575
  });
@@ -490,4 +490,31 @@ describe("AutoComplete", () => {
490
490
  expect(results.options).toEqual(expected);
491
491
  });
492
492
 
493
- });
493
+ test("Furthest Error", () => {
494
+ const branchOne = new Sequence("branch-1", [
495
+ new Literal("space-1-1", " "),
496
+ new Literal("space-1-2", " "),
497
+ new Options('branch-1-options', [
498
+ new Literal("AA", "AA"),
499
+ new Literal("AB", "AB"),
500
+ ])
501
+ ]);
502
+ const branchTwo = new Sequence("branch-2", [
503
+ new Literal("space-2-1", " "),
504
+ new Literal("space-2-2", " "),
505
+ new Options('branch-2-options', [
506
+ new Literal("BA", "BA"),
507
+ new Literal("BB", "BB")
508
+ ])
509
+ ]);
510
+ const eitherBranch = new Options("either-branch", [branchOne, branchTwo]);
511
+
512
+ const autoComplete = new AutoComplete(eitherBranch);
513
+ const results = autoComplete.suggestFor(" B");
514
+ const expected = [
515
+ { startIndex: 3, text: "A" },
516
+ { startIndex: 3, text: "B" },
517
+ ];
518
+ expect(results.options).toEqual(expected);
519
+ })
520
+ });
@@ -76,9 +76,10 @@ export class AutoComplete {
76
76
  errorAtIndex = this._cursor.furthestError.endIndex;
77
77
  error = this._cursor.furthestError;
78
78
 
79
- errorAtIndex = options.reduce((errorAtIndex, option) =>
80
- Math.max(errorAtIndex, option.startIndex),
81
- errorAtIndex);
79
+ errorAtIndex = options.reduce(
80
+ (errorAtIndex, option) => Math.max(errorAtIndex, option.startIndex),
81
+ errorAtIndex
82
+ );
82
83
  }
83
84
 
84
85
  return {
@@ -98,17 +99,19 @@ export class AutoComplete {
98
99
 
99
100
  private _getAllOptions() {
100
101
  const errorMatches = this._getOptionsFromErrors();
101
- const leafMatches = this._cursor.leafMatches.map((m) => this._createSuggestionsFromMatch(m)).flat();
102
- const finalResults: SuggestionOption[] = [];
102
+ const validLeafMatches = this._cursor.leafMatches.filter(v => v.node?.lastIndex === this._cursor.getLastIndex())
103
103
 
104
- [...leafMatches, ...errorMatches].forEach(m=>{
105
- const index = finalResults.findIndex(f=> m.text === f.text);
104
+ const leafMatchSuggestions = validLeafMatches.map((m) => this._createSuggestionsFromMatch(m)).flat();
105
+ const uniqueResults: SuggestionOption[] = [];
106
+
107
+ [...leafMatchSuggestions, ...errorMatches].forEach(m=>{
108
+ const index = uniqueResults.findIndex(f => m.text === f.text);
106
109
  if (index === -1){
107
- finalResults.push(m);
110
+ uniqueResults.push(m);
108
111
  }
109
112
  });
110
113
 
111
- return finalResults;
114
+ return uniqueResults;
112
115
  }
113
116
 
114
117
  private _getOptionsFromErrors() {
@@ -118,9 +121,9 @@ export class AutoComplete {
118
121
  const tokens = this._getTokensForPattern(e.pattern);
119
122
  const adjustedTokens = tokens.map(t => t.slice(e.endIndex - e.startIndex));
120
123
  return this._createSuggestions(e.endIndex, adjustedTokens);
121
- });
124
+ }).flat();
122
125
 
123
- return suggestions.flat();
126
+ return suggestions;
124
127
  }
125
128
 
126
129
  private _createSuggestionsFromRoot(): SuggestionOption[] {
@@ -4,13 +4,11 @@ import { Pattern } from "./Pattern";
4
4
  import { clonePatterns } from "./clonePatterns";
5
5
  import { findPattern } from "./findPattern";
6
6
  import { ParseResult } from "./ParseResult";
7
- import { DepthCache } from './DepthCache';
7
+ import { Context } from "./Context";
8
8
 
9
9
  /*
10
10
  The following is created to reduce the overhead of recursion check.
11
11
  */
12
-
13
- const depthCache = new DepthCache();
14
12
  let idIndex = 0;
15
13
 
16
14
  export class Options implements Pattern {
@@ -21,6 +19,7 @@ export class Options implements Pattern {
21
19
  private _children: Pattern[];
22
20
  private _isGreedy: boolean;
23
21
  private _firstIndex: number;
22
+ private _recursiveDepth: number;
24
23
 
25
24
  get id(): string {
26
25
  return this._id;
@@ -61,6 +60,42 @@ export class Options implements Pattern {
61
60
  this._children = children;
62
61
  this._firstIndex = 0;
63
62
  this._isGreedy = isGreedy;
63
+ this._recursiveDepth = this._calculateRecursiveDepth();
64
+ }
65
+
66
+ private _calculateRecursiveDepth() {
67
+ let depth = 0;
68
+
69
+ this._children.forEach((child) => {
70
+ let hasReference = false;
71
+ let descendant = child.children[0];
72
+
73
+ while (descendant != null) {
74
+ if (descendant.type === "reference" && descendant.name === this.name) {
75
+ hasReference = true;
76
+ break;
77
+ }
78
+
79
+ if (descendant.type === "context") {
80
+ const pattern = (descendant as Context).getPatternWithinContext(this.name);
81
+ if (pattern != null) {
82
+ break;
83
+ }
84
+ }
85
+
86
+ descendant = descendant.children[0];
87
+ }
88
+
89
+ if (hasReference) {
90
+ depth++;
91
+ }
92
+ });
93
+
94
+ if (depth === 0) {
95
+ depth = this.children.length;
96
+ }
97
+
98
+ return depth;
64
99
  }
65
100
 
66
101
  private _assignChildrenToParent(children: Pattern[]): void {
@@ -90,13 +125,9 @@ export class Options implements Pattern {
90
125
 
91
126
  parse(cursor: Cursor): Node | null {
92
127
  // This is a cache to help with speed
93
- this._firstIndex = cursor.index;
94
- depthCache.incrementDepth(this._id, this._firstIndex);
95
-
96
128
  this._firstIndex = cursor.index;
97
129
  const node = this._tryToParse(cursor);
98
130
 
99
- depthCache.decrementDepth(this._id, this._firstIndex);
100
131
 
101
132
  if (node != null) {
102
133
  cursor.moveTo(node.lastIndex);
@@ -109,14 +140,15 @@ export class Options implements Pattern {
109
140
  }
110
141
 
111
142
  private _tryToParse(cursor: Cursor): Node | null {
112
- if (depthCache.getDepth(this._id, this._firstIndex) > 2) {
113
- cursor.recordErrorAt(this._firstIndex, this._firstIndex, this);
114
- return null;
143
+ let children = this.children;
144
+
145
+ if (this._isBeyondRecursiveDepth()) {
146
+ children = children.slice().reverse();
115
147
  }
116
148
 
117
149
  const results: (Node | null)[] = [];
118
150
 
119
- for (const pattern of this._children) {
151
+ for (const pattern of children) {
120
152
  cursor.moveTo(this._firstIndex);
121
153
  let result = null;
122
154
 
@@ -139,6 +171,25 @@ export class Options implements Pattern {
139
171
  return nonNullResults[0] || null;
140
172
  }
141
173
 
174
+ private _isBeyondRecursiveDepth() {
175
+ let depth = 0;
176
+ let pattern: Pattern | null = this;
177
+
178
+ while (pattern != null) {
179
+ if (pattern.id === this.id) {
180
+ depth++;
181
+ }
182
+
183
+ if (depth > this._recursiveDepth) {
184
+ return true;
185
+ }
186
+
187
+ pattern = pattern.parent;
188
+ }
189
+
190
+ return false;
191
+ }
192
+
142
193
  getTokens(): string[] {
143
194
  const tokens: string[] = [];
144
195
 
@@ -4,9 +4,7 @@ import { Node } from "../ast/Node";
4
4
  import { clonePatterns } from "./clonePatterns";
5
5
  import { filterOutNull } from "./filterOutNull";
6
6
  import { findPattern } from "./findPattern";
7
- import { DepthCache } from "./DepthCache";
8
7
 
9
- const depthCache = new DepthCache();
10
8
  let idIndex = 0;
11
9
 
12
10
  export class Sequence implements Pattern {
@@ -87,12 +85,8 @@ export class Sequence implements Pattern {
87
85
  parse(cursor: Cursor): Node | null {
88
86
  // This is a cache to help with speed
89
87
  this._firstIndex = cursor.index;
90
- depthCache.incrementDepth(this._id, this._firstIndex);
91
-
92
88
  this._nodes = [];
93
-
94
89
  const passed = this.tryToParse(cursor);
95
- depthCache.decrementDepth(this._id, this._firstIndex);
96
90
 
97
91
  if (passed) {
98
92
  const node = this.createNode(cursor);
@@ -108,7 +102,7 @@ export class Sequence implements Pattern {
108
102
  }
109
103
 
110
104
  private tryToParse(cursor: Cursor): boolean {
111
- if (depthCache.getDepth(this._id, this._firstIndex) > 1) {
105
+ if (this._isBeyondRecursiveDepth()) {
112
106
  cursor.recordErrorAt(this._firstIndex, this._firstIndex, this);
113
107
  return false;
114
108
  }
@@ -173,6 +167,25 @@ export class Sequence implements Pattern {
173
167
  return passed;
174
168
  }
175
169
 
170
+ private _isBeyondRecursiveDepth() {
171
+ let depth = 0;
172
+ let pattern: Pattern | null = this;
173
+
174
+ while (pattern != null) {
175
+ if (pattern.id === this.id && this._firstIndex === (pattern as Sequence)._firstIndex) {
176
+ depth++;
177
+ }
178
+
179
+ if (depth > 1) {
180
+ return true;
181
+ }
182
+
183
+ pattern = pattern.parent;
184
+ }
185
+
186
+ return false;
187
+ }
188
+
176
189
  private getLastValidNode(): Node | null {
177
190
  const nodes = filterOutNull(this._nodes);
178
191
 
@@ -1,26 +0,0 @@
1
- export class DepthCache {
2
- private _depthMap: Record<string, Record<number, number>> = {};
3
-
4
- getDepth(name: string, cursorIndex: number) {
5
- if (this._depthMap[name] == null) {
6
- this._depthMap[name] = {};
7
- }
8
-
9
- if (this._depthMap[name][cursorIndex] == null) {
10
- this._depthMap[name][cursorIndex] = 0;
11
- }
12
-
13
-
14
- return this._depthMap[name][cursorIndex];
15
- }
16
-
17
- incrementDepth(name: string, cursorIndex: number) {
18
- const depth = this.getDepth(name, cursorIndex);
19
- this._depthMap[name][cursorIndex] = depth + 1;
20
- }
21
-
22
- decrementDepth(name: string, cursorIndex: number) {
23
- const depth = this.getDepth(name, cursorIndex);
24
- this._depthMap[name][cursorIndex] = depth - 1;
25
- }
26
- }