clarity-pattern-parser 6.0.2 → 7.0.1
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 +306 -0
- package/src/grammar/Grammar.ts +249 -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 +36 -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,306 @@
|
|
|
1
|
+
import { And } from "../patterns/And";
|
|
2
|
+
import { Literal } from "../patterns/Literal";
|
|
3
|
+
import { Not } from "../patterns/Not";
|
|
4
|
+
import { Or } from "../patterns/Or";
|
|
5
|
+
import { Pattern } from "../patterns/Pattern";
|
|
6
|
+
import { Regex } from "../patterns/Regex";
|
|
7
|
+
import { Repeat } from "../patterns/Repeat";
|
|
8
|
+
import { Grammar } from "./Grammar";
|
|
9
|
+
|
|
10
|
+
describe("Grammar", () => {
|
|
11
|
+
test("Literal", () => {
|
|
12
|
+
const expression = `
|
|
13
|
+
name = "John"
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const patterns = Grammar.parse(expression);
|
|
17
|
+
const namePattern = patterns.get("name");
|
|
18
|
+
const expected = new Literal("name", "John");
|
|
19
|
+
|
|
20
|
+
expect(namePattern).toEqual(expected);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("Regex", () => {
|
|
24
|
+
const expression = `
|
|
25
|
+
name = /\\w/
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const patterns = Grammar.parse(expression);
|
|
29
|
+
const pattern = patterns.get("name");
|
|
30
|
+
const name = new Regex("name", "\\w");
|
|
31
|
+
|
|
32
|
+
expect(pattern).toEqual(name);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("Or", () => {
|
|
36
|
+
const expression = `
|
|
37
|
+
john = "John"
|
|
38
|
+
jane = "Jane"
|
|
39
|
+
names = john | jane
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const patterns = Grammar.parse(expression);
|
|
43
|
+
const pattern = patterns.get("names");
|
|
44
|
+
const john = new Literal("john", "John");
|
|
45
|
+
const jane = new Literal("jane", "Jane");
|
|
46
|
+
const names = new Or("names", [john, jane]);
|
|
47
|
+
|
|
48
|
+
expect(pattern).toEqual(names);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("And", () => {
|
|
52
|
+
const expression = `
|
|
53
|
+
space = " "
|
|
54
|
+
first-name = /\\w/
|
|
55
|
+
last-name = /\\w/
|
|
56
|
+
full-name = first-name & space & last-name
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const patterns = Grammar.parse(expression);
|
|
60
|
+
const pattern = patterns.get("full-name");
|
|
61
|
+
const space = new Literal("space", " ");
|
|
62
|
+
const firstName = new Regex("first-name", "\\w");
|
|
63
|
+
const lastName = new Regex("last-name", "\\w");
|
|
64
|
+
const fullName = new And("full-name", [firstName, space, lastName]);
|
|
65
|
+
|
|
66
|
+
expect(pattern).toEqual(fullName);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("And With Optional Pattern", () => {
|
|
70
|
+
const expression = `
|
|
71
|
+
space = " "
|
|
72
|
+
first-name = /\\w/
|
|
73
|
+
last-name = /\\w/
|
|
74
|
+
middle-name = /\\w/
|
|
75
|
+
middle-name-with-space = middle-name & space
|
|
76
|
+
full-name = first-name & space & middle-name-with-space? & last-name
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const patterns = Grammar.parse(expression);
|
|
80
|
+
const pattern = patterns.get("full-name");
|
|
81
|
+
const space = new Literal("space", " ");
|
|
82
|
+
const firstName = new Regex("first-name", "\\w");
|
|
83
|
+
const lastName = new Regex("last-name", "\\w");
|
|
84
|
+
const middleName = new Regex("middle-name", "\\w");
|
|
85
|
+
const middleNameWithSpace = new And("middle-name-with-space", [middleName, space], true);
|
|
86
|
+
const fullName = new And("full-name", [firstName, space, middleNameWithSpace, lastName]);
|
|
87
|
+
|
|
88
|
+
expect(pattern).toEqual(fullName);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("And With Not Pattern", () => {
|
|
92
|
+
const expression = `
|
|
93
|
+
space = " "
|
|
94
|
+
first-name = /\\w/
|
|
95
|
+
last-name = /\\w/
|
|
96
|
+
middle-name = /\\w/
|
|
97
|
+
jack = "Jack"
|
|
98
|
+
middle-name-with-space = middle-name & space
|
|
99
|
+
full-name = !jack & first-name & space & middle-name-with-space? & last-name
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
const patterns = Grammar.parse(expression);
|
|
103
|
+
const pattern = patterns.get("full-name");
|
|
104
|
+
const space = new Literal("space", " ");
|
|
105
|
+
const firstName = new Regex("first-name", "\\w");
|
|
106
|
+
const lastName = new Regex("last-name", "\\w");
|
|
107
|
+
const middleName = new Regex("middle-name", "\\w");
|
|
108
|
+
const jack = new Literal("jack", "Jack");
|
|
109
|
+
const notJack = new Not("not-jack", jack);
|
|
110
|
+
const middleNameWithSpace = new And("middle-name-with-space", [middleName, space], true);
|
|
111
|
+
const fullName = new And("full-name", [notJack, firstName, space, middleNameWithSpace, lastName]);
|
|
112
|
+
|
|
113
|
+
expect(pattern).toEqual(fullName);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("Repeat", () => {
|
|
117
|
+
const expression = `
|
|
118
|
+
digit = /\\d/
|
|
119
|
+
digits = (digit)+
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
const patterns = Grammar.parse(expression);
|
|
123
|
+
const pattern = patterns.get("digits");
|
|
124
|
+
const digit = new Regex("digit", "\\d");
|
|
125
|
+
const digits = new Repeat("digits", digit);
|
|
126
|
+
|
|
127
|
+
expect(pattern).toEqual(digits);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("Repeat Zero Or More", () => {
|
|
131
|
+
const expression = `
|
|
132
|
+
digit = /\\d/
|
|
133
|
+
digits = (digit)*
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
const patterns = Grammar.parse(expression);
|
|
137
|
+
const pattern = patterns.get("digits");
|
|
138
|
+
const digit = new Regex("digit", "\\d");
|
|
139
|
+
const digits = new Repeat("digits", digit, { min: 0 });
|
|
140
|
+
|
|
141
|
+
expect(pattern).toEqual(digits);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("Repeat Lower Limit", () => {
|
|
145
|
+
const expression = `
|
|
146
|
+
digit = /\\d+/
|
|
147
|
+
digits = (digit){1,}
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
const patterns = Grammar.parse(expression);
|
|
151
|
+
const pattern = patterns.get("digits");
|
|
152
|
+
const digit = new Regex("digit", "\\d+");
|
|
153
|
+
const digits = new Repeat("digits", digit, { min: 1 });
|
|
154
|
+
|
|
155
|
+
expect(pattern).toEqual(digits);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("Repeat Bounded", () => {
|
|
159
|
+
const expression = `
|
|
160
|
+
digit = /\\d+/
|
|
161
|
+
digits = (digit){1,3}
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const patterns = Grammar.parse(expression);
|
|
165
|
+
const pattern = patterns.get("digits");
|
|
166
|
+
const digit = new Regex("digit", "\\d+");
|
|
167
|
+
const digits = new Repeat("digits", digit, { min: 1, max: 3 });
|
|
168
|
+
|
|
169
|
+
expect(pattern).toEqual(digits);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("Repeat Upper Limit", () => {
|
|
173
|
+
const expression = `
|
|
174
|
+
digit = /\\d+/
|
|
175
|
+
digits = (digit){,3}
|
|
176
|
+
`;
|
|
177
|
+
|
|
178
|
+
const patterns = Grammar.parse(expression);
|
|
179
|
+
const pattern = patterns.get("digits");
|
|
180
|
+
const digit = new Regex("digit", "\\d+");
|
|
181
|
+
const digits = new Repeat("digits", digit, { min: 0, max: 3 });
|
|
182
|
+
|
|
183
|
+
expect(pattern).toEqual(digits);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("Repeat Exact", () => {
|
|
187
|
+
const expression = `
|
|
188
|
+
digit = /\\d+/
|
|
189
|
+
digits = (digit){3}
|
|
190
|
+
`;
|
|
191
|
+
|
|
192
|
+
const patterns = Grammar.parse(expression);
|
|
193
|
+
const pattern = patterns.get("digits");
|
|
194
|
+
const digit = new Regex("digit", "\\d+");
|
|
195
|
+
const digits = new Repeat("digits", digit, { min: 3, max: 3 });
|
|
196
|
+
|
|
197
|
+
expect(pattern).toEqual(digits);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("Repeat Divider", () => {
|
|
201
|
+
const expression = `
|
|
202
|
+
digit = /\\d+/
|
|
203
|
+
comma = ","
|
|
204
|
+
digits = (digit, comma){3}
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
const patterns = Grammar.parse(expression);
|
|
208
|
+
const pattern = patterns.get("digits");
|
|
209
|
+
const digit = new Regex("digit", "\\d+");
|
|
210
|
+
const divider = new Literal("comma", ",");
|
|
211
|
+
const digits = new Repeat("digits", digit, { divider, min: 3, max: 3 });
|
|
212
|
+
|
|
213
|
+
expect(pattern).toEqual(digits);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("Repeat Divider With Trim Divider", () => {
|
|
217
|
+
const expression = `
|
|
218
|
+
digit = /\\d+/
|
|
219
|
+
comma = ","
|
|
220
|
+
digits = (digit, comma){3} -t
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
const patterns = Grammar.parse(expression);
|
|
224
|
+
const pattern = patterns.get("digits");
|
|
225
|
+
const digit = new Regex("digit", "\\d+");
|
|
226
|
+
const divider = new Literal("comma", ",");
|
|
227
|
+
const digits = new Repeat("digits", digit, { divider, min: 3, max: 3, trimDivider: true });
|
|
228
|
+
|
|
229
|
+
expect(pattern).toEqual(digits);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("Repeat Divider With Optional Pattern", () => {
|
|
233
|
+
const expression = `
|
|
234
|
+
digit = /\\d+/
|
|
235
|
+
comma = ","
|
|
236
|
+
digits = (digit?, comma)+
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
const patterns = Grammar.parse(expression);
|
|
240
|
+
const pattern = patterns.get("digits") as Pattern;
|
|
241
|
+
const digit = new Regex("digit", "\\d+", true);
|
|
242
|
+
const comma = new Literal("comma", ",");
|
|
243
|
+
const digits = new Repeat("digits", digit, { divider: comma });
|
|
244
|
+
|
|
245
|
+
expect(pattern).toEqual(digits)
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("Reference", () => {
|
|
249
|
+
const expression = `
|
|
250
|
+
digit = /\\d+/
|
|
251
|
+
divider = /\\s*,\\s*/
|
|
252
|
+
open-bracket = "["
|
|
253
|
+
close-bracket = "]"
|
|
254
|
+
spaces = /\\s+/
|
|
255
|
+
items = digit | array
|
|
256
|
+
array-items = (items, divider)* -t
|
|
257
|
+
array = open-bracket & spaces? & array-items? & spaces? & close-bracket
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
const patterns = Grammar.parse(expression);
|
|
261
|
+
const pattern = patterns.get("array") as Pattern;
|
|
262
|
+
|
|
263
|
+
let text = "[1, []]";
|
|
264
|
+
let result = pattern.exec(text);
|
|
265
|
+
|
|
266
|
+
expect(result.ast?.value).toEqual("[1, []]");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("Alias", () => {
|
|
270
|
+
const expression = `
|
|
271
|
+
name = /regex/
|
|
272
|
+
alias = name
|
|
273
|
+
`;
|
|
274
|
+
|
|
275
|
+
const patterns = Grammar.parse(expression);
|
|
276
|
+
|
|
277
|
+
const name = patterns.get("name");
|
|
278
|
+
const expectedName = new Regex("name", "regex");
|
|
279
|
+
|
|
280
|
+
const alias = patterns.get("alias");
|
|
281
|
+
const expectedAlias = new Regex("alias", "regex");
|
|
282
|
+
|
|
283
|
+
expect(name).toEqual(expectedName);
|
|
284
|
+
expect(alias).toEqual(expectedAlias);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("Bad Grammar At Beginning", () => {
|
|
288
|
+
|
|
289
|
+
expect(() => {
|
|
290
|
+
const expression = `Just Junk`;
|
|
291
|
+
Grammar.parse(expression);
|
|
292
|
+
}).toThrowError("[Parse Error] Found: 'Just Junk', expected: ' ='.");
|
|
293
|
+
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("Bad Grammar Further In", () => {
|
|
297
|
+
|
|
298
|
+
expect(() => {
|
|
299
|
+
const expression = `name = /\\w/
|
|
300
|
+
age = /()
|
|
301
|
+
`;
|
|
302
|
+
Grammar.parse(expression);
|
|
303
|
+
}).toThrowError("[Parse Error] Found: ' age = /()\n ', expected: ' age = !' or ' age = (' or ' age = [Pattern Name]' or ' age = [Regular Expression]' or ' age = [String]'.")
|
|
304
|
+
|
|
305
|
+
});
|
|
306
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { Node } from "../ast/Node";
|
|
2
|
+
import { Literal } from "../patterns/Literal";
|
|
3
|
+
import { Pattern } from "../patterns/Pattern";
|
|
4
|
+
import { Regex } from "../patterns/Regex";
|
|
5
|
+
import { Reference } from "../patterns/Reference";
|
|
6
|
+
import { grammar } from "./patterns/grammar";
|
|
7
|
+
import { Or } from "../patterns/Or";
|
|
8
|
+
import { Not } from "../patterns/Not";
|
|
9
|
+
import { And } from "../patterns/And";
|
|
10
|
+
import { Repeat, RepeatOptions } from "../patterns/Repeat";
|
|
11
|
+
import { AutoComplete } from "../intellisense/AutoComplete";
|
|
12
|
+
|
|
13
|
+
class ParseContext {
|
|
14
|
+
patternsByName = new Map<string, Pattern>();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Grammar {
|
|
18
|
+
private _parseContext: ParseContext;
|
|
19
|
+
private _autoComplete: AutoComplete;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this._parseContext = new ParseContext();
|
|
23
|
+
this._autoComplete = new AutoComplete(grammar, {
|
|
24
|
+
greedyPatternNames: ["spaces", "optional-spaces", "whitespace", "new-line"],
|
|
25
|
+
customTokens: {
|
|
26
|
+
"regex-literal": ["[Regular Expression]"],
|
|
27
|
+
"literal": ["[String]"],
|
|
28
|
+
"name": ["[Pattern Name]"],
|
|
29
|
+
"pattern-name": ["[Pattern Name]"]
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
parse(expression: string) {
|
|
35
|
+
this._parseContext = new ParseContext();
|
|
36
|
+
this._tryToParse(expression);
|
|
37
|
+
|
|
38
|
+
return this._parseContext.patternsByName;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private _tryToParse(expression: string) {
|
|
42
|
+
const { ast, cursor, options, isComplete } = this._autoComplete.suggestFor(expression);
|
|
43
|
+
|
|
44
|
+
if (!isComplete) {
|
|
45
|
+
const text = cursor?.text || "";
|
|
46
|
+
const index = options.reduce((num, o) => Math.max(o.startIndex, num), 0);
|
|
47
|
+
const foundText = text.slice(Math.max(index - 10, 0), index + 10);
|
|
48
|
+
const expectedTexts = "'" + options.map(o => {
|
|
49
|
+
const startText = text.slice(Math.max(o.startIndex - 10), o.startIndex);
|
|
50
|
+
return startText + o.text;
|
|
51
|
+
}).join("' or '") + "'";
|
|
52
|
+
const message = `[Parse Error] Found: '${foundText}', expected: ${expectedTexts}.`
|
|
53
|
+
throw new Error(message);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// If it is complete it will always have a node. So we have to cast it.
|
|
57
|
+
this._cleanAst(ast as Node);
|
|
58
|
+
this._buildPatterns(ast as Node);
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _cleanAst(ast: Node) {
|
|
63
|
+
ast.findAll(
|
|
64
|
+
n => n.name === "spaces" ||
|
|
65
|
+
n.name === "optional-spaces" ||
|
|
66
|
+
n.name === "new-line" ||
|
|
67
|
+
n.name.includes("whitespace") ||
|
|
68
|
+
n.name.includes("comment")
|
|
69
|
+
).forEach(n => n.remove());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private _buildPatterns(ast: Node) {
|
|
73
|
+
ast.children.forEach((n) => {
|
|
74
|
+
const typeNode = n.find(n => n.name.includes("literal"));
|
|
75
|
+
const type = typeNode?.name || "unknown";
|
|
76
|
+
|
|
77
|
+
switch (type) {
|
|
78
|
+
case "literal": {
|
|
79
|
+
this._buildLiteral(n)
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "regex-literal": {
|
|
83
|
+
this._buildRegex(n);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "or-literal": {
|
|
87
|
+
this._buildOr(n);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "and-literal": {
|
|
91
|
+
this._buildAnd(n)
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "repeat-literal": {
|
|
95
|
+
this._buildRepeat(n)
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "alias-literal": {
|
|
99
|
+
this._buildAlias(n)
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private _buildLiteral(statementNode: Node) {
|
|
107
|
+
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
108
|
+
const literalNode = statementNode.find(n => n.name === "literal") as Node;
|
|
109
|
+
const value = literalNode.value.slice(1, literalNode.value.length - 1);
|
|
110
|
+
const name = nameNode.value;
|
|
111
|
+
const literal = new Literal(name, value);
|
|
112
|
+
|
|
113
|
+
this._parseContext.patternsByName.set(name, literal)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private _buildRegex(statementNode: Node) {
|
|
117
|
+
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
118
|
+
const regexNode = statementNode.find(n => n.name === "regex-literal") as Node;
|
|
119
|
+
const value = regexNode.value.slice(1, regexNode.value.length - 1);
|
|
120
|
+
const name = nameNode.value;
|
|
121
|
+
|
|
122
|
+
const regex = new Regex(name, value);
|
|
123
|
+
|
|
124
|
+
this._parseContext.patternsByName.set(name, regex);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private _buildOr(statementNode: Node) {
|
|
128
|
+
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
129
|
+
const orNode = statementNode.find(n => n.name === "or-literal") as Node;
|
|
130
|
+
const patternNodes = orNode.children.filter(n => n.name == "pattern-name");
|
|
131
|
+
|
|
132
|
+
const name = nameNode.value;
|
|
133
|
+
const patterns = patternNodes.map(n => this._getPattern(n.value));
|
|
134
|
+
const or = new Or(name, patterns);
|
|
135
|
+
|
|
136
|
+
this._parseContext.patternsByName.set(name, or);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private _getPattern(name: string) {
|
|
140
|
+
const pattern = this._parseContext.patternsByName.get(name);
|
|
141
|
+
|
|
142
|
+
if (pattern == null) {
|
|
143
|
+
return new Reference(name);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return pattern;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private _buildAnd(statementNode: Node) {
|
|
150
|
+
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
151
|
+
const andNode = statementNode.find(n => n.name === "and-literal") as Node;
|
|
152
|
+
const patternNodes = andNode.children.filter(n => n.name == "pattern");
|
|
153
|
+
|
|
154
|
+
const name = nameNode.value;
|
|
155
|
+
const patterns = patternNodes.map(n => {
|
|
156
|
+
const nameNode = n.find(n => n.name === "pattern-name") as Node;
|
|
157
|
+
const isNot = n.find(n => n.name === "not") != null;
|
|
158
|
+
const isOptional = n.find(n => n.name === "is-optional") != null;
|
|
159
|
+
const name = nameNode.value;
|
|
160
|
+
const pattern = this._getPattern(name);
|
|
161
|
+
|
|
162
|
+
if (isNot) {
|
|
163
|
+
return new Not(`not-${name}`, pattern.clone(name, isOptional));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return pattern.clone(name, isOptional);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const and = new And(name, patterns);
|
|
170
|
+
|
|
171
|
+
this._parseContext.patternsByName.set(name, and);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private _buildRepeat(statementNode: Node) {
|
|
175
|
+
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
176
|
+
const repeatNode = statementNode.find(n => n.name === "repeat-literal") as Node;
|
|
177
|
+
const patternNode = repeatNode.find(n => n.name == "pattern") as Node;
|
|
178
|
+
const patternNameNode = patternNode.find(n => n.name === "pattern-name") as Node;
|
|
179
|
+
const dividerNode = repeatNode.find(n => n.name === "divider-pattern");
|
|
180
|
+
const bounds = repeatNode.find(n => n.name === "bounds");
|
|
181
|
+
const exactCount = repeatNode.find(n => n.name === "exact-count");
|
|
182
|
+
const quantifier = repeatNode.find(n => n.name === "quantifier-shorthand");
|
|
183
|
+
const isPatternOptional = repeatNode.find(n => n.name === "is-optional") != null;
|
|
184
|
+
const trimDivider = repeatNode.find(n => n.name === "trim-divider") != null;
|
|
185
|
+
|
|
186
|
+
const name = nameNode.value;
|
|
187
|
+
const pattern = this._getPattern(patternNameNode.value);
|
|
188
|
+
|
|
189
|
+
const options: RepeatOptions = {
|
|
190
|
+
min: 1,
|
|
191
|
+
max: Infinity
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (trimDivider) {
|
|
195
|
+
options.trimDivider = trimDivider;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (dividerNode != null) {
|
|
199
|
+
options.divider = this._getPattern(dividerNode.value);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (bounds != null) {
|
|
203
|
+
const minNode = bounds.find(p => p.name === "min");
|
|
204
|
+
const maxNode = bounds.find(p => p.name === "max");
|
|
205
|
+
|
|
206
|
+
const min = minNode == null ? 0 : Number(minNode.value);
|
|
207
|
+
const max = maxNode == null ? Infinity : Number(maxNode.value);
|
|
208
|
+
|
|
209
|
+
options.min = min;
|
|
210
|
+
options.max = max;
|
|
211
|
+
} else if (exactCount != null) {
|
|
212
|
+
const integerNode = exactCount.find(p => p.name === "integer") as Node;
|
|
213
|
+
const integer = Number(integerNode.value);
|
|
214
|
+
|
|
215
|
+
options.min = integer;
|
|
216
|
+
options.max = integer;
|
|
217
|
+
} else if (quantifier != null) {
|
|
218
|
+
const type = quantifier.value;
|
|
219
|
+
if (type === "+") {
|
|
220
|
+
options.min = 1;
|
|
221
|
+
options.max = Infinity;
|
|
222
|
+
} else {
|
|
223
|
+
options.min = 0;
|
|
224
|
+
options.max = Infinity;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const repeat = new Repeat(name, pattern.clone(pattern.name, isPatternOptional), options);
|
|
229
|
+
|
|
230
|
+
this._parseContext.patternsByName.set(name, repeat);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private _buildAlias(statementNode: Node) {
|
|
234
|
+
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
235
|
+
const aliasNode = statementNode.find(n => n.name === "alias-literal") as Node;
|
|
236
|
+
const aliasName = aliasNode.value;
|
|
237
|
+
const name = nameNode.value;
|
|
238
|
+
const pattern = this._getPattern(aliasName);
|
|
239
|
+
const alias = pattern.clone(name);
|
|
240
|
+
|
|
241
|
+
this._parseContext.patternsByName.set(name, alias)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static parse(expression: string) {
|
|
245
|
+
const grammar = new Grammar();
|
|
246
|
+
return grammar.parse(expression);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Repeat } from "../../patterns/Repeat"
|
|
2
|
+
import { Regex } from "../../patterns/Regex";
|
|
3
|
+
import { pattern } from "./pattern";
|
|
4
|
+
|
|
5
|
+
const divider = new Regex("and-divider", "\\s*[&]\\s*");
|
|
6
|
+
divider.setTokens([" & "]);
|
|
7
|
+
|
|
8
|
+
export const andLiteral = new Repeat("and-literal", pattern, { divider, min: 2 });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Or } from "../../patterns/Or";
|
|
2
|
+
import { Regex } from "../../patterns/Regex";
|
|
3
|
+
import { Repeat } from "../../patterns/Repeat";
|
|
4
|
+
import { comment } from "./comment";
|
|
5
|
+
import { statement } from "./statement";
|
|
6
|
+
|
|
7
|
+
const whitespace = new Regex("whitespace", "[ \\t]+");
|
|
8
|
+
const newLine = new Regex("new-line", "(\\r?\\n)+");
|
|
9
|
+
|
|
10
|
+
whitespace.setTokens([" "]);
|
|
11
|
+
newLine.setTokens(["\n"])
|
|
12
|
+
|
|
13
|
+
const line = new Or("line", [
|
|
14
|
+
comment,
|
|
15
|
+
statement,
|
|
16
|
+
whitespace
|
|
17
|
+
], true);
|
|
18
|
+
|
|
19
|
+
export const grammar = new Repeat("grammar", line, { divider: newLine });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Repeat } from "../../patterns/Repeat"
|
|
2
|
+
import { name } from "./name"
|
|
3
|
+
import { Regex } from "../../patterns/Regex";
|
|
4
|
+
|
|
5
|
+
const divider = new Regex("or-divider", "\\s*[|]\\s*");
|
|
6
|
+
divider.setTokens([" | "]);
|
|
7
|
+
|
|
8
|
+
export const orLiteral = new Repeat("or-literal", name.clone("pattern-name"), { divider, min: 2 });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { And } from "../../patterns/And";
|
|
2
|
+
import { Literal } from "../../patterns/Literal";
|
|
3
|
+
import { name } from "./name";
|
|
4
|
+
|
|
5
|
+
const optionalNot = new Literal("not", "!", true);
|
|
6
|
+
const optionalIsOptional = new Literal("is-optional", "?", true);
|
|
7
|
+
const patternName = name.clone("pattern-name");
|
|
8
|
+
|
|
9
|
+
export const pattern = new And("pattern", [
|
|
10
|
+
optionalNot,
|
|
11
|
+
patternName,
|
|
12
|
+
optionalIsOptional,
|
|
13
|
+
]);
|