clarity-pattern-parser 4.0.3 → 6.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 (133) hide show
  1. package/README.md +466 -1
  2. package/TODO.md +76 -2
  3. package/dist/ast/Node.d.ts +49 -11
  4. package/dist/ast/Visitor.d.ts +31 -31
  5. package/dist/index.browser.js +1513 -1495
  6. package/dist/index.browser.js.map +1 -1
  7. package/dist/index.d.ts +17 -17
  8. package/dist/index.esm.js +1480 -1459
  9. package/dist/index.esm.js.map +1 -1
  10. package/dist/index.js +1481 -1463
  11. package/dist/index.js.map +1 -1
  12. package/dist/intellisense/AutoComplete.d.ts +28 -0
  13. package/dist/intellisense/Suggestion.d.ts +11 -0
  14. package/dist/intellisense/SuggestionOption.d.ts +4 -0
  15. package/dist/patterns/And.d.ts +37 -24
  16. package/dist/patterns/Cursor.d.ts +37 -0
  17. package/dist/patterns/CursorHistory.d.ts +30 -0
  18. package/dist/patterns/Literal.d.ts +35 -19
  19. package/dist/patterns/Not.d.ts +29 -11
  20. package/dist/patterns/Or.d.ts +33 -22
  21. package/dist/patterns/ParseError.d.ts +6 -8
  22. package/dist/patterns/ParseResult.d.ts +6 -0
  23. package/dist/patterns/Pattern.d.ts +20 -26
  24. package/dist/patterns/Reference.d.ts +34 -12
  25. package/dist/patterns/Regex.d.ts +41 -21
  26. package/dist/patterns/Repeat.d.ts +38 -20
  27. package/dist/patterns/clonePatterns.d.ts +2 -0
  28. package/dist/patterns/filterOutNull.d.ts +2 -0
  29. package/dist/patterns/findPattern.d.ts +2 -0
  30. package/dist/patterns/getNextPattern.d.ts +2 -0
  31. package/jest.config.js +2 -1
  32. package/package.json +4 -5
  33. package/rollup.config.js +1 -1
  34. package/src/ast/Node.test.ts +364 -0
  35. package/src/ast/Node.ts +237 -23
  36. package/src/index.ts +25 -27
  37. package/src/intellisense/AutoComplete.test.ts +150 -0
  38. package/src/intellisense/AutoComplete.ts +200 -0
  39. package/src/intellisense/Suggestion.ts +12 -0
  40. package/src/intellisense/SuggestionOption.ts +4 -0
  41. package/src/{tests/cssPatterns → intellisense/css}/cssValue.ts +1 -1
  42. package/src/{tests/cssPatterns → intellisense/css}/divider.ts +2 -1
  43. package/src/intellisense/css/hex.ts +6 -0
  44. package/src/{tests/cssPatterns → intellisense/css}/method.ts +8 -9
  45. package/src/intellisense/css/name.ts +5 -0
  46. package/src/{tests/javascriptPatterns → intellisense/css}/number.ts +3 -3
  47. package/src/intellisense/css/spaces.ts +6 -0
  48. package/src/intellisense/css/unit.ts +10 -0
  49. package/src/{tests/cssPatterns → intellisense/css}/value.ts +1 -1
  50. package/src/{tests/cssPatterns → intellisense/css}/values.ts +1 -1
  51. package/src/intellisense/javascript/Javascript.test.ts +203 -0
  52. package/src/intellisense/javascript/arrayLiteral.ts +25 -0
  53. package/src/intellisense/javascript/deleteStatement.ts +14 -0
  54. package/src/intellisense/javascript/escapedCharacter.ts +49 -0
  55. package/src/intellisense/javascript/exponent.ts +24 -0
  56. package/src/intellisense/javascript/expression.ts +87 -0
  57. package/src/intellisense/javascript/expressionStatement.ts +29 -0
  58. package/src/intellisense/javascript/fraction.ts +11 -0
  59. package/src/intellisense/javascript/infixOperator.ts +36 -0
  60. package/src/intellisense/javascript/integer.ts +7 -0
  61. package/src/intellisense/javascript/invocation.ts +28 -0
  62. package/src/intellisense/javascript/literal.ts +14 -0
  63. package/src/intellisense/javascript/name.ts +3 -0
  64. package/src/intellisense/javascript/numberLiteral.ts +10 -0
  65. package/src/intellisense/javascript/objectLiteral.ts +30 -0
  66. package/src/intellisense/javascript/optionalSpaces.ts +3 -0
  67. package/src/intellisense/javascript/parameters.ts +20 -0
  68. package/src/intellisense/javascript/prefixOperator.ts +13 -0
  69. package/src/intellisense/javascript/propertyAccess.ts +23 -0
  70. package/src/intellisense/javascript/stringLiteral.ts +28 -0
  71. package/src/patterns/And.test.ts +310 -0
  72. package/src/patterns/And.ts +244 -119
  73. package/src/patterns/Cursor.test.ts +93 -0
  74. package/src/patterns/Cursor.ts +133 -0
  75. package/src/patterns/CursorHistory.test.ts +54 -0
  76. package/src/patterns/CursorHistory.ts +95 -0
  77. package/src/patterns/Literal.test.ts +166 -0
  78. package/src/patterns/Literal.ts +141 -62
  79. package/src/patterns/Not.test.ts +168 -0
  80. package/src/patterns/Not.ts +113 -32
  81. package/src/patterns/Or.test.ts +209 -0
  82. package/src/patterns/Or.ts +128 -97
  83. package/src/patterns/ParseError.ts +3 -7
  84. package/src/patterns/ParseResult.ts +7 -0
  85. package/src/patterns/Pattern.ts +21 -150
  86. package/src/patterns/Reference.test.ts +193 -0
  87. package/src/patterns/Reference.ts +114 -88
  88. package/src/patterns/Regex.test.ts +133 -0
  89. package/src/patterns/Regex.ts +117 -60
  90. package/src/patterns/Repeat.test.ts +218 -0
  91. package/src/patterns/Repeat.ts +220 -103
  92. package/src/patterns/clonePatterns.ts +5 -0
  93. package/src/patterns/filterOutNull.ts +13 -0
  94. package/src/patterns/findPattern.ts +25 -0
  95. package/src/Cursor.ts +0 -141
  96. package/src/CursorHistory.ts +0 -146
  97. package/src/TextSuggester.ts +0 -317
  98. package/src/ast/Visitor.ts +0 -271
  99. package/src/patterns/LookAhead.ts +0 -32
  100. package/src/patterns/Recursive.ts +0 -92
  101. package/src/tests/And.test.ts +0 -180
  102. package/src/tests/ComplexExamples.test.ts +0 -86
  103. package/src/tests/CssPatterns.test.ts +0 -90
  104. package/src/tests/CursorHistory.test.ts +0 -107
  105. package/src/tests/Cusor.test.ts +0 -174
  106. package/src/tests/HtmlPatterns.test.ts +0 -34
  107. package/src/tests/Literal.test.ts +0 -79
  108. package/src/tests/LookAhead.test.ts +0 -44
  109. package/src/tests/Not.test.ts +0 -51
  110. package/src/tests/Or.test.ts +0 -113
  111. package/src/tests/Pattern.test.ts +0 -290
  112. package/src/tests/Recursive.test.ts +0 -64
  113. package/src/tests/Reference.test.ts +0 -16
  114. package/src/tests/Repeat.test.ts +0 -75
  115. package/src/tests/SpeedTest.test.ts +0 -31
  116. package/src/tests/TextSuggester.test.ts +0 -297
  117. package/src/tests/Visitor.test.ts +0 -331
  118. package/src/tests/cssPatterns/hex.ts +0 -5
  119. package/src/tests/cssPatterns/name.ts +0 -5
  120. package/src/tests/cssPatterns/number.ts +0 -8
  121. package/src/tests/cssPatterns/spaces.ts +0 -5
  122. package/src/tests/cssPatterns/unit.ts +0 -8
  123. package/src/tests/htmlPatterns/element.ts +0 -49
  124. package/src/tests/javascriptPatterns/boolean.ts +0 -10
  125. package/src/tests/javascriptPatterns/json.ts +0 -67
  126. package/src/tests/javascriptPatterns/name.ts +0 -5
  127. package/src/tests/javascriptPatterns/objectLiteral.ts +0 -40
  128. package/src/tests/javascriptPatterns/string.ts +0 -84
  129. package/src/tests/javascriptPatterns/unit.ts +0 -8
  130. package/src/tests/javascriptPatterns/whitespace.ts +0 -44
  131. package/src/tests/naturalLanguage/filter.ts +0 -37
  132. package/src/tests/patterns/sentence.ts +0 -37
  133. /package/src/{tests/cssPatterns → intellisense/css}/optionalSpaces.ts +0 -0
