@bablr/agast-vm-helpers 0.1.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/LICENSE +21 -0
- package/README.md +3 -0
- package/lib/builders.js +573 -0
- package/lib/facades.js +37 -0
- package/lib/index.js +258 -0
- package/lib/languages.js +6 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Conrad Buck
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/lib/builders.js
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
// import { i } from '@bablr/boot/shorthand.macro';
|
|
2
|
+
import {
|
|
3
|
+
interpolateArray,
|
|
4
|
+
interpolateArrayChildren,
|
|
5
|
+
interpolateString,
|
|
6
|
+
} from '@bablr/agast-helpers/template';
|
|
7
|
+
import * as t from '@bablr/agast-helpers/shorthand';
|
|
8
|
+
import * as l from './languages.js';
|
|
9
|
+
|
|
10
|
+
const { getPrototypeOf, freeze } = Object;
|
|
11
|
+
const { isArray } = Array;
|
|
12
|
+
|
|
13
|
+
const when = (condition, value) => (condition ? value : { *[Symbol.iterator]() {} });
|
|
14
|
+
|
|
15
|
+
const isString = (val) => typeof val === 'string';
|
|
16
|
+
const isBoolean = (val) => typeof val === 'boolean';
|
|
17
|
+
|
|
18
|
+
function* repeat(times, ...values) {
|
|
19
|
+
for (let i = 0; i < times; i++) for (const value of values) yield value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const buildReference = (name, isArray) => {
|
|
23
|
+
return t.node(l.CSTML, 'Reference', [t.ref`name`, ...when(isArray, [t.ref`arrayOperator`])], {
|
|
24
|
+
name: buildIdentifier(name),
|
|
25
|
+
arrayOperator: isArray ? t.s_node(l.CSTML, 'Punctuator', '[]') : null,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const buildGap = () => {
|
|
30
|
+
return t.node(l.CSTML, 'Gap', [t.ref`value`], { value: t.s_node(l.CSTML, 'Punctuator', '<//>') });
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const buildFlags = (flags) => {
|
|
34
|
+
const { token = null, escape = null, trivia = null, expression = null } = flags;
|
|
35
|
+
|
|
36
|
+
if ((trivia && escape) || (expression && (trivia || escape))) {
|
|
37
|
+
throw new Error('invalid flags');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
children: [
|
|
42
|
+
...when(trivia, [t.ref`triviaFlag`]),
|
|
43
|
+
...when(token, [t.ref`tokenFlag`]),
|
|
44
|
+
...when(escape, [t.ref`escapeFlag`]),
|
|
45
|
+
...when(expression, [t.ref`expressionFlag`]),
|
|
46
|
+
],
|
|
47
|
+
properties: {
|
|
48
|
+
triviaFlag: trivia && t.s_node(l.CSTML, 'Punctuator', '#'),
|
|
49
|
+
tokenFlag: token && t.s_node(l.CSTML, 'Punctuator', '*'),
|
|
50
|
+
escapeFlag: escape && t.s_node(l.CSTML, 'Punctuator', '@'),
|
|
51
|
+
expressionFlag: expression && t.s_node(l.CSTML, 'Punctuator', '+'),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const buildSpamMatcher = (type, value, attributes = {}) => {
|
|
57
|
+
return buildFullyQualifiedSpamMatcher({}, null, type, value, attributes);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const buildFullyQualifiedSpamMatcher = (
|
|
61
|
+
flags,
|
|
62
|
+
language,
|
|
63
|
+
type,
|
|
64
|
+
intrinsicValue,
|
|
65
|
+
attributes = {},
|
|
66
|
+
) => {
|
|
67
|
+
const attributes_ = Object.entries(attributes).map(({ 0: key, 1: value }) =>
|
|
68
|
+
buildAttribute(key, value),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const lArr = language ? [...language] : [];
|
|
72
|
+
|
|
73
|
+
let language_ = lArr.length === 0 ? null : lArr;
|
|
74
|
+
|
|
75
|
+
const flags_ = buildFlags(flags);
|
|
76
|
+
return t.node(
|
|
77
|
+
l.Spamex,
|
|
78
|
+
'NodeMatcher',
|
|
79
|
+
[
|
|
80
|
+
t.ref`open`,
|
|
81
|
+
...flags_.children,
|
|
82
|
+
...when(language_, [t.ref`language`, t.ref`languageSeparator`]),
|
|
83
|
+
t.ref`type`,
|
|
84
|
+
...when(intrinsicValue, [t.embedded(buildSpace()), t.ref`intrinsicValue`]),
|
|
85
|
+
...when(attributes_.length, [t.embedded(buildSpace())]),
|
|
86
|
+
...interpolateArrayChildren(attributes, t.ref`attributes[]`, t.embedded(buildSpace())),
|
|
87
|
+
t.ref`close`,
|
|
88
|
+
],
|
|
89
|
+
{
|
|
90
|
+
open: t.s_node(l.CSTML, 'Punctuator', '<'),
|
|
91
|
+
...flags_.properties,
|
|
92
|
+
language: buildLanguage(language_),
|
|
93
|
+
languageSeparator: language_ && type && t.s_node(l.CSTML, 'Punctuator', ':'),
|
|
94
|
+
type: buildIdentifier(type),
|
|
95
|
+
intrinsicValue: intrinsicValue && buildString(intrinsicValue),
|
|
96
|
+
attributes: attributes_,
|
|
97
|
+
close: t.s_node(l.CSTML, 'Punctuator', '>'),
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const buildNodeOpenTag = (flags, language, type, intrinsicValue = null, attributes = {}) => {
|
|
103
|
+
const attributes_ = Object.entries(attributes).map(({ 0: key, 1: value }) =>
|
|
104
|
+
buildAttribute(key, value),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
let language_ = !language || language.length === 0 ? null : language;
|
|
108
|
+
|
|
109
|
+
const flags_ = buildFlags(flags);
|
|
110
|
+
|
|
111
|
+
return t.node(
|
|
112
|
+
l.CSTML,
|
|
113
|
+
'OpenNodeTag',
|
|
114
|
+
[
|
|
115
|
+
t.ref`open`,
|
|
116
|
+
...flags_.children,
|
|
117
|
+
...when(language_, [t.ref`language`, t.ref`languageSeparator`]),
|
|
118
|
+
t.ref`type`,
|
|
119
|
+
...when(intrinsicValue, [t.embedded(buildSpace()), t.ref`intrinsicValue`]),
|
|
120
|
+
...when(attributes_.length, [t.embedded(buildSpace())]),
|
|
121
|
+
...interpolateArrayChildren(attributes_, t.ref`attributes[]`, t.embedded(buildSpace())),
|
|
122
|
+
when(intrinsicValue, [t.embedded(buildSpace), t.ref`selfClosingToken`]),
|
|
123
|
+
t.ref`close`,
|
|
124
|
+
],
|
|
125
|
+
{
|
|
126
|
+
open: t.s_node(l.CSTML, 'Punctuator', '<'),
|
|
127
|
+
...flags_.properties,
|
|
128
|
+
language: buildLanguage(language_),
|
|
129
|
+
languageSeparator: language_ && type && t.s_node(l.CSTML, 'Punctuator', ':'),
|
|
130
|
+
type: buildIdentifier(type),
|
|
131
|
+
intrinsicValue: intrinsicValue && buildString(intrinsicValue),
|
|
132
|
+
attributes: attributes_,
|
|
133
|
+
selfClosingToken: intrinsicValue && t.s_node(l.CSTML, 'Punctuator', '/'),
|
|
134
|
+
close: t.s_node(l.CSTML, 'Punctuator', '>'),
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const buildDoctypeTag = (attributes) => {
|
|
140
|
+
const attributes_ = Object.entries(attributes).map(({ 0: key, 1: value }) =>
|
|
141
|
+
buildAttribute(key, value),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return t.node(
|
|
145
|
+
l.CSTML,
|
|
146
|
+
'DoctypeTag',
|
|
147
|
+
[
|
|
148
|
+
t.ref`open`,
|
|
149
|
+
t.ref`version`,
|
|
150
|
+
t.ref`versionSeparator`,
|
|
151
|
+
t.ref`doctype`,
|
|
152
|
+
...when(attributes_.length, [t.embedded(buildSpace())]),
|
|
153
|
+
...interpolateArrayChildren(attributes_, t.ref`attributes[]`, t.embedded(buildSpace())),
|
|
154
|
+
t.ref`close`,
|
|
155
|
+
],
|
|
156
|
+
{
|
|
157
|
+
open: t.s_node(l.CSTML, 'Punctuator', '<!'),
|
|
158
|
+
version: t.s_node(l.CSTML, 'PositiveInteger', '0'),
|
|
159
|
+
versionSeparator: t.s_node(l.CSTML, 'Punctuator', ':'),
|
|
160
|
+
doctype: t.s_node(l.CSTML, 'Keyword', 'cstml'),
|
|
161
|
+
attributes: attributes_,
|
|
162
|
+
close: t.s_node(l.CSTML, 'Punctuator', '>'),
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const buildFragmentFlags = (flags = {}) => {
|
|
168
|
+
const { escape = null, trivia = null } = flags;
|
|
169
|
+
if (!(escape || trivia)) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return t.node(
|
|
174
|
+
l.CSTML,
|
|
175
|
+
'NodeFlags',
|
|
176
|
+
[...when(trivia, [t.ref`trivia`]), ...when(escape, [t.ref`escape`])],
|
|
177
|
+
{
|
|
178
|
+
trivia: trivia && t.s_node(l.CSTML, 'Punctuator', '#'),
|
|
179
|
+
escape: escape && t.s_node(l.CSTML, 'Punctuator', '@'),
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const buildFragmentOpenTag = (flags) => {
|
|
185
|
+
return t.node(
|
|
186
|
+
l.CSTML,
|
|
187
|
+
'OpenFragmentTag',
|
|
188
|
+
[t.ref`open`, ...when(flags, [t.ref`flags`]), t.ref`close`],
|
|
189
|
+
{
|
|
190
|
+
open: t.s_node(l.CSTML, 'Punctuator', '<'),
|
|
191
|
+
flags: buildFragmentFlags(flags),
|
|
192
|
+
close: t.s_node(l.CSTML, 'Punctuator', '>'),
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const buildIdentifierPath = (path) => {
|
|
198
|
+
const path_ = [...path];
|
|
199
|
+
const segments = path_.map((name) => buildIdentifier(name));
|
|
200
|
+
const separators = path_.slice(0, -1).map((_) => t.s_node(l.CSTML, 'Punctuator', '.'));
|
|
201
|
+
|
|
202
|
+
if (!path_.length) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return t.node(
|
|
207
|
+
l.CSTML,
|
|
208
|
+
'IdentifierPath',
|
|
209
|
+
[...repeat(segments.length, t.ref`segments[]`, t.ref`separators[]`)].slice(0, -1),
|
|
210
|
+
{
|
|
211
|
+
segments,
|
|
212
|
+
separators,
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export const buildLanguage = (language) => {
|
|
218
|
+
return language && buildIdentifierPath(language);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const buildNodeCloseTag = (type, language) => {
|
|
222
|
+
return t.node(
|
|
223
|
+
l.CSTML,
|
|
224
|
+
'CloseNodeTag',
|
|
225
|
+
[
|
|
226
|
+
t.ref`open`,
|
|
227
|
+
...when(language, [t.ref`language`]),
|
|
228
|
+
...when(type && language, [t.ref`languageSeparator`]),
|
|
229
|
+
...when(type, [t.ref`type`]),
|
|
230
|
+
t.ref`close`,
|
|
231
|
+
],
|
|
232
|
+
{
|
|
233
|
+
open: t.s_node(l.CSTML, 'Punctuator', '</'),
|
|
234
|
+
language: buildLanguage(language),
|
|
235
|
+
languageSeparator: language && type ? t.s_node(l.CSTML, 'Punctuator', ':') : null,
|
|
236
|
+
type: type && buildIdentifier(type),
|
|
237
|
+
close: t.s_node(l.CSTML, 'Punctuator', '>'),
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const buildFragmentCloseTag = () => {
|
|
243
|
+
return t.node(l.CSTML, 'CloseFragmentTag', [t.ref`open`, t.ref`close`], {
|
|
244
|
+
open: t.s_node(l.CSTML, 'Punctuator', '</'),
|
|
245
|
+
close: t.s_node(l.CSTML, 'Punctuator', '>'),
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const buildLiteral = (value) => {
|
|
250
|
+
return t.node(l.CSTML, 'Literal', [t.ref`value`], { value });
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const buildTerminalProps = (matcher) => {
|
|
254
|
+
const { attributes, value } = matcher.properties;
|
|
255
|
+
|
|
256
|
+
return buildObject({ value, attributes });
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const buildSpace = () => {
|
|
260
|
+
return t.t_node(l.Comment, null, [t.embedded(t.t_node(l.Space, 'Space', [t.lit(' ')]))]);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export const buildIdentifier = (name) => {
|
|
264
|
+
return t.node(l.Instruction, 'Identifier', [t.lit(name)]);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export const buildCall = (verb, ...args) => {
|
|
268
|
+
return t.node(l.Instruction, 'Call', [t.ref`verb`, t.ref`arguments`], {
|
|
269
|
+
verb: buildIdentifier(verb),
|
|
270
|
+
arguments: buildTuple(args),
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const buildProperty = (key, value) => {
|
|
275
|
+
return t.node(
|
|
276
|
+
l.Instruction,
|
|
277
|
+
'Property',
|
|
278
|
+
[t.ref`key`, t.ref`mapOperator`, t.embedded(buildSpace()), t.ref`value`],
|
|
279
|
+
{
|
|
280
|
+
key: buildIdentifier(key),
|
|
281
|
+
mapOperator: t.s_node(l.Instruction, 'Punctuator', ':'),
|
|
282
|
+
value: buildExpression(value),
|
|
283
|
+
},
|
|
284
|
+
);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const escapables = {
|
|
288
|
+
'\r': 'r',
|
|
289
|
+
'\n': 'n',
|
|
290
|
+
'\t': 't',
|
|
291
|
+
'\0': '0',
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export const buildDigit = (value) => {
|
|
295
|
+
return t.s_node(l.CSTML, 'Digit', value);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const buildInteger = (value) => {
|
|
299
|
+
const digits = value.toString(10).split('');
|
|
300
|
+
|
|
301
|
+
return t.node(
|
|
302
|
+
l.CSTML,
|
|
303
|
+
'Integer',
|
|
304
|
+
digits.map((d) => t.ref`digits[]`),
|
|
305
|
+
{ digits: digits.map((digit) => buildDigit(digit)) },
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export const buildInfinity = (value) => {
|
|
310
|
+
let sign;
|
|
311
|
+
if (value === Infinity) {
|
|
312
|
+
sign = '+';
|
|
313
|
+
} else if (value === -Infinity) {
|
|
314
|
+
sign = '-';
|
|
315
|
+
} else {
|
|
316
|
+
throw new Error();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return t.node(l.CSTML, 'Infinity', [t.ref`sign`, t.ref`value`], {
|
|
320
|
+
sign: t.s_node(l.CSTML, 'Punctuator', sign),
|
|
321
|
+
value: t.s_node(l.CSTML, 'Keyword', 'Infinity'),
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export const buildNumber = (value) => {
|
|
326
|
+
if (Number.isFinite(value)) {
|
|
327
|
+
return buildInteger(value);
|
|
328
|
+
} else {
|
|
329
|
+
return buildInfinity(value);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const buildString = (value) => {
|
|
334
|
+
const pieces = isArray(value) ? value : [value];
|
|
335
|
+
const terminals = [];
|
|
336
|
+
let lit = '';
|
|
337
|
+
|
|
338
|
+
if (pieces.length === 1 && pieces[0] === "'") {
|
|
339
|
+
return t.node(l.CSTML, 'String', [t.ref`open`, t.ref`content`, t.ref`close`], {
|
|
340
|
+
open: t.s_node(l.CSTML, 'Punctuator', '"'),
|
|
341
|
+
content: interpolateString(
|
|
342
|
+
freeze({
|
|
343
|
+
type: 'Literal',
|
|
344
|
+
value,
|
|
345
|
+
}),
|
|
346
|
+
),
|
|
347
|
+
close: t.s_node(l.CSTML, 'Punctuator', '"'),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
for (const piece of pieces) {
|
|
352
|
+
if (isString(piece)) {
|
|
353
|
+
const value = piece;
|
|
354
|
+
|
|
355
|
+
for (const chr of value) {
|
|
356
|
+
if (
|
|
357
|
+
chr === '\\' ||
|
|
358
|
+
chr === "'" ||
|
|
359
|
+
chr === '\n' ||
|
|
360
|
+
chr === '\r' ||
|
|
361
|
+
chr === '\t' ||
|
|
362
|
+
chr === '\0'
|
|
363
|
+
) {
|
|
364
|
+
if (lit) {
|
|
365
|
+
terminals.push(freeze({ type: 'Literal', value: lit }));
|
|
366
|
+
lit = '';
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
terminals.push(
|
|
370
|
+
t.buildEmbedded(
|
|
371
|
+
t.e_node(
|
|
372
|
+
l.CSTML,
|
|
373
|
+
'Escape',
|
|
374
|
+
[t.ref`escape`, t.ref`escapee`],
|
|
375
|
+
{
|
|
376
|
+
escape: t.s_node(l.CSTML, 'Punctuator', '\\'),
|
|
377
|
+
escapee: t.s_node(l.CSTML, 'Literal', escapables[chr] || chr),
|
|
378
|
+
},
|
|
379
|
+
{ cooked: chr },
|
|
380
|
+
),
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
} else {
|
|
384
|
+
lit += chr;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
terminals.push(freeze({ type: 'Literal', value: lit }));
|
|
389
|
+
lit = '';
|
|
390
|
+
|
|
391
|
+
if (piece == null) {
|
|
392
|
+
throw new Error('not impelemented');
|
|
393
|
+
} else if (isString(piece.type)) {
|
|
394
|
+
terminals.push(piece);
|
|
395
|
+
} else {
|
|
396
|
+
throw new Error();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (lit) terminals.push(freeze({ type: 'Literal', value: lit }));
|
|
402
|
+
lit = '';
|
|
403
|
+
|
|
404
|
+
return t.node(l.CSTML, 'String', [t.ref`open`, t.ref`content`, t.ref`close`], {
|
|
405
|
+
open: t.s_node(l.CSTML, 'Punctuator', "'"),
|
|
406
|
+
content: interpolateString(terminals),
|
|
407
|
+
close: t.s_node(l.CSTML, 'Punctuator', "'"),
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export const buildBoolean = (value) => {
|
|
412
|
+
return t.node(l.Instruction, 'Boolean', [t.ref`value`], {
|
|
413
|
+
value: t.s_node(l.Instruction, 'Keyword', value ? 'true' : 'false'),
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export const buildNull = () => {
|
|
418
|
+
return t.node(l.Instruction, 'Null', [t.ref`value`], {
|
|
419
|
+
value: t.s_node(l.Instruction, 'Keyword', 'null'),
|
|
420
|
+
});
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
export const buildArray = (elements) => {
|
|
424
|
+
return t.node(
|
|
425
|
+
l.Instruction,
|
|
426
|
+
'Array',
|
|
427
|
+
[
|
|
428
|
+
t.ref`open`,
|
|
429
|
+
...interpolateArrayChildren(
|
|
430
|
+
elements,
|
|
431
|
+
t.ref`elements[]`,
|
|
432
|
+
t.embedded(
|
|
433
|
+
t.t_node(l.Comment, null, [t.embedded(t.t_node('Space', 'Space', [t.lit(' ')]))]),
|
|
434
|
+
),
|
|
435
|
+
),
|
|
436
|
+
t.ref`close`,
|
|
437
|
+
],
|
|
438
|
+
{
|
|
439
|
+
open: t.s_node(l.Instruction, 'Punctuator', '['),
|
|
440
|
+
elements: [...interpolateArray(elements)],
|
|
441
|
+
close: t.s_node(l.Instruction, 'Punctuator', ']'),
|
|
442
|
+
},
|
|
443
|
+
);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
export const buildTuple = (values) => {
|
|
447
|
+
return t.node(
|
|
448
|
+
l.Instruction,
|
|
449
|
+
'Tuple',
|
|
450
|
+
[
|
|
451
|
+
t.ref`open`,
|
|
452
|
+
...interpolateArrayChildren(
|
|
453
|
+
values,
|
|
454
|
+
t.ref`values[]`,
|
|
455
|
+
t.embedded(
|
|
456
|
+
t.t_node(l.Comment, null, [t.embedded(t.t_node('Space', 'Space', [t.lit(' ')]))]),
|
|
457
|
+
),
|
|
458
|
+
),
|
|
459
|
+
t.ref`close`,
|
|
460
|
+
],
|
|
461
|
+
{
|
|
462
|
+
open: t.s_node(l.Instruction, 'Punctuator', '('),
|
|
463
|
+
values: [...interpolateArray(values)],
|
|
464
|
+
close: t.s_node(l.Instruction, 'Punctuator', ')'),
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
export const buildObject = (properties) => {
|
|
470
|
+
return t.node(
|
|
471
|
+
l.Instruction,
|
|
472
|
+
'Object',
|
|
473
|
+
[
|
|
474
|
+
t.ref`open`,
|
|
475
|
+
...interpolateArrayChildren(
|
|
476
|
+
Object.entries(properties).map(([key, value]) => buildProperty(key, value)),
|
|
477
|
+
t.ref`properties[]`,
|
|
478
|
+
t.embedded(
|
|
479
|
+
t.t_node(l.Comment, null, [t.embedded(t.t_node('Space', 'Space', [t.lit(' ')]))]),
|
|
480
|
+
),
|
|
481
|
+
),
|
|
482
|
+
t.ref`close`,
|
|
483
|
+
],
|
|
484
|
+
{
|
|
485
|
+
open: t.s_node(l.Instruction, 'Punctuator', '{'),
|
|
486
|
+
properties: [
|
|
487
|
+
...interpolateArray(
|
|
488
|
+
Object.entries(properties).map(([key, value]) => buildProperty(key, value)),
|
|
489
|
+
),
|
|
490
|
+
],
|
|
491
|
+
close: t.s_node(l.Instruction, 'Punctuator', '}'),
|
|
492
|
+
},
|
|
493
|
+
{},
|
|
494
|
+
);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
export const buildMappingAttribute = (key, value) => {
|
|
498
|
+
return t.node(l.CSTML, 'MappingAttribute', [t.ref`key`, t.ref`mapOperator`, t.ref`value`], {
|
|
499
|
+
key: buildIdentifier(key),
|
|
500
|
+
mapOperator: t.s_node(l.CSTML, 'Punctuator', '='),
|
|
501
|
+
value: buildExpression(value),
|
|
502
|
+
});
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
export const buildBooleanAttribute = (key, value) => {
|
|
506
|
+
return t.node(l.CSTML, 'BooleanAttribute', [...when(!value, [t.ref`negateSigil`]), t.ref`key`], {
|
|
507
|
+
negateSigil: !value ? t.s_node(l.CSTML, 'Puncutator', '!') : null,
|
|
508
|
+
key: buildIdentifier(key),
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
export const buildAttribute = (key, value) => {
|
|
513
|
+
return isBoolean(value) ? buildBooleanAttribute(key, value) : buildMappingAttribute(key, value);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
export const buildExpression = (expr) => {
|
|
517
|
+
if (expr == null) return buildNull();
|
|
518
|
+
|
|
519
|
+
switch (typeof expr) {
|
|
520
|
+
case 'boolean':
|
|
521
|
+
return buildBoolean(expr);
|
|
522
|
+
case 'string':
|
|
523
|
+
return buildString(expr);
|
|
524
|
+
case 'object': {
|
|
525
|
+
switch (getPrototypeOf(expr)) {
|
|
526
|
+
case Array.prototype:
|
|
527
|
+
return buildArray(expr);
|
|
528
|
+
case Object.prototype:
|
|
529
|
+
if (expr.type && expr.language && expr.children && expr.properties) {
|
|
530
|
+
return expr;
|
|
531
|
+
}
|
|
532
|
+
return buildObject(expr);
|
|
533
|
+
default:
|
|
534
|
+
throw new Error();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
default:
|
|
538
|
+
throw new Error();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
export const buildNodeMatcher = (flags, language, type, attributes = {}) => {
|
|
543
|
+
const attributes_ = Object.entries(attributes).map(({ 0: key, 1: value }) =>
|
|
544
|
+
buildAttribute(key, value),
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
let language_ = !language || language.length === 0 ? null : language;
|
|
548
|
+
|
|
549
|
+
const flags_ = buildFlags(flags);
|
|
550
|
+
|
|
551
|
+
return t.node(
|
|
552
|
+
l.Spamex,
|
|
553
|
+
'NodeMatcher',
|
|
554
|
+
[
|
|
555
|
+
t.ref`open`,
|
|
556
|
+
...when(flags_, [t.ref`flags`]),
|
|
557
|
+
...when(language_, [t.ref`language`, t.ref`languageSeparator`]),
|
|
558
|
+
t.ref`type`,
|
|
559
|
+
...when(attributes_.length, [t.embedded(buildSpace())]),
|
|
560
|
+
...interpolateArrayChildren(attributes_, t.ref`attributes[]`, t.embedded(buildSpace())),
|
|
561
|
+
t.ref`close`,
|
|
562
|
+
],
|
|
563
|
+
{
|
|
564
|
+
open: t.s_node(l.CSTML, 'Punctuator', '<'),
|
|
565
|
+
language: buildLanguage(language_),
|
|
566
|
+
languageSeparator: language_ && type && t.s_node(l.CSTML, 'Punctuator', ':'),
|
|
567
|
+
flags: flags_,
|
|
568
|
+
type: buildIdentifier(type),
|
|
569
|
+
attributes: attributes_,
|
|
570
|
+
close: t.s_node(l.CSTML, 'Punctuator', '>'),
|
|
571
|
+
},
|
|
572
|
+
);
|
|
573
|
+
};
|
package/lib/facades.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const buildFacadeLayer = () => {
|
|
2
|
+
const _facades = new WeakMap();
|
|
3
|
+
|
|
4
|
+
const facades = {
|
|
5
|
+
get(actual) {
|
|
6
|
+
return actual == null ? actual : _facades.get(actual);
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
set(actual, facade) {
|
|
10
|
+
if (_facades.has(actual) || actual === facade) {
|
|
11
|
+
throw new Error('facade mappings must be 1:1');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_facades.set(actual, facade);
|
|
15
|
+
_actuals.set(facade, actual);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const _actuals = new WeakMap();
|
|
20
|
+
|
|
21
|
+
const actuals = {
|
|
22
|
+
get(facade) {
|
|
23
|
+
return facade == null ? facade : _actuals.get(facade);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
set(facade, actual) {
|
|
27
|
+
if (_facades.has(actual) || _actuals.has(facade) || actual === facade) {
|
|
28
|
+
throw new Error('facade mappings must be 1:1');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_facades.set(actual, facade);
|
|
32
|
+
_actuals.set(facade, actual);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return { facades, actuals };
|
|
37
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { getCooked } from '@bablr/agast-helpers/tree';
|
|
2
|
+
|
|
3
|
+
export * from './builders.js';
|
|
4
|
+
|
|
5
|
+
export const effectsFor = (verb) => {
|
|
6
|
+
switch (verb) {
|
|
7
|
+
case 'eat':
|
|
8
|
+
case 'holdFor':
|
|
9
|
+
return { success: 'eat', failure: 'fail' };
|
|
10
|
+
|
|
11
|
+
case 'eatMatch':
|
|
12
|
+
case 'holdForMatch':
|
|
13
|
+
return { success: 'eat', failure: 'none' };
|
|
14
|
+
|
|
15
|
+
case 'match':
|
|
16
|
+
return { success: 'none', failure: 'none' };
|
|
17
|
+
|
|
18
|
+
case 'guard':
|
|
19
|
+
return { success: 'none', failure: 'fail' };
|
|
20
|
+
|
|
21
|
+
default:
|
|
22
|
+
throw new Error('invalid match verb');
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const shouldBranch = (effects) => {
|
|
27
|
+
return effects ? effects.success === 'none' || effects.failure === 'none' : false;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const reifyFlags = (node) => {
|
|
31
|
+
let { triviaFlag, escapeFlag, tokenFlag, expressionFlag, intrinsicValue } = node.properties || {};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
token: !!tokenFlag,
|
|
35
|
+
escape: !!escapeFlag,
|
|
36
|
+
trivia: !!triviaFlag,
|
|
37
|
+
intrinsic: !!intrinsicValue,
|
|
38
|
+
expression: !!expressionFlag,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const reifyLanguage = (language) => {
|
|
43
|
+
const value = reifyExpression(language);
|
|
44
|
+
|
|
45
|
+
if (typeof value === 'string' && !value.startsWith('https://')) {
|
|
46
|
+
throw new Error('bad language');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const reifyExpression = (node) => {
|
|
53
|
+
if (node instanceof Promise) throw new Error();
|
|
54
|
+
|
|
55
|
+
if (!node) return null;
|
|
56
|
+
|
|
57
|
+
if (node.language === 'https://bablr.org/languages/core/cstml') {
|
|
58
|
+
switch (node.type) {
|
|
59
|
+
case 'DoctypeTag': {
|
|
60
|
+
let { doctype, version, attributes } = node.properties;
|
|
61
|
+
return {
|
|
62
|
+
type: 'DoctypeTag',
|
|
63
|
+
value: {
|
|
64
|
+
doctype: getCooked(doctype),
|
|
65
|
+
version: parseInt(getCooked(version), 10),
|
|
66
|
+
attributes: reifyAttributes(attributes),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case 'Reference': {
|
|
72
|
+
let { name, arrayOperator } = node.properties;
|
|
73
|
+
|
|
74
|
+
name = reifyExpression(name);
|
|
75
|
+
|
|
76
|
+
return { type: 'Reference', value: { name, isArray: !!arrayOperator } };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'Literal': {
|
|
80
|
+
let { value } = node.properties;
|
|
81
|
+
|
|
82
|
+
return { type: 'Literal', value: getCooked(value.properties.content) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case 'OpenFragmentTag': {
|
|
86
|
+
return { type: 'OpenFragmentTag', value: undefined };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case 'CloseFragmentTag': {
|
|
90
|
+
return { type: 'CloseFragmentTag', value: {} };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'IdentifierPath': {
|
|
94
|
+
return node.properties.segments.map((segment) => reifyExpression(segment));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case 'OpenNodeTag': {
|
|
98
|
+
let { language, type, attributes, intrinsicValue } = node.properties;
|
|
99
|
+
|
|
100
|
+
let flags = reifyFlags(node);
|
|
101
|
+
language = reifyLanguage(language);
|
|
102
|
+
type = reifyExpression(type);
|
|
103
|
+
attributes = reifyAttributes(attributes);
|
|
104
|
+
intrinsicValue = reifyExpression(intrinsicValue);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
type: 'OpenNodeTag',
|
|
108
|
+
value: { flags, language, type, intrinsicValue, properties: {}, attributes },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
case 'CloseNodeTag': {
|
|
113
|
+
let { language, type } = node.properties;
|
|
114
|
+
|
|
115
|
+
language = reifyLanguage(language);
|
|
116
|
+
type = reifyExpression(type);
|
|
117
|
+
|
|
118
|
+
return { type: 'CloseNodeTag', value: { language, type } };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case 'Integer': {
|
|
122
|
+
let { digits } = node.properties;
|
|
123
|
+
return parseInt(digits.map((digit) => getCooked(digit)).join(''), 10);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case 'Infinity': {
|
|
127
|
+
return node.properties.sign === '-' ? -Infinity : Infinity;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
case 'String':
|
|
131
|
+
return node.properties.content ? getCooked(node.properties.content) : '';
|
|
132
|
+
|
|
133
|
+
case 'Gap':
|
|
134
|
+
return { type: 'Gap', value: undefined };
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
throw new Error();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
![
|
|
143
|
+
'https://bablr.org/languages/core/bablr-vm-instruction',
|
|
144
|
+
'https://bablr.org/languages/core/cstml',
|
|
145
|
+
'https://bablr.org/languages/core/spamex',
|
|
146
|
+
].includes(node.language)
|
|
147
|
+
) {
|
|
148
|
+
return node;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
switch (node.type) {
|
|
152
|
+
case 'NodeMatcher':
|
|
153
|
+
let { language, type, attributes, intrinsicValue } = node.properties;
|
|
154
|
+
|
|
155
|
+
let flags = reifyFlags(node);
|
|
156
|
+
language = reifyExpression(language);
|
|
157
|
+
type = reifyExpression(type);
|
|
158
|
+
attributes = reifyAttributes(attributes);
|
|
159
|
+
intrinsicValue = reifyExpression(intrinsicValue);
|
|
160
|
+
|
|
161
|
+
return { flags, language, type, intrinsicValue, attributes };
|
|
162
|
+
|
|
163
|
+
case 'Call': {
|
|
164
|
+
const { verb, arguments: args } = node.properties;
|
|
165
|
+
return { verb: reifyExpression(verb), arguments: reifyExpression(args) };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case 'Object': {
|
|
169
|
+
const { properties } = node.properties;
|
|
170
|
+
|
|
171
|
+
return Object.fromEntries(
|
|
172
|
+
properties.map(({ properties: { key, value } }) => [
|
|
173
|
+
getCooked(key),
|
|
174
|
+
reifyExpression(value),
|
|
175
|
+
]),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case 'Tuple': {
|
|
180
|
+
const { values = [] } = node.properties;
|
|
181
|
+
|
|
182
|
+
return [...values.map((el) => reifyExpression(el))];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
case 'Array': {
|
|
186
|
+
const { elements } = node.properties;
|
|
187
|
+
|
|
188
|
+
return [...elements.map((el) => reifyExpression(el))];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case 'Literal':
|
|
192
|
+
case 'Identifier':
|
|
193
|
+
return getCooked(node);
|
|
194
|
+
|
|
195
|
+
case 'Boolean': {
|
|
196
|
+
// prettier-ignore
|
|
197
|
+
switch (getCooked(node.properties.value)) {
|
|
198
|
+
case 'true': return true;
|
|
199
|
+
case 'false': return false;
|
|
200
|
+
default: throw new Error();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
case 'Null':
|
|
205
|
+
return null;
|
|
206
|
+
|
|
207
|
+
default:
|
|
208
|
+
throw new Error('bad expression');
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const reifyExpressionShallow = (node) => {
|
|
213
|
+
if (!node) return null;
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
![
|
|
217
|
+
'https://bablr.org/languages/core/bablr-vm-instruction',
|
|
218
|
+
'https://bablr.org/languages/core/cstml',
|
|
219
|
+
].includes(node.language)
|
|
220
|
+
) {
|
|
221
|
+
return node;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
switch (node.type) {
|
|
225
|
+
case 'Object': {
|
|
226
|
+
const { properties } = node.properties;
|
|
227
|
+
|
|
228
|
+
return Object.fromEntries(
|
|
229
|
+
properties.map(({ properties: { key, value } }) => [getCooked(key), value]),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case 'Array':
|
|
234
|
+
return [...node.properties.elements];
|
|
235
|
+
|
|
236
|
+
case 'Tuple':
|
|
237
|
+
return [...node.properties.values];
|
|
238
|
+
|
|
239
|
+
default:
|
|
240
|
+
return reifyExpression(node);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const reifyAttributes = (attributes) => {
|
|
245
|
+
if (attributes == null) return {};
|
|
246
|
+
|
|
247
|
+
return Object.fromEntries(
|
|
248
|
+
attributes.map((attr) => {
|
|
249
|
+
if (attr.type === 'MappingAttribute') {
|
|
250
|
+
return [reifyExpression(attr.properties.key), reifyExpression(attr.properties.value)];
|
|
251
|
+
} else if (attr.type === 'BooleanAttribute') {
|
|
252
|
+
return [reifyExpression(attr.properties.key), !attr.properties.negateSigil];
|
|
253
|
+
} else {
|
|
254
|
+
throw new Error();
|
|
255
|
+
}
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
};
|
package/lib/languages.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const Spamex = 'https://bablr.org/languages/core/spamex';
|
|
2
|
+
export const CSTML = 'https://bablr.org/languages/core/cstml';
|
|
3
|
+
export const Regex = 'https://bablr.org/languages/core/bablr-regex-pattern';
|
|
4
|
+
export const Instruction = 'https://bablr.org/languages/core/bablr-vm-instruction';
|
|
5
|
+
export const Comment = 'https://bablr.org/languages/core/c-comments';
|
|
6
|
+
export const Space = 'https://bablr.org/languages/core/space-tab-newline';
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bablr/agast-vm-helpers",
|
|
3
|
+
"description": "Helper functions for working with the BABLR VM",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": "Conrad Buck<conartist6@gmail.com>",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./lib/index.js",
|
|
12
|
+
"./facades": "./lib/facades.js",
|
|
13
|
+
"./builders": "./lib/builders.js",
|
|
14
|
+
"./languages": "./lib/languages.js"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@bablr/agast-helpers": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#49f5952efed27f94ee9b94340eb1563c440bf64e",
|
|
22
|
+
"enhanced-resolve": "^5.12.0",
|
|
23
|
+
"eslint": "^8.32.0",
|
|
24
|
+
"eslint-import-resolver-enhanced-resolve": "^1.0.5",
|
|
25
|
+
"eslint-plugin-import": "^2.27.5",
|
|
26
|
+
"prettier": "^2.6.2"
|
|
27
|
+
},
|
|
28
|
+
"repository": "github:bablr-lang/agast-vm-helpers",
|
|
29
|
+
"homepage": "https://github.com/bablr-lang/agast-vm-helpers",
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|