clarity-pattern-parser 8.2.1 → 8.3.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/.vscode/settings.json +5 -1
- package/README.md +2 -2
- package/dist/grammar/Grammar.d.ts +22 -3
- package/dist/grammar/patterns/import.d.ts +2 -0
- package/dist/index.browser.js +181 -50
- package/dist/index.browser.js.map +1 -1
- package/dist/index.esm.js +181 -50
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +181 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ast/Node.ts +1 -1
- package/src/grammar/Grammar.test.ts +66 -20
- package/src/grammar/Grammar.ts +116 -9
- package/src/grammar/patterns/grammar.ts +9 -6
- package/src/grammar/patterns/import.ts +29 -0
- package/src/grammar/spec.md +26 -2
- package/src/patterns/And.ts +1 -5
- package/src/patterns/FiniteRepeat.ts +2 -1
- package/src/patterns/InfiniteRepeat.test.ts +9 -23
- package/src/patterns/InfiniteRepeat.ts +7 -2
|
@@ -13,7 +13,7 @@ describe("Grammar", () => {
|
|
|
13
13
|
name = "John"
|
|
14
14
|
`;
|
|
15
15
|
|
|
16
|
-
const patterns = Grammar.
|
|
16
|
+
const patterns = Grammar.parseString(expression);
|
|
17
17
|
const namePattern = patterns.get("name");
|
|
18
18
|
const expected = new Literal("name", "John");
|
|
19
19
|
|
|
@@ -25,7 +25,7 @@ describe("Grammar", () => {
|
|
|
25
25
|
name = /\\w/
|
|
26
26
|
`;
|
|
27
27
|
|
|
28
|
-
const patterns = Grammar.
|
|
28
|
+
const patterns = Grammar.parseString(expression);
|
|
29
29
|
const pattern = patterns.get("name");
|
|
30
30
|
const name = new Regex("name", "\\w");
|
|
31
31
|
|
|
@@ -39,7 +39,7 @@ describe("Grammar", () => {
|
|
|
39
39
|
names = john | jane
|
|
40
40
|
`;
|
|
41
41
|
|
|
42
|
-
const patterns = Grammar.
|
|
42
|
+
const patterns = Grammar.parseString(expression);
|
|
43
43
|
const pattern = patterns.get("names");
|
|
44
44
|
const john = new Literal("john", "John");
|
|
45
45
|
const jane = new Literal("jane", "Jane");
|
|
@@ -56,7 +56,7 @@ describe("Grammar", () => {
|
|
|
56
56
|
full-name = first-name & space & last-name
|
|
57
57
|
`;
|
|
58
58
|
|
|
59
|
-
const patterns = Grammar.
|
|
59
|
+
const patterns = Grammar.parseString(expression);
|
|
60
60
|
const pattern = patterns.get("full-name");
|
|
61
61
|
const space = new Literal("space", " ");
|
|
62
62
|
const firstName = new Regex("first-name", "\\w");
|
|
@@ -76,7 +76,7 @@ describe("Grammar", () => {
|
|
|
76
76
|
full-name = first-name & space & middle-name-with-space? & last-name
|
|
77
77
|
`;
|
|
78
78
|
|
|
79
|
-
const patterns = Grammar.
|
|
79
|
+
const patterns = Grammar.parseString(expression);
|
|
80
80
|
const pattern = patterns.get("full-name");
|
|
81
81
|
const space = new Literal("space", " ");
|
|
82
82
|
const firstName = new Regex("first-name", "\\w");
|
|
@@ -99,7 +99,7 @@ describe("Grammar", () => {
|
|
|
99
99
|
full-name = !jack & first-name & space & middle-name-with-space? & last-name
|
|
100
100
|
`;
|
|
101
101
|
|
|
102
|
-
const patterns = Grammar.
|
|
102
|
+
const patterns = Grammar.parseString(expression);
|
|
103
103
|
const pattern = patterns.get("full-name");
|
|
104
104
|
const space = new Literal("space", " ");
|
|
105
105
|
const firstName = new Regex("first-name", "\\w");
|
|
@@ -119,7 +119,7 @@ describe("Grammar", () => {
|
|
|
119
119
|
digits = (digit)+
|
|
120
120
|
`;
|
|
121
121
|
|
|
122
|
-
const patterns = Grammar.
|
|
122
|
+
const patterns = Grammar.parseString(expression);
|
|
123
123
|
const pattern = patterns.get("digits");
|
|
124
124
|
const digit = new Regex("digit", "\\d");
|
|
125
125
|
const digits = new Repeat("digits", digit);
|
|
@@ -133,7 +133,7 @@ describe("Grammar", () => {
|
|
|
133
133
|
digits = (digit)*
|
|
134
134
|
`;
|
|
135
135
|
|
|
136
|
-
const patterns = Grammar.
|
|
136
|
+
const patterns = Grammar.parseString(expression);
|
|
137
137
|
const pattern = patterns.get("digits");
|
|
138
138
|
const digit = new Regex("digit", "\\d");
|
|
139
139
|
const digits = new Repeat("digits", digit, { min: 0 });
|
|
@@ -147,7 +147,7 @@ describe("Grammar", () => {
|
|
|
147
147
|
digits = (digit){1,}
|
|
148
148
|
`;
|
|
149
149
|
|
|
150
|
-
const patterns = Grammar.
|
|
150
|
+
const patterns = Grammar.parseString(expression);
|
|
151
151
|
const pattern = patterns.get("digits");
|
|
152
152
|
const digit = new Regex("digit", "\\d+");
|
|
153
153
|
const digits = new Repeat("digits", digit, { min: 1 });
|
|
@@ -161,7 +161,7 @@ describe("Grammar", () => {
|
|
|
161
161
|
digits = (digit){1,3}
|
|
162
162
|
`;
|
|
163
163
|
|
|
164
|
-
const patterns = Grammar.
|
|
164
|
+
const patterns = Grammar.parseString(expression);
|
|
165
165
|
const pattern = patterns.get("digits");
|
|
166
166
|
const digit = new Regex("digit", "\\d+");
|
|
167
167
|
const digits = new Repeat("digits", digit, { min: 1, max: 3 });
|
|
@@ -175,7 +175,7 @@ describe("Grammar", () => {
|
|
|
175
175
|
digits = (digit){,3}
|
|
176
176
|
`;
|
|
177
177
|
|
|
178
|
-
const patterns = Grammar.
|
|
178
|
+
const patterns = Grammar.parseString(expression);
|
|
179
179
|
const pattern = patterns.get("digits");
|
|
180
180
|
const digit = new Regex("digit", "\\d+");
|
|
181
181
|
const digits = new Repeat("digits", digit, { min: 0, max: 3 });
|
|
@@ -189,7 +189,7 @@ describe("Grammar", () => {
|
|
|
189
189
|
digits = (digit){3}
|
|
190
190
|
`;
|
|
191
191
|
|
|
192
|
-
const patterns = Grammar.
|
|
192
|
+
const patterns = Grammar.parseString(expression);
|
|
193
193
|
const pattern = patterns.get("digits");
|
|
194
194
|
const digit = new Regex("digit", "\\d+");
|
|
195
195
|
const digits = new Repeat("digits", digit, { min: 3, max: 3 });
|
|
@@ -204,7 +204,7 @@ describe("Grammar", () => {
|
|
|
204
204
|
digits = (digit, comma){3}
|
|
205
205
|
`;
|
|
206
206
|
|
|
207
|
-
const patterns = Grammar.
|
|
207
|
+
const patterns = Grammar.parseString(expression);
|
|
208
208
|
const pattern = patterns.get("digits");
|
|
209
209
|
const digit = new Regex("digit", "\\d+");
|
|
210
210
|
const divider = new Literal("comma", ",");
|
|
@@ -220,7 +220,7 @@ describe("Grammar", () => {
|
|
|
220
220
|
digits = (digit, comma){3} -t
|
|
221
221
|
`;
|
|
222
222
|
|
|
223
|
-
const patterns = Grammar.
|
|
223
|
+
const patterns = Grammar.parseString(expression);
|
|
224
224
|
const pattern = patterns.get("digits");
|
|
225
225
|
const digit = new Regex("digit", "\\d+");
|
|
226
226
|
const divider = new Literal("comma", ",");
|
|
@@ -236,7 +236,7 @@ describe("Grammar", () => {
|
|
|
236
236
|
digits = (digit?, comma)+
|
|
237
237
|
`;
|
|
238
238
|
|
|
239
|
-
const patterns = Grammar.
|
|
239
|
+
const patterns = Grammar.parseString(expression);
|
|
240
240
|
const pattern = patterns.get("digits") as Pattern;
|
|
241
241
|
const digit = new Regex("digit", "\\d+", true);
|
|
242
242
|
const comma = new Literal("comma", ",");
|
|
@@ -257,7 +257,7 @@ describe("Grammar", () => {
|
|
|
257
257
|
array = open-bracket & spaces? & array-items? & spaces? & close-bracket
|
|
258
258
|
`;
|
|
259
259
|
|
|
260
|
-
const patterns = Grammar.
|
|
260
|
+
const patterns = Grammar.parseString(expression);
|
|
261
261
|
const pattern = patterns.get("array") as Pattern;
|
|
262
262
|
|
|
263
263
|
let text = "[1, []]";
|
|
@@ -272,8 +272,8 @@ describe("Grammar", () => {
|
|
|
272
272
|
alias = name
|
|
273
273
|
`;
|
|
274
274
|
|
|
275
|
-
const patterns = Grammar.
|
|
276
|
-
|
|
275
|
+
const patterns = Grammar.parseString(expression);
|
|
276
|
+
|
|
277
277
|
const name = patterns.get("name");
|
|
278
278
|
const expectedName = new Regex("name", "regex");
|
|
279
279
|
|
|
@@ -288,7 +288,7 @@ describe("Grammar", () => {
|
|
|
288
288
|
|
|
289
289
|
expect(() => {
|
|
290
290
|
const expression = `Just Junk`;
|
|
291
|
-
Grammar.
|
|
291
|
+
Grammar.parseString(expression);
|
|
292
292
|
}).toThrowError("[Parse Error] Found: 'Just Junk', expected: ' ='.");
|
|
293
293
|
|
|
294
294
|
});
|
|
@@ -299,8 +299,54 @@ describe("Grammar", () => {
|
|
|
299
299
|
const expression = `name = /\\w/
|
|
300
300
|
age = /()
|
|
301
301
|
`;
|
|
302
|
-
Grammar.
|
|
302
|
+
Grammar.parseString(expression);
|
|
303
303
|
}).toThrowError("[Parse Error] Found: ' age = /()\n ', expected: ' age = !' or ' age = (' or ' age = [Pattern Name]' or ' age = [Regular Expression]' or ' age = [String]'.")
|
|
304
304
|
|
|
305
305
|
});
|
|
306
|
+
|
|
307
|
+
test("Import", async () => {
|
|
308
|
+
const importExpression = `first-name = "John"`;
|
|
309
|
+
const expression = `
|
|
310
|
+
import { first-name } from "some/path/to/file.cpat"
|
|
311
|
+
last-name = "Doe"
|
|
312
|
+
space = " "
|
|
313
|
+
full-name = first-name & space & last-name
|
|
314
|
+
`
|
|
315
|
+
function resolveImport(path: string) {
|
|
316
|
+
expect(path).toBe("some/path/to/file.cpat");
|
|
317
|
+
return Promise.resolve({ expression: importExpression, path });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const patterns = await Grammar.parse(expression, { resolveImport });
|
|
321
|
+
const fullname = patterns.get("full-name") as Pattern;
|
|
322
|
+
const result = fullname.exec("John Doe");
|
|
323
|
+
|
|
324
|
+
expect(result?.ast?.value).toBe("John Doe");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("Imports", async () => {
|
|
328
|
+
const importExpression = `first-name = "John"`;
|
|
329
|
+
const spaceExpression = `space = " "`
|
|
330
|
+
|
|
331
|
+
const pathMap: Record<string, string> = {
|
|
332
|
+
"space.cpat": spaceExpression,
|
|
333
|
+
"first-name.cpat": importExpression
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const expression = `
|
|
337
|
+
import { first-name } from "first-name.cpat"
|
|
338
|
+
import { space } from "space.cpat"
|
|
339
|
+
last-name = "Doe"
|
|
340
|
+
full-name = first-name & space & last-name
|
|
341
|
+
`
|
|
342
|
+
function resolveImport(path: string) {
|
|
343
|
+
return Promise.resolve({ expression: pathMap[path], path });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const patterns = await Grammar.parse(expression, { resolveImport });
|
|
347
|
+
const fullname = patterns.get("full-name") as Pattern;
|
|
348
|
+
const result = fullname.exec("John Doe");
|
|
349
|
+
expect(result?.ast?.value).toBe("John Doe");
|
|
350
|
+
});
|
|
351
|
+
|
|
306
352
|
});
|
package/src/grammar/Grammar.ts
CHANGED
|
@@ -12,13 +12,36 @@ import { AutoComplete } from "../intellisense/AutoComplete";
|
|
|
12
12
|
|
|
13
13
|
class ParseContext {
|
|
14
14
|
patternsByName = new Map<string, Pattern>();
|
|
15
|
+
importedPatternsByName = new Map<string, Pattern>();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function defaultImportResolver(_path: string, _basePath: string | null): Promise<GrammarFile> {
|
|
19
|
+
throw new Error("No import resolver supplied.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GrammarMeta {
|
|
23
|
+
originPath?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GrammarFile {
|
|
27
|
+
path: string;
|
|
28
|
+
expression: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface GrammarOptions {
|
|
32
|
+
resolveImport?: (path: string, originPath: string | null) => Promise<GrammarFile>;
|
|
33
|
+
meta?: GrammarMeta | null;
|
|
15
34
|
}
|
|
16
35
|
|
|
17
36
|
export class Grammar {
|
|
37
|
+
private _meta: GrammarMeta | null;
|
|
38
|
+
private _resolveImport: (path: string, basePath: string | null) => Promise<GrammarFile>;
|
|
18
39
|
private _parseContext: ParseContext;
|
|
19
40
|
private _autoComplete: AutoComplete;
|
|
20
41
|
|
|
21
|
-
constructor() {
|
|
42
|
+
constructor(options: GrammarOptions = {}) {
|
|
43
|
+
this._meta = options.meta == null ? null : options.meta;
|
|
44
|
+
this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
|
|
22
45
|
this._parseContext = new ParseContext();
|
|
23
46
|
this._autoComplete = new AutoComplete(grammar, {
|
|
24
47
|
greedyPatternNames: ["spaces", "optional-spaces", "whitespace", "new-line"],
|
|
@@ -31,14 +54,39 @@ export class Grammar {
|
|
|
31
54
|
});
|
|
32
55
|
}
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
async import(path: string) {
|
|
58
|
+
const grammarFile = await this._resolveImport(path, null);
|
|
59
|
+
const grammar = new Grammar({ resolveImport: this._resolveImport, meta: { originPath: grammarFile.path } });
|
|
60
|
+
|
|
61
|
+
return grammar.parse(grammarFile.expression);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async parse(expression: string) {
|
|
35
65
|
this._parseContext = new ParseContext();
|
|
36
|
-
this._tryToParse(expression);
|
|
66
|
+
const ast = this._tryToParse(expression);
|
|
67
|
+
|
|
68
|
+
await this._resolveImports(ast);
|
|
69
|
+
this._buildPatterns(ast);
|
|
70
|
+
this._cleanAst(ast);
|
|
37
71
|
|
|
38
72
|
return this._parseContext.patternsByName;
|
|
39
73
|
}
|
|
40
74
|
|
|
41
|
-
|
|
75
|
+
parseString(expression: string) {
|
|
76
|
+
this._parseContext = new ParseContext();
|
|
77
|
+
const ast = this._tryToParse(expression);
|
|
78
|
+
|
|
79
|
+
if (this._hasImports(ast)) {
|
|
80
|
+
throw new Error("Cannot use imports on parseString, use parse instead.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this._buildPatterns(ast);
|
|
84
|
+
this._cleanAst(ast);
|
|
85
|
+
|
|
86
|
+
return this._parseContext.patternsByName;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private _tryToParse(expression: string): Node {
|
|
42
90
|
const { ast, cursor, options, isComplete } = this._autoComplete.suggestFor(expression);
|
|
43
91
|
|
|
44
92
|
if (!isComplete) {
|
|
@@ -54,9 +102,16 @@ export class Grammar {
|
|
|
54
102
|
}
|
|
55
103
|
|
|
56
104
|
// If it is complete it will always have a node. So we have to cast it.
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
return ast as Node;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _hasImports(ast: Node) {
|
|
109
|
+
const importBlock = ast.find(n => n.name === "import-block");
|
|
110
|
+
if (importBlock == null) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
59
113
|
|
|
114
|
+
return importBlock && importBlock.children.length > 0;
|
|
60
115
|
}
|
|
61
116
|
|
|
62
117
|
private _cleanAst(ast: Node) {
|
|
@@ -99,10 +154,48 @@ export class Grammar {
|
|
|
99
154
|
this._buildAlias(n)
|
|
100
155
|
break;
|
|
101
156
|
}
|
|
157
|
+
default: {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
102
160
|
}
|
|
103
161
|
});
|
|
104
162
|
}
|
|
105
163
|
|
|
164
|
+
private async _resolveImports(ast: Node) {
|
|
165
|
+
const parseContext = this._parseContext;
|
|
166
|
+
const importStatements = ast.findAll(n => n.name === "import-statement");
|
|
167
|
+
|
|
168
|
+
for (const importStatement of importStatements) {
|
|
169
|
+
const urlNode = importStatement.find(n => n.name === "url") as Node;
|
|
170
|
+
|
|
171
|
+
const url = urlNode.value.slice(1, -1);
|
|
172
|
+
const grammarFile = await this._resolveImport(url, this._meta?.originPath || null);
|
|
173
|
+
const grammar = new Grammar({ resolveImport: this._resolveImport, meta: { originPath: grammarFile.path } });
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const patterns = await grammar.parse(grammarFile.expression);
|
|
177
|
+
const importNames = importStatement.findAll(n => n.name === "import-name").map(n => n.value);
|
|
178
|
+
|
|
179
|
+
importNames.forEach((importName) => {
|
|
180
|
+
if (parseContext.importedPatternsByName.has(importName)) {
|
|
181
|
+
throw new Error(`'${importName}' was already used within another import.`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const pattern = patterns.get(importName);
|
|
185
|
+
if (pattern == null) {
|
|
186
|
+
throw new Error(`Couldn't find pattern with name: ${importName}, from import: ${url}.`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
parseContext.importedPatternsByName.set(importName, pattern);
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
} catch (e: any) {
|
|
193
|
+
throw new Error(`Failed loading expression from: "${url}". Error details: "${e.message}"`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
106
199
|
private _buildLiteral(statementNode: Node) {
|
|
107
200
|
const nameNode = statementNode.find(n => n.name === "name") as Node;
|
|
108
201
|
const literalNode = statementNode.find(n => n.name === "literal") as Node;
|
|
@@ -137,7 +230,11 @@ export class Grammar {
|
|
|
137
230
|
}
|
|
138
231
|
|
|
139
232
|
private _getPattern(name: string) {
|
|
140
|
-
|
|
233
|
+
let pattern = this._parseContext.patternsByName.get(name);
|
|
234
|
+
|
|
235
|
+
if (pattern == null) {
|
|
236
|
+
pattern = this._parseContext.importedPatternsByName.get(name);
|
|
237
|
+
}
|
|
141
238
|
|
|
142
239
|
if (pattern == null) {
|
|
143
240
|
return new Reference(name);
|
|
@@ -241,9 +338,19 @@ export class Grammar {
|
|
|
241
338
|
this._parseContext.patternsByName.set(name, alias)
|
|
242
339
|
}
|
|
243
340
|
|
|
244
|
-
static parse(expression: string) {
|
|
245
|
-
const grammar = new Grammar();
|
|
341
|
+
static parse(expression: string, options?: GrammarOptions) {
|
|
342
|
+
const grammar = new Grammar(options);
|
|
246
343
|
return grammar.parse(expression);
|
|
247
344
|
}
|
|
248
345
|
|
|
346
|
+
static import(path: string, options?: GrammarOptions) {
|
|
347
|
+
const grammar = new Grammar(options);
|
|
348
|
+
return grammar.import(path);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
static parseString(expression: string) {
|
|
352
|
+
const grammar = new Grammar();
|
|
353
|
+
return grammar.parseString(expression);
|
|
354
|
+
}
|
|
355
|
+
|
|
249
356
|
}
|
|
@@ -3,17 +3,20 @@ import { Regex } from "../../patterns/Regex";
|
|
|
3
3
|
import { Repeat } from "../../patterns/Repeat";
|
|
4
4
|
import { comment } from "./comment";
|
|
5
5
|
import { statement } from "./statement";
|
|
6
|
+
import { importStatement } from './import';
|
|
6
7
|
|
|
7
|
-
const whitespace = new Regex("whitespace", "[ \\t]+");
|
|
8
|
+
const whitespace = new Regex("whitespace", "[ \\t]+((\\r?\\n)+)?");
|
|
8
9
|
const newLine = new Regex("new-line", "(\\r?\\n)+");
|
|
9
10
|
|
|
10
11
|
whitespace.setTokens([" "]);
|
|
11
|
-
newLine.setTokens(["\n"])
|
|
12
|
+
newLine.setTokens(["\n"]);
|
|
12
13
|
|
|
13
14
|
const line = new Or("line", [
|
|
15
|
+
newLine,
|
|
16
|
+
whitespace,
|
|
14
17
|
comment,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
]
|
|
18
|
+
importStatement,
|
|
19
|
+
statement
|
|
20
|
+
]);
|
|
18
21
|
|
|
19
|
-
export const grammar = new Repeat("
|
|
22
|
+
export const grammar = new Repeat("grammer", line);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { And } from "../../patterns/And";
|
|
2
|
+
import { Repeat } from "../../patterns/Repeat";
|
|
3
|
+
import { Literal } from "../../patterns/Literal";
|
|
4
|
+
import { Regex } from "../../patterns/Regex";
|
|
5
|
+
import { literal } from "./literal";
|
|
6
|
+
|
|
7
|
+
const spaces = new Regex("spaces", "\\s+", true);
|
|
8
|
+
const importNameDivider = new Regex("import-name-divider", "(\\s+)?,(\\s+)?");
|
|
9
|
+
const importKeyword = new Literal("import", "import");
|
|
10
|
+
const fromKeyword = new Literal("from", "from");
|
|
11
|
+
const openBracket = new Literal("open-bracket", "{");
|
|
12
|
+
const closeBracket = new Literal("close-bracket", "}");
|
|
13
|
+
const name = new Regex("import-name", "[^}\\s,]+");
|
|
14
|
+
const importedNames = new Repeat("imported-names", name, { divider: importNameDivider });
|
|
15
|
+
const optionalSpaces = spaces.clone("optional-spaces", true);
|
|
16
|
+
|
|
17
|
+
export const importStatement = new And("import-statement", [
|
|
18
|
+
importKeyword,
|
|
19
|
+
optionalSpaces,
|
|
20
|
+
openBracket,
|
|
21
|
+
optionalSpaces,
|
|
22
|
+
importedNames,
|
|
23
|
+
optionalSpaces,
|
|
24
|
+
closeBracket,
|
|
25
|
+
optionalSpaces,
|
|
26
|
+
fromKeyword,
|
|
27
|
+
spaces,
|
|
28
|
+
literal.clone("url"),
|
|
29
|
+
]);
|
package/src/grammar/spec.md
CHANGED
|
@@ -69,7 +69,6 @@ digits = (digit)*
|
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
### Zero Or More Pattern
|
|
72
|
-
|
|
73
72
|
```
|
|
74
73
|
digit = /\d/
|
|
75
74
|
comma = ","
|
|
@@ -138,5 +137,30 @@ digits = (digit, comma){1,3}
|
|
|
138
137
|
```
|
|
139
138
|
digit = /\d/
|
|
140
139
|
comma = ","
|
|
141
|
-
digits = (digit, comma){1,
|
|
140
|
+
digits = (digit, comma){1,}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Import
|
|
144
|
+
```
|
|
145
|
+
import { spaces } from "https://some.cdn.com/some/spaces.cpat"
|
|
146
|
+
|
|
147
|
+
first-name = "John"
|
|
148
|
+
last-name = "Doe"
|
|
149
|
+
full-name = first-name & spaces & last-name
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Muliple Named Imports
|
|
153
|
+
```
|
|
154
|
+
import { spaces, first-name } from "https://some.cdn.com/some/spaces.cpat"
|
|
155
|
+
|
|
156
|
+
last-name = "Doe"
|
|
157
|
+
full-name = first-name & spaces & last-name
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Muliple Imports
|
|
161
|
+
```
|
|
162
|
+
import { spaces, first-name } from "https://some.cdn.com/some/patterns.cpat"
|
|
163
|
+
import { last-name } from "https://some.cdn.com/some/last-name.cpat"
|
|
164
|
+
|
|
165
|
+
full-name = first-name & spaces & last-name
|
|
142
166
|
```
|
package/src/patterns/And.ts
CHANGED
|
@@ -190,7 +190,6 @@ export class And implements Pattern {
|
|
|
190
190
|
const children = filterOutNull(this._nodes);
|
|
191
191
|
|
|
192
192
|
const lastIndex = children[children.length - 1].lastIndex;
|
|
193
|
-
const value = cursor.getChars(this._firstIndex, lastIndex);
|
|
194
193
|
|
|
195
194
|
cursor.moveTo(lastIndex)
|
|
196
195
|
|
|
@@ -250,15 +249,12 @@ export class And implements Pattern {
|
|
|
250
249
|
|
|
251
250
|
getPatternsAfter(childReference: Pattern): Pattern[] {
|
|
252
251
|
const patterns: Pattern[] = [];
|
|
253
|
-
let nextSibling: Pattern | null = null;
|
|
254
252
|
let nextSiblingIndex = -1;
|
|
255
253
|
let index = -1;
|
|
256
254
|
|
|
257
255
|
for (let i = 0; i < this._children.length; i++) {
|
|
258
256
|
if (this._children[i] === childReference) {
|
|
259
|
-
|
|
260
|
-
nextSibling = this._children[i + 1];
|
|
261
|
-
}
|
|
257
|
+
|
|
262
258
|
nextSiblingIndex = i + 1;
|
|
263
259
|
index = i;
|
|
264
260
|
break;
|
|
@@ -111,8 +111,9 @@ export class FiniteRepeat implements Pattern {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (matchCount < this._min) {
|
|
114
|
+
const lastIndex = cursor.index;
|
|
114
115
|
cursor.moveTo(startIndex);
|
|
115
|
-
cursor.recordErrorAt(startIndex,
|
|
116
|
+
cursor.recordErrorAt(startIndex, lastIndex, this);
|
|
116
117
|
return null;
|
|
117
118
|
} else if (nodes.length === 0) {
|
|
118
119
|
cursor.resolveError();
|
|
@@ -109,29 +109,6 @@ describe("InfiniteRepeat", () => {
|
|
|
109
109
|
expect(cursor.hasError).toBeFalsy()
|
|
110
110
|
});
|
|
111
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
112
|
test("Failed (Optional)", () => {
|
|
136
113
|
const digit = new Regex("digit", "\\d");
|
|
137
114
|
const integer = new InfiniteRepeat("number", digit, { min: 0 });
|
|
@@ -293,4 +270,13 @@ describe("InfiniteRepeat", () => {
|
|
|
293
270
|
|
|
294
271
|
expect(clone).toEqual(expected);
|
|
295
272
|
});
|
|
273
|
+
|
|
274
|
+
test("No Results, min is 0", () => {
|
|
275
|
+
const numbers = new InfiniteRepeat("numbers", new Regex("number", "\\d"), { divider: new Literal(",", ","), min: 0 });
|
|
276
|
+
const result = numbers.exec("j");
|
|
277
|
+
expect(result.ast).toBeNull();
|
|
278
|
+
expect(result.cursor.index).toBe(0);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
|
|
296
282
|
});
|
|
@@ -56,9 +56,9 @@ export class InfiniteRepeat implements Pattern {
|
|
|
56
56
|
let children: Pattern[];
|
|
57
57
|
|
|
58
58
|
if (divider != null) {
|
|
59
|
-
children = [pattern.clone(), divider.clone(divider.name, false)]
|
|
59
|
+
children = [pattern.clone(pattern.name, false), divider.clone(divider.name, false)]
|
|
60
60
|
} else {
|
|
61
|
-
children = [pattern.clone()]
|
|
61
|
+
children = [pattern.clone(pattern.name, false)]
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
this._assignChildrenToParent(children);
|
|
@@ -208,6 +208,11 @@ export class InfiniteRepeat implements Pattern {
|
|
|
208
208
|
cursor.moveTo(dividerNode.firstIndex);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// if (this._nodes.length === 0) {
|
|
212
|
+
// cursor.moveTo(this._firstIndex);
|
|
213
|
+
// return null;
|
|
214
|
+
// }
|
|
215
|
+
|
|
211
216
|
const lastIndex = this._nodes[this._nodes.length - 1].lastIndex;
|
|
212
217
|
cursor.moveTo(lastIndex);
|
|
213
218
|
|