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,481 @@
|
|
|
1
|
+
import { FiniteRepeat } from "./FiniteRepeat";
|
|
2
|
+
import { Cursor } from "./Cursor";
|
|
3
|
+
import { Regex } from "./Regex";
|
|
4
|
+
import { Node } from "../ast/Node";
|
|
5
|
+
import { Literal } from "./Literal";
|
|
6
|
+
import { And } from "./And";
|
|
7
|
+
|
|
8
|
+
describe("BoundedRepeat", () => {
|
|
9
|
+
test("Bounds Without Divider", () => {
|
|
10
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, {
|
|
11
|
+
min: 2
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
let cursor = new Cursor("1");
|
|
15
|
+
let result = numbers.parse(cursor);
|
|
16
|
+
let expected: Node | null = null;
|
|
17
|
+
expect(result).toBeNull();
|
|
18
|
+
expect(cursor.hasError).toBeTruthy();
|
|
19
|
+
|
|
20
|
+
cursor = new Cursor("12");
|
|
21
|
+
result = numbers.parse(cursor);
|
|
22
|
+
expected = new Node("finite-repeat", "numbers", 0, 1, [
|
|
23
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
24
|
+
new Node("regex", "number", 1, 1, [], "2"),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
expect(result).toEqual(expected);
|
|
28
|
+
expect(cursor.hasError).toBeFalsy();
|
|
29
|
+
|
|
30
|
+
cursor = new Cursor("123");
|
|
31
|
+
result = numbers.parse(cursor);
|
|
32
|
+
expected = new Node("finite-repeat", "numbers", 0, 2, [
|
|
33
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
34
|
+
new Node("regex", "number", 1, 1, [], "2"),
|
|
35
|
+
new Node("regex", "number", 2, 2, [], "3"),
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
expect(result).toEqual(expected);
|
|
39
|
+
expect(cursor.hasError).toBeFalsy();
|
|
40
|
+
|
|
41
|
+
cursor = new Cursor("1234");
|
|
42
|
+
result = numbers.parse(cursor);
|
|
43
|
+
expected = new Node("finite-repeat", "numbers", 0, 2, [
|
|
44
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
45
|
+
new Node("regex", "number", 1, 1, [], "2"),
|
|
46
|
+
new Node("regex", "number", 2, 2, [], "3"),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
expect(result).toEqual(expected);
|
|
50
|
+
expect(cursor.hasError).toBeFalsy();
|
|
51
|
+
expect(cursor.index).toBe(2);
|
|
52
|
+
|
|
53
|
+
cursor = new Cursor("12f");
|
|
54
|
+
result = numbers.parse(cursor);
|
|
55
|
+
expected = new Node("finite-repeat", "numbers", 0, 1, [
|
|
56
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
57
|
+
new Node("regex", "number", 1, 1, [], "2"),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
expect(result).toEqual(expected);
|
|
61
|
+
expect(cursor.hasError).toBeFalsy();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("Bounds Are Equal Without Divider", () => {
|
|
65
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { min: 3});
|
|
66
|
+
|
|
67
|
+
let cursor = new Cursor("1");
|
|
68
|
+
let result = numbers.parse(cursor);
|
|
69
|
+
let expected: Node | null = null;
|
|
70
|
+
expect(result).toBeNull();
|
|
71
|
+
expect(cursor.hasError).toBeTruthy();
|
|
72
|
+
|
|
73
|
+
cursor = new Cursor("12");
|
|
74
|
+
result = numbers.parse(cursor);
|
|
75
|
+
expected = null;
|
|
76
|
+
|
|
77
|
+
expect(result).toBeNull();
|
|
78
|
+
expect(cursor.hasError).toBeTruthy();
|
|
79
|
+
|
|
80
|
+
cursor = new Cursor("123");
|
|
81
|
+
result = numbers.parse(cursor);
|
|
82
|
+
expected = new Node("finite-repeat", "numbers", 0, 2, [
|
|
83
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
84
|
+
new Node("regex", "number", 1, 1, [], "2"),
|
|
85
|
+
new Node("regex", "number", 2, 2, [], "3"),
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
expect(result).toEqual(expected);
|
|
89
|
+
expect(cursor.hasError).toBeFalsy();
|
|
90
|
+
|
|
91
|
+
cursor = new Cursor("1234");
|
|
92
|
+
result = numbers.parse(cursor);
|
|
93
|
+
expected = new Node("finite-repeat", "numbers", 0, 2, [
|
|
94
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
95
|
+
new Node("regex", "number", 1, 1, [], "2"),
|
|
96
|
+
new Node("regex", "number", 2, 2, [], "3"),
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
expect(result).toEqual(expected);
|
|
100
|
+
expect(cursor.hasError).toBeFalsy();
|
|
101
|
+
expect(cursor.index).toBe(2);
|
|
102
|
+
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("Bounds With Divider", () => {
|
|
106
|
+
const divider = new Literal("comma", ",");
|
|
107
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { divider, min: 2, trimDivider: true });
|
|
108
|
+
|
|
109
|
+
let cursor = new Cursor("1,");
|
|
110
|
+
let result = numbers.parse(cursor);
|
|
111
|
+
let expected: Node | null = null;
|
|
112
|
+
expect(result).toBeNull();
|
|
113
|
+
expect(cursor.hasError).toBeTruthy();
|
|
114
|
+
|
|
115
|
+
cursor = new Cursor("1,2");
|
|
116
|
+
result = numbers.parse(cursor);
|
|
117
|
+
expected = new Node("finite-repeat", "numbers", 0, 2, [
|
|
118
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
119
|
+
new Node("literal", "comma", 1, 1, [], ","),
|
|
120
|
+
new Node("regex", "number", 2, 2, [], "2"),
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
expect(result).toEqual(expected);
|
|
124
|
+
expect(cursor.hasError).toBeFalsy();
|
|
125
|
+
|
|
126
|
+
cursor = new Cursor("1,2,3,");
|
|
127
|
+
result = numbers.parse(cursor);
|
|
128
|
+
expected = new Node("finite-repeat", "numbers", 0, 4, [
|
|
129
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
130
|
+
new Node("literal", "comma", 1, 1, [], ","),
|
|
131
|
+
new Node("regex", "number", 2, 2, [], "2"),
|
|
132
|
+
new Node("literal", "comma", 3, 3, [], ","),
|
|
133
|
+
new Node("regex", "number", 4, 4, [], "3"),
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
expect(result).toEqual(expected);
|
|
137
|
+
expect(cursor.hasError).toBeFalsy();
|
|
138
|
+
|
|
139
|
+
cursor = new Cursor("1,2,3,4");
|
|
140
|
+
result = numbers.parse(cursor);
|
|
141
|
+
expected = new Node("finite-repeat", "numbers", 0, 4, [
|
|
142
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
143
|
+
new Node("literal", "comma", 1, 1, [], ","),
|
|
144
|
+
new Node("regex", "number", 2, 2, [], "2"),
|
|
145
|
+
new Node("literal", "comma", 3, 3, [], ","),
|
|
146
|
+
new Node("regex", "number", 4, 4, [], "3"),
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
expect(result).toEqual(expected);
|
|
150
|
+
expect(cursor.hasError).toBeFalsy();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("Optional Repeating Pattern", () => {
|
|
154
|
+
const digit = new Regex("digit", "\\d+", true);
|
|
155
|
+
const divider = new Regex("divider", "\\s");
|
|
156
|
+
const integer = new FiniteRepeat("number", digit, 4, { divider });
|
|
157
|
+
const cursor = new Cursor(
|
|
158
|
+
"\n" +
|
|
159
|
+
"3\n" +
|
|
160
|
+
"\n" +
|
|
161
|
+
"\n"
|
|
162
|
+
);
|
|
163
|
+
const result = integer.parse(cursor);
|
|
164
|
+
const expected = new Node("finite-repeat", "number", 0, 4, [
|
|
165
|
+
new Node("regex", "divider", 0, 0, [], "\n"),
|
|
166
|
+
new Node("regex", "digit", 1, 1, [], "3"),
|
|
167
|
+
new Node("regex", "divider", 2, 2, [], "\n"),
|
|
168
|
+
new Node("regex", "divider", 3, 3, [], "\n"),
|
|
169
|
+
new Node("regex", "divider", 4, 4, [], "\n"),
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
expect(result).toEqual(expected)
|
|
173
|
+
expect(cursor.hasError).toBeFalsy()
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("Bounds Are Equal With Divider", () => {
|
|
177
|
+
const divider = new Literal("comma", ",");
|
|
178
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { divider, min: 3, trimDivider: true });
|
|
179
|
+
|
|
180
|
+
let cursor = new Cursor("1,");
|
|
181
|
+
let result = numbers.parse(cursor);
|
|
182
|
+
let expected: Node | null = null;
|
|
183
|
+
expect(result).toBeNull();
|
|
184
|
+
expect(cursor.hasError).toBeTruthy();
|
|
185
|
+
|
|
186
|
+
cursor = new Cursor("1,2");
|
|
187
|
+
result = numbers.parse(cursor);
|
|
188
|
+
expected = null;
|
|
189
|
+
|
|
190
|
+
expect(result).toEqual(expected);
|
|
191
|
+
expect(cursor.hasError).toBeTruthy();
|
|
192
|
+
|
|
193
|
+
cursor = new Cursor("1,2,3,");
|
|
194
|
+
result = numbers.parse(cursor);
|
|
195
|
+
expected = new Node("finite-repeat", "numbers", 0, 4, [
|
|
196
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
197
|
+
new Node("literal", "comma", 1, 1, [], ","),
|
|
198
|
+
new Node("regex", "number", 2, 2, [], "2"),
|
|
199
|
+
new Node("literal", "comma", 3, 3, [], ","),
|
|
200
|
+
new Node("regex", "number", 4, 4, [], "3"),
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
expect(result).toEqual(expected);
|
|
204
|
+
expect(cursor.hasError).toBeFalsy();
|
|
205
|
+
|
|
206
|
+
cursor = new Cursor("1,2,3,4");
|
|
207
|
+
result = numbers.parse(cursor);
|
|
208
|
+
expected = new Node("finite-repeat", "numbers", 0, 4, [
|
|
209
|
+
new Node("regex", "number", 0, 0, [], "1"),
|
|
210
|
+
new Node("literal", "comma", 1, 1, [], ","),
|
|
211
|
+
new Node("regex", "number", 2, 2, [], "2"),
|
|
212
|
+
new Node("literal", "comma", 3, 3, [], ","),
|
|
213
|
+
new Node("regex", "number", 4, 4, [], "3"),
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
expect(result).toEqual(expected);
|
|
217
|
+
expect(cursor.hasError).toBeFalsy();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("Test", () => {
|
|
221
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3);
|
|
222
|
+
const result = numbers.test("1");
|
|
223
|
+
|
|
224
|
+
expect(result).toBeTruthy();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("Exec", () => {
|
|
228
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3);
|
|
229
|
+
const result = numbers.exec("1");
|
|
230
|
+
|
|
231
|
+
expect(result.ast).not.toBeNull();
|
|
232
|
+
expect(result.cursor.hasError).toBeFalsy();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("Fail", () => {
|
|
236
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3);
|
|
237
|
+
const result = numbers.exec("f");
|
|
238
|
+
|
|
239
|
+
expect(result.ast).toBeNull();
|
|
240
|
+
expect(result.cursor.hasError).toBeTruthy();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("Optional", () => {
|
|
244
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
245
|
+
const result = numbers.exec("f");
|
|
246
|
+
|
|
247
|
+
expect(result.ast).toBeNull();
|
|
248
|
+
expect(result.cursor.hasError).toBeFalsy();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("Optional With Multiple Matches But Still Below Min", () => {
|
|
252
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
253
|
+
const result = numbers.exec("12f");
|
|
254
|
+
|
|
255
|
+
expect(result.ast).toBeNull();
|
|
256
|
+
expect(result.cursor.hasError).toBeFalsy();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("Properties", () => {
|
|
260
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
261
|
+
|
|
262
|
+
expect(numbers.type).toBe("finite-repeat");
|
|
263
|
+
expect(numbers.name).toBe("numbers");
|
|
264
|
+
expect(numbers.parent).toBeNull();
|
|
265
|
+
expect(numbers.children.length).toBe(3);
|
|
266
|
+
expect(numbers.min).toBe(0);
|
|
267
|
+
expect(numbers.max).toBe(3);
|
|
268
|
+
expect(numbers.isOptional).toBeTruthy();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("Clone", () => {
|
|
272
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
273
|
+
const clone = numbers.clone() as FiniteRepeat;
|
|
274
|
+
|
|
275
|
+
expect(clone.type).toBe(numbers.type);
|
|
276
|
+
expect(clone.name).toBe(numbers.name);
|
|
277
|
+
expect(clone.parent).toBeNull();
|
|
278
|
+
expect(clone.children.length).toBe(numbers.children.length);
|
|
279
|
+
expect(clone.min).toBe(numbers.min);
|
|
280
|
+
expect(clone.max).toBe(numbers.max);
|
|
281
|
+
expect(clone.isOptional).toBe(numbers.isOptional);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("Clone With Custom Overrides", () => {
|
|
285
|
+
const numbers = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
286
|
+
let clone = numbers.clone();
|
|
287
|
+
let expected = new FiniteRepeat("numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
288
|
+
|
|
289
|
+
expect(clone).toEqual(expected);
|
|
290
|
+
|
|
291
|
+
clone = numbers.clone("cloned-numbers");
|
|
292
|
+
expected = new FiniteRepeat("cloned-numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
293
|
+
|
|
294
|
+
expect(clone).toEqual(expected);
|
|
295
|
+
|
|
296
|
+
clone = numbers.clone("cloned-numbers", true);
|
|
297
|
+
expected = new FiniteRepeat("cloned-numbers", new Regex("number", "\\d"), 3, { min: 0 });
|
|
298
|
+
|
|
299
|
+
expect(clone).toEqual(expected);
|
|
300
|
+
|
|
301
|
+
clone = numbers.clone("cloned-numbers", false);
|
|
302
|
+
expected = new FiniteRepeat("cloned-numbers", new Regex("number", "\\d"), 3, { min: 1 });
|
|
303
|
+
|
|
304
|
+
expect(clone).toEqual(expected);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("Get Tokens", () => {
|
|
308
|
+
const numbers = new FiniteRepeat(
|
|
309
|
+
"numbers",
|
|
310
|
+
new Literal("one", "1"),
|
|
311
|
+
3,
|
|
312
|
+
{
|
|
313
|
+
divider: new Literal("comma", ","),
|
|
314
|
+
min: 0
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const tokens = numbers.getTokens();
|
|
318
|
+
|
|
319
|
+
expect(tokens).toEqual(["1"]);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("Get Tokens After Without Parent", () => {
|
|
323
|
+
const numbers = new FiniteRepeat(
|
|
324
|
+
"numbers",
|
|
325
|
+
new Literal("one", "1"),
|
|
326
|
+
2,
|
|
327
|
+
{
|
|
328
|
+
divider: new Literal("comma", ","),
|
|
329
|
+
min: 0,
|
|
330
|
+
trimDivider: true
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
let child = numbers.children[0];
|
|
334
|
+
let tokens = numbers.getTokensAfter(child);
|
|
335
|
+
|
|
336
|
+
expect(tokens).toEqual([","]);
|
|
337
|
+
|
|
338
|
+
child = numbers.children[2];
|
|
339
|
+
tokens = numbers.getTokensAfter(child);
|
|
340
|
+
|
|
341
|
+
expect(tokens).toEqual([]);
|
|
342
|
+
|
|
343
|
+
child = numbers.children[3];
|
|
344
|
+
tokens = numbers.getTokensAfter(child);
|
|
345
|
+
|
|
346
|
+
expect(tokens).toEqual([]);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("Get Tokens After With Parent", () => {
|
|
350
|
+
const numbers = new FiniteRepeat(
|
|
351
|
+
"numbers",
|
|
352
|
+
new Literal("one", "1"),
|
|
353
|
+
2,
|
|
354
|
+
{
|
|
355
|
+
divider: new Literal("comma", ","),
|
|
356
|
+
trimDivider: true,
|
|
357
|
+
min: 0
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const parent = new And("parent", [numbers, new Literal("b", "B")]);
|
|
361
|
+
const numbersClone = parent.children[0];
|
|
362
|
+
let child = numbersClone.children[0];
|
|
363
|
+
let tokens = numbersClone.getTokensAfter(child);
|
|
364
|
+
|
|
365
|
+
expect(tokens).toEqual([","]);
|
|
366
|
+
|
|
367
|
+
child = numbersClone.children[2];
|
|
368
|
+
tokens = numbersClone.getTokensAfter(child);
|
|
369
|
+
|
|
370
|
+
expect(tokens).toEqual(["B"]);
|
|
371
|
+
|
|
372
|
+
child = numbersClone.children[3];
|
|
373
|
+
tokens = numbersClone.getTokensAfter(child);
|
|
374
|
+
|
|
375
|
+
expect(tokens).toEqual([]);
|
|
376
|
+
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("Get Next Tokens", () => {
|
|
380
|
+
const numbers = new FiniteRepeat(
|
|
381
|
+
"numbers",
|
|
382
|
+
new Literal("one", "1"),
|
|
383
|
+
2,
|
|
384
|
+
{
|
|
385
|
+
divider: new Literal("comma", ","),
|
|
386
|
+
min: 0
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const parent = new And("parent", [numbers, new Literal("b", "B")]);
|
|
391
|
+
const numbersClone = parent.children[0];
|
|
392
|
+
const tokens = numbersClone.getNextTokens();
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
expect(tokens).toEqual(["B"]);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("Get Next Tokens Without Parent", () => {
|
|
399
|
+
const numbers = new FiniteRepeat(
|
|
400
|
+
"numbers",
|
|
401
|
+
new Literal("one", "1"),
|
|
402
|
+
2,
|
|
403
|
+
{
|
|
404
|
+
divider: new Literal("comma", ","),
|
|
405
|
+
min: 0
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const tokens = numbers.getNextTokens();
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
expect(tokens).toEqual([]);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("Get Patterns", () => {
|
|
416
|
+
const numbers = new FiniteRepeat(
|
|
417
|
+
"numbers",
|
|
418
|
+
new Literal("one", "1"),
|
|
419
|
+
2,
|
|
420
|
+
{
|
|
421
|
+
divider: new Literal("comma", ","),
|
|
422
|
+
min: 0
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
const patterns = numbers.getPatterns();
|
|
427
|
+
|
|
428
|
+
expect(patterns).toEqual([numbers.children[0]]);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("Get Next Patterns Without Parent", () => {
|
|
432
|
+
const numbers = new FiniteRepeat(
|
|
433
|
+
"numbers",
|
|
434
|
+
new Literal("one", "1"),
|
|
435
|
+
2,
|
|
436
|
+
{
|
|
437
|
+
divider: new Literal("comma", ","),
|
|
438
|
+
min: 0
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const patterns = numbers.getNextPatterns();
|
|
443
|
+
|
|
444
|
+
expect(patterns).toEqual([]);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("Get Next Patterns With Parent", () => {
|
|
448
|
+
const numbers = new FiniteRepeat(
|
|
449
|
+
"numbers",
|
|
450
|
+
new Literal("one", "1"),
|
|
451
|
+
2,
|
|
452
|
+
{
|
|
453
|
+
divider: new Literal("comma", ","),
|
|
454
|
+
min: 0
|
|
455
|
+
}
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const parent = new And("parent", [numbers, new Literal("b", "B")]);
|
|
459
|
+
const numbersClone = parent.children[0];
|
|
460
|
+
|
|
461
|
+
const patterns = numbersClone.getNextPatterns();
|
|
462
|
+
|
|
463
|
+
expect(patterns).toEqual([parent.children[1]]);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("Find Pattern", () => {
|
|
467
|
+
const numbers = new FiniteRepeat(
|
|
468
|
+
"numbers",
|
|
469
|
+
new Literal("one", "1"),
|
|
470
|
+
2,
|
|
471
|
+
{
|
|
472
|
+
divider: new Literal("comma", ","),
|
|
473
|
+
min: 0
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
const comma = numbers.find(p => p.name === "comma");
|
|
478
|
+
expect(comma).toBe(numbers.children[1]);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Node } from "../ast/Node";
|
|
2
|
+
import { Cursor } from "./Cursor";
|
|
3
|
+
import { findPattern } from "./findPattern";
|
|
4
|
+
import { ParseResult } from "./ParseResult";
|
|
5
|
+
import { Pattern } from "./Pattern";
|
|
6
|
+
|
|
7
|
+
export interface FiniteRepeatOptions {
|
|
8
|
+
divider?: Pattern;
|
|
9
|
+
min?: number;
|
|
10
|
+
trimDivider?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class FiniteRepeat implements Pattern {
|
|
14
|
+
private _type: string;
|
|
15
|
+
private _name: string;
|
|
16
|
+
private _parent: Pattern | null;
|
|
17
|
+
private _children: Pattern[];
|
|
18
|
+
private _hasDivider: boolean;
|
|
19
|
+
private _min: number;
|
|
20
|
+
private _max: number;
|
|
21
|
+
private _trimDivider: boolean;
|
|
22
|
+
|
|
23
|
+
get type() {
|
|
24
|
+
return this._type;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get name() {
|
|
28
|
+
return this._name;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get parent() {
|
|
32
|
+
return this._parent;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set parent(value: Pattern | null) {
|
|
36
|
+
this._parent = value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get children() {
|
|
40
|
+
return this._children;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get isOptional() {
|
|
44
|
+
return this._min === 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get min() {
|
|
48
|
+
return this._min;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get max() {
|
|
52
|
+
return this._max;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
constructor(name: string, pattern: Pattern, repeatAmount: number, options: FiniteRepeatOptions = {}) {
|
|
56
|
+
this._type = "finite-repeat";
|
|
57
|
+
this._name = name;
|
|
58
|
+
this._parent = null;
|
|
59
|
+
this._children = [];
|
|
60
|
+
this._hasDivider = options.divider != null;
|
|
61
|
+
this._min = options.min != null ? options.min : 1;
|
|
62
|
+
this._max = repeatAmount;
|
|
63
|
+
this._trimDivider = options.trimDivider == null ? false : options.trimDivider;
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < repeatAmount; i++) {
|
|
66
|
+
this._children.push(pattern.clone(pattern.name));
|
|
67
|
+
|
|
68
|
+
if (options.divider != null && (i < repeatAmount - 1 || !this._trimDivider)) {
|
|
69
|
+
this._children.push(options.divider.clone(options.divider.name, false));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
parse(cursor: Cursor): Node | null {
|
|
75
|
+
const startIndex = cursor.index;
|
|
76
|
+
const nodes: Node[] = [];
|
|
77
|
+
const modulo = this._hasDivider ? 2 : 1;
|
|
78
|
+
let matchCount = 0;
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < this._children.length; i++) {
|
|
82
|
+
const childPattern = this._children[i];
|
|
83
|
+
const node = childPattern.parse(cursor);
|
|
84
|
+
|
|
85
|
+
if (i % modulo === 0 && !cursor.hasError) {
|
|
86
|
+
matchCount++
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (cursor.hasError) {
|
|
90
|
+
cursor.resolveError();
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (node != null) {
|
|
95
|
+
nodes.push(node);
|
|
96
|
+
|
|
97
|
+
if (cursor.hasNext()) {
|
|
98
|
+
cursor.next();
|
|
99
|
+
} else {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (this._trimDivider && this._hasDivider) {
|
|
107
|
+
if (cursor.leafMatch.pattern === this.children[1]) {
|
|
108
|
+
const node = nodes.pop() as Node;
|
|
109
|
+
cursor.moveTo(node.firstIndex)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (matchCount < this._min) {
|
|
114
|
+
cursor.moveTo(startIndex);
|
|
115
|
+
cursor.recordErrorAt(startIndex, this);
|
|
116
|
+
return null;
|
|
117
|
+
} else if (nodes.length === 0) {
|
|
118
|
+
cursor.resolveError();
|
|
119
|
+
cursor.moveTo(startIndex)
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const firstIndex = nodes[0].firstIndex;
|
|
124
|
+
const lastIndex = nodes[nodes.length - 1].lastIndex;
|
|
125
|
+
|
|
126
|
+
cursor.moveTo(lastIndex);
|
|
127
|
+
|
|
128
|
+
return new Node(this._type, this.name, firstIndex, lastIndex, nodes);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
test(text: string): boolean {
|
|
132
|
+
const cursor = new Cursor(text);
|
|
133
|
+
const ast = this.parse(cursor);
|
|
134
|
+
|
|
135
|
+
return ast?.value === text;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
exec(text: string): ParseResult {
|
|
139
|
+
const cursor = new Cursor(text);
|
|
140
|
+
const ast = this.parse(cursor);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
ast: ast?.value === text ? ast : null,
|
|
144
|
+
cursor
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
clone(name = this._name, isOptional?: boolean): Pattern {
|
|
149
|
+
let min = this._min;
|
|
150
|
+
|
|
151
|
+
if (isOptional != null) {
|
|
152
|
+
if (isOptional) {
|
|
153
|
+
min = 0
|
|
154
|
+
} else {
|
|
155
|
+
min = Math.max(this._min, 1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return new FiniteRepeat(
|
|
160
|
+
name,
|
|
161
|
+
this._children[0],
|
|
162
|
+
this._max,
|
|
163
|
+
{
|
|
164
|
+
divider: this._hasDivider ? this._children[1] : undefined,
|
|
165
|
+
min,
|
|
166
|
+
trimDivider: this._trimDivider
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getTokens(): string[] {
|
|
172
|
+
return this._children[0].getTokens();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getTokensAfter(childReference: Pattern): string[] {
|
|
176
|
+
const patterns = this.getPatternsAfter(childReference);
|
|
177
|
+
const tokens: string[] = [];
|
|
178
|
+
|
|
179
|
+
patterns.forEach(p => tokens.push(...p.getTokens()));
|
|
180
|
+
|
|
181
|
+
return tokens;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getNextTokens(): string[] {
|
|
185
|
+
if (this._parent == null) {
|
|
186
|
+
return []
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return this._parent.getTokensAfter(this);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getPatterns(): Pattern[] {
|
|
193
|
+
return this._children[0].getPatterns();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getPatternsAfter(childReference: Pattern): Pattern[] {
|
|
197
|
+
const childIndex = this._children.indexOf(childReference);
|
|
198
|
+
|
|
199
|
+
// If Reference Pattern isn't a child.
|
|
200
|
+
if (childIndex == -1) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// If Reference Pattern is the last pattern. Ask for the parents next patterns
|
|
205
|
+
if (childIndex === this._children.length - 1) {
|
|
206
|
+
if (this._parent == null) {
|
|
207
|
+
return [];
|
|
208
|
+
} else {
|
|
209
|
+
return this._parent.getPatternsAfter(this);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Get the next childs patterns.
|
|
214
|
+
const nextChild = this._children[childIndex + 1];
|
|
215
|
+
|
|
216
|
+
return nextChild.getPatterns()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getNextPatterns(): Pattern[] {
|
|
220
|
+
if (this._parent == null) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return this._parent.getPatternsAfter(this);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
find(predicate: (p: Pattern) => boolean): Pattern | null {
|
|
228
|
+
return findPattern(this, predicate);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
}
|