clarity-pattern-parser 6.0.2 → 7.0.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.
Files changed (88) hide show
  1. package/TODO.md +1 -78
  2. package/dist/ast/Node.d.ts +1 -0
  3. package/dist/grammar/Grammar.d.ts +17 -0
  4. package/dist/grammar/patterns/andLiteral.d.ts +2 -0
  5. package/dist/grammar/patterns/comment.d.ts +2 -0
  6. package/dist/grammar/patterns/grammar.d.ts +2 -0
  7. package/dist/grammar/patterns/literal.d.ts +2 -0
  8. package/dist/grammar/patterns/name.d.ts +2 -0
  9. package/dist/grammar/patterns/orLiteral.d.ts +2 -0
  10. package/dist/grammar/patterns/pattern.d.ts +2 -0
  11. package/dist/grammar/patterns/regexLiteral.d.ts +2 -0
  12. package/dist/grammar/patterns/repeatLiteral.d.ts +3 -0
  13. package/dist/grammar/patterns/spaces.d.ts +2 -0
  14. package/dist/grammar/patterns/statement.d.ts +2 -0
  15. package/dist/index.browser.js +1161 -556
  16. package/dist/index.browser.js.map +1 -1
  17. package/dist/index.d.ts +5 -4
  18. package/dist/index.esm.js +1159 -555
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +1159 -554
  21. package/dist/index.js.map +1 -1
  22. package/dist/intellisense/AutoComplete.d.ts +2 -6
  23. package/dist/patterns/And.d.ts +1 -1
  24. package/dist/patterns/Cursor.d.ts +1 -0
  25. package/dist/patterns/CursorHistory.d.ts +2 -1
  26. package/dist/patterns/FiniteRepeat.d.ts +39 -0
  27. package/dist/patterns/InfiniteRepeat.d.ts +47 -0
  28. package/dist/patterns/Literal.d.ts +1 -1
  29. package/dist/patterns/Not.d.ts +1 -1
  30. package/dist/patterns/Or.d.ts +1 -1
  31. package/dist/patterns/Pattern.d.ts +1 -1
  32. package/dist/patterns/Reference.d.ts +1 -1
  33. package/dist/patterns/Regex.d.ts +1 -1
  34. package/dist/patterns/Repeat.d.ts +18 -22
  35. package/jest.config.js +0 -1
  36. package/jest.coverage.config.js +13 -0
  37. package/package.json +3 -3
  38. package/src/ast/Node.test.ts +15 -0
  39. package/src/ast/Node.ts +12 -6
  40. package/src/grammar/Grammar.test.ts +288 -0
  41. package/src/grammar/Grammar.ts +234 -0
  42. package/src/grammar/patterns/andLiteral.ts +8 -0
  43. package/src/grammar/patterns/comment.ts +3 -0
  44. package/src/grammar/patterns/grammar.ts +19 -0
  45. package/src/grammar/patterns/literal.ts +5 -0
  46. package/src/grammar/patterns/name.ts +3 -0
  47. package/src/grammar/patterns/orLiteral.ts +8 -0
  48. package/src/grammar/patterns/pattern.ts +13 -0
  49. package/src/grammar/patterns/regexLiteral.ts +4 -0
  50. package/src/grammar/patterns/repeatLiteral.ts +72 -0
  51. package/src/grammar/patterns/spaces.ts +4 -0
  52. package/src/grammar/patterns/statement.ts +35 -0
  53. package/src/grammar/spec.md +142 -0
  54. package/src/index.ts +6 -3
  55. package/src/intellisense/AutoComplete.test.ts +21 -2
  56. package/src/intellisense/AutoComplete.ts +14 -25
  57. package/src/intellisense/Suggestion.ts +0 -1
  58. package/src/intellisense/css/cssValue.ts +1 -1
  59. package/src/intellisense/css/method.ts +1 -1
  60. package/src/intellisense/css/values.ts +1 -1
  61. package/src/intellisense/javascript/Javascript.test.ts +0 -1
  62. package/src/intellisense/javascript/arrayLiteral.ts +1 -1
  63. package/src/intellisense/javascript/expression.ts +1 -1
  64. package/src/intellisense/javascript/invocation.ts +1 -1
  65. package/src/intellisense/javascript/objectLiteral.ts +1 -1
  66. package/src/intellisense/javascript/parameters.ts +1 -1
  67. package/src/intellisense/javascript/stringLiteral.ts +2 -4
  68. package/src/patterns/And.test.ts +5 -5
  69. package/src/patterns/And.ts +11 -17
  70. package/src/patterns/Cursor.ts +4 -0
  71. package/src/patterns/CursorHistory.ts +34 -5
  72. package/src/patterns/FiniteRepeat.test.ts +481 -0
  73. package/src/patterns/FiniteRepeat.ts +231 -0
  74. package/src/patterns/InfiniteRepeat.test.ts +296 -0
  75. package/src/patterns/InfiniteRepeat.ts +329 -0
  76. package/src/patterns/Literal.test.ts +4 -4
  77. package/src/patterns/Literal.ts +1 -1
  78. package/src/patterns/Not.test.ts +11 -11
  79. package/src/patterns/Not.ts +1 -1
  80. package/src/patterns/Or.test.ts +9 -9
  81. package/src/patterns/Or.ts +1 -1
  82. package/src/patterns/Pattern.ts +1 -1
  83. package/src/patterns/Reference.test.ts +8 -8
  84. package/src/patterns/Reference.ts +1 -1
  85. package/src/patterns/Regex.test.ts +4 -4
  86. package/src/patterns/Regex.ts +1 -1
  87. package/src/patterns/Repeat.test.ts +160 -165
  88. package/src/patterns/Repeat.ts +95 -230
