@bablr/boot 0.4.0 → 0.6.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,169 +1,228 @@
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;
36
59
 
37
- counters.set(name, 1);
38
- }
60
+ ++states.value.idx;
39
61
 
40
- return this;
41
- }
62
+ this.popped = false;
42
63
 
43
- resolve(reference) {
44
- let { name, isArray } = reference.value;
45
- const { counters } = this;
46
- let path = name;
64
+ switch (tag.type) {
65
+ case ReferenceTag: {
66
+ const { name, isArray } = tag.value;
67
+ const { properties } = states.value;
47
68
 
48
- if (isArray) {
49
- const count = counters.get(name) || 0;
69
+ this.reference = tag;
50
70
 
51
- path += '.' + count;
52
- }
71
+ let state = properties.get(name);
53
72
 
54
- return path;
55
- }
73
+ if (isArray) {
74
+ if (state && !state.isArray) throw new Error();
56
75
 
57
- get(reference) {
58
- if (!this.node) throw new Error('Cannot get from a resolver with no node');
76
+ const { count = -1 } = state || {};
59
77
 
60
- return get(this.node, this.resolve(reference));
61
- }
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
+ }
62
84
 
63
- branch() {
64
- return new Resolver(this.node, new Map(this.counters));
65
- }
85
+ properties.set(name, state);
86
+
87
+ if (!isArray || state.count > 0) {
88
+ this.states = states.push({ properties: new Map(), idx: 0 });
89
+ }
66
90
 
67
- accept(resolver) {
68
- this.counters = resolver.counters;
91
+ break;
92
+ }
69
93
 
70
- return this;
71
- }
72
- }
94
+ case EmbeddedNode: {
95
+ this.reference = tag;
73
96
 
74
- const buildFrame = (node) => {
75
- if (!node) throw new Error();
76
- return { node, childrenIdx: -1, resolver: new Resolver(node) };
77
- };
97
+ this.states = states.push({ properties: new Map(), idx: 0 });
98
+ break;
99
+ }
78
100
 
79
- const buildNull = () => {
80
- return freeze({ type: 'Null', value: undefined });
81
- };
101
+ case OpenNodeTag: {
102
+ const { flags } = tag.value;
103
+ const isRootNode = states.size === 1;
82
104
 
83
- const buildDoctypeTag = () => {
84
- return freeze({ type: 'DoctypeTag', value: { doctype: 'cstml', version: 0 } });
85
- };
105
+ if (!isRootNode && !this.reference && !(flags.trivia || flags.escape)) {
106
+ throw new Error();
107
+ }
86
108
 
87
- const buildNodeOpenTag = (flags = {}, language = null, type = null, attributes = {}) => {
88
- let { token, trivia, escape } = flags;
109
+ if (this.reference?.type !== EmbeddedNode && (flags.trivia || flags.escape)) {
110
+ this.states = states.push({ properties: new Map(), idx: 0 });
111
+ }
89
112
 
90
- token = !!token;
91
- trivia = !!trivia;
92
- escape = !!escape;
113
+ this.reference = null;
114
+ break;
115
+ }
93
116
 
94
- return freeze({
95
- type: 'OpenNodeTag',
96
- value: freeze({ flags: freeze({ token, trivia, escape }), language, type, attributes }),
97
- });
98
- };
117
+ case ArrayTag: {
118
+ if (!this.reference) throw new Error();
99
119
 
100
- const buildNodeCloseTag = (type = null, language = null) => {
101
- return freeze({ type: 'CloseNodeTag', value: freeze({ language, type }) });
102
- };
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
+ }
144
+ }
145
+
146
+ return this;
147
+ }
148
+
149
+ resolve(reference) {
150
+ let { name, isArray } = reference.value;
151
+ const { states } = this;
152
+ const state = states.value.properties.get(name);
153
+ let path = name;
154
+
155
+ if (isArray) {
156
+ const count = state?.count || 0;
157
+ path += '.' + count;
158
+ }
159
+
160
+ return path;
161
+ }
162
+ }
103
163
 
