@bablr/boot 0.3.0 → 0.5.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/lib/builders.js +94 -55
- package/lib/index.js +108 -34
- package/lib/index.mjs +15 -1
- package/lib/languages/cstml.js +34 -29
- package/lib/languages/instruction.js +1 -1
- package/lib/languages/regex.js +2 -2
- package/lib/languages/spamex.js +41 -8
- package/lib/match.js +1 -1
- package/lib/miniparser.js +5 -4
- package/lib/print.js +199 -125
- package/package.json +3 -2
- package/shorthand.macro.js +111 -63
- package/lib/symbols.js +0 -4
package/lib/print.js
CHANGED
|
@@ -1,40 +1,146 @@
|
|
|
1
1
|
const emptyStack = require('@iter-tools/imm-stack');
|
|
2
|
+
const sym = require('@bablr/boot-helpers/symbols');
|
|
3
|
+
const {
|
|
4
|
+
DoctypeTag,
|
|
5
|
+
OpenNodeTag,
|
|
6
|
+
CloseNodeTag,
|
|
7
|
+
ReferenceTag,
|
|
8
|
+
GapTag,
|
|
9
|
+
NullTag,
|
|
10
|
+
ArrayTag,
|
|
11
|
+
LiteralTag,
|
|
12
|
+
EmbeddedNode,
|
|
13
|
+
} = require('@bablr/boot-helpers/symbols');
|
|
14
|
+
const btree = require('@bablr/boot-helpers/btree');
|
|
2
15
|
|
|
3
16
|
const { isInteger, isFinite } = Number;
|
|
4
17
|
const { isArray } = Array;
|
|
5
18
|
const isString = (val) => typeof val === 'string';
|
|
6
19
|
const isNumber = (val) => typeof val === 'number';
|
|
7
20
|
|
|
8
|
-
const { freeze } = Object;
|
|
21
|
+
const { freeze, hasOwn } = Object;
|
|
9
22
|
|
|
10
23
|
const get = (node, path) => {
|
|
24
|
+
const { type, properties } = node;
|
|
11
25
|
const { 1: name, 2: index } = /^([^\.]+)(?:\.(\d+))?/.exec(path) || [];
|
|
12
26
|
|
|
27
|
+
if (!hasOwn(properties, name)) {
|
|
28
|
+
throw new Error(`Cannot find {name: ${name}} on node of {type: ${type}}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
13
31
|
if (index != null) {
|
|
14
|
-
return
|
|
32
|
+
return btree.getAt(parseInt(index, 10), properties[name]);
|
|
15
33
|
} else {
|
|
16
|
-
return
|
|
34
|
+
return properties[name];
|
|
17
35
|
}
|
|
18
36
|
};
|
|
19
37
|
|
|
20
38
|
class Resolver {
|
|
21
|
-
constructor(
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
constructor(
|
|
40
|
+
states = emptyStack.push({ properties: new Map(), idx: 0 }),
|
|
41
|
+
reference = null,
|
|
42
|
+
popped = false,
|
|
43
|
+
) {
|
|
44
|
+
this.states = states;
|
|
45
|
+
this.reference = reference;
|
|
46
|
+
this.popped = popped;
|
|
24
47
|
}
|
|
25
48
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
49
|
+
get idx() {
|
|
50
|
+
return this.states.value.idx;
|
|
51
|
+
}
|
|
29
52
|
|
|
30
|
-
|
|
31
|
-
|
|
53
|
+
get properties() {
|
|
54
|
+
return this.states.value.properties;
|
|
55
|
+
}
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
57
|
+
advance(tag) {
|
|
58
|
+
const { states } = this;
|
|
59
|
+
|
|
60
|
+
++states.value.idx;
|
|
61
|
+
|
|
62
|
+
this.popped = false;
|
|
63
|
+
|
|
64
|
+
switch (tag.type) {
|
|
65
|
+
case ReferenceTag: {
|
|
66
|
+
const { name, isArray } = tag.value;
|
|
67
|
+
const { properties } = states.value;
|
|
68
|
+
|
|
69
|
+
this.reference = tag;
|
|
70
|
+
|
|
71
|
+
let state = properties.get(name);
|
|
72
|
+
|
|
73
|
+
if (isArray) {
|
|
74
|
+
if (state && !state.isArray) throw new Error();
|
|
75
|
+
|
|
76
|
+
const { count = -1 } = state || {};
|
|
77
|
+
|
|
78
|
+
state = { count: count + 1, isArray };
|
|
79
|
+
} else if (state) {
|
|
80
|
+
throw new Error(`attempted to consume property {name: ${name}} twice`);
|
|
81
|
+
} else {
|
|
82
|
+
state = { count: 1, isArray: false };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
properties.set(name, state);
|
|
86
|
+
|
|
87
|
+
if (!isArray || state.count > 0) {
|
|
88
|
+
this.states = states.push({ properties: new Map(), idx: 0 });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case EmbeddedNode: {
|
|
95
|
+
this.reference = tag;
|
|
36
96
|
|
|
37
|
-
|
|
97
|
+
this.states = states.push({ properties: new Map(), idx: 0 });
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case OpenNodeTag: {
|
|
102
|
+
const { flags } = tag.value;
|
|
103
|
+
const isRootNode = states.size === 1;
|
|
104
|
+
|
|
105
|
+
if (!isRootNode && !this.reference && !(flags.trivia || flags.escape)) {
|
|
106
|
+
throw new Error();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (this.reference?.type !== EmbeddedNode && (flags.trivia || flags.escape)) {
|
|
110
|
+
this.states = states.push({ properties: new Map(), idx: 0 });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.reference = null;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
case ArrayTag: {
|
|
118
|
+
if (!this.reference) throw new Error();
|
|
119
|
+
|
|
120
|
+
const { name } = this.reference.value;
|
|
121
|
+
const { properties } = states.value;
|
|
122
|
+
const state = properties.get(name);
|
|
123
|
+
|
|
124
|
+
if (!state || !state.isArray || state.count !== 0) throw new Error();
|
|
125
|
+
|
|
126
|
+
properties.set(name, { count: 0, isArray: true });
|
|
127
|
+
|
|
128
|
+
this.reference = null;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
case NullTag:
|
|
133
|
+
case GapTag: {
|
|
134
|
+
this.states = states.pop();
|
|
135
|
+
this.popped = true;
|
|
136
|
+
this.reference = null;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case CloseNodeTag: {
|
|
141
|
+
this.states = states.pop();
|
|
142
|
+
this.popped = true;
|
|
143
|
+
}
|
|
38
144
|
}
|
|
39
145
|
|
|
40
146
|
return this;
|
|
@@ -42,128 +148,87 @@ class Resolver {
|
|
|
42
148
|
|
|
43
149
|
resolve(reference) {
|
|
44
150
|
let { name, isArray } = reference.value;
|
|
45
|
-
const {
|
|
151
|
+
const { states } = this;
|
|
152
|
+
const state = states.value.properties.get(name);
|
|
46
153
|
let path = name;
|
|
47
154
|
|
|
48
155
|
if (isArray) {
|
|
49
|
-
const count =
|
|
50
|
-
|
|
156
|
+
const count = state?.count || 0;
|
|
51
157
|
path += '.' + count;
|
|
52
158
|
}
|
|
53
159
|
|
|
54
160
|
return path;
|
|
55
161
|
}
|
|
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
162
|
}
|
|
73
163
|
|
|
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
164
|
const buildDoctypeTag = () => {
|
|
84
|
-
return freeze({ type:
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const buildNodeOpenTag = (flags = {}, language = null, type = null, 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 buildNodeCloseTag = (type = null, language = null) => {
|
|
101
|
-
return freeze({ type: 'CloseNodeTag', value: freeze({ language, type }) });
|
|
165
|
+
return freeze({ type: DoctypeTag, value: { doctype: 'cstml', version: 0 } });
|
|
102
166
|
};
|
|
103
167
|
|
|
104
168
|
function* streamFromTree(rootNode) {
|
|
105
|
-
if (!rootNode || rootNode.type ===
|
|
169
|
+
if (!rootNode || rootNode.type === GapTag) {
|
|
106
170
|
return rootNode;
|
|
107
171
|
}
|
|
108
172
|
|
|
109
|
-
yield buildDoctypeTag();
|
|
110
|
-
yield buildNodeOpenTag(undefined, rootNode.language[0]);
|
|
173
|
+
yield buildDoctypeTag(rootNode.attributes);
|
|
111
174
|
|
|
112
|
-
let stack = emptyStack.push(
|
|
175
|
+
let stack = emptyStack.push(rootNode);
|
|
176
|
+
const resolver = new Resolver();
|
|
113
177
|
|
|
114
178
|
stack: while (stack.size) {
|
|
115
|
-
const
|
|
116
|
-
const {
|
|
117
|
-
const { language, type, attributes, flags } = node;
|
|
179
|
+
const node = stack.value;
|
|
180
|
+
const { children } = node;
|
|
118
181
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
182
|
+
while (true) {
|
|
183
|
+
const tag = btree.getAt(resolver.idx, children);
|
|
122
184
|
|
|
123
|
-
|
|
124
|
-
|
|
185
|
+
switch (tag.type) {
|
|
186
|
+
case EmbeddedNode: {
|
|
187
|
+
stack = stack.push(tag.value);
|
|
125
188
|
|
|
126
|
-
|
|
127
|
-
case 'Literal':
|
|
128
|
-
case 'Gap':
|
|
129
|
-
case 'Null': {
|
|
130
|
-
yield terminal;
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
189
|
+
resolver.advance(tag);
|
|
133
190
|
|
|
134
|
-
case 'Embedded': {
|
|
135
|
-
stack = stack.push(buildFrame(terminal.value));
|
|
136
191
|
continue stack;
|
|
137
192
|
}
|
|
138
193
|
|
|
139
|
-
case
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
194
|
+
case ReferenceTag: {
|
|
195
|
+
const resolvedPath = resolver.resolve(tag);
|
|
196
|
+
const resolved = get(stack.value, resolvedPath);
|
|
197
|
+
const { name, isArray: refIsArray } = tag.value;
|
|
198
|
+
|
|
199
|
+
if (!resolved) throw new Error();
|
|
200
|
+
|
|
201
|
+
yield tag;
|
|
202
|
+
|
|
203
|
+
resolver.advance(tag);
|
|
143
204
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
205
|
+
const resolverState = resolver.properties.get(name);
|
|
206
|
+
|
|
207
|
+
const isEmptyArray = resolverState?.count === 0;
|
|
208
|
+
|
|
209
|
+
if (!refIsArray || !isEmptyArray) {
|
|
210
|
+
if (isArray(resolved)) throw new Error();
|
|
211
|
+
stack = stack.push(resolved);
|
|
151
212
|
}
|
|
213
|
+
continue stack;
|
|
152
214
|
}
|
|
153
215
|
|
|
154
|
-
|
|
155
|
-
|
|
216
|
+
case GapTag:
|
|
217
|
+
case NullTag:
|
|
218
|
+
case CloseNodeTag: {
|
|
219
|
+
stack = stack.pop();
|
|
220
|
+
resolver.advance(tag);
|
|
221
|
+
yield tag;
|
|
222
|
+
continue stack;
|
|
156
223
|
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
224
|
|
|
160
|
-
|
|
161
|
-
|
|
225
|
+
default:
|
|
226
|
+
resolver.advance(tag);
|
|
227
|
+
yield tag;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
162
230
|
}
|
|
163
|
-
|
|
164
|
-
stack = stack.pop();
|
|
165
231
|
}
|
|
166
|
-
yield buildNodeCloseTag();
|
|
167
232
|
}
|
|
168
233
|
|
|
169
234
|
const printExpression = (expr) => {
|
|
@@ -213,58 +278,66 @@ const printFlags = (flags) => {
|
|
|
213
278
|
return `${hash}${star}${at}`;
|
|
214
279
|
};
|
|
215
280
|
|
|
216
|
-
const
|
|
217
|
-
switch (
|
|
218
|
-
case
|
|
281
|
+
const printTag = (tag) => {
|
|
282
|
+
switch (tag?.type || NullTag) {
|
|
283
|
+
case NullTag: {
|
|
219
284
|
return 'null';
|
|
220
285
|
}
|
|
221
286
|
|
|
222
|
-
case
|
|
287
|
+
case GapTag: {
|
|
223
288
|
return `<//>`;
|
|
224
289
|
}
|
|
225
290
|
|
|
226
|
-
case
|
|
227
|
-
return
|
|
291
|
+
case ArrayTag: {
|
|
292
|
+
return `[]`;
|
|
228
293
|
}
|
|
229
294
|
|
|
230
|
-
case
|
|
231
|
-
|
|
295
|
+
case LiteralTag: {
|
|
296
|
+
return printString(tag.value);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case DoctypeTag: {
|
|
300
|
+
let { doctype, attributes } = tag.value;
|
|
232
301
|
|
|
233
302
|
attributes = attributes ? ` ${printAttributes(attributes)}` : '';
|
|
234
303
|
|
|
235
304
|
return `<!${doctype}${attributes}>`;
|
|
236
305
|
}
|
|
237
306
|
|
|
238
|
-
case
|
|
239
|
-
const { name, isArray } =
|
|
307
|
+
case ReferenceTag: {
|
|
308
|
+
const { name, isArray } = tag.value;
|
|
240
309
|
const pathBraces = isArray ? '[]' : '';
|
|
241
310
|
|
|
242
311
|
return `${name}${pathBraces}:`;
|
|
243
312
|
}
|
|
244
313
|
|
|
245
|
-
case
|
|
246
|
-
const { flags, language: tagLanguage, type, attributes } =
|
|
314
|
+
case OpenNodeTag: {
|
|
315
|
+
const { flags, language: tagLanguage, type, attributes } = tag.value;
|
|
247
316
|
const printedAttributes = attributes && printAttributes(attributes);
|
|
248
317
|
const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
|
|
249
318
|
|
|
319
|
+
if (type === sym.gap) {
|
|
320
|
+
return '';
|
|
321
|
+
}
|
|
322
|
+
|
|
250
323
|
if (flags.escape && flags.trivia) throw new Error('Node cannot be escape and trivia');
|
|
251
324
|
|
|
252
325
|
return `<${printFlags(flags)}${printTagPath(tagLanguage, type)}${attributesFrag}>`;
|
|
253
326
|
}
|
|
254
327
|
|
|
255
|
-
case
|
|
328
|
+
case CloseNodeTag: {
|
|
256
329
|
return `</>`;
|
|
257
330
|
}
|
|
258
331
|
|
|
259
332
|
default:
|
|
260
|
-
throw new Error();
|
|
333
|
+
throw new Error(`Unexpected tag {type: ${tag?.type}}`);
|
|
261
334
|
}
|
|
262
335
|
};
|
|
263
336
|
|
|
264
337
|
const printPrettyCSTML = (tree, indent = ' ') => {
|
|
265
|
-
const
|
|
338
|
+
const tags = streamFromTree(tree);
|
|
266
339
|
|
|
267
|
-
if (!
|
|
340
|
+
if (!tags) {
|
|
268
341
|
return '<//>';
|
|
269
342
|
}
|
|
270
343
|
|
|
@@ -272,23 +345,23 @@ const printPrettyCSTML = (tree, indent = ' ') => {
|
|
|
272
345
|
let indentLevel = 0;
|
|
273
346
|
let first = true;
|
|
274
347
|
|
|
275
|
-
for (const
|
|
276
|
-
if (!first &&
|
|
348
|
+
for (const tag of tags) {
|
|
349
|
+
if (!first && tag.type !== NullTag) {
|
|
277
350
|
printed += '\n';
|
|
278
351
|
}
|
|
279
352
|
|
|
280
|
-
if (
|
|
353
|
+
if (tag.type === CloseNodeTag) {
|
|
281
354
|
indentLevel--;
|
|
282
355
|
}
|
|
283
356
|
|
|
284
|
-
if (
|
|
357
|
+
if (tag.type !== NullTag) {
|
|
285
358
|
printed += indent.repeat(indentLevel);
|
|
286
359
|
} else {
|
|
287
360
|
printed += ' ';
|
|
288
361
|
}
|
|
289
|
-
printed +=
|
|
362
|
+
printed += printTag(tag);
|
|
290
363
|
|
|
291
|
-
if (
|
|
364
|
+
if (tag.type === OpenNodeTag) {
|
|
292
365
|
indentLevel++;
|
|
293
366
|
}
|
|
294
367
|
|
|
@@ -323,9 +396,10 @@ const printString = (str) => {
|
|
|
323
396
|
};
|
|
324
397
|
|
|
325
398
|
module.exports = {
|
|
399
|
+
Resolver,
|
|
326
400
|
printExpression,
|
|
327
401
|
printAttributes,
|
|
328
|
-
|
|
402
|
+
printTag,
|
|
329
403
|
printPrettyCSTML,
|
|
330
404
|
printSingleString,
|
|
331
405
|
printDoubleString,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/boot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Compile-time tools for bootstrapping BABLR VM",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=12.0.0"
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"@babel/helper-module-imports": "^7.22.15",
|
|
23
23
|
"@babel/template": "^7.22.15",
|
|
24
24
|
"@babel/types": "7.23.0",
|
|
25
|
-
"@bablr/boot-helpers": "0.
|
|
25
|
+
"@bablr/boot-helpers": "0.3.0",
|
|
26
|
+
"@iter-tools/imm-stack": "1.1.0",
|
|
26
27
|
"escape-string-regexp": "4.0.0",
|
|
27
28
|
"iter-tools": "^7.5.3",
|
|
28
29
|
"jest-diff": "29.7.0"
|