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