clarity-pattern-parser 6.0.2 → 7.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/TODO.md +1 -78
- package/dist/ast/Node.d.ts +1 -0
- package/dist/grammar/Grammar.d.ts +17 -0
- package/dist/grammar/patterns/andLiteral.d.ts +2 -0
- package/dist/grammar/patterns/comment.d.ts +2 -0
- package/dist/grammar/patterns/grammar.d.ts +2 -0
- package/dist/grammar/patterns/literal.d.ts +2 -0
- package/dist/grammar/patterns/name.d.ts +2 -0
- package/dist/grammar/patterns/orLiteral.d.ts +2 -0
- package/dist/grammar/patterns/pattern.d.ts +2 -0
- package/dist/grammar/patterns/regexLiteral.d.ts +2 -0
- package/dist/grammar/patterns/repeatLiteral.d.ts +3 -0
- package/dist/grammar/patterns/spaces.d.ts +2 -0
- package/dist/grammar/patterns/statement.d.ts +2 -0
- package/dist/index.browser.js +1161 -556
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.esm.js +1159 -555
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1159 -554
- package/dist/index.js.map +1 -1
- package/dist/intellisense/AutoComplete.d.ts +2 -6
- package/dist/patterns/And.d.ts +1 -1
- package/dist/patterns/Cursor.d.ts +1 -0
- package/dist/patterns/CursorHistory.d.ts +2 -1
- package/dist/patterns/FiniteRepeat.d.ts +39 -0
- package/dist/patterns/InfiniteRepeat.d.ts +47 -0
- package/dist/patterns/Literal.d.ts +1 -1
- package/dist/patterns/Not.d.ts +1 -1
- package/dist/patterns/Or.d.ts +1 -1
- package/dist/patterns/Pattern.d.ts +1 -1
- package/dist/patterns/Reference.d.ts +1 -1
- package/dist/patterns/Regex.d.ts +1 -1
- package/dist/patterns/Repeat.d.ts +18 -22
- package/jest.config.js +0 -1
- package/jest.coverage.config.js +13 -0
- package/package.json +3 -3
- package/src/ast/Node.test.ts +15 -0
- package/src/ast/Node.ts +12 -6
- package/src/grammar/Grammar.test.ts +288 -0
- package/src/grammar/Grammar.ts +234 -0
- package/src/grammar/patterns/andLiteral.ts +8 -0
- package/src/grammar/patterns/comment.ts +3 -0
- package/src/grammar/patterns/grammar.ts +19 -0
- package/src/grammar/patterns/literal.ts +5 -0
- package/src/grammar/patterns/name.ts +3 -0
- package/src/grammar/patterns/orLiteral.ts +8 -0
- package/src/grammar/patterns/pattern.ts +13 -0
- package/src/grammar/patterns/regexLiteral.ts +4 -0
- package/src/grammar/patterns/repeatLiteral.ts +72 -0
- package/src/grammar/patterns/spaces.ts +4 -0
- package/src/grammar/patterns/statement.ts +35 -0
- package/src/grammar/spec.md +142 -0
- package/src/index.ts +6 -3
- package/src/intellisense/AutoComplete.test.ts +21 -2
- package/src/intellisense/AutoComplete.ts +14 -25
- package/src/intellisense/Suggestion.ts +0 -1
- package/src/intellisense/css/cssValue.ts +1 -1
- package/src/intellisense/css/method.ts +1 -1
- package/src/intellisense/css/values.ts +1 -1
- package/src/intellisense/javascript/Javascript.test.ts +0 -1
- package/src/intellisense/javascript/arrayLiteral.ts +1 -1
- package/src/intellisense/javascript/expression.ts +1 -1
- package/src/intellisense/javascript/invocation.ts +1 -1
- package/src/intellisense/javascript/objectLiteral.ts +1 -1
- package/src/intellisense/javascript/parameters.ts +1 -1
- package/src/intellisense/javascript/stringLiteral.ts +2 -4
- package/src/patterns/And.test.ts +5 -5
- package/src/patterns/And.ts +11 -17
- package/src/patterns/Cursor.ts +4 -0
- package/src/patterns/CursorHistory.ts +34 -5
- package/src/patterns/FiniteRepeat.test.ts +481 -0
- package/src/patterns/FiniteRepeat.ts +231 -0
- package/src/patterns/InfiniteRepeat.test.ts +296 -0
- package/src/patterns/InfiniteRepeat.ts +329 -0
- package/src/patterns/Literal.test.ts +4 -4
- package/src/patterns/Literal.ts +1 -1
- package/src/patterns/Not.test.ts +11 -11
- package/src/patterns/Not.ts +1 -1
- package/src/patterns/Or.test.ts +9 -9
- package/src/patterns/Or.ts +1 -1
- package/src/patterns/Pattern.ts +1 -1
- package/src/patterns/Reference.test.ts +8 -8
- package/src/patterns/Reference.ts +1 -1
- package/src/patterns/Regex.test.ts +4 -4
- package/src/patterns/Regex.ts +1 -1
- package/src/patterns/Repeat.test.ts +160 -165
- package/src/patterns/Repeat.ts +95 -230
|
@@ -0,0 +1,296 @@
|
|
|
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 { InfiniteRepeat } from "./InfiniteRepeat";
|
|
9
|
+
|
|
10
|
+
describe("InfiniteRepeat", () => {
|
|
11
|
+
test("Successful Parse", () => {
|
|
12
|
+
const digit = new Regex("digit", "\\d");
|
|
13
|
+
const integer = new InfiniteRepeat("number", digit);
|
|
14
|
+
const cursor = new Cursor("337");
|
|
15
|
+
const result = integer.parse(cursor);
|
|
16
|
+
const expected = new Node("infinite-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("Bounds", () => {
|
|
27
|
+
const digit = new Regex("digit", "\\d");
|
|
28
|
+
const integer = new InfiniteRepeat("number", digit, { min: 2 });
|
|
29
|
+
|
|
30
|
+
let cursor = new Cursor("3");
|
|
31
|
+
let result = integer.parse(cursor);
|
|
32
|
+
let expected: Node | null = null;
|
|
33
|
+
|
|
34
|
+
expect(result).toEqual(expected);
|
|
35
|
+
expect(cursor.hasError).toBeTruthy();
|
|
36
|
+
|
|
37
|
+
cursor = new Cursor("33");
|
|
38
|
+
result = integer.parse(cursor);
|
|
39
|
+
expected = new Node("infinite-repeat", "number", 0, 1, [
|
|
40
|
+
new Node("regex", "digit", 0, 0, [], "3"),
|
|
41
|
+
new Node("regex", "digit", 1, 1, [], "3")
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual(expected);
|
|
45
|
+
expect(cursor.hasError).toBeFalsy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("Failed Parse", () => {
|
|
49
|
+
const digit = new Regex("digit", "\\d");
|
|
50
|
+
const integer = new InfiniteRepeat("number", digit);
|
|
51
|
+
const cursor = new Cursor("John");
|
|
52
|
+
const result = integer.parse(cursor);
|
|
53
|
+
|
|
54
|
+
expect(result).toBeNull()
|
|
55
|
+
expect(cursor.hasError).toBeTruthy()
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("Successful Parse With Divider", () => {
|
|
59
|
+
const digit = new Regex("digit", "\\d");
|
|
60
|
+
const divider = new Literal("divider", ",");
|
|
61
|
+
const integer = new InfiniteRepeat("number", digit, { divider });
|
|
62
|
+
const cursor = new Cursor("3,3,7");
|
|
63
|
+
const result = integer.parse(cursor);
|
|
64
|
+
const expected = new Node("infinite-repeat", "number", 0, 4, [
|
|
65
|
+
new Node("regex", "digit", 0, 0, [], "3"),
|
|
66
|
+
new Node("literal", "divider", 1, 1, [], ","),
|
|
67
|
+
new Node("regex", "digit", 2, 2, [], "3"),
|
|
68
|
+
new Node("literal", "divider", 3, 3, [], ","),
|
|
69
|
+
new Node("regex", "digit", 4, 4, [], "7"),
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
expect(result).toEqual(expected)
|
|
73
|
+
expect(cursor.hasError).toBeFalsy()
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("Successful Parse Text Ends With Divider", () => {
|
|
77
|
+
const digit = new Regex("digit", "\\d");
|
|
78
|
+
const divider = new Literal("divider", ",");
|
|
79
|
+
const integer = new InfiniteRepeat("number", digit, { divider, trimDivider: true });
|
|
80
|
+
const cursor = new Cursor("3,3,7,");
|
|
81
|
+
const result = integer.parse(cursor);
|
|
82
|
+
const expected = new Node("infinite-repeat", "number", 0, 4, [
|
|
83
|
+
new Node("regex", "digit", 0, 0, [], "3"),
|
|
84
|
+
new Node("literal", "divider", 1, 1, [], ","),
|
|
85
|
+
new Node("regex", "digit", 2, 2, [], "3"),
|
|
86
|
+
new Node("literal", "divider", 3, 3, [], ","),
|
|
87
|
+
new Node("regex", "digit", 4, 4, [], "7"),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
expect(result).toEqual(expected)
|
|
91
|
+
expect(cursor.hasError).toBeFalsy()
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("Successful Parse Trailing Comma", () => {
|
|
95
|
+
const digit = new Regex("digit", "\\d");
|
|
96
|
+
const divider = new Literal("divider", ",");
|
|
97
|
+
const integer = new InfiniteRepeat("number", digit, { divider, trimDivider: true });
|
|
98
|
+
const cursor = new Cursor("3,3,7,t");
|
|
99
|
+
const result = integer.parse(cursor);
|
|
100
|
+
const expected = new Node("infinite-repeat", "number", 0, 4, [
|
|
101
|
+
new Node("regex", "digit", 0, 0, [], "3"),
|
|
102
|
+
new Node("literal", "divider", 1, 1, [], ","),
|
|
103
|
+
new Node("regex", "digit", 2, 2, [], "3"),
|
|
104
|
+
new Node("literal", "divider", 3, 3, [], ","),
|
|
105
|
+
new Node("regex", "digit", 4, 4, [], "7"),
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
expect(result).toEqual(expected)
|
|
109
|
+
expect(cursor.hasError).toBeFalsy()
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("Optional Repeating Pattern", () => {
|
|
113
|
+
const digit = new Regex("digit", "\\d+", true);
|
|
114
|
+
const divider = new Regex("divider", "\\s");
|
|
115
|
+
const integer = new InfiniteRepeat("number", digit, { divider });
|
|
116
|
+
const cursor = new Cursor(
|
|
117
|
+
"\n" +
|
|
118
|
+
"3\n" +
|
|
119
|
+
"\n" +
|
|
120
|
+
"\n"
|
|
121
|
+
);
|
|
122
|
+
const result = integer.parse(cursor);
|
|
123
|
+
const expected = new Node("infinite-repeat", "number", 0, 4, [
|
|
124
|
+
new Node("regex", "divider", 0, 0, [], "\n"),
|
|
125
|
+
new Node("regex", "digit", 1, 1, [], "3"),
|
|
126
|
+
new Node("regex", "divider", 2, 2, [], "\n"),
|
|
127
|
+
new Node("regex", "divider", 3, 3, [], "\n"),
|
|
128
|
+
new Node("regex", "divider", 4, 4, [], "\n"),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
expect(result).toEqual(expected)
|
|
132
|
+
expect(cursor.hasError).toBeFalsy()
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("Failed (Optional)", () => {
|
|
136
|
+
const digit = new Regex("digit", "\\d");
|
|
137
|
+
const integer = new InfiniteRepeat("number", digit, { min: 0 });
|
|
138
|
+
const cursor = new Cursor("John");
|
|
139
|
+
const result = integer.parse(cursor);
|
|
140
|
+
|
|
141
|
+
expect(result).toBeNull()
|
|
142
|
+
expect(cursor.hasError).toBeFalsy()
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("Get Tokens", () => {
|
|
146
|
+
const a = new Literal("a", "A");
|
|
147
|
+
const manyA = new InfiniteRepeat("number", a);
|
|
148
|
+
const tokens = manyA.getTokens();
|
|
149
|
+
const expected = ["A"];
|
|
150
|
+
|
|
151
|
+
expect(tokens).toEqual(expected)
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("Get Tokens After With Bogus Pattern", () => {
|
|
155
|
+
const a = new Literal("a", "A");
|
|
156
|
+
const manyA = new InfiniteRepeat("many-a", a);
|
|
157
|
+
const tokens = manyA.getTokensAfter(new Literal("bogus", "bogus"));
|
|
158
|
+
const expected: string[] = [];
|
|
159
|
+
|
|
160
|
+
expect(tokens).toEqual(expected)
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("Get Tokens After With Divider", () => {
|
|
164
|
+
const a = new Literal("a", "A");
|
|
165
|
+
const b = new Literal("b", "B");
|
|
166
|
+
const divider = new Literal("divider", ",");
|
|
167
|
+
const manyA = new InfiniteRepeat("many-a", a, { divider });
|
|
168
|
+
const parent = new And("parent", [manyA, b]);
|
|
169
|
+
|
|
170
|
+
const clonedManyA = findPattern(parent, p => p.name == "many-a");
|
|
171
|
+
let tokens = clonedManyA?.getTokensAfter(clonedManyA.children[0]);
|
|
172
|
+
let expected = [",", "B"];
|
|
173
|
+
|
|
174
|
+
expect(tokens).toEqual(expected)
|
|
175
|
+
|
|
176
|
+
tokens = clonedManyA?.getTokensAfter(clonedManyA.children[1]);
|
|
177
|
+
expected = ["A"];
|
|
178
|
+
|
|
179
|
+
expect(tokens).toEqual(expected)
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("Get Tokens After Without Divider", () => {
|
|
183
|
+
const a = new Literal("a", "A");
|
|
184
|
+
const b = new Literal("b", "B");
|
|
185
|
+
const manyA = new InfiniteRepeat("many-a", a);
|
|
186
|
+
const parent = new And("parent", [manyA, b]);
|
|
187
|
+
|
|
188
|
+
const clonedManyA = findPattern(parent, p => p.name == "many-a");
|
|
189
|
+
const tokens = clonedManyA?.getTokensAfter(clonedManyA.children[0]);
|
|
190
|
+
const expected = ["A", "B"];
|
|
191
|
+
|
|
192
|
+
expect(tokens).toEqual(expected)
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("Properties", () => {
|
|
196
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
197
|
+
|
|
198
|
+
expect(integer.type).toBe("infinite-repeat");
|
|
199
|
+
expect(integer.name).toBe("integer");
|
|
200
|
+
expect(integer.min).toBe(1);
|
|
201
|
+
expect(integer.isOptional).toBeFalsy()
|
|
202
|
+
expect(integer.parent).toBeNull();
|
|
203
|
+
expect(integer.children[0].name).toBe("digit");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("Exec", () => {
|
|
207
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
208
|
+
const { ast: result } = integer.exec("B");
|
|
209
|
+
expect(result).toBeNull()
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("Test With Match", () => {
|
|
213
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
214
|
+
const result = integer.test("1");
|
|
215
|
+
expect(result).toBeTruthy()
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("Test With No Match", () => {
|
|
219
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
220
|
+
const result = integer.test("b");
|
|
221
|
+
expect(result).toBeFalsy()
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("Get Next Tokens", () => {
|
|
225
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
226
|
+
const parent = new And("parent", [integer, new Literal("pow", "!")]);
|
|
227
|
+
const integerClone = parent.find(p => p.name === "integer") as Pattern;
|
|
228
|
+
const tokens = integerClone.getNextTokens();
|
|
229
|
+
|
|
230
|
+
expect(tokens).toEqual(["!"])
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("Get Next Tokens With Null Parents", () => {
|
|
234
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
235
|
+
const tokens = integer.getNextTokens();
|
|
236
|
+
|
|
237
|
+
expect(tokens.length).toBe(0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("Find Pattern", () => {
|
|
241
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
242
|
+
const digitClone = integer.find(p => p.name === "digit") as Pattern;
|
|
243
|
+
|
|
244
|
+
expect(digitClone).not.toBeNull();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("Get Patterns", () => {
|
|
248
|
+
const a = new Literal("a", "A");
|
|
249
|
+
const manyA = new InfiniteRepeat("number", a);
|
|
250
|
+
const patterns = manyA.getPatterns();
|
|
251
|
+
const expected = [manyA.find(p => p.name === "a")];
|
|
252
|
+
|
|
253
|
+
expect(patterns).toEqual(expected)
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("Get Next Patterns", () => {
|
|
257
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
258
|
+
const parent = new And("parent", [integer, new Literal("pow", "!")]);
|
|
259
|
+
const integerClone = parent.find(p => p.name === "integer") as Pattern;
|
|
260
|
+
const powClone = parent.find(p => p.name === "pow") as Pattern;
|
|
261
|
+
const patterns = integerClone.getNextPatterns();
|
|
262
|
+
|
|
263
|
+
expect(patterns.length).toBe(1);
|
|
264
|
+
expect(patterns[0]).toBe(powClone);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("Get Next Patterns With Null Parents", () => {
|
|
268
|
+
const integer = new InfiniteRepeat("integer", new Regex("digit", "\\d"));
|
|
269
|
+
const patterns = integer.getNextPatterns();
|
|
270
|
+
|
|
271
|
+
expect(patterns.length).toBe(0);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("Clone With Custom Overrides", () => {
|
|
275
|
+
const numbers = new InfiniteRepeat("numbers", new Regex("number", "\\d"), { min: 0 });
|
|
276
|
+
let clone = numbers.clone();
|
|
277
|
+
let expected = new InfiniteRepeat("numbers", new Regex("number", "\\d"), { min: 0 });
|
|
278
|
+
|
|
279
|
+
expect(clone).toEqual(expected);
|
|
280
|
+
|
|
281
|
+
clone = numbers.clone("cloned-numbers");
|
|
282
|
+
expected = new InfiniteRepeat("cloned-numbers", new Regex("number", "\\d"), { min: 0 });
|
|
283
|
+
|
|
284
|
+
expect(clone).toEqual(expected);
|
|
285
|
+
|
|
286
|
+
clone = numbers.clone("cloned-numbers", true);
|
|
287
|
+
expected = new InfiniteRepeat("cloned-numbers", new Regex("number", "\\d"), { min: 0 });
|
|
288
|
+
|
|
289
|
+
expect(clone).toEqual(expected);
|
|
290
|
+
|
|
291
|
+
clone = numbers.clone("cloned-numbers", false);
|
|
292
|
+
expected = new InfiniteRepeat("cloned-numbers", new Regex("number", "\\d"), { min: 1 });
|
|
293
|
+
|
|
294
|
+
expect(clone).toEqual(expected);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
@@ -0,0 +1,329 @@
|
|
|
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 interface InfiniteRepeatOptions {
|
|
8
|
+
divider?: Pattern;
|
|
9
|
+
min?: number;
|
|
10
|
+
trimDivider?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class InfiniteRepeat implements Pattern {
|
|
14
|
+
private _type: string;
|
|
15
|
+
private _name: string;
|
|
16
|
+
private _parent: Pattern | null;
|
|
17
|
+
private _children: Pattern[];
|
|
18
|
+
private _pattern: Pattern;
|
|
19
|
+
private _divider: Pattern | null;
|
|
20
|
+
private _nodes: Node[];
|
|
21
|
+
private _firstIndex: number;
|
|
22
|
+
private _min: number;
|
|
23
|
+
private _trimDivider: boolean;
|
|
24
|
+
|
|
25
|
+
get type(): string {
|
|
26
|
+
return this._type;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get name(): string {
|
|
30
|
+
return this._name;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get parent(): Pattern | null {
|
|
34
|
+
return this._parent;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set parent(pattern: Pattern | null) {
|
|
38
|
+
this._parent = pattern;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get children(): Pattern[] {
|
|
42
|
+
return this._children;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get isOptional(): boolean {
|
|
46
|
+
return this._min === 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get min(): number {
|
|
50
|
+
return this._min;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
constructor(name: string, pattern: Pattern, options: InfiniteRepeatOptions = {}) {
|
|
54
|
+
const min = options.min != null ? options.min : 1;
|
|
55
|
+
const divider = options.divider;
|
|
56
|
+
let children: Pattern[];
|
|
57
|
+
|
|
58
|
+
if (divider != null) {
|
|
59
|
+
children = [pattern.clone(), divider.clone(divider.name, false)]
|
|
60
|
+
} else {
|
|
61
|
+
children = [pattern.clone()]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this._assignChildrenToParent(children);
|
|
65
|
+
|
|
66
|
+
this._type = "infinite-repeat";
|
|
67
|
+
this._name = name;
|
|
68
|
+
this._min = min;
|
|
69
|
+
this._parent = null;
|
|
70
|
+
this._children = children;
|
|
71
|
+
this._pattern = children[0];
|
|
72
|
+
this._divider = children[1];
|
|
73
|
+
this._firstIndex = -1
|
|
74
|
+
this._nodes = [];
|
|
75
|
+
this._trimDivider = options.trimDivider == null ? false : options.trimDivider;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private _assignChildrenToParent(children: Pattern[]): void {
|
|
79
|
+
for (const child of children) {
|
|
80
|
+
child.parent = this;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test(text: string) {
|
|
85
|
+
const cursor = new Cursor(text);
|
|
86
|
+
const ast = this.parse(cursor);
|
|
87
|
+
|
|
88
|
+
return ast?.value === text;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
exec(text: string) {
|
|
92
|
+
const cursor = new Cursor(text);
|
|
93
|
+
const ast = this.parse(cursor);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
ast: ast?.value === text ? ast : null,
|
|
97
|
+
cursor
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
parse(cursor: Cursor): Node | null {
|
|
102
|
+
this._firstIndex = cursor.index;
|
|
103
|
+
this._nodes = [];
|
|
104
|
+
|
|
105
|
+
const passed = this._tryToParse(cursor);
|
|
106
|
+
|
|
107
|
+
if (passed) {
|
|
108
|
+
cursor.resolveError();
|
|
109
|
+
const node = this._createNode(cursor);
|
|
110
|
+
|
|
111
|
+
if (node != null) {
|
|
112
|
+
cursor.moveTo(node.lastIndex);
|
|
113
|
+
cursor.recordMatch(this, node);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return node;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (this._min > 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
cursor.resolveError();
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private _meetsMin() {
|
|
128
|
+
if (this._divider != null) {
|
|
129
|
+
return Math.ceil(this._nodes.length / 2) >= this._min;
|
|
130
|
+
}
|
|
131
|
+
return this._nodes.length >= this._min;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _tryToParse(cursor: Cursor): boolean {
|
|
135
|
+
let passed = false;
|
|
136
|
+
|
|
137
|
+
while (true) {
|
|
138
|
+
const runningCursorIndex = cursor.index;
|
|
139
|
+
const repeatedNode = this._pattern.parse(cursor);
|
|
140
|
+
|
|
141
|
+
if (cursor.hasError) {
|
|
142
|
+
const lastValidNode = this._getLastValidNode();
|
|
143
|
+
|
|
144
|
+
if (lastValidNode != null) {
|
|
145
|
+
passed = true;
|
|
146
|
+
} else {
|
|
147
|
+
cursor.moveTo(runningCursorIndex);
|
|
148
|
+
cursor.recordErrorAt(runningCursorIndex, this._pattern);
|
|
149
|
+
passed = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
break;
|
|
153
|
+
} else {
|
|
154
|
+
if (repeatedNode != null) {
|
|
155
|
+
this._nodes.push(repeatedNode);
|
|
156
|
+
|
|
157
|
+
if (!cursor.hasNext()) {
|
|
158
|
+
passed = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
cursor.next();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (this._divider != null) {
|
|
166
|
+
const dividerNode = this._divider.parse(cursor);
|
|
167
|
+
|
|
168
|
+
if (cursor.hasError) {
|
|
169
|
+
passed = true;
|
|
170
|
+
break;
|
|
171
|
+
} else if (dividerNode != null) {
|
|
172
|
+
this._nodes.push(dividerNode);
|
|
173
|
+
|
|
174
|
+
if (!cursor.hasNext()) {
|
|
175
|
+
passed = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
cursor.next();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const hasMinimum = this._meetsMin();
|
|
186
|
+
|
|
187
|
+
if (hasMinimum) {
|
|
188
|
+
return passed;
|
|
189
|
+
} else if (!hasMinimum && passed) {
|
|
190
|
+
cursor.recordErrorAt(cursor.index, this);
|
|
191
|
+
cursor.moveTo(this._firstIndex);
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return passed;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private _createNode(cursor: Cursor): Node | null {
|
|
199
|
+
const hasDivider = this._divider != null;
|
|
200
|
+
|
|
201
|
+
if (
|
|
202
|
+
hasDivider &&
|
|
203
|
+
this._trimDivider &&
|
|
204
|
+
cursor.leafMatch.pattern === this._divider
|
|
205
|
+
) {
|
|
206
|
+
const dividerNode = this._nodes.pop() as Node;
|
|
207
|
+
cursor.moveTo(dividerNode.firstIndex);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const lastIndex = this._nodes[this._nodes.length - 1].lastIndex;
|
|
211
|
+
cursor.moveTo(lastIndex);
|
|
212
|
+
|
|
213
|
+
return new Node(
|
|
214
|
+
this._type,
|
|
215
|
+
this._name,
|
|
216
|
+
this._firstIndex,
|
|
217
|
+
lastIndex,
|
|
218
|
+
this._nodes
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private _getLastValidNode(): Node | null {
|
|
223
|
+
const nodes = this._nodes.filter((node) => node !== null);
|
|
224
|
+
|
|
225
|
+
if (nodes.length === 0) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return nodes[nodes.length - 1];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getTokens(): string[] {
|
|
233
|
+
return this._pattern.getTokens();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getTokensAfter(childReference: Pattern): string[] {
|
|
237
|
+
const patterns = this.getPatternsAfter(childReference);
|
|
238
|
+
const tokens: string[] = [];
|
|
239
|
+
|
|
240
|
+
patterns.forEach(p => tokens.push(...p.getTokens()));
|
|
241
|
+
|
|
242
|
+
return tokens;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getNextTokens(): string[] {
|
|
246
|
+
if (this._parent == null) {
|
|
247
|
+
return []
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return this._parent.getTokensAfter(this);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getPatterns(): Pattern[] {
|
|
254
|
+
return this._pattern.getPatterns();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getPatternsAfter(childReference: Pattern): Pattern[] {
|
|
258
|
+
let index = -1;
|
|
259
|
+
const patterns: Pattern[] = [];
|
|
260
|
+
|
|
261
|
+
for (let i = 0; i < this._children.length; i++) {
|
|
262
|
+
if (this._children[i] === childReference) {
|
|
263
|
+
index = i;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// If the last match isn't a child of this pattern.
|
|
268
|
+
if (index === -1) {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// If the last match was the repeated patterns, then suggest the divider.
|
|
273
|
+
if (index === 0 && this._divider) {
|
|
274
|
+
patterns.push(this._children[1]);
|
|
275
|
+
|
|
276
|
+
if (this._parent) {
|
|
277
|
+
patterns.push(...this._parent.getPatternsAfter(this));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Suggest the pattern because the divider was the last match.
|
|
282
|
+
if (index === 1) {
|
|
283
|
+
patterns.push(this._children[0]);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// If there is no divider then suggest the repeating pattern and the next pattern after.
|
|
287
|
+
if (index === 0 && !this._divider && this._parent) {
|
|
288
|
+
patterns.push(this._children[0]);
|
|
289
|
+
patterns.push(...this._parent.getPatternsAfter(this));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return patterns;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
getNextPatterns(): Pattern[] {
|
|
296
|
+
if (this._parent == null) {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return this._parent.getPatternsAfter(this)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
find(predicate: (p: Pattern) => boolean): Pattern | null {
|
|
304
|
+
return findPattern(this, predicate);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
clone(name = this._name, isOptional?: boolean): Pattern {
|
|
308
|
+
let min = this._min;
|
|
309
|
+
|
|
310
|
+
if (isOptional != null) {
|
|
311
|
+
if (isOptional) {
|
|
312
|
+
min = 0
|
|
313
|
+
} else {
|
|
314
|
+
min = Math.max(this._min, 1);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return new InfiniteRepeat(
|
|
319
|
+
name,
|
|
320
|
+
this._pattern,
|
|
321
|
+
{
|
|
322
|
+
divider: this._divider == null ? undefined : this._divider,
|
|
323
|
+
min: min,
|
|
324
|
+
trimDivider: this._trimDivider
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
@@ -119,7 +119,7 @@ describe("Literal", () => {
|
|
|
119
119
|
const sequence = new And("sequence", [new Literal("a", "A")]);
|
|
120
120
|
const parent = new And("parent", [sequence, new Literal("b", "B")]);
|
|
121
121
|
|
|
122
|
-
const a = parent.
|
|
122
|
+
const a = parent.find(p => p.name === "a");
|
|
123
123
|
const tokens = a?.getNextTokens() || [];
|
|
124
124
|
|
|
125
125
|
expect(tokens[0]).toBe("B");
|
|
@@ -145,9 +145,9 @@ describe("Literal", () => {
|
|
|
145
145
|
const sequence = new And("sequence", [new Literal("a", "A")]);
|
|
146
146
|
const parent = new And("parent", [sequence, new Literal("b", "B")]);
|
|
147
147
|
|
|
148
|
-
const a = parent.
|
|
148
|
+
const a = parent.find(p => p.name === "a");
|
|
149
149
|
const nextPatterns = a?.getNextPatterns() || [];
|
|
150
|
-
const b = parent.
|
|
150
|
+
const b = parent.find(p => p.name === "b")
|
|
151
151
|
|
|
152
152
|
expect(nextPatterns[0]).toBe(b);
|
|
153
153
|
});
|
|
@@ -168,7 +168,7 @@ describe("Literal", () => {
|
|
|
168
168
|
|
|
169
169
|
test("Find Pattern", () => {
|
|
170
170
|
const a = new Literal("a", "A");
|
|
171
|
-
const pattern = a.
|
|
171
|
+
const pattern = a.find(p => p.name === "nada");
|
|
172
172
|
|
|
173
173
|
expect(pattern).toBeNull();
|
|
174
174
|
});
|
package/src/patterns/Literal.ts
CHANGED
|
@@ -167,7 +167,7 @@ export class Literal implements Pattern {
|
|
|
167
167
|
return this.parent.getPatternsAfter(this)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
find(_predicate: (p: Pattern) => boolean): Pattern | null {
|
|
171
171
|
return null;
|
|
172
172
|
}
|
|
173
173
|
|