@@ -0,0 +1,35 @@
1
+ import { And } from "../../patterns/And";
2
+ import { Literal } from "../../patterns/Literal";
3
+ import { Or } from "../../patterns/Or";
4
+ import { andLiteral } from "./andLiteral";
5
+ import { name } from "./name";
6
+ import { orLiteral } from "./orLiteral";
7
+ import { regexLiteral } from "./regexLiteral";
8
+ import { repeatLiteral } from "./repeatLiteral";
9
+ import { spaces } from "./spaces";
10
+ import { literal } from "./literal";
11
+ import { comment } from "./comment";
12
+
13
+ const optionalSpaces = spaces.clone("optional-spaces", true);
14
+ const assignOperator = new Literal("assign-operator", "=");
15
+ const optionalComment = comment.clone("inline-comment", true);
16
+
17
+ const statements = new Or("statements", [
18
+ literal,
19
+ regexLiteral,
20
+ orLiteral,
21
+ andLiteral,
22
+ repeatLiteral,
23
+ ]);
24
+
25
+ export const statement = new And("statement", [
26
+ optionalSpaces,
27
+ name,
28
+ optionalSpaces,
29
+ assignOperator,
30
+ optionalSpaces,
31
+ statements,
32
+ optionalSpaces,
33
+ optionalComment,
34
+ optionalSpaces,
35
+ ]);
@@ -0,0 +1,142 @@
1
+ # Grammar
2
+
3
+ ## Literal
4
+
5
+ ```
6
+ name = "Literal"
7
+ ```
8
+
9
+ ## Regex
10
+
11
+ ```
12
+ digits = /\d+/
13
+ ```
14
+
15
+ ### Regex Caveats
16
+
17
+ Do not use "^" at the beginning or "$" at the end of your regular expression. If
18
+ you are creating a regular expression that is concerned about the beginning and
19
+ end of the text you should probably just use a regular expression.
20
+
21
+ ## Or
22
+ This succeeds if it matches any of the options.
23
+ ```
24
+ multiply = "*"
25
+ divide = "/"
26
+ operators = mulitply | divide
27
+ ```
28
+
29
+ ## And
30
+ This succeeds if it matches all of the patterns in the order they are placed.
31
+ ```
32
+ space = " "
33
+ first-name = "John"
34
+ last-name = "Doe"
35
+ full-name = first-name & space & last-name
36
+ ```
37
+
38
+ ### Optional pattern
39
+ Patterns within the and sequence can be optional.
40
+ ```
41
+ space = " "
42
+ first-name = /\w+/
43
+ last-name = /\w+/
44
+ middle-name = /\w+/
45
+ middle-name-with-space = middle-name & space
46
+
47
+ full-name = first-name & space & middle-name-with-space? & last-name
48
+ ```
49
+
50
+ ### Negative Look Ahead
51
+ This will ensure that the first name isn't `Jack` before continuing to match for
52
+ a name.
53
+
54
+ ```
55
+ space = " "
56
+ first-name = /\w+/
57
+ last-name = /\w+/
58
+ middle-name = /\w+/
59
+ middle-name-with-space = m-middle-name & space
60
+ jack = "Jack"
61
+
62
+ full-name = !jack & first-name & space & middle-name-with-space? & last-name
63
+ ```
64
+
65
+ ## Repeat
66
+ ```
67
+ digit = /\d/
68
+ digits = (digit)*
69
+ ```
70
+
71
+ ### Zero Or More Pattern
72
+
73
+ ```
74
+ digit = /\d/
75
+ comma = ","
76
+ digits = (digit, comma)*
77
+ ```
78
+
79
+ ### Zero Or More & Trim Trailing Divider
80
+ ```
81
+ digit = /\d/
82
+ comma = ","
83
+ digits = (digit, comma)* -t
84
+ ```
85
+
86
+ This is a useful feature if you don't want the divider to be the last pattern found. Let's look at the example below to understand.
87
+
88
+ ```ts
89
+ const expression = `
90
+ digit = /\d/
91
+ comma = ","
92
+ digits = (digit, comma)* -t
93
+ `;
94
+
95
+ const { digits } = Gammar.parse(expression);
96
+
97
+ let result = digits.exec("1,2,3");
98
+ expect(result.ast?.value).toBe("1,2,3");
99
+
100
+ result = digits.exec("1,2,");
101
+ expect(result.ast).toBeNull();
102
+ ```
103
+
104
+ ### Zero Or More With Optional Repeated Pattern
105
+
106
+ ```
107
+ digit = /\d/
108
+ comma = ","
109
+ digits = (digit?, comma)*
110
+ ```
111
+
112
+ ### One Or More With Optional Repeated Pattern
113
+
114
+ ```
115
+ digit = /\d/
116
+ comma = ","
117
+ digits = (digit?, comma)+
118
+ ```
119
+
120
+ ### Upper Limit
121
+
122
+ ```
123
+ digit = /\d/
124
+ comma = ","
125
+ digits = (digit, comma){,3}
126
+ ```
127
+
128
+ ### Bounded
129
+
130
+ ```
131
+ digit = /\d/
132
+ comma = ","
133
+ digits = (digit, comma){1,3}
134
+ ```
135
+
136
+ ### Lower Limit
137
+
138
+ ```
139
+ digit = /\d/
140
+ comma = ","
141
+ digits = (digit, comma){1,3}
142
+ ```
package/src/index.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { Node } from "./ast/Node";
2
+ import { Grammar } from "./grammar/Grammar";
3
+ import { Suggestion } from "./intellisense/Suggestion";
4
+ import { SuggestionOption } from "./intellisense/SuggestionOption";
5
+ import { AutoComplete, AutoCompleteOptions } from './intellisense/AutoComplete';
2
6
  import { Cursor } from "./patterns/Cursor";