104
164
  function* streamFromTree(rootNode) {
105
- if (!rootNode || rootNode.type === 'Gap') {
165
+ if (!rootNode || rootNode.type === GapTag) {
106
166
  return rootNode;
107
167
  }
108
168
 
109
- yield buildDoctypeTag();
110
- yield buildNodeOpenTag(undefined, rootNode.language[0]);
111
-
112
- let stack = emptyStack.push(buildFrame(rootNode));
169
+ let stack = emptyStack.push(rootNode);
170
+ const resolver = new Resolver();
113
171
 
114
172
  stack: while (stack.size) {
115
- const frame = stack.value;
116
- const { node, resolver } = frame;
117
- const { language, type, attributes, flags } = node;
173
+ const node = stack.value;
174
+ const { children } = node;
118
175
 
119
- if (frame.childrenIdx === -1 && stack.size > 1) {
120
- yield buildNodeOpenTag(flags, language, type, attributes);
121
- }
176
+ while (true) {
177
+ const tag = btree.getAt(resolver.idx, children);
122
178
 
123
- while (++frame.childrenIdx < node.children.length) {
124
- const terminal = node.children[frame.childrenIdx];
179
+ switch (tag.type) {
180
+ case EmbeddedNode: {
181
+ stack = stack.push(tag.value);
125
182
 
126
- switch (terminal.type) {
127
- case 'Literal':
128
- case 'Gap':
129
- case 'Null': {
130
- yield terminal;
131
- break;
132
- }
183
+ resolver.advance(tag);
133
184
 
134
- case 'EmbeddedNode': {
135
- stack = stack.push(buildFrame(terminal.value));
136
185
  continue stack;
137
186
  }
138
187
 
139
- case 'Reference': {
140
- if (stack.size > 1) {
141
- yield terminal;
142
- }
188
+ case ReferenceTag: {
189
+ const resolvedPath = resolver.resolve(tag);
190
+ const resolved = get(stack.value, resolvedPath);
191
+ const { name, isArray: refIsArray } = tag.value;
192
+
193
+ if (!resolved) throw new Error();
194
+
195
+ yield tag;
196
+
197
+ resolver.advance(tag);
143
198
 
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;
199
+ const resolverState = resolver.properties.get(name);
200
+
201
+ const isEmptyArray = resolverState?.count === 0;
202
+
203
+ if (!refIsArray || !isEmptyArray) {
204
+ if (isArray(resolved)) throw new Error();
205
+ stack = stack.push(resolved);
151
206
  }
207
+ continue stack;
152
208
  }
153
209
 
154
- default: {
155
- throw new Error();
210
+ case GapTag:
211
+ case NullTag:
212
+ case CloseNodeTag: {
213
+ stack = stack.pop();
214
+ resolver.advance(tag);
215
+ yield tag;
216
+ continue stack;
156
217
  }
157
- }
158
- }
159
218
 
160
- if (stack.size > 1) {
161
- yield buildNodeCloseTag(node.type, node.language);
219
+ default:
220
+ resolver.advance(tag);
221
+ yield tag;
222
+ break;
223
+ }
162
224
  }
163
-
164
- stack = stack.pop();
165
225
  }
166
- yield buildNodeCloseTag();
167
226
  }
168
227
 
169
228
  const printExpression = (expr) => {
@@ -213,58 +272,66 @@ const printFlags = (flags) => {
213
272
  return `${hash}${star}${at}`;
214
273
  };
215
274
 
216
- const printTerminal = (terminal) => {
217
- switch (terminal?.type || 'Null') {
218
- case 'Null': {
275
+ const printTag = (tag) => {
276
+ switch (tag?.type || NullTag) {
277
+ case NullTag: {
219
278
  return 'null';
220
279
  }
221
280
 
222
- case 'Gap': {
281
+ case GapTag: {
223
282
  return `<//>`;
224
283
  }
225
284
 
226
- case 'Literal': {
227
- return printString(terminal.value);
285
+ case ArrayTag: {
286
+ return `[]`;
228
287
  }
229
288
 
230
- case 'DoctypeTag': {
231
- let { doctype, attributes } = terminal.value;
289
+ case LiteralTag: {
290
+ return printString(tag.value);
291
+ }
292
+
293
+ case DoctypeTag: {
294
+ let { doctype, attributes } = tag.value;
232
295
 
233
296
  attributes = attributes ? ` ${printAttributes(attributes)}` : '';
234
297
 
235
298
  return `<!${doctype}${attributes}>`;
236
299
  }
237
300
 
238
- case 'Reference': {
239
- const { name, isArray } = terminal.value;
301
+ case ReferenceTag: {
302
+ const { name, isArray } = tag.value;
240
303
  const pathBraces = isArray ? '[]' : '';
241
304
 
242
305
  return `${name}${pathBraces}:`;
243
306
  }
244
307
 
245
- case 'OpenNodeTag': {
246
- const { flags, language: tagLanguage, type, attributes } = terminal.value;
308
+ case OpenNodeTag: {
309
+ const { flags, language: tagLanguage, type, attributes } = tag.value;
247
310
  const printedAttributes = attributes && printAttributes(attributes);
248
311
  const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
249
312
 
313
+ if (type === sym.gap) {
314
+ return '';
315
+ }
316
+
250
317
  if (flags.escape && flags.trivia) throw new Error('Node cannot be escape and trivia');
251
318
 
252
319
  return `<${printFlags(flags)}${printTagPath(tagLanguage, type)}${attributesFrag}>`;
253
320
  }
254
321
 
255
- case 'CloseNodeTag': {
322
+ case CloseNodeTag: {
256
323
  return `</>`;
257
324
  }
258
325
 
259
326
  default:
260
- throw new Error();
327
+ throw new Error(`Unexpected tag {type: ${tag?.type}}`);
261
328
  }
262
329
  };
263
330
 
264
331
  const printPrettyCSTML = (tree, indent = ' ') => {
265
- const terminals = streamFromTree(tree);
332
+ const tags = streamFromTree(tree);
266
333
 
267
- if (!terminals) {
334
+ if (!tags) {
268
335
  return '<//>';
269
336
  }
270
337
 
@@ -272,23 +339,23 @@ const printPrettyCSTML = (tree, indent = ' ') => {
272
339
  let indentLevel = 0;
273
340
  let first = true;
274
341
 
275
- for (const terminal of terminals) {
276
- if (!first && terminal.type !== 'Null') {
342
+ for (const tag of tags) {
343
+ if (!first && tag.type !== NullTag) {
277
344
  printed += '\n';
278
345
  }
279
346
 
280
- if (terminal.type === 'CloseNodeTag') {
347
+ if (tag.type === CloseNodeTag) {
281
348
  indentLevel--;
282
349
  }
283
350
 
284
- if (terminal.type !== 'Null') {
351
+ if (tag.type !== NullTag) {
285
352
  printed += indent.repeat(indentLevel);
286
353
  } else {
287
354
  printed += ' ';
288
355
  }
289
- printed += printTerminal(terminal);
356
+ printed += printTag(tag);
290
357
 
291
- if (terminal.type === 'OpenNodeTag') {
358
+ if (tag.type === OpenNodeTag) {
292
359
  indentLevel++;
293
360
  }
294
361
 
@@ -323,9 +390,10 @@ const printString = (str) => {
323
390
  };
324
391
 
325
392
  module.exports = {
393
+ Resolver,
326
394
  printExpression,
327
395
  printAttributes,
328
- printTerminal,
396
+ printTag,
329
397
  printPrettyCSTML,
330
398
  printSingleString,
331
399
  printDoubleString,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bablr/boot",
3
- "version": "0.4.0",
3
+ "version": "0.6.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.1",
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"