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
@@ -1,178 +1,303 @@
1
- import ParseError from "../patterns/ParseError";
2
- import Cursor from "../Cursor";
3
- import Pattern from "./Pattern";
4
- import Node from "../ast/Node";
1
+ import { Cursor } from "./Cursor";
2
+ import { Pattern } from "./Pattern";
3
+ import { Node } from "../ast/Node";
4
+ import { clonePatterns } from "./clonePatterns";
5
+ import { filterOutNull } from "./filterOutNull";
6
+ import { findPattern } from "./findPattern";
7
+
8
+ export class And implements Pattern {
9
+ private _type: string;
10
+ private _name: string;
11
+ private _parent: Pattern | null;
12
+ private _children: Pattern[];
13
+ private _isOptional: boolean;
14
+ private _nodes: (Node | null)[];
15
+ private _firstIndex: number;
16
+
17
+ get type(): string {
18
+ return this._type;
19
+ }
20
+
21
+ get name(): string {
22
+ return this._name;
23
+ }
5
24
 
6
- export default class And extends Pattern {
7
- public onPatternIndex: number = 0;
8
- public nodes: (Node | null)[] = [];
9
- public node: Node | null = null;
10
- public cursor: Cursor | null = null;
11
- public mark: number = 0;
25
+ get parent(): Pattern | null {
26
+ return this._parent;
27
+ }
12
28
 
13
- constructor(name: string, patterns: Pattern[], isOptional = false) {
14
- super("and", name, patterns, isOptional);
29
+ set parent(pattern: Pattern | null) {
30
+ this._parent = pattern;
15
31
  }
16
32
 
17
- parse(cursor: Cursor) {
18
- this.resetState(cursor);
19
- this.tryToParse();
33
+ get children(): Pattern[] {
34
+ return this._children;
35
+ }
20
36
 
21
- return this.node;
37
+ get isOptional(): boolean {
38
+ return this._isOptional;
22
39
  }
23
40
 
24
- clone(name?: string, isOptional?: boolean) {
25
- if (name == null) {
26
- name = this.name;
41
+ constructor(name: string, sequence: Pattern[], isOptional = false) {
42
+ if (sequence.length === 0) {
43
+ throw new Error("Need at least one pattern with an 'and' pattern.");
27
44
  }
28
45
 
29
- if (isOptional == null){
30
- isOptional = this._isOptional;
46
+ const children = clonePatterns(sequence);
47
+ this._assignChildrenToParent(children)
48
+
49
+ this._type = "and";
50
+ this._name = name;
51
+ this._isOptional = isOptional;
52
+ this._parent = null;
53
+ this._children = children;
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;
31
61
  }
62
+ }
63
+
64
+ test(text: string) {
65
+ const cursor = new Cursor(text);
66
+ const ast = this.parse(cursor);
67
+
68
+ return ast?.value === text;
69
+ }
32
70
 
33
- return new And(name, this._children, isOptional);
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
+ };
34
79
  }
35
80
 
36
- getTokens() {
37
- let tokens: string[] = [];
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
+ const node = this.createNode(cursor);
89
+
90
+ if (node !== null) {
91
+ cursor.recordMatch(this, node);
92
+ }
93
+
94
+ return node;
95
+ }
96
+
97
+ if (this._isOptional) {
98
+ cursor.resolveError();
99
+ }
38
100
 
39
- for (let x = 0; x < this._children.length; x++) {
40
- const child = this._children[x];
101
+ return null;
102
+ }
41
103
 
42
- if (child.isOptional) {
43
- tokens = tokens.concat(child.getTokens());
104
+ private tryToParse(cursor: Cursor): boolean {
105
+ let passed = false;
106
+
107
+ for (let i = 0; i < this._children.length; i++) {
108
+ const runningCursorIndex = cursor.index;
109
+ const nextPatternIndex = i + 1;
110
+ const hasMorePatterns = nextPatternIndex < this._children.length;
111
+
112
+ const node = this._children[i].parse(cursor);
113
+ const hasNoError = !cursor.hasError;
114
+ const hadMatch = node !== null;
115
+
116
+ if (hasNoError) {
117
+ this._nodes.push(node);
118
+
119
+ if (hasMorePatterns) {
120
+ if (hadMatch) {
121
+ if (cursor.hasNext()) {
122
+ // We had a match. Increment the cursor and use the next pattern.
123
+ cursor.next();
124
+ continue;
125
+ } else {
126
+ // We are at the end of the text, it may still be valid, if all the
127
+ // following patterns are optional.
128
+ if (this.areRemainingPatternsOptional(i)) {
129
+ passed = true;
130
+ break;
131
+ }
132
+
133
+ // We didn't finish the parsing sequence.
134
+ cursor.recordErrorAt(cursor.index + 1, this);
135
+ break;
136
+ }
137
+ } else {
138
+ // An optional pattern did not matched, try from the same spot on the next
139
+ // pattern.
140
+ cursor.moveTo(runningCursorIndex);
141
+ continue;
142
+ }
143
+ } else {
144
+ // If we don't have any results from what we parsed then record error.
145
+ const lastNode = this.getLastValidNode();
146
+ if (lastNode === null) {
147
+ cursor.recordErrorAt(cursor.index, this);
148
+ break;
149
+ }
150
+
151
+ // The sequence was parsed fully.
152
+ passed = true;
153
+ break;
154
+ }
44
155
  } else {
45
- tokens = tokens.concat(child.getTokens());
156
+ // The pattern failed.
157
+ cursor.moveTo(this._firstIndex);
46
158
  break;
47
159
  }
48
160
  }
49
161
 
50
- return tokens;
162
+ return passed;
51
163
  }
52
164
 
53
- private resetState(cursor: Cursor) {
54
- this.onPatternIndex = 0;
55
- this.nodes = [];
56
- this.node = null;
57
- this.cursor = cursor;
58
- this.mark = this.cursor.mark();
59
- }
165
+ private getLastValidNode(): Node | null {
166
+ const nodes = filterOutNull(this._nodes);
60
167
 
61
- private tryToParse() {
62
- const cursor = this.safelyGetCursor();
168
+ if (nodes.length === 0) {
169
+ return null;
170
+ }
63
171
 
64
- while (true) {
65
- const pattern = this._children[this.onPatternIndex];
66
- const node = pattern.parse(cursor);
172
+ return nodes[nodes.length - 1];
173
+ }
67
174
 
68
- if (cursor.hasUnresolvedError()) {
69
- this.processError();
70
- break;
71
- } else {
72
- this.nodes.push(node);
73
- }
175
+ private areRemainingPatternsOptional(fromIndex: number): boolean {
176
+ const startOnIndex = fromIndex + 1;
177
+ const length = this._children.length;
74
178
 
75
- if (!this.shouldProceed()) {
76
- this.processResult();
77
- break;
179
+ for (let i = startOnIndex; i < length; i++) {
180
+ const pattern = this._children[i];
181
+ if (!pattern.isOptional) {
182
+ return false;
78
183
  }
79
184
  }
185
+
186
+ return true;
80
187
  }
81
188
 
82
- private safelyGetCursor() {
83
- const cursor = this.cursor;
189
+ private createNode(cursor: Cursor): Node | null {
190
+ const children = filterOutNull(this._nodes);
84
191
 
85
- if (cursor == null) {
86
- throw new Error("Couldn't find cursor.");
87
- }
88
- return cursor;
192
+ const lastIndex = children[children.length - 1].lastIndex;
193
+ const value = cursor.getChars(this._firstIndex, lastIndex);
194
+
195
+ cursor.moveTo(lastIndex)
196
+
197
+ return new Node(
198
+ "and",
199
+ this._name,
200
+ this._firstIndex,
201
+ lastIndex,
202
+ children
203
+ );
89
204
  }
90
205
 
91
- private processResult() {
92
- const cursor = this.safelyGetCursor();
206
+ getTokens(): string[] {
207
+ const tokens: string[] = [];
93
208
 
94
- if (cursor.hasUnresolvedError()) {
95
- this.processError();
96
- } else {
97
- this.processSuccess();
209
+ for (const child of this._children) {
210
+ tokens.push(...child.getTokens());
211
+
212
+ if (!child.isOptional) {
213
+ break;
214
+ }
98
215
  }
216
+
217
+ return tokens;
99
218
  }
100
219
 
101
- private processError() {
102
- const cursor = this.safelyGetCursor();
220
+ getTokensAfter(childReference: Pattern): string[] {
221
+ const patterns = this.getPatternsAfter(childReference);
222
+ const tokens: string[] = [];
103
223
 
104
- if (this.isOptional) {
105
- cursor.moveToMark(this.mark);
106
- cursor.resolveError();
107
- }
108
- this.node = null;
224
+ patterns.forEach(p => tokens.push(...p.getTokens()));
225
+
226
+ return tokens;
109
227
  }
110
228
 
111
- private shouldProceed() {
112
- const cursor = this.safelyGetCursor();
229
+ getNextTokens(): string[] {
230
+ if (this.parent == null) {
231
+ return []
232
+ }
113
233
 
114
- if (this.hasMorePatterns()) {
115
- const lastNode = this.nodes[this.nodes.length - 1];
116
- const wasOptional = lastNode == null;
234
+ return this.parent.getTokensAfter(this);
235
+ }
117
236
 
118
- if (cursor.hasNext()) {
119
- if (!wasOptional) {
120
- cursor.next();
121
- }
237
+ getPatternsAfter(childReference: Pattern): Pattern[] {
238
+ let nextSibling: Pattern | null = null;
239
+ let nextSiblingIndex = -1;
240
+ let index = -1;
241
+ const patterns: Pattern[] = [];
122
242
 
123
- this.onPatternIndex++;
124
- return true;
125
- } else if (wasOptional) {
126
- this.onPatternIndex++;
127
- return true;
243
+ for (let i = 0; i < this._children.length; i++) {
244
+ if (this._children[i] === childReference) {
245
+ if (i + 1 < this._children.length) {
246
+ nextSibling = this._children[i + 1];
247
+ }
248
+ nextSiblingIndex = i + 1;
249
+ index = i;
250
+ break;
128
251
  }
252
+ }
129
253
 
130
- this.assertRestOfPatternsAreOptional();
131
- return false;
132
- } else {
133
- return false;
254
+ // The child reference isn't one of the child patterns.
255
+ if (index === -1) {
256
+ return [];
134
257
  }
135
- }
136
258
 
137
- private hasMorePatterns() {
138
- return this.onPatternIndex + 1 < this._children.length;
139
- }
259
+ // The reference pattern is the last child. So ask the parent for the next pattern.
260
+ if (nextSiblingIndex === this._children.length && this._parent !== null) {
261
+ return this._parent.getPatternsAfter(this);
262
+ }
140
263
 
141
- private assertRestOfPatternsAreOptional() {
142
- const cursor = this.safelyGetCursor();
143
- const areTheRestOptional = this.areTheRemainingPatternsOptional();
264
+ // Next pattern isn't optional so send it back as the next patterns.
265
+ if (nextSibling !== null && !nextSibling.isOptional) {
266
+ return [nextSibling];
267
+ }
144
268
 
145
- if (!areTheRestOptional) {
146
- const parseError = new ParseError(
147
- `Could not match ${this.name} before string ran out.`,
148
- this.onPatternIndex,
149
- this
150
- );
269
+ // Send back as many optional patterns as possible.
270
+ if (nextSibling !== null && nextSibling.isOptional) {
271
+ for (let i = nextSiblingIndex; i < this._children.length; i++) {
272
+ const child = this._children[i];
273
+ patterns.push(child);
151
274
 
152
- cursor.throwError(parseError);
275
+ if (!child.isOptional) {
276
+ break;
277
+ }
278
+
279
+ if (i === this._children.length - 1 && this._parent !== null) {
280
+ patterns.push(...this._parent.getPatternsAfter(this));
281
+ }
282
+ }
153
283
  }
154
- }
155
284
 
156
- private areTheRemainingPatternsOptional() {
157
- return this.children
158
- .slice(this.onPatternIndex + 1)
159
- .map((p) => p.isOptional)
160
- .every((r) => r);
285
+ return patterns;
161
286
  }
162
287
 
163
- private processSuccess() {
164
- const cursor = this.safelyGetCursor();
165
- const nodes = this.nodes.filter((node) => node != null) as Node[];
166
- this.nodes = nodes;
288
+ getNextPatterns(): Pattern[] {
289
+ if (this.parent == null) {
290
+ return [];
291
+ }
167
292
 
168
- const lastNode = nodes[this.nodes.length - 1];
169
- const startIndex = this.mark;
170
- const endIndex = lastNode.endIndex;
171
- const value = nodes.map((node) => node.value).join("");
293
+ return this.parent.getPatternsAfter(this)
294
+ }
172
295
 
173
- this.node = new Node("and", this.name, startIndex, endIndex, nodes, value);
296
+ findPattern(predicate: (p: Pattern) => boolean): Pattern | null {
297
+ return findPattern(this, predicate);
298
+ }
174
299
 
175
- cursor.index = this.node.endIndex;
176
- cursor.addMatch(this, this.node);
300
+ clone(name = this._name, isOptional = this._isOptional): Pattern {
301
+ return new And(name, this._children, isOptional)
177
302
  }
178
303
  }
@@ -0,0 +1,93 @@
1
+ import { Cursor } from "./Cursor";
2
+ import { Literal } from "./Literal";
3
+ import { Node } from "../ast/Node"
4
+
5
+ describe("Cursor", () => {
6
+ test("Empty Text", () => {
7
+ expect(() => {
8
+ new Cursor("");
9
+ }).toThrowError()
10
+ });
11
+
12
+ test("Move Cursor", () => {
13
+ const cursor = new Cursor("Hello World!");
14
+ cursor.moveTo(6);
15
+
16
+ expect(cursor.currentChar).toBe("W")
17
+
18
+ cursor.moveToFirstChar();
19
+ cursor.previous();
20
+
21
+ expect(cursor.isOnFirst).toBeTruthy();
22
+ expect(cursor.currentChar).toBe("H");
23
+
24
+ cursor.next();
25
+
26
+ expect(cursor.currentChar).toBe("e");
27
+
28
+ cursor.moveToLastChar();
29
+ cursor.next();
30
+
31
+ expect(cursor.isOnLast).toBeTruthy()
32
+ expect(cursor.currentChar).toBe("!");
33
+
34
+ cursor.previous();
35
+
36
+ expect(cursor.currentChar).toBe("d");
37
+ });
38
+
39
+ test("Error Handling", () => {
40
+ const pattern = new Literal("a", "A");
41
+ const cursor = new Cursor("Hello World!");
42
+
43
+ cursor.recordErrorAt(0, pattern);
44
+
45
+ expect(cursor.hasError).toBeTruthy();
46
+ expect(cursor.error?.index).toBe(0);
47
+ expect(cursor.error?.pattern).toBe(pattern);
48
+
49
+ cursor.resolveError();
50
+
51
+ expect(cursor.hasError).toBeFalsy();
52
+ expect(cursor.error).toBe(null);
53
+ expect(cursor.furthestError?.index).toBe(0);
54
+ expect(cursor.furthestError?.pattern).toBe(pattern);
55
+ });
56
+
57
+ test("Error Handling", () => {
58
+ const pattern = new Literal("h", "H");
59
+ const node = new Node("literal", "h", 0, 0, [], "H");
60
+
61
+ const cursor = new Cursor("Hello World!");
62
+
63
+ cursor.recordMatch(pattern, node)
64
+
65
+ expect(cursor.leafMatch.node).toBe(node);
66
+ expect(cursor.leafMatch.pattern).toBe(pattern);
67
+
68
+ expect(cursor.rootMatch.node).toBe(node);
69
+ expect(cursor.rootMatch.pattern).toBe(pattern);
70
+ });
71
+
72
+ test("Recording", () => {
73
+ const cursor = new Cursor("Hello World!");
74
+ cursor.startRecording();
75
+
76
+ expect(cursor.isRecording).toBeTruthy();
77
+
78
+ cursor.stopRecording();
79
+
80
+ expect(cursor.isRecording).toBeFalsy();
81
+ });
82
+
83
+
84
+ test("Text Information", () => {
85
+ const cursor = new Cursor("Hello World!");
86
+ const hello = cursor.getChars(0, 4);
87
+
88
+ expect(hello).toBe("Hello");
89
+ expect(cursor.length).toBe(12);
90
+ expect(cursor.text).toBe("Hello World!")
91
+ expect(cursor.index).toBe(0);
92
+ });
93
+ });
@@ -0,0 +1,133 @@
1
+ import { Node } from "../ast/Node";
2
+ import { CursorHistory, Match } from "./CursorHistory";
3
+ import { ParseError } from "./ParseError";
4
+ import { Pattern } from "./Pattern";
5
+
6
+ export class Cursor {
7
+ private _text: string;
8
+ private _index: number;
9
+ private _length: number;
10
+ private _history: CursorHistory;
11
+
12
+ get text(): string {
13
+ return this._text;
14
+ }
15
+
16
+ get isOnFirst(): boolean {
17
+ return this._index === 0;
18
+ }
19
+
20
+ get isOnLast(): boolean {
21
+ return this._index === this.getLastIndex();
22
+ }
23
+
24
+ get isRecording(): boolean {
25
+ return this._history.isRecording;
26
+ }
27
+
28
+ get rootMatch(): Match {
29
+ return this._history.rootMatch;
30
+ }
31
+
32
+ get leafMatch(): Match {
33
+ return this._history.leafMatch;
34
+ }
35
+
36
+ get furthestError(): ParseError | null {
37
+ return this._history.furthestError;
38
+ }
39
+
40
+ get error() {
41
+ return this._history.error;
42
+ }
43
+
44
+ get index(): number {
45
+ return this._index;
46
+ }
47
+
48
+ get length(): number {
49
+ return this._length;
50
+ }
51
+
52
+ get hasError(): boolean {
53
+ return this._history.error != null
54
+ }
55
+
56
+ get currentChar(): string {
57
+ return this._text[this._index]
58
+ }
59
+
60
+ constructor(text: string) {
61
+ if (text.length === 0) {
62
+ throw new Error("Cannot have a empty string.");
63
+ }
64
+
65
+ this._text = text;
66
+ this._index = 0;
67
+ this._length = text.length;
68
+ this._history = new CursorHistory();
69
+ }
70
+
71
+ hasNext(): boolean {
72
+ return this._index + 1 < this._length;
73
+ }
74
+
75
+ next(): void {
76
+ if (this.hasNext()) {
77
+ this._index++;
78
+ }
79
+ }
80
+
81
+ hasPrevious(): boolean {
82
+ return this._index - 1 >= 0;
83
+ }
84
+
85
+ previous(): void {
86
+ if (this.hasPrevious()) {
87
+ this._index--;
88
+ }
89
+ }
90
+
91
+ moveTo(position: number): void {
92
+ if (position >= 0 && position < this._length) {
93
+ this._index = position;
94
+ }
95
+ }
96
+
97
+ moveToFirstChar(): void {
98
+ this._index = 0;
99
+ }
100
+
101
+ moveToLastChar(): void {
102
+ this._index = this.getLastIndex();
103
+ }
104
+
105
+ getLastIndex(): number {
106
+ return this._length - 1;
107
+ }
108
+
109
+ getChars(first: number, last: number): string {
110
+ return this._text.slice(first, last + 1);
111
+ }
112
+
113
+ recordMatch(pattern: Pattern, node: Node): void {
114
+ this._history.recordMatch(pattern, node);
115
+ }
116
+
117
+ recordErrorAt(index: number, onPattern: Pattern): void {
118
+ this._history.recordErrorAt(index, onPattern);
119
+ }
120
+
121
+ resolveError(): void {
122
+ this._history.resolveError()
123
+ }
124
+
125
+ startRecording(): void {
126
+ this._history.startRecording();
127
+ }
128
+
129
+ stopRecording(): void {
130
+ this._history.stopRecording();
131
+ }
132
+
133
+ }