@bablr/agast-helpers 0.2.0 → 0.3.1

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 CHANGED
@@ -1,5 +1,60 @@
1
+ import * as sym from './symbols.js';
2
+
1
3
  const { freeze } = Object;
2
4
 
5
+ const isObject = (val) => val !== null && typeof value !== 'object';
6
+
7
+ export const buildEmbeddedExpression = (expr) => {
8
+ if (!isObject(expr)) return expr;
9
+ return freeze({ type: 'EmbeddedExpression', value: expr });
10
+ };
11
+
12
+ export const buildEmbeddedTag = (tag) => {
13
+ return freeze({ type: 'EmbeddedTag', value: tag });
14
+ };
15
+
16
+ export const buildEffect = (value) => {
17
+ return freeze({ type: 'Effect', value });
18
+ };
19
+
20
+ export const buildWriteEffect = (text, options = {}) => {
21
+ return buildEffect(
22
+ buildEmbeddedExpression(
23
+ freeze({
24
+ verb: 'write',
25
+ value: buildEmbeddedExpression(
26
+ freeze({ text, options: buildEmbeddedExpression(freeze(options)) }),
27
+ ),
28
+ }),
29
+ ),
30
+ );
31
+ };
32
+
33
+ export const buildAnsiPushEffect = (spans = '') => {
34
+ return buildEffect(
35
+ buildEmbeddedExpression(
36
+ freeze({
37
+ verb: 'ansi-push',
38
+ value: buildEmbeddedExpression(
39
+ freeze({ spans: spans === '' ? freeze([]) : freeze(spans.split(' ')) }),
40
+ ),
41
+ }),
42
+ ),
43
+ );
44
+ };
45
+
46
+ export const buildAnsiPopEffect = () => {
47
+ return buildEffect(buildEmbeddedExpression(freeze({ verb: 'ansi-pop', value: undefined })));
48
+ };
49
+
50
+ export const buildTokenGroup = (tokens) => {
51
+ return freeze({ type: 'TokenGroup', value: tokens });
52
+ };
53
+
54
+ export const buildCall = (verb, ...args) => {
55
+ return { verb, arguments: args };
56
+ };
57
+
3
58
  export const buildBeginningOfStreamToken = () => {
4
59
  return freeze({ type: Symbol.for('@bablr/beginning-of-stream'), value: undefined });
5
60
  };
@@ -20,8 +75,8 @@ export const buildShift = () => {
20
75
  return freeze({ type: 'Shift', value: undefined });
21
76
  };
22
77
 
23
- export const buildEmbedded = (node) => {
24
- return freeze({ type: 'Embedded', value: node });
78
+ export const buildEmbeddedNode = (node) => {
79
+ return freeze({ type: 'EmbeddedNode', value: node });
25
80
  };
26
81
 
27
82
  export const buildDoctypeTag = (attributes) => {
@@ -31,13 +86,7 @@ export const buildDoctypeTag = (attributes) => {
31
86
  });
32
87
  };
33
88
 
34
- export const buildNodeOpenTag = (
35
- flags = {},
36
- language = null,
37
- type = null,
38
- intrinsicValue = null,
39
- attributes = {},
40
- ) => {
89
+ export const buildNodeOpenTag = (flags = {}, language = null, type = null, attributes = {}) => {
41
90
  let { token, trivia, escape, expression, intrinsic } = flags;
42
91
 
43
92
  token = !!token;
@@ -52,7 +101,6 @@ export const buildNodeOpenTag = (
52
101
  flags: freeze({ token, trivia, escape, intrinsic, expression }),
53
102
  language,
54
103
  type,
55
- intrinsicValue,
56
104
  attributes,
57
105
  }),
58
106
  });
@@ -223,20 +271,13 @@ export const buildTriviaNode = (language, type, children = [], properties = {},
223
271
  attributes: freeze(attributes),
224
272
  });
225
273
 