@@ -0,0 +1,218 @@
1
+ import { Node } from "../ast/Node";
2
+ import { And } from "./And";
3
+ import { Cursor } from "./Cursor";
4
+ import { findPattern } from "./findPattern";
5
+ import { Literal } from "./Literal";
6
+ import { Pattern } from "./Pattern";
7
+ import { Regex } from "./Regex";
8
+ import { Repeat } from "./Repeat";
9
+
10
+ describe("Repeat", () => {
11
+ test("Successful Parse", () => {
12
+ const digit = new Regex("digit", "\\d");
13
+ const integer = new Repeat("number", digit);
14
+ const cursor = new Cursor("337");
15
+ const result = integer.parse(cursor);
16
+ const expected = new Node("repeat", "number", 0, 2, [
17
+ new Node("regex", "digit", 0, 0, [], "3"),
18
+ new Node("regex", "digit", 1, 1, [], "3"),
19
+ new Node("regex", "digit", 2, 2, [], "7"),
20
+ ]);
21
+
22
+ expect(result).toEqual(expected)
23
+ expect(cursor.hasError).toBeFalsy()
24
+ });
25
+
26
+ test("Failed Parse", () => {
27
+ const digit = new Regex("digit", "\\d");
28
+ const integer = new Repeat("number", digit);
29
+ const cursor = new Cursor("John");
30
+ const result = integer.parse(cursor);
31
+
32
+ expect(result).toBeNull()
33
+ expect(cursor.hasError).toBeTruthy()
34
+ });
35
+
36
+ test("Successful Parse With Divider", () => {
37
+ const digit = new Regex("digit", "\\d");
38
+ const divider = new Literal("divider", ",");
39
+ const integer = new Repeat("number", digit, divider);
40
+ const cursor = new Cursor("3,3,7");
41
+ const result = integer.parse(cursor);
42
+ const expected = new Node("repeat", "number", 0, 4, [
43
+ new Node("regex", "digit", 0, 0, [], "3"),
44
+ new Node("literal", "divider", 1, 1, [], ","),
45
+ new Node("regex", "digit", 2, 2, [], "3"),
46
+ new Node("literal", "divider", 3, 3, [], ","),
47
+ new Node("regex", "digit", 4, 4, [], "7"),
48
+ ]);
49
+
50
+ expect(result).toEqual(expected)
51
+ expect(cursor.hasError).toBeFalsy()
52
+ });
53
+
54
+ test("Successful Parse Text Ends With Divider", () => {
55
+ const digit = new Regex("digit", "\\d");
56
+ const divider = new Literal("divider", ",");
57
+ const integer = new Repeat("number", digit, divider);
58
+ const cursor = new Cursor("3,3,7,");
59
+ const result = integer.parse(cursor);
60
+ const expected = new Node("repeat", "number", 0, 4, [
61
+ new Node("regex", "digit", 0, 0, [], "3"),
62
+ new Node("literal", "divider", 1, 1, [], ","),
63
+ new Node("regex", "digit", 2, 2, [], "3"),
64
+ new Node("literal", "divider", 3, 3, [], ","),
65
+ new Node("regex", "digit", 4, 4, [], "7"),
66
+ ]);
67
+
68
+ expect(result).toEqual(expected)
69
+ expect(cursor.hasError).toBeFalsy()
70
+ });
71
+
72
+ test("Successful Parse Trailing Comma", () => {
73
+ const digit = new Regex("digit", "\\d");
74
+ const divider = new Literal("divider", ",");
75
+ const integer = new Repeat("number", digit, divider);
76
+ const cursor = new Cursor("3,3,7,t");
77
+ const result = integer.parse(cursor);
78
+ const expected = new Node("repeat", "number", 0, 4, [
79
+ new Node("regex", "digit", 0, 0, [], "3"),
80
+ new Node("literal", "divider", 1, 1, [], ","),
81
+ new Node("regex", "digit", 2, 2, [], "3"),
82
+ new Node("literal", "divider", 3, 3, [], ","),
83
+ new Node("regex", "digit", 4, 4, [], "7"),
84
+ ]);
85
+
86
+ expect(result).toEqual(expected)
87
+ expect(cursor.hasError).toBeFalsy()
88
+ });
89
+
90
+ test("Failed (Optional)", () => {
91
+ const digit = new Regex("digit", "\\d");
92
+ const integer = new Repeat("number", digit, undefined, true);
93
+ const cursor = new Cursor("John");
94
+ const result = integer.parse(cursor);
95
+
96
+ expect(result).toBeNull()
97
+ expect(cursor.hasError).toBeFalsy()
98
+ });
99
+
100
+ test("Get Tokens", () => {
101
+ const a = new Literal("a", "A");
102
+ const manyA = new Repeat("number", a);
103
+ const tokens = manyA.getTokens();
104
+ const expected = ["A"];
105
+
106
+ expect(tokens).toEqual(expected)
107
+ });
108
+
109
+ test("Get Tokens After With Bogus Pattern", () => {
110
+ const a = new Literal("a", "A");
111
+ const manyA = new Repeat("many-a", a);
112
+ const tokens = manyA.getTokensAfter(new Literal("bogus", "bogus"));
113
+ const expected: string[] = [];
114
+
115
+ expect(tokens).toEqual(expected)
116
+ });
117
+
118
+ test("Get Tokens After With Divider", () => {
119
+ const a = new Literal("a", "A");
120
+ const b = new Literal("b", "B");
121
+ const divider = new Literal("divider", ",");
122
+ const manyA = new Repeat("many-a", a, divider);
123
+ const parent = new And("parent", [manyA, b]);
124
+
125
+ const clonedManyA = findPattern(parent, p => p.name == "many-a");
126
+ let tokens = clonedManyA?.getTokensAfter(clonedManyA.children[0]);
127
+ let expected = [",", "B"];
128
+
129
+ expect(tokens).toEqual(expected)
130
+
131
+ tokens = clonedManyA?.getTokensAfter(clonedManyA.children[1]);
132
+ expected = ["A"];
133
+
134
+ expect(tokens).toEqual(expected)
135
+ });
136
+
137
+ test("Get Tokens After Without Divider", () => {
138
+ const a = new Literal("a", "A");
139
+ const b = new Literal("b", "B");
140
+ const manyA = new Repeat("many-a", a);
141
+ const parent = new And("parent", [manyA, b]);
142
+
143
+ const clonedManyA = findPattern(parent, p => p.name == "many-a");
144
+ const tokens = clonedManyA?.getTokensAfter(clonedManyA.children[0]);
145
+ const expected = ["A", "B"];
146
+
147
+ expect(tokens).toEqual(expected)
148
+ });
149
+
150
+ test("Properties", () => {
151
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
152
+
153
+ expect(integer.type).toBe("repeat");
154
+ expect(integer.name).toBe("integer");
155
+ expect(integer.isOptional).toBeFalsy()
156
+ expect(integer.parent).toBeNull();
157
+ expect(integer.children[0].name).toBe("digit");
158
+ });
159
+
160
+ test("Exec", () => {
161
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
162
+ const { ast: result } = integer.exec("B");
163
+ expect(result).toBeNull()
164
+ });
165
+
166
+ test("Test With Match", () => {
167
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
168
+ const result = integer.test("1");
169
+ expect(result).toBeTruthy()
170
+ });
171
+
172
+ test("Test With No Match", () => {
173
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
174
+ const result = integer.test("b");
175
+ expect(result).toBeFalsy()
176
+ });
177
+
178
+ test("Get Next Tokens", () => {
179
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
180
+ const parent = new And("parent", [integer, new Literal("pow", "!")]);
181
+ const integerClone = parent.findPattern(p => p.name === "integer") as Pattern;
182
+ const tokens = integerClone.getNextTokens();
183
+
184
+ expect(tokens).toEqual(["!"])
185
+ });
186
+
187
+ test("Get Next Tokens With Null Parents", () => {
188
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
189
+ const tokens = integer.getNextTokens();
190
+
191
+ expect(tokens.length).toBe(0);
192
+ });
193
+
194
+ test("Find Pattern", () => {
195
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
196
+ const digitClone = integer.findPattern(p => p.name === "digit") as Pattern;
197
+
198
+ expect(digitClone).not.toBeNull();
199
+ });
200
+
201
+ test("Get Next Patterns", () => {
202
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
203
+ const parent = new And("parent", [integer, new Literal("pow", "!")]);
204
+ const integerClone = parent.findPattern(p => p.name === "integer") as Pattern;
205
+ const powClone = parent.findPattern(p => p.name === "pow") as Pattern;
206
+ const patterns = integerClone.getNextPatterns();
207
+
208
+ expect(patterns.length).toBe(1);
209
+ expect(patterns[0]).toBe(powClone);
210
+ });
211
+
212
+ test("Get Next Patterns With Null Parents", () => {
213
+ const integer = new Repeat("integer", new Regex("digit", "\\d"));
214
+ const patterns = integer.getNextPatterns();
215
+
216
+ expect(patterns.length).toBe(0);
217
+ });
218
+ });
@@ -1,87 +1,149 @@
1
- import Pattern from "./Pattern";
2
- import Node from "../ast/Node";
3
- import ParseError from "./ParseError";
4
- import Cursor from "../Cursor";
5
-
6
- export default class Repeat extends Pattern {
7
- public _pattern: Pattern;
8
- public _divider: Pattern;
9
- public nodes: Node[] = [];
10
- public cursor!: Cursor;
11
- public mark: number = 0;
12
- public node: Node | null = null;
13
-
14
- constructor(
15
- name: string,
16
- pattern: Pattern,
17
- divider?: Pattern,
18
- isOptional = false
19
- ) {
20
- super(
21
- "repeat",
22
- name,
23
- divider != null ? [pattern, divider] : [pattern],
24
- isOptional
25
- );
1
+ import { Node } from "../ast/Node";
2
+ import { Cursor } from "./Cursor";
3
+ import { Pattern } from "./Pattern";
4
+ import { clonePatterns } from "./clonePatterns";
5
+ import { findPattern } from "./findPattern";
6
+
7
+ export class Repeat implements Pattern {
8
+ private _type: string;
9
+ private _name: string;
10
+ private _parent: Pattern | null;
11
+ private _children: Pattern[];
12
+ private _pattern: Pattern;
13
+ private _divider: Pattern;
14
+ private _isOptional: boolean;
15
+ private _nodes: Node[];
16
+ private _firstIndex: number;
17
+
18
+ get type(): string {
19
+ return this._type;
20
+ }
21
+
22
+ get name(): string {
23
+ return this._name;
24
+ }
25
+
26
+ get parent(): Pattern | null {
27
+ return this._parent;
28
+ }
26
29
 
27
- this._pattern = this.children[0];
28
- this._divider = this.children[1];
29
- this.assertArguments();
30
+ set parent(pattern: Pattern | null) {
31
+ this._parent = pattern;
30
32
  }
31
33
 
32
- private assertArguments() {
33
- if (this._pattern.isOptional) {
34
- throw new Error(
35
- "Invalid Arguments: The pattern cannot be a optional pattern."
36
- );
34
+ get children(): Pattern[] {
35
+ return this._children;
36
+ }
37
+
38
+ get isOptional(): boolean {
39
+ return this._isOptional;
40
+ }
41
+
42
+ constructor(name: string, pattern: Pattern, divider?: Pattern, isOptional = false) {
43
+ const patterns = divider != null ? [pattern, divider] : [pattern];
44
+ const children: Pattern[] = clonePatterns(patterns, false);
45
+ this._assignChildrenToParent(children);
46
+
47
+ this._type = "repeat";
48
+ this._name = name;
49
+ this._isOptional = isOptional;
50
+ this._parent = null;
51
+ this._children = children;
52
+ this._pattern = children[0];
53
+ this._divider = children[1];
54
+ this._firstIndex = -1
55
+ this._nodes = [];
56
+ }
57
+
58
+ private _assignChildrenToParent(children: Pattern[]): void {
59
+ for (const child of children) {
60
+ child.parent = this;
37
61
  }
38
62
  }
39
63
 
40
- private _reset(cursor: Cursor) {
41
- this.nodes = [];
42
- this.cursor = cursor;
43
- this.mark = this.cursor.mark();
64
+ test(text: string) {
65
+ const cursor = new Cursor(text);
66
+ const ast = this.parse(cursor);
67
+
68
+ return ast?.value === text;
69
+ }
70
+
71
+ exec(text: string) {
72
+ const cursor = new Cursor(text);
73
+ const ast = this.parse(cursor);
74
+
75
+ return {
76
+ ast: ast?.value === text ? ast : null,
77
+ cursor
78
+ };
44
79
  }
45
80
 
46
- parse(cursor: Cursor) {
47
- this._reset(cursor);
48
- this.tryToParse();
81
+ parse(cursor: Cursor): Node | null {
82
+ this._firstIndex = cursor.index;
83
+ this._nodes = [];
84
+
85
+ const passed = this.tryToParse(cursor);
86
+
87
+ if (passed) {
88
+ cursor.resolveError();
89
+ const node = this.createNode(cursor);
90
+
91
+ if (node != null) {
92
+ cursor.recordMatch(this, node);
93
+ }
94
+
95
+ return node;
96
+ }
97
+
98
+ if (!this._isOptional) {
99
+ return null;
100
+ }
49
101
 
50
- return this.node;
102
+ cursor.resolveError();
103
+ cursor.moveTo(this._firstIndex);
104
+ return null;
51
105
  }
52
106
 
53
- private tryToParse() {
54
- const cursor = this.safelyGetCursor();
107
+ private tryToParse(cursor: Cursor): boolean {
108
+ let passed = false;
55
109
 
56
110
  while (true) {
57
- const node = this._pattern.parse(cursor);
111
+ const runningCursorIndex = cursor.index;
112
+ const repeatedNode = this._pattern.parse(cursor);
113
+
114
+ if (cursor.hasError) {
115
+ const lastValidNode = this.getLastValidNode();
116
+
117
+ if (lastValidNode != null) {
118
+ passed = true;
119
+ } else {
120
+ cursor.moveTo(runningCursorIndex);
121
+ cursor.recordErrorAt(runningCursorIndex, this._pattern);
122
+ passed = false;
123
+ }
58
124
 
59
- if (cursor.hasUnresolvedError()) {
60
- this.processResult();
61
125
  break;
62
- } else if (node != null) {
63
- this.nodes.push(node);
126
+ } else if (repeatedNode) {
127
+ this._nodes.push(repeatedNode);
64
128
 
65
- if (node.endIndex === cursor.lastIndex()) {
66
- this.processResult();
129
+ if (!cursor.hasNext()) {
130
+ passed = true;
67
131
  break;
68
132
  }
69
133
 
70
134
  cursor.next();
71
135
 
72
136
  if (this._divider != null) {
73
- const mark = cursor.mark();
74
- const node = this._divider.parse(cursor);
137
+ const dividerNode = this._divider.parse(cursor);
75
138
 
76
- if (cursor.hasUnresolvedError()) {
77
- cursor.moveToMark(mark);
78
- this.processResult();
139
+ if (cursor.hasError) {
140
+ passed = true;
79
141
  break;
80
- } else if (node != null) {
81
- this.nodes.push(node);
142
+ } else if (dividerNode != null) {
143
+ this._nodes.push(dividerNode);
82
144
 
83
- if (node.endIndex === cursor.lastIndex()) {
84
- this.processResult();
145
+ if (!cursor.hasNext()) {
146
+ passed = true;
85
147
  break;
86
148
  }
87
149
 
@@ -90,66 +152,121 @@ export default class Repeat extends Pattern {
90
152
  }
91
153
  }
92
154
  }
93
- }
94
155
 
95
- private processResult() {
96
- const endsOnDivider = this.nodes.length % 2 === 0;
97
- const noMatch = this.nodes.length === 0;
98
- const hasDivider = this._divider != null;
156
+ return passed;
157
+ }
99
158
 
100
- this.cursor.resolveError();
159
+ private createNode(cursor: Cursor): Node | null {
160
+ let children: Node[] = [];
101
161
 
102
- if ((hasDivider && endsOnDivider) || noMatch) {
103
- if (this._isOptional) {
104
- this.cursor.moveToMark(this.mark);
105
- } else {
106
- const parseError = new ParseError(
107
- `Did not find a repeating match of ${this.name}.`,
108
- this.mark,
109
- this
110
- );
111
- this.cursor.throwError(parseError);
112
- }
113
- this.node = null;
162
+ if (!this._divider) {
163
+ children = this._nodes;
114
164
  } else {
115
- const value = this.nodes.map((node) => node.value).join("");
116
-
117
- this.node = new Node(
118
- "repeat",
119
- this.name,
120
- this.nodes[0].startIndex,
121
- this.nodes[this.nodes.length - 1].endIndex,
122
- this.nodes,
123
- value
124
- );
125
-
126
- this.cursor.index = this.node.endIndex;
127
- this.cursor.addMatch(this, this.node);
165
+ if (this._nodes.length % 2 !== 1) {
166
+ const dividerNode = this._nodes[this._nodes.length - 1];
167
+ cursor.moveTo(dividerNode.firstIndex);
168
+ children = this._nodes.slice(0, this._nodes.length - 1);
169
+ } else {
170
+ children = this._nodes;
171
+ }
128
172
  }
173
+
174
+ const lastIndex = children[children.length - 1].lastIndex;
175
+ const value = cursor.getChars(this._firstIndex, lastIndex);
176
+ cursor.moveTo(lastIndex);
177
+
178
+ return new Node(
179
+ "repeat",
180
+ this._name,
181
+ this._firstIndex,
182
+ lastIndex,
183
+ children,
184
+ undefined
185
+ );
186
+ }
187
+
188
+ private getLastValidNode(): Node | null {
189
+ const nodes = this._nodes.filter((node) => node !== null);
190
+
191
+ if (nodes.length === 0) {
192
+ return null;
193
+ }
194
+
195
+ return nodes[nodes.length - 1];
196
+ }
197
+
198
+ getTokens(): string[] {
199
+ return this._pattern.getTokens();
129
200
  }
130
201
 
131
- private safelyGetCursor() {
132
- const cursor = this.cursor;
202
+ getTokensAfter(childReference: Pattern): string[] {
203
+ const patterns = this.getPatternsAfter(childReference);
204
+ const tokens: string[] = [];
205
+
206
+ patterns.forEach(p => tokens.push(...p.getTokens()));
207
+
208
+ return tokens;
209
+ }
133
210
 
134
- if (cursor == null) {
135
- throw new Error("Couldn't find cursor.");
211
+ getNextTokens(): string[] {
212
+ if (this.parent == null) {
213
+ return []
136
214
  }
137
- return cursor;
215
+
216
+ return this.parent.getTokensAfter(this);
138
217
  }
139
218
 
140
- clone(name?: string, isOptional?: boolean) {
141
- if (name == null) {
142
- name = this.name;
219
+ getPatternsAfter(childReference: Pattern): Pattern[] {
220
+ let index = -1;
221
+ const patterns: Pattern[] = [];
222
+
223
+ for (let i = 0; i < this._children.length; i++) {
224
+ if (this._children[i] === childReference) {
225
+ index = i;
226
+ }
143
227
  }
144
228
 
145
- if (isOptional == null) {
146
- isOptional = this._isOptional;
229
+ // If the last match isn't a child of this pattern.
230
+ if (index === -1) {
231
+ return [];
147
232
  }
148
233
 
149
- return new Repeat(name, this._pattern, this._divider, isOptional);
234
+ // If the last match was the repeated patterns, then suggest the divider.
235
+ if (index === 0 && this._divider) {
236
+ patterns.push(this._children[1]);
237
+
238
+ if (this._parent) {
239
+ patterns.push(...this._parent.getPatternsAfter(this));
240
+ }
241
+ }
242
+
243
+ // Suggest the pattern because the divider was the last match.
244
+ if (index === 1) {
245
+ patterns.push(this._children[0]);
246
+ }
247
+
248
+ if (index === 0 && !this._divider && this._parent) {
249
+ patterns.push(this._children[0]);
250
+ patterns.push(...this._parent.getPatternsAfter(this));
251
+ }
252
+
253
+ return patterns;
150
254
  }
151
255
 
152
- getTokens() {
153
- return this._pattern.getTokens();
256
+ getNextPatterns(): Pattern[] {
257
+ if (this.parent == null) {
258
+ return [];
259
+ }
260
+
261
+ return this.parent.getPatternsAfter(this)
262
+ }
263
+
264
+ findPattern(predicate: (p: Pattern) => boolean): Pattern | null {
265
+ return findPattern(this, predicate);
266
+ }
267
+
268
+ clone(name = this._name, isOptional = this._isOptional): Pattern {
269
+ return new Repeat(name, this._pattern, this._divider, isOptional);
154
270
  }
155
271
  }
272
+
@@ -0,0 +1,5 @@
1
+ import { Pattern } from "./Pattern";
2
+
3
+ export function clonePatterns(patterns: Pattern[], isOptional?: boolean): Pattern[] {
4
+ return patterns.map(p => p.clone(p.name, isOptional))
5
+ }
@@ -0,0 +1,13 @@
1
+ import { Node } from "../ast/Node"
2
+
3
+ export function filterOutNull(nodes: (Node | null)[]): Node[] {
4
+ const filteredNodes: Node[] = [];
5
+
6
+ for (const node of nodes) {
7
+ if (node !== null) {
8
+ filteredNodes.push(node);
9
+ }
10
+ }
11
+
12
+ return filteredNodes;
13
+ }
@@ -0,0 +1,25 @@
1
+ import { Pattern } from "./Pattern";
2
+
3
+ export function findPattern(pattern: Pattern, predicate: (pattern: Pattern) => boolean): Pattern | null {
4
+ let children: Pattern[] = [];
5
+
6
+ if (pattern.type === "reference") {
7
+ children = [];
8
+ } else {
9
+ children = pattern.children;
10
+ }
11
+
12
+ for (const child of children) {
13
+ const result = findPattern(child, predicate);
14
+
15
+ if (result !== null) {
16
+ return result;
17
+ }
18
+ }
19
+
20
+ if (predicate(pattern)) {
21
+ return pattern;
22
+ } else {
23
+ return null;
24
+ }
25
+ }