@bablr/boot 0.6.3 → 0.7.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 DELETED
@@ -1,401 +0,0 @@
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');
15
-
16
- const { isInteger, isFinite } = Number;
17
- const { isArray } = Array;
18
- const isString = (val) => typeof val === 'string';
19
- const isNumber = (val) => typeof val === 'number';
20
-
21
- const { freeze, hasOwn } = Object;
22
-
23
- const get = (node, path) => {
24
- const { type, properties } = node;
25
- const { 1: name, 2: index } = /^([^\.]+)(?:\.(\d+))?/.exec(path) || [];
26
-
27
- if (!hasOwn(properties, name)) {
28
- throw new Error(`Cannot find {name: ${name}} on node of {type: ${type}}`);
29
- }
30
-
31
- if (index != null) {
32
- return btree.getAt(parseInt(index, 10), properties[name]);
33
- } else {
34
- return properties[name];
35
- }
36
- };
37
-
38
- class Resolver {
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;
47
- }
48
-
49
- get idx() {
50
- return this.states.value.idx;
51
- }
52
-
53
- get properties() {
54
- return this.states.value.properties;
55
- }
56
-
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;
96
-
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
- }
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
- }
163
-
164
- function* streamFromTree(rootNode) {
165
- if (!rootNode || rootNode.type === GapTag) {
166
- return rootNode;
167
- }
168
-
169
- let stack = emptyStack.push(rootNode);
170
- const resolver = new Resolver();
171
-
172
- stack: while (stack.size) {
173
- const node = stack.value;
174
- const { children } = node;
175
-
176
- while (true) {
177
- const tag = btree.getAt(resolver.idx, children);
178
-
179
- switch (tag.type) {
180
- case EmbeddedNode: {
181
- stack = stack.push(tag.value);
182
-
183
- resolver.advance(tag);
184
-
185
- continue stack;
186
- }
187
-
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);
198
-
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);
206
- }
207
- continue stack;
208
- }
209
-
210
- case GapTag:
211
- case NullTag:
212
- case CloseNodeTag: {
213
- stack = stack.pop();
214
- resolver.advance(tag);
215
- yield tag;
216
- continue stack;
217
- }
218
-
219
- default:
220
- resolver.advance(tag);
221
- yield tag;
222
- break;
223
- }
224
- }
225
- }
226
- }
227
-
228
- const printExpression = (expr) => {
229
- if (isString(expr)) {
230
- return printString(expr);
231
- } else if (expr == null || typeof expr === 'boolean') {
232
- return String(expr);
233
- } else if (isNumber(expr)) {
234
- if (!isInteger(expr) && isFinite(expr)) {
235
- throw new Error();
236
- }
237
- return String(expr);
238
- } else if (isArray(expr)) {
239
- return `[${expr.map((v) => printExpression(v)).join(', ')}]`;
240
- } else if (typeof expr === 'object') {
241
- return `{${Object.entries(expr).map(([k, v]) => `${k}: ${printExpression(v)}`)}}`;
242
- } else {
243
- throw new Error();
244
- }
245
- };
246
-
247
- const printLanguage = (language) => {
248
- if (isString(language)) {
249
- return printSingleString(language);
250
- } else {
251
- return language.join('.');
252
- }
253
- };
254
-
255
- const printTagPath = (language, type) => {
256
- return language?.length ? `${printLanguage(language)}:${type}` : type;
257
- };
258
-
259
- const printAttributes = (attributes) => {
260
- return Object.entries(attributes)
261
- .map(([k, v]) => (v === true ? k : `${k}=${printExpression(v)}`))
262
- .join(' ');
263
- };
264
-
265
- const printFlags = (flags) => {
266
- const hash = flags.trivia ? '#' : '';
267
- const star = flags.token ? '*' : '';
268
- const at = flags.escape ? '@' : '';
269
-
270
- if (flags.escape && flags.trivia) throw new Error('Node cannot be escape and trivia');
271
-
272
- return `${hash}${star}${at}`;
273
- };
274
-
275
- const printTag = (tag) => {
276
- switch (tag?.type || NullTag) {
277
- case NullTag: {
278
- return 'null';
279
- }
280
-
281
- case GapTag: {
282
- return `<//>`;
283
- }
284
-
285
- case ArrayTag: {
286
- return `[]`;
287
- }
288
-
289
- case LiteralTag: {
290
- return printString(tag.value);
291
- }
292
-
293
- case DoctypeTag: {
294
- let { doctype, attributes } = tag.value;
295
-
296
- attributes = attributes ? ` ${printAttributes(attributes)}` : '';
297
-
298
- return `<!${doctype}${attributes}>`;
299
- }
300
-
301
- case ReferenceTag: {
302
- const { name, isArray } = tag.value;
303
- const pathBraces = isArray ? '[]' : '';
304
-
305
- return `${name}${pathBraces}:`;
306
- }
307
-
308
- case OpenNodeTag: {
309
- const { flags, language: tagLanguage, type, attributes } = tag.value;
310
- const printedAttributes = attributes && printAttributes(attributes);
311
- const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
312
-
313
- if (type === sym.gap) {
314
- return '';
315
- }
316
-
317
- if (flags.escape && flags.trivia) throw new Error('Node cannot be escape and trivia');
318
-
319
- return `<${printFlags(flags)}${printTagPath(tagLanguage, type)}${attributesFrag}>`;
320
- }
321
-
322
- case CloseNodeTag: {
323
- return `</>`;
324
- }
325
-
326
- default:
327
- throw new Error(`Unexpected tag {type: ${tag?.type}}`);
328
- }
329
- };
330
-
331
- const printPrettyCSTML = (tree, indent = ' ') => {
332
- const tags = streamFromTree(tree);
333
-
334
- if (!tags) {
335
- return '<//>';
336
- }
337
-
338
- let printed = '';
339
- let indentLevel = 0;
340
- let first = true;
341
-
342
- for (const tag of tags) {
343
- if (!first && tag.type !== NullTag) {
344
- printed += '\n';
345
- }
346
-
347
- if (tag.type === CloseNodeTag) {
348
- indentLevel--;
349
- }
350
-
351
- if (tag.type !== NullTag) {
352
- printed += indent.repeat(indentLevel);
353
- } else {
354
- printed += ' ';
355
- }
356
- printed += printTag(tag);
357
-
358
- if (tag.type === OpenNodeTag) {
359
- indentLevel++;
360
- }
361
-
362
- first = false;
363
- }
364
-
365
- return printed;
366
- };
367
-
368
- const escapeReplacer = (esc) => {
369
- if (esc === '\r') {
370
- return '\\r';
371
- } else if (esc === '\n') {
372
- return '\\n';
373
- } else if (esc === '\0') {
374
- return '\\0';
375
- } else {
376
- return `\\${esc}`;
377
- }
378
- };
379
-
380
- const printSingleString = (str) => {
381
- return `'${str.replace(/['\\\0\r\n]/g, escapeReplacer)}'`;
382
- };
383
-
384
- const printDoubleString = (str) => {
385
- return `"${str.replace(/["\\\0\r\n]/g, escapeReplacer)}"`;
386
- };
387
-
388
- const printString = (str) => {
389
- return str === "'" ? printDoubleString(str) : printSingleString(str);
390
- };
391
-
392
- module.exports = {
393
- Resolver,
394
- printExpression,
395
- printAttributes,
396
- printTag,
397
- printPrettyCSTML,
398
- printSingleString,
399
- printDoubleString,
400
- printString,
401
- };