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