@bablr/boot 0.1.9 → 0.2.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/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 +31 -60
- 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/lib/print.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
const emptyStack = require('@iter-tools/imm-stack');
|
|
2
|
+
|
|
3
|
+
const { isInteger, isFinite } = Number;
|
|
4
|
+
const { isArray } = Array;
|
|
5
|
+
const isString = (val) => typeof val === 'string';
|
|
6
|
+
const isNumber = (val) => typeof val === 'number';
|
|
7
|
+
|
|
8
|
+
const { freeze } = Object;
|
|
9
|
+
|
|
10
|
+
const get = (node, path) => {
|
|
11
|
+
const { 1: name, 2: index } = /^([^\.]+)(?:\.(\d+))?/.exec(path) || [];
|
|
12
|
+
|
|
13
|
+
if (index != null) {
|
|
14
|
+
return node.properties[name]?.[parseInt(index, 10)];
|
|
15
|
+
} else {
|
|
16
|
+
return node.properties[name];
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class Resolver {
|
|
21
|
+
constructor(node, counters = new Map()) {
|
|
22
|
+
this.node = node;
|
|
23
|
+
this.counters = counters;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
consume(reference) {
|
|
27
|
+
const { name, isArray } = reference.value;
|
|
28
|
+
const { counters } = this;
|
|
29
|
+
|
|
30
|
+
if (isArray) {
|
|
31
|
+
const count = counters.get(name) + 1 || 0;
|
|
32
|
+
|
|
33
|
+
counters.set(name, count);
|
|
34
|
+
} else {
|
|
35
|
+
if (counters.has(name)) throw new Error('attempted to consume property twice');
|
|
36
|
+
|
|
37
|
+
counters.set(name, 1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
resolve(reference) {
|
|
44
|
+
let { name, isArray } = reference.value;
|
|
45
|
+
const { counters } = this;
|
|
46
|
+
let path = name;
|
|
47
|
+
|
|
48
|
+
if (isArray) {
|
|
49
|
+
const count = counters.get(name) || 0;
|
|
50
|
+
|
|
51
|
+
path += '.' + count;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return path;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get(reference) {
|
|
58
|
+
if (!this.node) throw new Error('Cannot get from a resolver with no node');
|
|
59
|
+
|
|
60
|
+
return get(this.node, this.resolve(reference));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
branch() {
|
|
64
|
+
return new Resolver(this.node, new Map(this.counters));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
accept(resolver) {
|
|
68
|
+
this.counters = resolver.counters;
|
|
69
|
+
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const buildFrame = (node) => {
|
|
75
|
+
if (!node) throw new Error();
|
|
76
|
+
return { node, childrenIdx: -1, resolver: new Resolver(node) };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const buildNull = () => {
|
|
80
|
+
return freeze({ type: 'Null', value: undefined });
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const buildDoctypeTag = () => {
|
|
84
|
+
return freeze({ type: 'DoctypeTag', value: { doctype: 'cstml', version: 0 } });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const buildNodeOpenTag = (flags, language, type, attributes = {}) => {
|
|
88
|
+
let { token, trivia, escape } = flags;
|
|
89
|
+
|
|
90
|
+
token = !!token;
|
|
91
|
+
trivia = !!trivia;
|
|
92
|
+
escape = !!escape;
|
|
93
|
+
|
|
94
|
+
return freeze({
|
|
95
|
+
type: 'OpenNodeTag',
|
|
96
|
+
value: freeze({ flags: freeze({ token, trivia, escape }), language, type, attributes }),
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const nodeFlags = freeze({ escape: false, trivia: false, token: false });
|
|
101
|
+
|
|
102
|
+
const buildFragmentOpenTag = (flags = nodeFlags) => {
|
|
103
|
+
return freeze({ type: 'OpenFragmentTag', value: freeze({ flags: freeze(flags) }) });
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const buildNodeCloseTag = (type = null, language = null) => {
|
|
107
|
+
return freeze({ type: 'CloseNodeTag', value: freeze({ language, type }) });
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const buildFragmentCloseTag = () => {
|
|
111
|
+
return freeze({ type: 'CloseFragmentTag', value: freeze({}) });
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function* streamFromTree(rootNode) {
|
|
115
|
+
if (!rootNode || rootNode.type === 'Gap') {
|
|
116
|
+
return rootNode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
yield buildDoctypeTag();
|
|
120
|
+
yield buildFragmentOpenTag(undefined, rootNode.language[0]);
|
|
121
|
+
|
|
122
|
+
let stack = emptyStack.push(buildFrame(rootNode));
|
|
123
|
+
|
|
124
|
+
stack: while (stack.size) {
|
|
125
|
+
const frame = stack.value;
|
|
126
|
+
const { node, resolver } = frame;
|
|
127
|
+
const { language, type, attributes, flags } = node;
|
|
128
|
+
|
|
129
|
+
if (frame.childrenIdx === -1 && stack.size > 1) {
|
|
130
|
+
yield buildNodeOpenTag(flags, language, type, attributes);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
while (++frame.childrenIdx < node.children.length) {
|
|
134
|
+
const terminal = node.children[frame.childrenIdx];
|
|
135
|
+
|
|
136
|
+
switch (terminal.type) {
|
|
137
|
+
case 'Literal':
|
|
138
|
+
case 'Gap':
|
|
139
|
+
case 'Null': {
|
|
140
|
+
yield terminal;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case 'Embedded': {
|
|
145
|
+
stack = stack.push(buildFrame(terminal.value));
|
|
146
|
+
continue stack;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
case 'Reference': {
|
|
150
|
+
if (stack.size > 1) {
|
|
151
|
+
yield terminal;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const resolved = resolver.consume(terminal).get(terminal);
|
|
155
|
+
if (resolved) {
|
|
156
|
+
stack = stack.push(buildFrame(resolved));
|
|
157
|
+
continue stack;
|
|
158
|
+
} else {
|
|
159
|
+
yield buildNull();
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
default: {
|
|
165
|
+
throw new Error();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (stack.size > 1) {
|
|
171
|
+
yield buildNodeCloseTag(node.type, node.language);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
stack = stack.pop();
|
|
175
|
+
}
|
|
176
|
+
yield buildFragmentCloseTag();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const printExpression = (expr) => {
|
|
180
|
+
if (isString(expr)) {
|
|
181
|
+
return printString(expr);
|
|
182
|
+
} else if (expr == null || typeof expr === 'boolean') {
|
|
183
|
+
return String(expr);
|
|
184
|
+
} else if (isNumber(expr)) {
|
|
185
|
+
if (!isInteger(expr) && isFinite(expr)) {
|
|
186
|
+
throw new Error();
|
|
187
|
+
}
|
|
188
|
+
return String(expr);
|
|
189
|
+
} else if (isArray(expr)) {
|
|
190
|
+
return `[${expr.map((v) => printExpression(v)).join(', ')}]`;
|
|
191
|
+
} else if (typeof expr === 'object') {
|
|
192
|
+
return `{${Object.entries(expr).map(([k, v]) => `${k}: ${printExpression(v)}`)}}`;
|
|
193
|
+
} else {
|
|
194
|
+
throw new Error();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const printLanguage = (language) => {
|
|
199
|
+
if (isString(language)) {
|
|
200
|
+
return printSingleString(language);
|
|
201
|
+
} else {
|
|
202
|
+
return language.join('.');
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const printTagPath = (language, type) => {
|
|
207
|
+
return language?.length ? `${printLanguage(language)}:${type}` : type;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const printAttributes = (attributes) => {
|
|
211
|
+
return Object.entries(attributes)
|
|
212
|
+
.map(([k, v]) => (v === true ? k : `${k}=${printExpression(v)}`))
|
|
213
|
+
.join(' ');
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const printFlags = (flags) => {
|
|
217
|
+
const hash = flags.trivia ? '#' : '';
|
|
218
|
+
const star = flags.token ? '*' : '';
|
|
219
|
+
const at = flags.escape ? '@' : '';
|
|
220
|
+
|
|
221
|
+
if (flags.escape && flags.trivia) throw new Error('Node cannot be escape and trivia');
|
|
222
|
+
|
|
223
|
+
return `${hash}${star}${at}`;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const printTerminal = (terminal) => {
|
|
227
|
+
switch (terminal?.type || 'Null') {
|
|
228
|
+
case 'Null': {
|
|
229
|
+
return 'null';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
case 'Gap': {
|
|
233
|
+
return `<//>`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'Literal': {
|
|
237
|
+
return printString(terminal.value);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case 'DoctypeTag': {
|
|
241
|
+
let { doctype, attributes } = terminal.value;
|
|
242
|
+
|
|
243
|
+
attributes = attributes ? ` ${printAttributes(attributes)}` : '';
|
|
244
|
+
|
|
245
|
+
return `<!${doctype}${attributes}>`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
case 'Reference': {
|
|
249
|
+
const { name, isArray } = terminal.value;
|
|
250
|
+
const pathBraces = isArray ? '[]' : '';
|
|
251
|
+
|
|
252
|
+
return `${name}${pathBraces}:`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case 'OpenNodeTag': {
|
|
256
|
+
const { flags, language: tagLanguage, type, attributes } = terminal.value;
|
|
257
|
+
const printedAttributes = attributes && printAttributes(attributes);
|
|
258
|
+
const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
|
|
259
|
+
|
|
260
|
+
if (flags.escape && flags.trivia) throw new Error('Node cannot be escape and trivia');
|
|
261
|
+
|
|
262
|
+
return `<${printFlags(flags)}${printTagPath(tagLanguage, type)}${attributesFrag}>`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case 'OpenFragmentTag': {
|
|
266
|
+
const { flags } = terminal.value;
|
|
267
|
+
return `<${printFlags(flags)}>`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
case 'CloseNodeTag': {
|
|
271
|
+
return `</>`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
case 'CloseFragmentTag': {
|
|
275
|
+
return `</>`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
default:
|
|
279
|
+
throw new Error();
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const printPrettyCSTML = (tree, indent = ' ') => {
|
|
284
|
+
const terminals = streamFromTree(tree);
|
|
285
|
+
|
|
286
|
+
if (!terminals) {
|
|
287
|
+
return '<//>';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let printed = '';
|
|
291
|
+
let indentLevel = 0;
|
|
292
|
+
let first = true;
|
|
293
|
+
|
|
294
|
+
for (const terminal of terminals) {
|
|
295
|
+
if (!first && terminal.type !== 'Null') {
|
|
296
|
+
printed += '\n';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (['CloseNodeTag', 'CloseFragmentTag'].includes(terminal.type)) {
|
|
300
|
+
indentLevel--;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (terminal.type !== 'Null') {
|
|
304
|
+
printed += indent.repeat(indentLevel);
|
|
305
|
+
} else {
|
|
306
|
+
printed += ' ';
|
|
307
|
+
}
|
|
308
|
+
printed += printTerminal(terminal);
|
|
309
|
+
|
|
310
|
+
if (['OpenFragmentTag', 'OpenNodeTag'].includes(terminal.type)) {
|
|
311
|
+
indentLevel++;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
first = false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return printed;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const escapeReplacer = (esc) => {
|
|
321
|
+
if (esc === '\r') {
|
|
322
|
+
return '\\r';
|
|
323
|
+
} else if (esc === '\n') {
|
|
324
|
+
return '\\n';
|
|
325
|
+
} else if (esc === '\0') {
|
|
326
|
+
return '\\0';
|
|
327
|
+
} else {
|
|
328
|
+
return `\\${esc}`;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const printSingleString = (str) => {
|
|
333
|
+
return `'${str.replace(/['\\\0\r\n]/g, escapeReplacer)}'`;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const printDoubleString = (str) => {
|
|
337
|
+
return `"${str.replace(/["\\\0\r\n]/g, escapeReplacer)}"`;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const printString = (str) => {
|
|
341
|
+
return str === "'" ? printDoubleString(str) : printSingleString(str);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
module.exports = {
|
|
345
|
+
printExpression,
|
|
346
|
+
printAttributes,
|
|
347
|
+
printTerminal,
|
|
348
|
+
printPrettyCSTML,
|
|
349
|
+
printSingleString,
|
|
350
|
+
printDoubleString,
|
|
351
|
+
printString,
|
|
352
|
+
};
|
package/lib/utils.js
CHANGED
|
@@ -53,25 +53,26 @@ const buildCovers = (rawAliases) => {
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const set = (obj, path, value) => {
|
|
56
|
-
const { pathIsArray,
|
|
56
|
+
const { isArray: pathIsArray, name } = path;
|
|
57
57
|
|
|
58
|
-
if (!
|
|
58
|
+
if (!name) {
|
|
59
59
|
throw new Error();
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (pathIsArray) {
|
|
63
|
-
if (!obj[
|
|
64
|
-
obj[
|
|
63
|
+
if (!obj[name]) {
|
|
64
|
+
obj[name] = [];
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
if (!isArray(obj[
|
|
67
|
+
if (!isArray(obj[name])) throw new Error('bad array value');
|
|
68
68
|
|
|
69
|
-
obj[
|
|
69
|
+
obj[name].push(value);
|
|
70
70
|
} else {
|
|
71
|
-
if (hasOwn(obj,
|
|
71
|
+
if (hasOwn(obj, name)) {
|
|
72
72
|
throw new Error('duplicate child name');
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
|
|
75
|
+
obj[name] = value;
|
|
75
76
|
}
|
|
76
77
|
};
|
|
77
78
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/boot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Compile-time tools for bootstrapping BABLR VM",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=12.0.0"
|
|
7
7
|
},
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"require": "./lib/index.js",
|
|
11
|
+
"import": "./lib/index.mjs"
|
|
12
|
+
},
|
|
10
13
|
"./shorthand.macro": "./shorthand.macro.js"
|
|
11
14
|
},
|
|
12
15
|
"files": [
|
|
@@ -18,9 +21,10 @@
|
|
|
18
21
|
"@babel/helper-module-imports": "^7.22.15",
|
|
19
22
|
"@babel/template": "^7.22.15",
|
|
20
23
|
"@babel/types": "7.23.0",
|
|
21
|
-
"@bablr/boot-helpers": "0.
|
|
24
|
+
"@bablr/boot-helpers": "0.2.0",
|
|
22
25
|
"escape-string-regexp": "4.0.0",
|
|
23
|
-
"iter-tools-es": "^7.5.3"
|
|
26
|
+
"iter-tools-es": "^7.5.3",
|
|
27
|
+
"jest-diff": "29.7.0"
|
|
24
28
|
},
|
|
25
29
|
"devDependencies": {
|
|
26
30
|
"@babel/cli": "^7.23.0",
|
|
@@ -28,7 +32,6 @@
|
|
|
28
32
|
"@babel/plugin-proposal-decorators": "^7.23.2",
|
|
29
33
|
"@babel/plugin-transform-runtime": "^7.23.2",
|
|
30
34
|
"@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#a49dbe6861a82d96864ee89fbf0ec1844a8a5e8e",
|
|
31
|
-
"@bablr/helpers": "github:bablr-lang/helpers#8ef702024395635c9a05fe4c9f803a48f87d902c",
|
|
32
35
|
"babel-plugin-macros": "^3.1.0",
|
|
33
36
|
"enhanced-resolve": "^5.12.0",
|
|
34
37
|
"eslint": "^8.47.0",
|