@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/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 node.properties[name]?.[parseInt(index, 10)];
32
+ return btree.getAt(parseInt(index, 10), properties[name]);
15
33
  } else {
16
- return node.properties[name];
34
+ return properties[name];
17
35
  }
18
36
  };
19
37
 
20
38
  class Resolver {
21
- constructor(node, counters = new Map()) {
22
- this.node = node;
23
- this.counters = counters;
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
- consume(reference) {
27
- const { name, isArray } = reference.value;
28
- const { counters } = this;
49
+ get idx() {
50
+ return this.states.value.idx;
51
+ }
29
52
 
30
- if (isArray) {
31
- const count = counters.get(name) + 1 || 0;
53
+ get properties() {
54
+ return this.states.value.properties;
55
+ }
32
56
 
33
- counters.set(name, count);
34
- } else {
35
- if (counters.has(name)) throw new Error('attempted to consume property twice');
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
- counters.set(name, 1);
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 { counters } = this;
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 = counters.get(name) || 0;
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: 'DoctypeTag', value: { doctype: 'cstml', version: 0 } });
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 === 'Gap') {
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(buildFrame(rootNode));
175
+ let stack = emptyStack.push(rootNode);
176
+ const resolver = new Resolver();
113
177
 
114
178
  stack: while (stack.size) {
115
- const frame = stack.value;
116
- const { node, resolver } = frame;
117
- const { language, type, attributes, flags } = node;
179
+ const node = stack.value;
180
+ const { children } = node;
118
181
 
119
- if (frame.childrenIdx === -1 && stack.size > 1) {
120
- yield buildNodeOpenTag(flags, language, type, attributes);
121
- }
182
+ while (true) {
183
+ const tag = btree.getAt(resolver.idx, children);
122
184
 
123
- while (++frame.childrenIdx < node.children.length) {
124
- const terminal = node.children[frame.childrenIdx];
185
+ switch (tag.type) {
186
+ case EmbeddedNode: {
187
+ stack = stack.push(tag.value);
125
188
 
126
- switch (terminal.type) {
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 'Reference': {
140
- if (stack.size > 1) {
141
- yield terminal;
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 resolved = resolver.consume(terminal).get(terminal);
145
- if (resolved) {
146
- stack = stack.push(buildFrame(resolved));
147
- continue stack;
148
- } else {
149
- yield buildNull();
150
- break;
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
- default: {
155
- throw new Error();
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
- if (stack.size > 1) {
161
- yield buildNodeCloseTag(node.type, node.language);
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 printTerminal = (terminal) => {
217
- switch (terminal?.type || 'Null') {
218
- case 'Null': {
281
+ const printTag = (tag) => {
282
+ switch (tag?.type || NullTag) {
283
+ case NullTag: {
219
284
  return 'null';
220
285
  }
221
286
 
222
- case 'Gap': {
287
+ case GapTag: {
223
288
  return `<//>`;
224
289
  }
225
290
 
226
- case 'Literal': {
227
- return printString(terminal.value);
291
+ case ArrayTag: {
292
+ return `[]`;
228
293
  }
229
294
 
230
- case 'DoctypeTag': {
231
- let { doctype, attributes } = terminal.value;
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 'Reference': {
239
- const { name, isArray } = terminal.value;
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 'OpenNodeTag': {
246
- const { flags, language: tagLanguage, type, attributes } = terminal.value;
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 'CloseNodeTag': {
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 terminals = streamFromTree(tree);
338
+ const tags = streamFromTree(tree);
266
339
 
267
- if (!terminals) {
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 terminal of terminals) {
276
- if (!first && terminal.type !== 'Null') {
348
+ for (const tag of tags) {
349
+ if (!first && tag.type !== NullTag) {
277
350
  printed += '\n';
278
351
  }
279
352
 
280
- if (terminal.type === 'CloseNodeTag') {
353
+ if (tag.type === CloseNodeTag) {
281
354
  indentLevel--;
282
355
  }
283
356
 
284
- if (terminal.type !== 'Null') {
357
+ if (tag.type !== NullTag) {
285
358
  printed += indent.repeat(indentLevel);
286
359
  } else {
287
360
  printed += ' ';
288
361
  }
289
- printed += printTerminal(terminal);
362
+ printed += printTag(tag);
290
363
 
291
- if (terminal.type === 'OpenNodeTag') {
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
- printTerminal,
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.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.2.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"