@bablr/boot 0.1.9 → 0.2.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/README.md +19 -0
- package/lib/builders.js +422 -0
- package/lib/index.js +124 -19
- package/lib/languages/cstml.js +258 -90
- package/lib/languages/instruction.js +10 -5
- package/lib/languages/regex.js +124 -36
- package/lib/languages/spamex.js +30 -59
- package/lib/miniparser.js +12 -10
- package/lib/path.js +3 -3
- package/lib/print.js +352 -0
- package/lib/utils.js +9 -8
- package/package.json +8 -5
- package/shorthand.macro.js +200 -104
- package/lib/languages/number.js +0 -38
- package/lib/languages/string.js +0 -86
package/README.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
1
|
## @bablr/boot
|
|
2
2
|
|
|
3
3
|
What a great name. BABLR is defined in terms of BABLR, so this repo helps BABLR boot up without getting stuck in dependency cycles!
|
|
4
|
+
|
|
5
|
+
### Bootstrapping
|
|
6
|
+
|
|
7
|
+
This package's job is to break dependency cycles that arise when evolving the system by using code generation. The nature of the cycles is this:
|
|
8
|
+
|
|
9
|
+
- The VM needs parsed instructions to process code
|
|
10
|
+
- The parser for the instruction language needs a VM with the instruction language already defined
|
|
11
|
+
- The instruction language now needs itself to parse itself
|
|
12
|
+
|
|
13
|
+
Using code-generation to bypass this conundrum is a form of "bootstrapping", which is actually not a one-time event but a continuous process, since the dependency cycle cannot fully be eliminated. In general we use this package to define code generation that uses the last generation of parsers to codegen instructions that are valid definitions for the "core languages" in the next version of the parser.
|
|
14
|
+
|
|
15
|
+
This allows us to do the following:
|
|
16
|
+
|
|
17
|
+
- Import the "core grammars" compiled for the next vm, and the next VM into the helpers
|
|
18
|
+
- Import template tags defined by the helpers into non-core grammars
|
|
19
|
+
- Define non-core grammar in terms of template tags like `` i`eat(' ')` ``
|
|
20
|
+
- These result of evaluating these template tags is a parse tree not a string
|
|
21
|
+
- Execute the grammar, using the helper VM to parse template tag instructions on the fly
|
|
22
|
+
- Cache parsed instructions when they are static using `WeakMap` using `quasis` (first) argument to the template string iterpolator as the cache key, a trick that is possible because `quasis` is an array (a valid `WeakMap` key) whose identity does not change between subsequent invocations. This is how a pattern that looks slow can be fast! Using template string interpolation, e.g. `` i`eat(${something})` `` will prevent caching.
|
package/lib/builders.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
const { freeze, getPrototypeOf } = Object;
|
|
2
|
+
const { isArray } = Array;
|
|
3
|
+
|
|
4
|
+
const spreads = new WeakMap();
|
|
5
|
+
|
|
6
|
+
const spread = (arg) => {
|
|
7
|
+
const wrapper = { value: arg };
|
|
8
|
+
spreads.set(wrapper, true);
|
|
9
|
+
return wrapper;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const interpolateArray = (values, buildSeparator) => {
|
|
13
|
+
const children = [];
|
|
14
|
+
for (const value of values) {
|
|
15
|
+
if (spreads.has(value)) {
|
|
16
|
+
let first = true;
|
|
17
|
+
|
|
18
|
+
for (const element of value.value) {
|
|
19
|
+
if (!first && buildSeparator) {
|
|
20
|
+
children.push(buildSeparator());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
children.push(element);
|
|
24
|
+
|
|
25
|
+
first = false;
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
children.push(value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return children;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const buildTerminal = (term) => {
|
|
35
|
+
switch (term?.type) {
|
|
36
|
+
case 'Literal':
|
|
37
|
+
return term;
|
|
38
|
+
|
|
39
|
+
// case 'Escape':
|
|
40
|
+
// return buildEscapeNode;
|
|
41
|
+
|
|
42
|
+
default:
|
|
43
|
+
throw new Error('Invalid terminal of type ' + term.type);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const interpolateString = (value) => {
|
|
48
|
+
const children = [];
|
|
49
|
+
if (isArray(value)) {
|
|
50
|
+
for (const element of value) {
|
|
51
|
+
children.push(buildTerminal(element));
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// we can't safely interpolate strings here, though I wish we could
|
|
55
|
+
children.push(buildTerminal(value));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return buildNode('String', 'Content', children);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const buildReference = (name, isArray) => {
|
|
62
|
+
return freeze({ type: 'Reference', value: freeze({ name, isArray }) });
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const buildGap = () => {
|
|
66
|
+
return freeze({ type: 'Gap', value: undefined });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const buildNodeOpenTag = (flags, type, attributes = {}) => {
|
|
70
|
+
return freeze({ type: 'OpenNodeTag', value: freeze({ flags, type, attributes }) });
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const buildFragmentOpenTag = (flags = {}, language) => {
|
|
74
|
+
return freeze({ type: 'OpenFragmentTag', value: freeze({ flags: freeze(flags), language }) });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const buildNodeCloseTag = (type) => {
|
|
78
|
+
return freeze({ type: 'CloseNodeTag', value: freeze({ type }) });
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const buildFragmentCloseTag = () => {
|
|
82
|
+
return freeze({ type: 'CloseFragmentTag', value: freeze({}) });
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const buildLiteral = (value) => {
|
|
86
|
+
return freeze({ type: 'Literal', value });
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const buildProperty = (key, value) => {
|
|
90
|
+
return node('Instruction', 'Property', [ref`key`, ref`mapOperator`, buildSpace(), ref`value`], {
|
|
91
|
+
key: buildIdentifier(key),
|
|
92
|
+
mapOperator: s_node('Instruction', 'Punctuator', ':'),
|
|
93
|
+
value: buildExpression(value),
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const buildString = (value) => {
|
|
98
|
+
const terminals = [];
|
|
99
|
+
let literal = '';
|
|
100
|
+
for (const chr of value) {
|
|
101
|
+
if (chr === "'") {
|
|
102
|
+
if (literal)
|
|
103
|
+
terminals.push(
|
|
104
|
+
freeze({
|
|
105
|
+
type: 'Literal',
|
|
106
|
+
value: literal,
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
terminals.push(
|
|
110
|
+
e_node(
|
|
111
|
+
'String',
|
|
112
|
+
'Escape',
|
|
113
|
+
[ref`escape`, ref`escapee`],
|
|
114
|
+
{
|
|
115
|
+
escape: s_node('String', 'Punctuator', '\\'),
|
|
116
|
+
escapee: s_node('String', 'Literal', "'"),
|
|
117
|
+
},
|
|
118
|
+
{ cooked: chr },
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
} else if (chr === '\\') {
|
|
122
|
+
if (literal)
|
|
123
|
+
terminals.push({
|
|
124
|
+
type: 'Literal',
|
|
125
|
+
value: literal,
|
|
126
|
+
});
|
|
127
|
+
terminals.push(
|
|
128
|
+
e_node(
|
|
129
|
+
'String',
|
|
130
|
+
'Escape',
|
|
131
|
+
[ref`escape`, ref`escapee`],
|
|
132
|
+
{
|
|
133
|
+
escape: s_node('String', 'Punctuator', '\\'),
|
|
134
|
+
escapee: s_node('String', 'Literal', '\\'),
|
|
135
|
+
},
|
|
136
|
+
{ cooked: chr },
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
} else {
|
|
140
|
+
literal += chr;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (literal)
|
|
144
|
+
terminals.push(
|
|
145
|
+
freeze({
|
|
146
|
+
type: 'Literal',
|
|
147
|
+
value: literal,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
return node(
|
|
151
|
+
'String',
|
|
152
|
+
'String',
|
|
153
|
+
[ref`open`, ref`content`, ref`close`],
|
|
154
|
+
{
|
|
155
|
+
open: s_node('String', 'Punctuator', "'"),
|
|
156
|
+
content: interpolateString(terminals),
|
|
157
|
+
close: s_node('String', 'Punctuator', "'"),
|
|
158
|
+
},
|
|
159
|
+
{},
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const buildBoolean = (value) => {
|
|
164
|
+
return value
|
|
165
|
+
? node(
|
|
166
|
+
'Instruction',
|
|
167
|
+
'Boolean',
|
|
168
|
+
[ref`value`],
|
|
169
|
+
{
|
|
170
|
+
value: s_node('Instruction', 'Keyword', 'true'),
|
|
171
|
+
},
|
|
172
|
+
{},
|
|
173
|
+
)
|
|
174
|
+
: node(
|
|
175
|
+
'Instruction',
|
|
176
|
+
'Boolean',
|
|
177
|
+
[ref`value`],
|
|
178
|
+
{
|
|
179
|
+
value: s_node('Instruction', 'Keyword', 'false'),
|
|
180
|
+
},
|
|
181
|
+
{},
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const buildNull = () => {
|
|
186
|
+
return node(
|
|
187
|
+
'Instruction',
|
|
188
|
+
'Null',
|
|
189
|
+
[ref`value`],
|
|
190
|
+
{
|
|
191
|
+
value: s_node('Instruction', 'Keyword', 'null'),
|
|
192
|
+
},
|
|
193
|
+
{},
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const buildArray = (elements) => {
|
|
198
|
+
return node(
|
|
199
|
+
'Instruction',
|
|
200
|
+
'Array',
|
|
201
|
+
[ref`open`, ref`elements[]`, ref`close`],
|
|
202
|
+
{
|
|
203
|
+
open: s_node('Instruction', 'Punctuator', '['),
|
|
204
|
+
elements: [...interpolateArray(spread(elements, buildSpace))],
|
|
205
|
+
close: s_node('Instruction', 'Punctuator', ']'),
|
|
206
|
+
},
|
|
207
|
+
{},
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const buildTuple = (elements) => {
|
|
212
|
+
return node(
|
|
213
|
+
'Instruction',
|
|
214
|
+
'Tuple',
|
|
215
|
+
[ref`open`, ref`values[]`, ref`close`],
|
|
216
|
+
{
|
|
217
|
+
open: s_node('Instruction', 'Punctuator', '('),
|
|
218
|
+
values: [...interpolateArray(spread(elements, buildSpace))],
|
|
219
|
+
close: s_node('Instruction', 'Punctuator', ')'),
|
|
220
|
+
},
|
|
221
|
+
{},
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const buildObject = (properties) => {
|
|
226
|
+
return node(
|
|
227
|
+
'Instruction',
|
|
228
|
+
'Object',
|
|
229
|
+
[ref`open`, ref`properties[]`, ref`close`],
|
|
230
|
+
{
|
|
231
|
+
open: s_node('Instruction', 'Punctuator', '{'),
|
|
232
|
+
properties: [
|
|
233
|
+
...interpolateArray(
|
|
234
|
+
spread(Object.entries(properties).map(([key, value]) => buildProperty(key, value))),
|
|
235
|
+
),
|
|
236
|
+
],
|
|
237
|
+
close: s_node('Instruction', 'Punctuator', '}'),
|
|
238
|
+
},
|
|
239
|
+
{},
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const buildSpace = () => {
|
|
244
|
+
return t_node('Comment', null, [t_node('Space', 'Space', lit` `)]);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const buildIdentifier = (name) => {
|
|
248
|
+
return node('Instruction', 'Identifier', [buildLiteral(name)]);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const buildAttribute = (key, value) => {
|
|
252
|
+
return buildNode('CSTML', 'Attribute', [ref`key`, ref`mapOperator`, ref`value`], {
|
|
253
|
+
key: buildIdentifier(key),
|
|
254
|
+
mapOperator: s_node('CSTML', 'Punctuator', '='),
|
|
255
|
+
value: buildExpression(value),
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const buildExpression = (expr) => {
|
|
260
|
+
if (expr == null) return buildNull();
|
|
261
|
+
|
|
262
|
+
switch (typeof expr) {
|
|
263
|
+
case 'boolean':
|
|
264
|
+
return buildBoolean(expr);
|
|
265
|
+
case 'string':
|
|
266
|
+
return buildString(expr);
|
|
267
|
+
case 'object': {
|
|
268
|
+
switch (getPrototypeOf(expr)) {
|
|
269
|
+
case Array.prototype:
|
|
270
|
+
return buildArray(expr);
|
|
271
|
+
case Object.prototype:
|
|
272
|
+
if (expr.type && expr.language && expr.children && expr.properties) {
|
|
273
|
+
return expr;
|
|
274
|
+
}
|
|
275
|
+
return buildObject(expr);
|
|
276
|
+
default:
|
|
277
|
+
throw new Error();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
default:
|
|
281
|
+
throw new Error();
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const nodeFlags = freeze({ syntactic: false, escape: false, trivia: false });
|
|
286
|
+
|
|
287
|
+
const buildNode = (language, type, children = [], properties = {}, attributes = {}) =>
|
|
288
|
+
freeze({
|
|
289
|
+
flags: nodeFlags,
|
|
290
|
+
language,
|
|
291
|
+
type,
|
|
292
|
+
children: freeze(children),
|
|
293
|
+
properties: freeze(properties),
|
|
294
|
+
attributes: freeze(attributes),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const syntacticFlags = freeze({ syntactic: true, escape: false, trivia: false });
|
|
298
|
+
|
|
299
|
+
const buildSyntacticNode = (language, type, value, attributes = {}) =>
|
|
300
|
+
freeze({
|
|
301
|
+
flags: syntacticFlags,
|
|
302
|
+
language,
|
|
303
|
+
type,
|
|
304
|
+
children: [buildLiteral(value)],
|
|
305
|
+
properties: freeze({}),
|
|
306
|
+
attributes: freeze(attributes),
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const escapeFlags = freeze({ syntactic: false, escape: true, trivia: false });
|
|
310
|
+
|
|
311
|
+
const buildEscapeNode = (language, type, children = [], properties = {}, attributes = {}) =>
|
|
312
|
+
freeze({
|
|
313
|
+
flags: escapeFlags,
|
|
314
|
+
language,
|
|
315
|
+
type,
|
|
316
|
+
children: freeze(children),
|
|
317
|
+
properties: freeze(properties),
|
|
318
|
+
attributes: freeze(attributes),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const syntacticEscapeFlags = freeze({ syntactic: true, escape: true, trivia: false });
|
|
322
|
+
|
|
323
|
+
const buildSyntacticEscapeNode = (
|
|
324
|
+
language,
|
|
325
|
+
type,
|
|
326
|
+
children = [],
|
|
327
|
+
properties = {},
|
|
328
|
+
attributes = {},
|
|
329
|
+
) =>
|
|
330
|
+
freeze({
|
|
331
|
+
flags: syntacticEscapeFlags,
|
|
332
|
+
language,
|
|
333
|
+
type,
|
|
334
|
+
children: freeze(children),
|
|
335
|
+
properties: freeze(properties),
|
|
336
|
+
attributes: freeze(attributes),
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const triviaFlags = freeze({ syntactic: false, escape: false, trivia: true });
|
|
340
|
+
|
|
341
|
+
const buildTriviaNode = (language, type, children = [], properties = {}, attributes = {}) =>
|
|
342
|
+
freeze({
|
|
343
|
+
flags: triviaFlags,
|
|
344
|
+
language,
|
|
345
|
+
type,
|
|
346
|
+
children: freeze(children),
|
|
347
|
+
properties: freeze(properties),
|
|
348
|
+
attributes: freeze(attributes),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const stripArray = (val) => {
|
|
352
|
+
if (isArray(val)) {
|
|
353
|
+
if (val.length > 1) {
|
|
354
|
+
throw new Error();
|
|
355
|
+
}
|
|
356
|
+
return val[0];
|
|
357
|
+
} else {
|
|
358
|
+
return val;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const ref = (path) => {
|
|
363
|
+
if (isArray(path)) {
|
|
364
|
+
const pathIsArray = path[0].endsWith('[]');
|
|
365
|
+
const name = pathIsArray ? path[0].slice(0, -2) : path[0];
|
|
366
|
+
return buildReference(name, pathIsArray);
|
|
367
|
+
} else {
|
|
368
|
+
const { name, pathIsArray } = path;
|
|
369
|
+
return buildReference(name, pathIsArray);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const lit = (str) => buildLiteral(stripArray(str));
|
|
374
|
+
|
|
375
|
+
const gap = buildGap;
|
|
376
|
+
const nodeOpen = buildNodeOpenTag;
|
|
377
|
+
const fragOpen = buildFragmentOpenTag;
|
|
378
|
+
const nodeClose = buildNodeCloseTag;
|
|
379
|
+
const fragClose = buildFragmentCloseTag;
|
|
380
|
+
const node = buildNode;
|
|
381
|
+
const s_node = buildSyntacticNode;
|
|
382
|
+
const e_node = buildEscapeNode;
|
|
383
|
+
const s_e_node = buildSyntacticEscapeNode;
|
|
384
|
+
const t_node = buildTriviaNode;
|
|
385
|
+
|
|
386
|
+
module.exports = {
|
|
387
|
+
buildProperty,
|
|
388
|
+
buildString,
|
|
389
|
+
buildBoolean,
|
|
390
|
+
buildNull,
|
|
391
|
+
buildArray,
|
|
392
|
+
buildTuple,
|
|
393
|
+
buildObject,
|
|
394
|
+
buildExpression,
|
|
395
|
+
buildSpace,
|
|
396
|
+
buildIdentifier,
|
|
397
|
+
buildAttribute,
|
|
398
|
+
buildReference,
|
|
399
|
+
buildGap,
|
|
400
|
+
buildNodeOpenTag,
|
|
401
|
+
buildFragmentOpenTag,
|
|
402
|
+
buildNodeCloseTag,
|
|
403
|
+
buildFragmentCloseTag,
|
|
404
|
+
buildLiteral,
|
|
405
|
+
buildNode,
|
|
406
|
+
buildSyntacticNode,
|
|
407
|
+
buildEscapeNode,
|
|
408
|
+
buildSyntacticEscapeNode,
|
|
409
|
+
buildTriviaNode,
|
|
410
|
+
ref,
|
|
411
|
+
lit,
|
|
412
|
+
gap,
|
|
413
|
+
nodeOpen,
|
|
414
|
+
fragOpen,
|
|
415
|
+
nodeClose,
|
|
416
|
+
fragClose,
|
|
417
|
+
node,
|
|
418
|
+
s_node,
|
|
419
|
+
e_node,
|
|
420
|
+
s_e_node,
|
|
421
|
+
t_node,
|
|
422
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
const
|
|
2
|
-
const regex = require('./languages/regex.js');
|
|
3
|
-
const spamex = require('./languages/spamex.js');
|
|
4
|
-
const string = require('./languages/string.js');
|
|
5
|
-
const number = require('./languages/number.js');
|
|
1
|
+
const { PathResolver } = require('@bablr/boot-helpers/path');
|
|
6
2
|
const cstml = require('./languages/cstml.js');
|
|
3
|
+
const regex = require('./languages/regex.js');
|
|
4
|
+
const instruction = require('./languages/instruction.js');
|
|
5
|
+
const { buildLiteral } = require('./builders.js');
|
|
7
6
|
const { TemplateParser } = require('./miniparser.js');
|
|
8
7
|
|
|
8
|
+
const { isArray } = Array;
|
|
9
|
+
const { hasOwn } = Object;
|
|
10
|
+
|
|
11
|
+
const set = (obj, path, value) => {
|
|
12
|
+
const { name, isArray: pathIsArray } = path;
|
|
13
|
+
if (pathIsArray) {
|
|
14
|
+
if (!obj[name]) {
|
|
15
|
+
obj[name] = [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!isArray(obj[name])) throw new Error('bad array value');
|
|
19
|
+
|
|
20
|
+
obj[name].push(value);
|
|
21
|
+
} else {
|
|
22
|
+
if (hasOwn(obj, name)) {
|
|
23
|
+
throw new Error('duplicate child name');
|
|
24
|
+
}
|
|
25
|
+
obj[name] = value;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
9
29
|
const buildTag = (language, defaultType) => {
|
|
10
30
|
const defaultTag = (quasis, ...exprs) => {
|
|
11
|
-
return
|
|
12
|
-
language
|
|
13
|
-
|
|
14
|
-
|
|
31
|
+
return getAgASTValue(
|
|
32
|
+
language,
|
|
33
|
+
new TemplateParser(language, quasis.raw, exprs).eval({
|
|
34
|
+
language: language.name,
|
|
35
|
+
type: defaultType,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
15
38
|
};
|
|
16
39
|
|
|
17
40
|
return new Proxy(defaultTag, {
|
|
@@ -21,20 +44,102 @@ const buildTag = (language, defaultType) => {
|
|
|
21
44
|
|
|
22
45
|
get(_, type) {
|
|
23
46
|
return (quasis, ...exprs) => {
|
|
24
|
-
return
|
|
25
|
-
language
|
|
26
|
-
|
|
27
|
-
|
|
47
|
+
return getAgASTValue(
|
|
48
|
+
language,
|
|
49
|
+
new TemplateParser(language, quasis.raw, exprs).eval({
|
|
50
|
+
language: language.name,
|
|
51
|
+
type,
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
28
54
|
};
|
|
29
55
|
},
|
|
30
56
|
});
|
|
31
57
|
};
|
|
32
58
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
const parse = (language, type, sourceText) => {
|
|
60
|
+
return new TemplateParser(language, [sourceText], []).eval({
|
|
61
|
+
language: language.name,
|
|
62
|
+
type,
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getAgASTValue = (language, miniNode) => {
|
|
67
|
+
if (!miniNode) return miniNode;
|
|
68
|
+
|
|
69
|
+
const { language: languageName, type, attributes } = miniNode;
|
|
70
|
+
const flags = { escape: false, trivia: false, token: false, intrinsic: false };
|
|
71
|
+
const properties = {};
|
|
72
|
+
const children = [];
|
|
73
|
+
const resolver = new PathResolver(miniNode);
|
|
74
|
+
const resolvedLanguage =
|
|
75
|
+
languageName !== language.name ? language.dependencies[languageName] : language;
|
|
76
|
+
|
|
77
|
+
if (languageName.startsWith('https://')) {
|
|
78
|
+
return miniNode; // This node is already processed, possibly because it was interpolated
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!resolvedLanguage) {
|
|
82
|
+
throw new Error();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
type === 'Punctuator' ||
|
|
87
|
+
type === 'Keyword' ||
|
|
88
|
+
type === 'Identifier' ||
|
|
89
|
+
type === 'StringContent'
|
|
90
|
+
) {
|
|
91
|
+
flags.token = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (type === 'Punctuator' || type === 'Keyword') {
|
|
95
|
+
flags.intrinsic = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const child of miniNode.children) {
|
|
99
|
+
if (child.type === 'Reference') {
|
|
100
|
+
const path = child.value;
|
|
101
|
+
const node = resolver.get(path);
|
|
102
|
+
set(properties, path, getAgASTValue(resolvedLanguage, node));
|
|
103
|
+
children.push(child);
|
|
104
|
+
} else if (child.type === 'Trivia') {
|
|
105
|
+
children.push({
|
|
106
|
+
type: 'Embedded',
|
|
107
|
+
value: {
|
|
108
|
+
flags: { escape: false, token: true, trivia: true, intrinsic: false },
|
|
109
|
+
language: 'https://bablr.org/languages/core/space-tab-newline',
|
|
110
|
+
type: 'Space',
|
|
111
|
+
children: [buildLiteral(child.value)],
|
|
112
|
+
properties: {},
|
|
113
|
+
attributes: {},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
} else if (child.type === 'Escape') {
|
|
117
|
+
const { cooked, raw } = child.value;
|
|
118
|
+
const attributes = { cooked };
|
|
119
|
+
|
|
120
|
+
children.push({
|
|
121
|
+
type: 'Embedded',
|
|
122
|
+
value: {
|
|
123
|
+
flags: { escape: true, token: true, trivia: false, intrinsic: false },
|
|
124
|
+
language: cstml.canonicalURL,
|
|
125
|
+
type: 'Escape',
|
|
126
|
+
children: [buildLiteral(raw)],
|
|
127
|
+
properties: {},
|
|
128
|
+
attributes,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
children.push(child);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { flags, language: resolvedLanguage.canonicalURL, type, children, properties, attributes };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const str = buildTag(cstml, 'String');
|
|
140
|
+
const num = buildTag(cstml, 'Integer');
|
|
38
141
|
const cst = buildTag(cstml, 'Fragment');
|
|
142
|
+
const re = buildTag(regex, 'Pattern');
|
|
143
|
+
const i = buildTag(instruction, 'Call');
|
|
39
144
|
|
|
40
|
-
module.exports = {
|
|
145
|
+
module.exports = { str, num, cst, re, i, buildTag, set, getAgASTValue, TemplateParser, parse };
|