226
- export const buildEffect = (value) => {
227
- return freeze({ type: 'Effect', value });
228
- };
229
-
230
- export const buildWriteEffect = (text, options = {}) => {
231
- return buildEffect(freeze({ verb: 'write', value: freeze({ text, options }) }));
232
- };
233
-
234
- export const buildAnsiPushEffect = (spans = '') => {
235
- return buildEffect(
236
- freeze({ verb: 'ansi-push', value: { spans: spans === '' ? [] : spans.split(' ') } }),
237
- );
238
- };
239
-
240
- export const buildAnsiPopEffect = () => {
241
- return buildEffect(freeze({ verb: 'ansi-pop', value: undefined }));
274
+ export const buildNullNode = () => {
275
+ return freeze({
276
+ flags: nodeFlags,
277
+ language: null,
278
+ type: sym.null,
279
+ children: freeze([buildNull()]),
280
+ properties: freeze({}),
281
+ attributes: freeze({}),
282
+ });
242
283
  };
package/lib/print.js CHANGED
@@ -8,6 +8,22 @@ const isFunction = (val) => typeof val === 'function';
8
8
  const when = (condition, value) =>
9
9
  condition ? (isFunction(value) ? value() : value) : { *[Symbol.iterator]() {} };
10
10
 
11
+ export const printCall = (call) => {
12
+ const { verb, arguments: args } = call;
13
+ return `${verb}${printTuple(args)}`;
14
+ };
15
+
16
+ export const printArray = (arr) => `[${arr.map((v) => printExpression(v)).join(', ')}]`;
17
+
18
+ export const printTuple = (tup) => `(${tup.map((v) => printExpression(v)).join(', ')})`;
19
+
20
+ export const printObject = (obj) => {
21
+ const entries = Object.entries(obj);
22
+ return entries.length
23
+ ? `{ ${entries.map(([k, v]) => `${k}: ${printExpression(v)}`).join(', ')} }`
24
+ : '{}';
25
+ };
26
+
11
27
  export const printExpression = (expr) => {
12
28
  if (isString(expr)) {
13
29
  return printString(expr);
@@ -23,14 +39,33 @@ export const printExpression = (expr) => {
23
39
  throw new Error();
24
40
  }
25
41
  } else if (isArray(expr)) {
26
- return `[${expr.map((v) => printExpression(v)).join(', ')}]`;
42
+ return printArray(expr);
27
43
  } else if (typeof expr === 'object') {
28
- return `{${Object.entries(expr).map(([k, v]) => `${k}: ${printExpression(v)}`)}}`;
44
+ return printEmbedded(expr);
29
45
  } else {
30
46
  throw new Error();
31
47
  }
32
48
  };
33
49
 
50
+ export const printEmbedded = (value) => {
51
+ switch (value.type) {
52
+ case 'EmbeddedTag':
53
+ return printTerminal(value.value);
54
+
55
+ case 'EmbeddedExpression': {
56
+ return printObject(value.value);
57
+ }
58
+
59
+ case 'EmbeddedNode': {
60
+ throw new Error('not implemented');
61
+ break;
62
+ }
63
+
64
+ default:
65
+ throw new Error();
66
+ }
67
+ };
68
+
34
69
  export const printAttributes = (attributes) => {
35
70
  return Object.entries(attributes)
36
71
  .map(([k, v]) => (v === true ? k : v === false ? `!${k}` : `${k}=${printExpression(v)}`))
@@ -46,9 +81,10 @@ export const printLanguage = (language) => {
46
81
  };
47
82
 
48
83
  export const printTagPath = (language, type) => {
49
- return [...when(language?.length, () => [printLanguage(language)]), ...when(type, [type])].join(
50
- ':',
51
- );
84
+ return [
85
+ ...when(type && language?.length, () => [printLanguage(language)]),
86
+ ...when(type, [type]),
87
+ ].join(':');
52
88
  };
53
89
 
54
90
  const escapeReplacer = (esc) => {
@@ -121,7 +157,8 @@ export const printDoctypeTag = (terminal) => {
121
157
 
122
158
  let { doctype, version, attributes } = terminal.value;
123
159
 
124
- attributes = attributes ? ` ${printAttributes(attributes)}` : '';
160
+ attributes =
161
+ attributes && Object.values(attributes).length ? ` ${printAttributes(attributes)}` : '';
125
162
 
126
163
  return `<!${version}:${doctype}${attributes}>`;
127
164
  };
@@ -147,17 +184,27 @@ export const printFlags = (flags) => {
147
184
  export const printOpenNodeTag = (terminal) => {
148
185
  if (terminal?.type !== 'OpenNodeTag') throw new Error();
149
186
 
150
- const { flags, language: tagLanguage, type, intrinsicValue, attributes } = terminal.value;
187
+ const { flags, language: tagLanguage, type, attributes } = terminal.value;
188
+
189
+ const printedAttributes = attributes && printAttributes(attributes);
190
+ const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
191
+
192
+ return `<${printFlags(flags)}${printTagPath(tagLanguage, type)}${attributesFrag}>`;
193
+ };
194
+
195
+ export const printSelfClosingNodeTag = (terminal, intrinsicValue) => {
196
+ if (terminal?.type !== 'OpenNodeTag') throw new Error();
197
+
198
+ const { flags, language: tagLanguage, type, attributes } = terminal.value;
151
199
 
152
200
  const printedAttributes = attributes && printAttributes(attributes);
153
201
  const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
154
202
  const intrinsicFrag = intrinsicValue ? ` ${printString(intrinsicValue)}` : '';
155
- const selfCloser = intrinsicValue ? ' /' : '';
156
203
 
157
204
  return `<${printFlags(flags)}${printTagPath(
158
205
  tagLanguage,
159
206
  type,
160
- )}${intrinsicFrag}${attributesFrag}${selfCloser}>`;
207
+ )}${intrinsicFrag}${attributesFrag} />`;
161
208
  };
162
209
 
163
210
  export const printCloseNodeTag = (terminal) => {
package/lib/shorthand.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildReference,
3
3
  buildGap,
4
- buildEmbedded,
4
+ buildEmbeddedNode,
5
5
  buildNodeOpenTag,
6
6
  buildNodeCloseTag,
7
7
  buildLiteral,
@@ -12,6 +12,7 @@ import {
12
12
  buildSyntacticEscapeNode,
13
13
  buildSyntacticTriviaNode,
14
14
  buildTriviaNode,
15
+ buildNullNode,
15
16
  } from './builders.js';
16
17
 
17
18
  export * from './builders.js';
@@ -43,7 +44,7 @@ export const ref = (path) => {
43
44
  export const lit = (str) => buildLiteral(stripArray(str));
44
45
 
45
46
  export const gap = buildGap;
46
- export const embedded = buildEmbedded;
47
+ export const embedded = buildEmbeddedNode;
47
48
  export const nodeOpen = buildNodeOpenTag;
48
49
  export const nodeClose = buildNodeCloseTag;
49
50
  export const node = buildNode;
@@ -53,3 +54,4 @@ export const e_node = buildEscapeNode;
53
54
  export const s_e_node = buildSyntacticEscapeNode;
54
55
  export const s_t_node = buildSyntacticTriviaNode;
55
56
  export const t_node = buildTriviaNode;
57
+ export const null_node = buildNullNode;
package/lib/stream.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import { Coroutine } from '@bablr/coroutine';
2
- import { printTerminal } from './print.js';
3
- import { buildWriteEffect } from './builders.js';
2
+ import emptyStack from '@iter-tools/imm-stack';
3
+ import { printSelfClosingNodeTag, printTerminal } from './print.js';
4
+ import { buildWriteEffect, buildTokenGroup } from './builders.js';
4
5
  export * from './print.js';
5
6
 
7
+ const getEmbeddedExpression = (obj) => {
8
+ if (obj.type !== 'EmbeddedExpression') throw new Error();
9
+ return obj.value;
10
+ };
11
+
6
12
  export const getStreamIterator = (obj) => {
7
13
  return obj[Symbol.for('@@streamIterator')]?.() || obj[Symbol.iterator]?.();
8
14
  };
@@ -83,6 +89,12 @@ export class AsyncGenerator {
83
89
  }
84
90
  }
85
91
 
92
+ export const isIntrinsicToken = (terminal) => {
93
+ return (
94
+ terminal.type === 'OpenNodeTag' && terminal.value.flags.intrinsic && terminal.value.flags.token
95
+ );
96
+ };
97
+
86
98
  export class StreamGenerator {
87
99
  constructor(embeddedGenerator) {
88
100
  this.generator = embeddedGenerator;
@@ -137,45 +149,6 @@ export const maybeWait = (maybePromise, callback) => {
137
149
  }
138
150
  };
139
151
 
140
- function* __generateCSTMLStrategy(terminals) {
141
- if (!terminals) {
142
- yield buildWriteEffect('<//>');
143
- return;
144
- }
145
-
146
- let prevTerminal = null;
147
-
148
- const co = new Coroutine(getStreamIterator(terminals));
149
-
150
- for (;;) {
151
- co.advance();
152
-
153
- if (co.current instanceof Promise) {
154
- co.current = yield co.current;
155
- }
156
- if (co.done) break;
157
-
158
- const terminal = co.value;
159
-
160
- if (terminal.type === 'Reference' && prevTerminal.type === 'Null') {
161
- yield buildWriteEffect(' ');
162
- }
163
-
164
- if (terminal.type !== 'Effect') {
165
- buildWriteEffect(printTerminal(terminal));
166
-
167
- prevTerminal = terminal;
168
- } else {
169
- yield terminal;
170
- }
171
- }
172
-
173
- yield buildWriteEffect('\n');
174
- }
175
-
176
- export const generateCSTMLStrategy = (terminals) =>
177
- new StreamIterable(__generateCSTMLStrategy(terminals));
178
-
179
152
  function* __generateStandardOutput(terminals) {
180
153
  const co = new Coroutine(getStreamIterator(terminals));
181
154
 
@@ -190,9 +163,12 @@ function* __generateStandardOutput(terminals) {
190
163
  const terminal = co.value;
191
164
 
192
165
  if (terminal.type === 'Effect') {
193
- const effect = terminal.value;
194
- if (effect.verb === 'write' && (effect.value.stream == null || effect.value.stream === 1)) {
195
- yield* effect.value.text;
166
+ const effect = getEmbeddedExpression(terminal.value);
167
+ if (effect.verb === 'write') {
168
+ const writeEffect = getEmbeddedExpression(effect.value);
169
+ if (writeEffect.stream == null || writeEffect.stream === 1) {
170
+ yield* writeEffect.text;
171
+ }
196
172
  }
197
173
  }
198
174
  }
@@ -217,14 +193,15 @@ function* __generateAllOutput(terminals) {
217
193
  const terminal = co.value;
218
194
 
219
195
  if (terminal.type === 'Effect') {
220
- const effect = terminal.value;
196
+ const effect = getEmbeddedExpression(terminal.value);
221
197
  if (effect.verb === 'write') {
198
+ const writeEffect = getEmbeddedExpression(effect.value);
222
199
  const prevStream = currentStream;
223
- currentStream = effect.value.options.stream || 1;
224
- if (prevStream && prevStream !== currentStream && !effect.value.text.startsWith('\n')) {
200
+ currentStream = getEmbeddedExpression(writeEffect.options).stream || 1;
201
+ if (prevStream && prevStream !== currentStream && !writeEffect.text.startsWith('\n')) {
225
202
  yield* '\n';
226
203
  }
227
- yield* effect.value.text;
204
+ yield* writeEffect.text;
228
205
  }
229
206
  }
230
207
  }
@@ -278,17 +255,142 @@ export const stringFromStream = (stream) => {
278
255
  return str;
279
256
  };
280
257
 
281
- function* __generatePrettyCSTMLStrategy(terminals, options) {
282
- const { indent = ' ', emitEffects = false, inline: inlineOption = true } = options;
258
+ function* __generateCSTMLStrategy(terminals, options) {
259
+ let { emitEffects = false, inline: inlineOption = true } = options;
260
+
261
+ if (emitEffects) {
262
+ throw new Error('You must use generatePrettyCSTML with emitEffects');
263
+ }
283
264
 
284
265
  if (!terminals) {
285
266
  yield buildWriteEffect('<//>');
286
267
  return;
287
268
  }
288
269
 
270
+ let prevTerminal = null;
271
+
272
+ const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
273
+
274
+ for (;;) {
275
+ co.advance();
276
+
277
+ if (co.current instanceof Promise) {
278
+ co.current = yield co.current;
279
+ }
280
+ if (co.done) break;
281
+
282
+ const terminal = co.value;
283
+
284
+ if (terminal.type === 'Reference' && prevTerminal.type === 'Null') {
285
+ yield buildWriteEffect(' ');
286
+ }
287
+
288
+ if (terminal.type === 'Effect') {
289
+ const effect = terminal.value;
290
+ if (emitEffects && effect.verb === 'write') {
291
+ yield buildWriteEffect(effect.value.text, effect.value.options);
292
+ }
293
+ continue;
294
+ }
295
+
296
+ if (terminal.type === 'TokenGroup') {
297
+ const intrinsicValue = getCooked(terminal.value.slice(1, -1));
298
+ yield buildWriteEffect(printSelfClosingNodeTag(terminal.value[0], intrinsicValue));
299
+ } else {
300
+ yield buildWriteEffect(printTerminal(terminal));
301
+ }
302
+
303
+ prevTerminal = terminal;
304
+ }
305
+
306
+ yield buildWriteEffect('\n');
307
+ }
308
+
309
+ export const prettyGroupTokens = (terminals) => new StreamIterable(__prettyGroupTokens(terminals));
310
+
311
+ function* __prettyGroupTokens(terminals) {
312
+ let states = emptyStack.push({ holding: [], broken: false, open: null });
313
+ let state = states.value;
314
+
289
315
  const co = new Coroutine(getStreamIterator(terminals));
316
+
317
+ for (;;) {
318
+ co.advance();
319
+
320
+ if (co.done) break;
321
+
322
+ if (co.current instanceof Promise) {
323
+ co.current = yield co.current;
324
+ }
325
+
326
+ const terminal = co.value;
327
+ const isOpenClose =
328
+ terminal.type === 'CloseNodeTag' || (terminal.type === 'OpenNodeTag' && terminal.value.type);
329
+
330
+ if (
331
+ (terminal.type === 'Effect' && terminal.value.verb === 'write') ||
332
+ ['Reference', 'DoctypeTag', 'Gap', 'Null'].includes(terminal.type) ||
333
+ (terminal.type === 'OpenNodeTag' && !terminal.value.type) ||
334
+ (state.open &&
335
+ !isIntrinsicToken(state.open) &&
336
+ (terminal.type === 'Literal' ||
337
+ (terminal.type === 'OpenNodeTag' && terminal.value.flags.escape)))
338
+ ) {
339
+ state.broken = true;
340
+
341
+ if (state.holding.length) {
342
+ yield* state.holding;
343
+ state.holding = [];
344
+ }
345
+
346
+ if (!isOpenClose && terminal.type !== 'Effect') {
347
+ yield terminal;
348
+ }
349
+ } else if (!isOpenClose && terminal.type !== 'Effect') {
350
+ state.holding.push(terminal);
351
+ }
352
+
353
+ if (terminal.type === 'Effect') {
354
+ yield terminal;
355
+ }
356
+
357
+ if (terminal.type === 'CloseNodeTag') {
358
+ if (!state.broken && (isIntrinsicToken(state.open) || state.holding.length === 1)) {
359
+ state.holding.push(terminal);
360
+ yield buildTokenGroup(state.holding);
361
+ } else {
362
+ if (state.holding.length) {
363
+ yield* state.holding;
364
+ }
365
+ yield terminal;
366
+ }
367
+
368
+ states = states.pop();
369
+ state = states.value;
370
+ }
371
+
372
+ if (terminal.type === 'OpenNodeTag' && terminal.value.type) {
373
+ states = states.push({ holding: [terminal], broken: false, open: terminal });
374
+ state = states.value;
375
+ }
376
+ }
377
+ }
378
+
379
+ export const generateCSTMLStrategy = (terminals, options = {}) =>
380
+ new StreamIterable(__generateCSTMLStrategy(terminals, options));
381
+
382
+ function* __generatePrettyCSTMLStrategy(terminals, options) {
383
+ let { indent = ' ', emitEffects = false, inline: inlineOption = true } = options;
384
+
385
+ if (!terminals) {
386
+ yield buildWriteEffect('<//>');
387
+ return;
388
+ }
389
+
390
+ const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
290
391
  let indentLevel = 0;
291
392
  let first = true;
393
+ let inline = false;
292
394
 
293
395
  for (;;) {
294
396
  co.advance();
@@ -302,22 +404,24 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
302
404
  const terminal = co.value;
303
405
 
304
406
  if (terminal.type === 'Effect') {
305
- const effect = terminal.value;
407
+ const effect = getEmbeddedExpression(terminal.value);
306
408
  if (emitEffects && effect.verb === 'write') {
307
- yield buildWriteEffect((first ? '' : '\n') + effect.value.text, effect.value.options);
409
+ const writeEffect = getEmbeddedExpression(effect.value);
410
+ yield buildWriteEffect(
411
+ (first ? '' : '\n') + writeEffect.text,
412
+ getEmbeddedExpression(writeEffect.options),
413
+ );
308
414
 
415
+ inline = false;
309
416
  first = false;
310
417
  }
311
418
  continue;
312
419
  }
313
420
 
314
- const inline =
421
+ inline =
315
422
  inlineOption &&
316
- (terminal.type === 'Null' ||
317
- terminal.type === 'Gap' ||
318
- (terminal.type === 'OpenNodeTag' &&
319
- terminal.value.intrinsicValue &&
320
- terminal.value.flags.intrinsic));
423
+ inline &&
424
+ (terminal.type === 'Null' || terminal.type === 'Gap' || terminal.type === 'TokenGroup');
321
425
 
322
426
  if (!first && !inline) {
323
427
  yield buildWriteEffect('\n');
@@ -336,12 +440,19 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
336
440
  } else {
337
441
  yield buildWriteEffect(' ');
338
442
  }
339
- yield buildWriteEffect(printTerminal(terminal));
340
443
 
341
- if (
342
- terminal.type === 'OpenNodeTag' &&
343
- (!terminal.value.intrinsicValue || !terminal.value.type)
344
- ) {
444
+ if (terminal.type === 'TokenGroup') {
445
+ const intrinsicValue = getCooked(terminal.value.slice(1, -1));
446
+ yield buildWriteEffect(printSelfClosingNodeTag(terminal.value[0], intrinsicValue));
447
+ } else {
448
+ yield buildWriteEffect(printTerminal(terminal));
449
+ }
450
+
451
+ if (terminal.type === 'Reference') {
452
+ inline = true;
453
+ }
454
+
455
+ if (terminal.type === 'OpenNodeTag') {
345
456
  indentLevel++;
346
457
  }
347
458
 
@@ -408,8 +519,6 @@ export const printSource = (terminals) => {
408
519
  for (const terminal of terminals) {
409
520
  if (terminal.type === 'Literal') {
410
521
  printed += terminal.value;
411
- } else if (terminal.type === 'OpenNodeTag' && terminal.value.intrinsicValue) {
412
- printed += terminal.value.intrinsicValue;
413
522
  }
414
523
  }
415
524
 
@@ -420,21 +529,8 @@ export function* generateSourceTextFor(terminals) {
420
529
  for (const terminal of terminals) {
421
530
  if (terminal.type === 'Literal') {
422
531
  yield* terminal.value;
423
- } else if (terminal.type === 'OpenNodeTag' && terminal.value.intrinsicValue) {
424
- yield* terminal.value.intrinsicValue;
425
532
  }
426
533
  }
427
534
  }
428
535
 
429
536
  export const sourceTextFor = printSource;
430
-
431
- export const startsDocument = (terminal) => {
432
- const { type, value } = terminal;
433
- if ((type === 'OpenNodeTag' && !value.type) || type === 'DoctypeTag') {
434
- return true;
435
- } else if (type === 'OpenNodeTag') {
436
- const { flags } = value;
437
-
438
- return flags.trivia || flags.escape;
439
- }
440
- };
package/lib/symbols.js ADDED
@@ -0,0 +1,9 @@
1
+ export const node = Symbol.for('@bablr/node');
2
+
3
+ export const gap = Symbol.for('@bablr/gap');
4
+
5
+ export const fragment = Symbol.for('@bablr/fragment');
6
+
7
+ export const null_ = Symbol.for('@bablr/null');
8
+
9
+ export { null_ as null };
package/lib/template.js CHANGED
@@ -28,10 +28,10 @@ export const interpolateArrayChildren = (value, ref, sep) => {
28
28
  };
29
29
 
30
30
  const validateTerminal = (term) => {
31
- if (!term || (term.type !== 'Literal' && term.type !== 'Embedded')) {
31
+ if (!term || (term.type !== 'Literal' && term.type !== 'EmbeddedNode')) {
32
32
  throw new Error('Invalid terminal');
33
33
  }
34
- if (term.type === 'Embedded' && !term.value.flags.escape) {
34
+ if (term.type === 'EmbeddedNode' && !term.value.flags.escape) {
35
35
  throw new Error();
36
36
  }
37
37
  };
package/lib/tree.js CHANGED
@@ -1,19 +1,13 @@
1
1
  import emptyStack from '@iter-tools/imm-stack';
2
2
  import { Coroutine } from '@bablr/coroutine';
3
- import {
4
- buildNodeCloseTag,
5
- buildNodeOpenTag,
6
- buildNull,
7
- buildEmbedded,
8
- nodeFlags,
9
- buildDoctypeTag,
10
- } from './builders.js';
3
+ import { buildNull, nodeFlags, buildDoctypeTag, buildEmbeddedNode } from './builders.js';
11
4
  import {
12
5
  printPrettyCSTML as printPrettyCSTMLFromStream,
13
6
  printCSTML as printCSTMLFromStream,
14
7
  getStreamIterator,
15
8
  StreamIterable,
16
9
  } from './stream.js';
10
+ import * as sym from './symbols.js';
17
11
  export * from './builders.js';
18
12
  export * from './print.js';
19
13
 
@@ -21,6 +15,8 @@ const arrayLast = (arr) => arr[arr.length - 1];
21
15
 
22
16
  const isString = (str) => typeof str === 'string';
23
17
 
18
+ const { isArray } = Array;
19
+
24
20
  const buildFrame = (node) => {
25
21
  if (!node) throw new Error();
26
22
  return { node, childrenIdx: -1, resolver: new Resolver(node) };
@@ -28,13 +24,33 @@ const buildFrame = (node) => {
28
24
 
29
25
  const { hasOwn, freeze } = Object;
30
26
 
31
- const get = (node, path) => {
27
+ export const get = (node, path) => {
28
+ const { type, properties } = node;
32
29
  const { 1: name, 2: index } = /^([^\.]+)(?:\.(\d+))?/.exec(path) || [];
33
30
 
31
+ if (!hasOwn(properties, name)) {
32
+ throw new Error(`Cannot find {name: ${name}} on node of {type: ${type}}`);
33
+ }
34
+
34
35
  if (index != null) {
35
- return node.properties[name]?.[parseInt(index, 10)];
36
+ return properties[name]?.[parseInt(index, 10)];
36
37
  } else {
37
- return node.properties[name];
38
+ return properties[name];
39
+ }
40
+ };
41
+
42
+ export const add = (node, ref, value) => {
43
+ const { name, isArray } = ref.value;
44
+
45
+ if (isArray) {
46
+ if (!hasOwn(node.properties, name)) {
47
+ node.properties[name] = [];
48
+ }
49
+ const array = node.properties[name];
50
+
51
+ array.push(value);
52
+ } else {
53
+ node.properties[name] = value;
38
54
  }
39
55
  };
40
56
 
@@ -64,6 +80,7 @@ function* __treeFromStream(tokens) {
64
80
 
65
81
  rootNode = freeze({
66
82
  flags: nodeFlags,
83
+ language: attributes['bablr-language'],
67
84
  type: null,
68
85
  children: [],
69
86
  properties: {},
@@ -79,6 +96,13 @@ function* __treeFromStream(tokens) {
79
96
 
80
97
  switch (token.type) {
81
98
  case 'Null': {
99
+ const node = nodes.value;
100
+ const ref = arrayLast(node.children);
101
+
102
+ if (ref.type !== 'Reference') throw new Error();
103
+
104
+ add(node, ref, null);
105
+
82
106
  break;
83
107
  }
84
108
 
@@ -90,19 +114,12 @@ function* __treeFromStream(tokens) {
90
114
 
91
115
  case 'Gap': {
92
116
  if (held) {
93
- const { children, properties } = nodes.value;
94
- const ref = arrayLast(children);
117
+ const node = nodes.value;
118
+ const ref = arrayLast(node.children);
95
119
 
96
120
  if (ref.type !== 'Reference') throw new Error();
97
121
 
98
- if (ref.value.isArray) {
99
- if (!properties[ref.value.name]) {
100
- properties[ref.value.name] = [];
101
- }
102
- properties[ref.value.name].push(held);
103
- } else {
104
- properties[ref.value.name] = held;
105
- }
122
+ add(node, ref, held);
106
123
 
107
124
  held = null;
108
125
  }
@@ -127,12 +144,7 @@ function* __treeFromStream(tokens) {
127
144
  }
128
145
 
129
146
  case 'OpenNodeTag': {
130
- const buildStringTerminals = (str) => {
131
- // do better
132
- return [{ type: 'Literal', value: str }];
133
- };
134
-
135
- const { flags, language, type, intrinsicValue, attributes } = token.value;
147
+ const { flags, language, type, attributes } = token.value;
136
148
  const node = nodes.value;
137
149
 
138
150
  if (!type) {
@@ -143,7 +155,7 @@ function* __treeFromStream(tokens) {
143
155
  flags,
144
156
  language,
145
157
  type,
146
- children: intrinsicValue && flags.intrinsic ? buildStringTerminals(intrinsicValue) : [],
158
+ children: [],
147
159
  properties: {},
148
160
  attributes: freeze(attributes),
149
161
  });
@@ -153,24 +165,9 @@ function* __treeFromStream(tokens) {
153
165
  throw new Error('Nodes must follow references');
154
166
  }
155
167
 
156
- const { name, isArray } = arrayLast(node.children).value;
157
-
158
- if (isArray) {
159
- if (!hasOwn(node.properties, name)) {
160
- node.properties[name] = [];
161
- }
162
- const array = node.properties[name];
168
+ const ref = arrayLast(node.children);
163
169
 
164
- array.push(newNode);
165
- } else {
166
- node.properties[name] = newNode;
167
- }
168
- }
169
-
170
- if (intrinsicValue && flags.intrinsic) {
171
- freeze(newNode.children);
172
- freeze(newNode.properties);
173
- break;
170
+ add(node, ref, newNode);
174
171
  }
175
172
 
176
173
  nodes = nodes.push(newNode);
@@ -184,7 +181,7 @@ function* __treeFromStream(tokens) {
184
181
  if (flags.escape || flags.trivia) {
185
182
  const parentChildren = nodes.prev.value.children;
186
183
 
187
- parentChildren.push(buildEmbedded(completedNode));
184
+ parentChildren.push(buildEmbeddedNode(completedNode));
188
185
  }
189
186
 
190
187
  freeze(completedNode.properties);
@@ -235,75 +232,66 @@ export const evaluateReturnAsync = async (generator) => {
235
232
  return co.value;
236
233
  };
237
234
 
238
- export function* streamFromTree(rootNode) {
235
+ export const streamFromTree = (rootNode) => __streamFromTree(rootNode);
236
+
237
+ function* __streamFromTree(rootNode) {
239
238
  if (!rootNode || rootNode.type === 'Gap') {
240
239
  return rootNode;
241
240
  }
242
241
 
243
242
  yield buildDoctypeTag(rootNode.attributes);
244
- yield buildNodeOpenTag();
245
243
 
246
244
  let stack = emptyStack.push(buildFrame(rootNode));
247
245
 
248
246
  stack: while (stack.size) {
249
247
  const frame = stack.value;
250
248
  const { node, resolver } = frame;
251
- const { flags, language, type, children, attributes } = node;
249
+ const { children } = node;
252
250
 
253
- const intrinsicValue = flags.token && flags.intrinsic ? children[0]?.value : null;
251
+ while (++frame.childrenIdx < children.length) {
252
+ const terminal = children[frame.childrenIdx];
254
253
 
255
- const children_ = intrinsicValue ? [intrinsicValue] : node.children;
254
+ switch (terminal.type) {
255
+ case 'EmbeddedNode': {
256
+ stack = stack.push(buildFrame(terminal.value));
256
257
 
257
- if (frame.childrenIdx === -1 && stack.size > 1) {
258
- yield buildNodeOpenTag(flags, language, type, intrinsicValue, attributes);
259
- }
258
+ break;
259
+ }
260
260
 
261
- if (!intrinsicValue) {
262
- while (++frame.childrenIdx < children_.length) {
263
- const terminal = children_[frame.childrenIdx];
261
+ case 'Reference': {
262
+ const resolved = resolver.consume(terminal).get(terminal);
264
263
 
265
- switch (terminal.type) {
266
- case 'Literal':
267
- case 'Gap':
268
- case 'Null': {
269
- yield terminal;
270
- break;
271
- }
264
+ yield terminal;
272
265
 
273
- case 'Embedded': {
274
- stack = stack.push(buildFrame(terminal.value));
275
- continue stack;
276
- }
266
+ if (terminal.value.isArray && !resolved) {
267
+ // TODO evaluate if this is still smart
268
+ yield buildNull();
269
+ } else {
270
+ if (!resolved) throw new Error();
277
271
 
278
- case 'Reference': {
279
- if (stack.size > 1) {
280
- yield terminal;
281
- }
282
-
283
- const resolved = resolver.consume(terminal).get(terminal);
284
- if (resolved) {
285
- stack = stack.push(buildFrame(resolved));
286
- continue stack;
287
- } else {
288
- yield buildNull();
289
- break;
290
- }
272
+ stack = stack.push(buildFrame(resolved));
291
273
  }
292
274
 
293
- default: {
294
- throw new Error();
295
- }
275
+ break;
276
+ }
277
+
278
+ case 'Null': {
279
+ yield terminal;
280
+
281
+ break;
296
282
  }
283
+
284
+ default:
285
+ yield terminal;
297
286
  }
298
287
 
299
- if (stack.size > 1) {
300
- yield buildNodeCloseTag(type, language);
288
+ if (terminal.type === 'EmbeddedNode' || terminal.type === 'Reference') {
289
+ continue stack;
301
290
  }
302
291
  }
303
292
 
304
293
  stack = stack.pop();
305
294
  }
306
- yield buildNodeCloseTag();
307
295
  }
308
296
 
309
297
  export const getCooked = (cookable) => {
@@ -321,7 +309,7 @@ export const getCooked = (cookable) => {
321
309
  throw new Error('cookable nodes must not contain other nodes');
322
310
  }
323
311
 
324
- case 'Embedded': {
312
+ case 'EmbeddedNode': {
325
313
  const { flags, attributes } = terminal.value;
326
314
 
327
315
  if (!(flags.trivia || (flags.escape && attributes.cooked))) {
@@ -358,26 +346,28 @@ export const printCSTML = (rootNode) => {
358
346
  return printCSTMLFromStream(streamFromTree(rootNode));
359
347
  };
360
348
 
361
- export const printPrettyCSTML = (rootNode, indent = ' ') => {
362
- return printPrettyCSTMLFromStream(streamFromTree(rootNode), indent);
349
+ export const printPrettyCSTML = (rootNode, options = {}) => {
350
+ return printPrettyCSTMLFromStream(streamFromTree(rootNode), options);
363
351
  };
364
352
 
365
353
  export const printSource = (node) => {
366
354
  const resolver = new Resolver(node);
367
355
  let printed = '';
368
356
 
357
+ if (!node) return '';
358
+
369
359
  if (node instanceof Promise) {
370
360
  printed += '$Promise';
371
361
  } else {
372
362
  for (const child of node.children) {
373
363
  if (child.type === 'Literal') {
374
364
  printed += child.value;
375
- } else if (child.type === 'Embedded') {
365
+ } else if (child.type === 'EmbeddedNode') {
376
366
  printed += printSource(child.value);
377
367
  } else if (child.type === 'Reference') {
378
368
  const node_ = resolver.consume(child).get(child);
379
369
 
380
- if (node_ || !child.value.isArray) {
370
+ if (node_) {
381
371
  printed += printSource(node_);
382
372
  }
383
373
  }
@@ -389,6 +379,88 @@ export const printSource = (node) => {
389
379
 
390
380
  export const sourceTextFor = printSource;
391
381
 
382
+ export const getOpenTag = (node) => {
383
+ const tag = node.children[0];
384
+ if (tag && tag.type !== 'OpenNodeTag') throw new Error();
385
+ return tag;
386
+ };
387
+
388
+ export const getCloseTag = (node) => {
389
+ const { children } = node;
390
+ const tag = children[children.length - 1];
391
+ if (tag.type !== 'CloseNodeTag') return null;
392
+ return tag;
393
+ };
394
+
395
+ export const getRange = (node) => {
396
+ const { children } = node;
397
+ return children.length ? [children[0], children[children.length - 1]] : null;
398
+ };
399
+
400
+ export const createNode = (openTag) => {
401
+ const { flags, language, type, attributes } = openTag.value;
402
+ return { flags, language, type, children: [], properties: {}, attributes };
403
+ };
404
+
405
+ export const finalizeNode = (node) => {
406
+ freeze(node);
407
+ freeze(node.children);
408
+ freeze(node.properties);
409
+ freeze(node.attributes);
410
+ return node;
411
+ };
412
+
413
+ export const notNull = (node) => {
414
+ return node && node.type !== sym.null;
415
+ };
416
+
417
+ export const isNull = (node) => {
418
+ return !node || node.type === sym.null;
419
+ };
420
+
421
+ export const branchProperties = (properties) => {
422
+ const copy = { ...properties };
423
+
424
+ for (const { 0: key, 1: value } of Object.entries(copy)) {
425
+ if (isArray(value)) {
426
+ copy[key] = [...value];
427
+ }
428
+ }
429
+
430
+ return copy;
431
+ };
432
+
433
+ export const branchNode = (node) => {
434
+ const { flags, language, type, children, properties, attributes } = node;
435
+ return {
436
+ flags,
437
+ language,
438
+ type,
439
+ children: [...children],
440
+ properties: branchProperties(properties),
441
+ attributes: { ...attributes },
442
+ };
443
+ };
444
+
445
+ export const acceptNode = (node, accepted) => {
446
+ const { children, properties, attributes } = accepted;
447
+ node.children = children;
448
+ node.properties = properties;
449
+ node.attributes = attributes;
450
+ return node;
451
+ };
452
+
453
+ export const getRoot = (fragmentNode) => {
454
+ if (!fragmentNode) return null;
455
+
456
+ for (const terminal of fragmentNode.children) {
457
+ if (terminal.type === 'Reference') {
458
+ if (terminal.value.isArray) throw new Error();
459
+ return fragmentNode.properties[terminal.value.name];
460
+ }
461
+ }
462
+ };
463
+
392
464
  export class Resolver {
393
465
  constructor(node, counters = new Map()) {
394
466
  this.node = node;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bablr/agast-helpers",
3
3
  "description": "Helper functions for working with agAST trees",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [
@@ -13,6 +13,7 @@
13
13
  "./print": "./lib/print.js",
14
14
  "./shorthand": "./lib/shorthand.js",
15
15
  "./stream": "./lib/stream.js",
16
+ "./symbols": "./lib/symbols.js",
16
17
  "./template": "./lib/template.js",
17
18
  "./tree": "./lib/tree.js"
18
19
  },