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