3
7
  import { Regex } from "./patterns/Regex";
4
8
  import { And } from "./patterns/And";
@@ -9,15 +13,14 @@ import { Repeat } from "./patterns/Repeat";
9
13
  import { ParseError } from "./patterns/ParseError";
10
14
  import { Pattern } from "./patterns/Pattern";
11
15
  import { Reference } from "./patterns/Reference";
12
- import { AutoComplete } from './intellisense/AutoComplete';
13
16
  import { CursorHistory, Match } from "./patterns/CursorHistory";
14
17
  import { ParseResult } from "./patterns/ParseResult";
15
- import { Suggestion } from "./intellisense/Suggestion";
16
- import { SuggestionOption } from "./intellisense/SuggestionOption";
17
18
 
18
19
  export {
19
20
  Node,
21
+ Grammar,
20
22
  AutoComplete,
23
+ AutoCompleteOptions,
21
24
  Suggestion,
22
25
  SuggestionOption,
23
26
  And,
@@ -1,5 +1,4 @@
1
1
  import { And } from "../patterns/And";
2
- import { findPattern } from "../patterns/findPattern";
3
2
  import { Literal } from "../patterns/Literal";
4
3
  import { Or } from "../patterns/Or";
5
4
  import { Regex } from "../patterns/Regex";
@@ -71,7 +70,7 @@ describe("AutoComplete", () => {
71
70
  divider.setTokens([", "])
72
71
 
73
72
  const text = "John Doe";
74
- const autoComplete = new AutoComplete(new Repeat("last-names", name, divider));
73
+ const autoComplete = new AutoComplete(new Repeat("last-names", name, { divider }));
75
74
  const result = autoComplete.suggestFor(text);
76
75
  const expectedOptions = [{
77
76
  text: ", ",
@@ -88,6 +87,7 @@ describe("AutoComplete", () => {
88
87
  test("Partial", () => {
89
88
  const name = new Literal("name", "Name");
90
89
  const autoComplete = new AutoComplete(name);
90
+ // Use deprecated suggest for code coverage.
91
91
  const result = autoComplete.suggestFor("Na");
92
92
  const expectedOptions = [{
93
93
  text: "me",
@@ -214,4 +214,23 @@ describe("AutoComplete", () => {
214
214
 
215
215
  });
216
216
 
217
+ test("Match On Different Pattern Roots", () => {
218
+ const start = new Literal("start", "John went to");
219
+ const a = new Literal("a", "a bank.");
220
+ const the = new Literal("the", "the store.");
221
+
222
+ const first = new And("first", [start, a]);
223
+ const second = new And("second", [start, the]);
224
+
225
+ const both = new Or("both", [first, second]);
226
+
227
+ const autoComplete = new AutoComplete(both);
228
+ const result = autoComplete.suggestFor("John went to a gas station.");
229
+ const expected = [
230
+ { text: "the store.", startIndex: 12 },
231
+ { text: "a bank.", startIndex: 12 }
232
+ ];
233
+ expect(result.options).toEqual(expected);
234
+ });
235
+
217
236
  });
@@ -1,4 +1,5 @@
1
1
  import { Cursor } from "../patterns/Cursor";
2
+ import { Match } from "../patterns/CursorHistory";
2
3
  import { Pattern } from "../patterns/Pattern";
3
4
  import { Suggestion } from "./Suggestion";
4
5
  import { SuggestionOption } from "./SuggestionOption";
@@ -31,14 +32,6 @@ export class AutoComplete {
31
32
  this._text = "";
32
33
  }
33
34
 
34
- /**
35
- * @deprecated Use suggestFor instead.
36
- * @param text The text to suggest for.
37
- */
38
- suggest(text: string): Suggestion {
39
- return this.suggestFor(text);
40
- }
41
-
42
35
  suggestFor(text: string): Suggestion {
43
36
  if (text.length === 0) {
44
37
  return {
@@ -52,18 +45,12 @@ export class AutoComplete {
52
45
 
53
46
  this._text = text;
54
47
  this._cursor = new Cursor(text);
55
- const ast = this._pattern.parse(this._cursor);
56
-
57
- const leafPattern = this._cursor.leafMatch.pattern;
58
- const isComplete = ast?.value === text;
59
- const options = this._createSuggestionsFromTokens();
60
48
 
61
- let nextPatterns = [this._pattern];
62
49
  let errorAtIndex = null;
63
50
 
64
- if (leafPattern != null) {
65
- nextPatterns = leafPattern.getNextPatterns();
66
- }
51
+ const ast = this._pattern.parse(this._cursor);
52
+ const isComplete = ast?.value === text;
53
+ const options = this._getAllOptions();
67
54
 
68
55
  if (this._cursor.hasError && this._cursor.furthestError != null) {
69
56
  errorAtIndex = this._cursor.furthestError.index;
@@ -82,6 +69,10 @@ export class AutoComplete {
82
69
  }
83
70
  }
84
71
 
72
+ private _getAllOptions() {
73
+ return this._cursor.leafMatches.map((m) => this._createSuggestionsFromMatch(m)).flat();
74
+ }
75
+
85
76
  private _createSuggestionsFromRoot(): SuggestionOption[] {
86
77
  const suggestions: SuggestionOption[] = [];
87
78
  const tokens = this._pattern.getTokens();
@@ -93,17 +84,15 @@ export class AutoComplete {
93
84
  return suggestions;
94
85
  }
95
86
 
96
- private _createSuggestionsFromTokens(): SuggestionOption[] {
97
- const leafMatch = this._cursor.leafMatch;
98
-
99
- if (!leafMatch.pattern) {
87
+ private _createSuggestionsFromMatch(match: Match): SuggestionOption[] {
88
+ if (!match.pattern) {
100
89
  return this._createSuggestions(-1, this._getTokensForPattern(this._pattern));
101
90
  }
102
91
 
103
- const leafPattern = leafMatch.pattern;
104
- const parent = leafMatch.pattern.parent;
92
+ const leafPattern = match.pattern;
93
+ const parent = match.pattern.parent;
105
94
 
106
- if (parent !== null && leafMatch.node != null) {
95
+ if (parent !== null && match.node != null) {
107
96
  const patterns = leafPattern.getNextPatterns();
108
97
 
109
98
  const tokens = patterns.reduce((acc: string[], pattern) => {
@@ -111,7 +100,7 @@ export class AutoComplete {
111
100
  return acc;
112
101
  }, []);
113
102
 
114
- return this._createSuggestions(leafMatch.node.lastIndex, tokens);
103
+ return this._createSuggestions(match.node.lastIndex, tokens);
115
104
  } else {
116
105
  return [];
117
106
  }
@@ -1,6 +1,5 @@
1
1
  import { Node } from "../ast/Node";
2
2
  import { Cursor } from "../patterns/Cursor";
3
- import { Pattern } from "../patterns/Pattern";
4
3
  import { SuggestionOption } from "./SuggestionOption";
5
4
 
6
5
  export interface Suggestion {
@@ -2,6 +2,6 @@ import { Repeat } from "../../patterns/Repeat";
2
2
  import divider from "./divider";
3
3
  import values from "./values";
4
4
 
5
- const cssValue = new Repeat("css-value", values, divider);
5
+ const cssValue = new Repeat("css-value", values, { divider });
6
6
 
7
7
  export default cssValue;
@@ -9,7 +9,7 @@ import divider from "./divider";
9
9
  const openParen = new Literal("open-paren", "(");
10
10
  const closeParen = new Literal("close-paren", ")");
11
11
  const values = new Reference("values");
12
- const args = new Repeat("arguments", values, divider, true);
12
+ const args = new Repeat("arguments", values, { divider, min: 0 });
13
13
  const methodName = name.clone("method-name");
14
14
  methodName.setTokens(["rgba", "radial-gradient", "linear-gradient"]);
15
15
 
@@ -2,6 +2,6 @@ import { Repeat } from "../../patterns/Repeat";
2
2
  import value from "./value";
3
3
  import spaces from "./spaces";
4
4
 
5
- const values = new Repeat("values", value, spaces);
5
+ const values = new Repeat("values", value, { divider: spaces });
6
6
 
7
7
  export default values;
@@ -8,7 +8,6 @@ import { integer } from "./integer";
8
8
  import { name } from "./name";
9
9
  import { parameters } from "./parameters";
10
10
  import { prefixOperator } from "./prefixOperator";
11
- import { objectAccess } from "./objectAccess";
12
11
 
13
12
  describe("Ecmascript 3", () => {
14
13
  test("Escaped Character", () => {
@@ -7,7 +7,7 @@ import { Repeat } from "../../patterns/Repeat";
7
7
  import { optionalSpaces } from "./optionalSpaces";
8
8
 
9
9
  const divider = new Regex("array-divider", "\\s*,\\s*");
10
- const arrayItems = new Repeat("array-items", new Reference("expression"), divider, true);
10
+ const arrayItems = new Repeat("array-items", new Reference("expression"), { divider, min: 0 });
11
11
 
12
12
  export const arrayLiteral = new Or("array-literal",
13
13
  [new And("empty-array-literal", [
@@ -1,8 +1,8 @@
1
- import { Regex } from "../..";
2
1
  import { And } from "../../patterns/And";
3
2
  import { Literal } from "../../patterns/Literal";
4
3
  import { Or } from "../../patterns/Or";
5
4
  import { Reference } from "../../patterns/Reference";
5
+ import { Regex } from "../../patterns/Regex";
6
6
  import { Repeat } from "../../patterns/Repeat";
7
7
  import { infixOperator } from "./infixOperator";
8
8
  import { invocation } from "./invocation";
@@ -11,7 +11,7 @@ const divider = new Regex("invocation-divider", "\\s*,\\s*");
11
11
  const invocationWithArguments = new And("invocation-with-arguments", [
12
12
  new Literal("open-paren", "("),
13
13
  optionalSpaces,
14
- new Repeat("expressions", new Reference("expression"), divider, true),
14
+ new Repeat("expressions", new Reference("expression"), { divider, min: 0 }),
15
15
  optionalSpaces,
16
16
  new Literal("close-paren", ")"),
17
17
  ]);
@@ -17,7 +17,7 @@ const property = new And("property", [
17
17
  new Reference("expression"),
18
18
  ]);
19
19
  const divider = new Regex("property-divider", "\\s*,\\s*");
20
- const optionalProperties = new Repeat("properties", property, divider, true);
20
+ const optionalProperties = new Repeat("properties", property, { divider, min: 0 });
21
21
 
22
22
  const objectLiteral = new And("object-literal", [
23
23
  new Literal("open-curly-bracket", "{"),
@@ -12,7 +12,7 @@ const optionalSpace = new Regex("optional-space", "\\s", true)
12
12
  const parameters = new And("parameters", [
13
13
  new Literal("open-paren", "("),
14
14
  optionalSpace,
15
- new Repeat("arguments", name, divider),
15
+ new Repeat("arguments", name, { divider, trimDivider: true }),
16
16
  optionalSpace,
17
17
  new Literal("close-paren", ")"),
18
18
  ]);
@@ -12,8 +12,7 @@ const doubleQuoteStringLiteral = new And("double-string-literal", [
12
12
  new Regex("normal-characters", "[^\\\"]+"),
13
13
  escapedCharacter
14
14
  ]),
15
- undefined,
16
- true
15
+ { min: 0 }
17
16
  ),
18
17
  new Literal("double-quote", "\""),
19
18
  ]);
@@ -25,8 +24,7 @@ const singleQuoteStringLiteral = new And("single-string-literal", [
25
24
  new Regex("normal-characters", "[^\\']+"),
26
25
  escapedCharacter
27
26
  ]),
28
- undefined,
29
- true
27
+ { min: 0 }
30
28
  ),
31
29
  new Literal("single-quote", "'"),
32
30
  ]);
@@ -276,7 +276,7 @@ describe("And", () => {
276
276
  const sequence = new And("sequence", [new Literal("a", "A")]);
277
277
  const parent = new And("parent", [sequence, new Literal("b", "B")]);
278
278
 
279
- const sequenceClone = parent.findPattern(p => p.name === "sequence");
279
+ const sequenceClone = parent.find(p => p.name === "sequence");
280
280
  const tokens = sequenceClone?.getNextTokens() || [];
281
281
 
282
282
  expect(tokens[0]).toBe("B");
@@ -295,8 +295,8 @@ describe("And", () => {
295
295
  new Literal("b", "B"),
296
296
  ], true);
297
297
  const tokens = sequence.getPatterns();
298
- const a = sequence.findPattern(p=>p.name === "a");
299
- const b = sequence.findPattern(p=>p.name === "b");
298
+ const a = sequence.find(p=>p.name === "a");
299
+ const b = sequence.find(p=>p.name === "b");
300
300
  const expected = [a, b]
301
301
 
302
302
  expect(tokens).toEqual(expected);
@@ -306,9 +306,9 @@ describe("And", () => {
306
306
  const sequence = new And("sequence", [new Literal("a", "A")]);
307
307
  const parent = new And("parent", [sequence, new Literal("b", "B")]);
308
308
 
309
- const sequenceClone = parent.findPattern(p => p.name === "sequence");
309
+ const sequenceClone = parent.find(p => p.name === "sequence");
310
310
  const nextPatterns = sequenceClone?.getNextPatterns() || [];
311
- const b = parent.findPattern(p => p.name === "b")
311
+ const b = parent.find(p => p.name === "b")
312
312
 
313
313
  expect(nextPatterns[0]).toBe(b);
314
314
  });
@@ -249,10 +249,10 @@ export class And implements Pattern {
249
249
  }
250
250
 
251
251
  getPatternsAfter(childReference: Pattern): Pattern[] {
252
+ const patterns: Pattern[] = [];
252
253
  let nextSibling: Pattern | null = null;
253
254
  let nextSiblingIndex = -1;
254
255
  let index = -1;
255
- const patterns: Pattern[] = [];
256
256
 
257
257
  for (let i = 0; i < this._children.length; i++) {
258
258
  if (this._children[i] === childReference) {
@@ -275,24 +275,18 @@ export class And implements Pattern {
275
275
  return this._parent.getPatternsAfter(this);
276
276
  }
277
277
 
278
- // Next pattern isn't optional so send it back as the next patterns.
279
- if (nextSibling !== null && !nextSibling.isOptional) {
280
- return [nextSibling];
281
- }
282
-
283
278
  // Send back as many optional patterns as possible.
284
- if (nextSibling !== null && nextSibling.isOptional) {
285
- for (let i = nextSiblingIndex; i < this._children.length; i++) {
286
- const child = this._children[i];
287
- patterns.push(child);
279
+ for (let i = nextSiblingIndex; i < this._children.length; i++) {
280
+ const child = this._children[i];
281
+ patterns.push(child);
288
282
 
289
- if (!child.isOptional) {
290
- break;
291
- }
283
+ if (!child.isOptional) {
284
+ break;
285
+ }
292
286
 
293
- if (i === this._children.length - 1 && this._parent !== null) {
294
- patterns.push(...this._parent.getPatternsAfter(this));
295
- }
287
+ // If we are on the last child and its options then ask for the next pattern from the parent.
288
+ if (i === this._children.length - 1 && this._parent !== null) {
289
+ patterns.push(...this._parent.getPatternsAfter(this));
296
290
  }
297
291
  }
298
292
 
@@ -307,7 +301,7 @@ export class And implements Pattern {
307
301
  return this.parent.getPatternsAfter(this)
308
302
  }
309
303
 
310
- findPattern(predicate: (p: Pattern) => boolean): Pattern | null {
304
+ find(predicate: (p: Pattern) => boolean): Pattern | null {
311
305
  return findPattern(this, predicate);
312
306
  }
313
307
 
@@ -33,6 +33,10 @@ export class Cursor {
33
33
  return this._history.leafMatch;
34
34
  }
35
35
 
36
+ get leafMatches(): Match[] {
37
+ return this._history.leafMatches;
38
+ }
39
+
36
40
  get furthestError(): ParseError | null {
37
41
  return this._history.furthestError;
38
42
  }
@@ -9,7 +9,7 @@ export interface Match {
9
9
 
10
10
  export class CursorHistory {
11
11
  private _isRecording: boolean = false;
12
- private _leafMatch: Match = { pattern: null, node: null };
12
+ private _leafMatches: Match[] = [{ pattern: null, node: null }];
13
13
  private _furthestError: ParseError | null = null;
14
14
  private _currentError: ParseError | null = null;
15
15
  private _rootMatch: Match = { pattern: null, node: null };
@@ -26,7 +26,11 @@ export class CursorHistory {
26
26
  }
27
27
 
28
28
  get leafMatch(): Match {
29
- return this._leafMatch;
29
+ return this._leafMatches[this._leafMatches.length - 1];
30
+ }
31
+
32
+ get leafMatches() {
33
+ return this._leafMatches;
30
34
  }
31
35
 
32
36
  get furthestError(): ParseError | null {
@@ -57,13 +61,38 @@ export class CursorHistory {
57
61
 
58
62
  this._rootMatch.pattern = pattern;
59
63
  this._rootMatch.node = node;
64
+ const leafMatch = this._leafMatches[this._leafMatches.length - 1];
60
65
 
61
66
  const isFurthestMatch =
62
- this._leafMatch.node === null || node.lastIndex > this._leafMatch.node.lastIndex;
67
+ leafMatch.node === null || node.lastIndex > leafMatch.node.lastIndex;
68
+
69
+ const isSameIndexMatch =
70
+ leafMatch.node === null || node.lastIndex === leafMatch.node.lastIndex
63
71
 
64
72
  if (isFurthestMatch) {
65
- this._leafMatch.pattern = pattern;
66
- this._leafMatch.node = node;
73
+ // This is to save on GC churn.
74
+ const match = this._leafMatches.pop() as Match;
75
+ match.pattern = pattern;
76
+ match.node = node;
77
+
78
+ this._leafMatches.length = 0;
79
+ this._leafMatches.push(match);
80
+ } else if (isSameIndexMatch) {
81
+ const isAncestor = this._leafMatches.some((m) => {
82
+ let parent = m.pattern?.parent;
83
+
84
+ while (parent != null) {
85
+ if (parent == pattern.parent) {
86
+ return true;
87
+ }
88
+ parent = parent.parent
89
+ }
90
+ return false;
91
+ })
92
+
93
+ if (!isAncestor) {
94
+ this._leafMatches.unshift({ pattern, node });
95
+ }
67
96
  }
68
97
  }
69
98