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.
- package/README.md +466 -1
- package/TODO.md +76 -2
- package/dist/ast/Node.d.ts +49 -11
- package/dist/ast/Visitor.d.ts +31 -31
- package/dist/index.browser.js +1513 -1495
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +17 -17
- package/dist/index.esm.js +1480 -1459
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1481 -1463
- package/dist/index.js.map +1 -1
- package/dist/intellisense/AutoComplete.d.ts +28 -0
- package/dist/intellisense/Suggestion.d.ts +11 -0
- package/dist/intellisense/SuggestionOption.d.ts +4 -0
- package/dist/patterns/And.d.ts +37 -24
- package/dist/patterns/Cursor.d.ts +37 -0
- package/dist/patterns/CursorHistory.d.ts +30 -0
- package/dist/patterns/Literal.d.ts +35 -19
- package/dist/patterns/Not.d.ts +29 -11
- package/dist/patterns/Or.d.ts +33 -22
- package/dist/patterns/ParseError.d.ts +6 -8
- package/dist/patterns/ParseResult.d.ts +6 -0
- package/dist/patterns/Pattern.d.ts +20 -26
- package/dist/patterns/Reference.d.ts +34 -12
- package/dist/patterns/Regex.d.ts +41 -21
- package/dist/patterns/Repeat.d.ts +38 -20
- package/dist/patterns/clonePatterns.d.ts +2 -0
- package/dist/patterns/filterOutNull.d.ts +2 -0
- package/dist/patterns/findPattern.d.ts +2 -0
- package/dist/patterns/getNextPattern.d.ts +2 -0
- package/jest.config.js +2 -1
- package/package.json +4 -5
- package/rollup.config.js +1 -1
- package/src/ast/Node.test.ts +364 -0
- package/src/ast/Node.ts +237 -23
- package/src/index.ts +25 -27
- package/src/intellisense/AutoComplete.test.ts +150 -0
- package/src/intellisense/AutoComplete.ts +200 -0
- package/src/intellisense/Suggestion.ts +12 -0
- package/src/intellisense/SuggestionOption.ts +4 -0
- package/src/{tests/cssPatterns → intellisense/css}/cssValue.ts +1 -1
- package/src/{tests/cssPatterns → intellisense/css}/divider.ts +2 -1
- package/src/intellisense/css/hex.ts +6 -0
- package/src/{tests/cssPatterns → intellisense/css}/method.ts +8 -9
- package/src/intellisense/css/name.ts +5 -0
- package/src/{tests/javascriptPatterns → intellisense/css}/number.ts +3 -3
- package/src/intellisense/css/spaces.ts +6 -0
- package/src/intellisense/css/unit.ts +10 -0
- package/src/{tests/cssPatterns → intellisense/css}/value.ts +1 -1
- package/src/{tests/cssPatterns → intellisense/css}/values.ts +1 -1
- package/src/intellisense/javascript/Javascript.test.ts +203 -0
- package/src/intellisense/javascript/arrayLiteral.ts +25 -0
- package/src/intellisense/javascript/deleteStatement.ts +14 -0
- package/src/intellisense/javascript/escapedCharacter.ts +49 -0
- package/src/intellisense/javascript/exponent.ts +24 -0
- package/src/intellisense/javascript/expression.ts +87 -0
- package/src/intellisense/javascript/expressionStatement.ts +29 -0
- package/src/intellisense/javascript/fraction.ts +11 -0
- package/src/intellisense/javascript/infixOperator.ts +36 -0
- package/src/intellisense/javascript/integer.ts +7 -0
- package/src/intellisense/javascript/invocation.ts +28 -0
- package/src/intellisense/javascript/literal.ts +14 -0
- package/src/intellisense/javascript/name.ts +3 -0
- package/src/intellisense/javascript/numberLiteral.ts +10 -0
- package/src/intellisense/javascript/objectLiteral.ts +30 -0
- package/src/intellisense/javascript/optionalSpaces.ts +3 -0
- package/src/intellisense/javascript/parameters.ts +20 -0
- package/src/intellisense/javascript/prefixOperator.ts +13 -0
- package/src/intellisense/javascript/propertyAccess.ts +23 -0
- package/src/intellisense/javascript/stringLiteral.ts +28 -0
- package/src/patterns/And.test.ts +310 -0
- package/src/patterns/And.ts +244 -119
- package/src/patterns/Cursor.test.ts +93 -0
- package/src/patterns/Cursor.ts +133 -0
- package/src/patterns/CursorHistory.test.ts +54 -0
- package/src/patterns/CursorHistory.ts +95 -0
- package/src/patterns/Literal.test.ts +166 -0
- package/src/patterns/Literal.ts +141 -62
- package/src/patterns/Not.test.ts +168 -0
- package/src/patterns/Not.ts +113 -32
- package/src/patterns/Or.test.ts +209 -0
- package/src/patterns/Or.ts +128 -97
- package/src/patterns/ParseError.ts +3 -7
- package/src/patterns/ParseResult.ts +7 -0
- package/src/patterns/Pattern.ts +21 -150
- package/src/patterns/Reference.test.ts +193 -0
- package/src/patterns/Reference.ts +114 -88
- package/src/patterns/Regex.test.ts +133 -0
- package/src/patterns/Regex.ts +117 -60
- package/src/patterns/Repeat.test.ts +218 -0
- package/src/patterns/Repeat.ts +220 -103
- package/src/patterns/clonePatterns.ts +5 -0
- package/src/patterns/filterOutNull.ts +13 -0
- package/src/patterns/findPattern.ts +25 -0
- package/src/Cursor.ts +0 -141
- package/src/CursorHistory.ts +0 -146
- package/src/TextSuggester.ts +0 -317
- package/src/ast/Visitor.ts +0 -271
- package/src/patterns/LookAhead.ts +0 -32
- package/src/patterns/Recursive.ts +0 -92
- package/src/tests/And.test.ts +0 -180
- package/src/tests/ComplexExamples.test.ts +0 -86
- package/src/tests/CssPatterns.test.ts +0 -90
- package/src/tests/CursorHistory.test.ts +0 -107
- package/src/tests/Cusor.test.ts +0 -174
- package/src/tests/HtmlPatterns.test.ts +0 -34
- package/src/tests/Literal.test.ts +0 -79
- package/src/tests/LookAhead.test.ts +0 -44
- package/src/tests/Not.test.ts +0 -51
- package/src/tests/Or.test.ts +0 -113
- package/src/tests/Pattern.test.ts +0 -290
- package/src/tests/Recursive.test.ts +0 -64
- package/src/tests/Reference.test.ts +0 -16
- package/src/tests/Repeat.test.ts +0 -75
- package/src/tests/SpeedTest.test.ts +0 -31
- package/src/tests/TextSuggester.test.ts +0 -297
- package/src/tests/Visitor.test.ts +0 -331
- package/src/tests/cssPatterns/hex.ts +0 -5
- package/src/tests/cssPatterns/name.ts +0 -5
- package/src/tests/cssPatterns/number.ts +0 -8
- package/src/tests/cssPatterns/spaces.ts +0 -5
- package/src/tests/cssPatterns/unit.ts +0 -8
- package/src/tests/htmlPatterns/element.ts +0 -49
- package/src/tests/javascriptPatterns/boolean.ts +0 -10
- package/src/tests/javascriptPatterns/json.ts +0 -67
- package/src/tests/javascriptPatterns/name.ts +0 -5
- package/src/tests/javascriptPatterns/objectLiteral.ts +0 -40
- package/src/tests/javascriptPatterns/string.ts +0 -84
- package/src/tests/javascriptPatterns/unit.ts +0 -8
- package/src/tests/javascriptPatterns/whitespace.ts +0 -44
- package/src/tests/naturalLanguage/filter.ts +0 -37
- package/src/tests/patterns/sentence.ts +0 -37
- /package/src/{tests/cssPatterns → intellisense/css}/optionalSpaces.ts +0 -0
package/src/patterns/And.ts
CHANGED
|
@@ -1,178 +1,303 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
14
|
-
|
|
29
|
+
set parent(pattern: Pattern | null) {
|
|
30
|
+
this._parent = pattern;
|
|
15
31
|
}
|
|
16
32
|
|
|
17
|
-
|
|
18
|
-
this.
|
|
19
|
-
|
|
33
|
+
get children(): Pattern[] {
|
|
34
|
+
return this._children;
|
|
35
|
+
}
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
get isOptional(): boolean {
|
|
38
|
+
return this._isOptional;
|
|
22
39
|
}
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
41
103
|
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
156
|
+
// The pattern failed.
|
|
157
|
+
cursor.moveTo(this._firstIndex);
|
|
46
158
|
break;
|
|
47
159
|
}
|
|
48
160
|
}
|
|
49
161
|
|
|
50
|
-
return
|
|
162
|
+
return passed;
|
|
51
163
|
}
|
|
52
164
|
|
|
53
|
-
private
|
|
54
|
-
|
|
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
|
-
|
|
62
|
-
|
|
168
|
+
if (nodes.length === 0) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
63
171
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const node = pattern.parse(cursor);
|
|
172
|
+
return nodes[nodes.length - 1];
|
|
173
|
+
}
|
|
67
174
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
83
|
-
const
|
|
189
|
+
private createNode(cursor: Cursor): Node | null {
|
|
190
|
+
const children = filterOutNull(this._nodes);
|
|
84
191
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
92
|
-
const
|
|
206
|
+
getTokens(): string[] {
|
|
207
|
+
const tokens: string[] = [];
|
|
93
208
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
const
|
|
220
|
+
getTokensAfter(childReference: Pattern): string[] {
|
|
221
|
+
const patterns = this.getPatternsAfter(childReference);
|
|
222
|
+
const tokens: string[] = [];
|
|
103
223
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
this.node = null;
|
|
224
|
+
patterns.forEach(p => tokens.push(...p.getTokens()));
|
|
225
|
+
|
|
226
|
+
return tokens;
|
|
109
227
|
}
|
|
110
228
|
|
|
111
|
-
|
|
112
|
-
|
|
229
|
+
getNextTokens(): string[] {
|
|
230
|
+
if (this.parent == null) {
|
|
231
|
+
return []
|
|
232
|
+
}
|
|
113
233
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const wasOptional = lastNode == null;
|
|
234
|
+
return this.parent.getTokensAfter(this);
|
|
235
|
+
}
|
|
117
236
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
288
|
+
getNextPatterns(): Pattern[] {
|
|
289
|
+
if (this.parent == null) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
167
292
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const endIndex = lastNode.endIndex;
|
|
171
|
-
const value = nodes.map((node) => node.value).join("");
|
|
293
|
+
return this.parent.getPatternsAfter(this)
|
|
294
|
+
}
|
|
172
295
|
|
|
173
|
-
|
|
296
|
+
findPattern(predicate: (p: Pattern) => boolean): Pattern | null {
|
|
297
|
+
return findPattern(this, predicate);
|
|
298
|
+
}
|
|
174
299
|
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
}
|