@bablr/agast-helpers 0.3.2 → 0.4.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/stream.js CHANGED
@@ -1,11 +1,25 @@
1
1
  import { Coroutine } from '@bablr/coroutine';
2
2
  import emptyStack from '@iter-tools/imm-stack';
3
- import { printSelfClosingNodeTag, printTerminal } from './print.js';
3
+ import { printSelfClosingNodeTag, printTag } from './print.js';
4
4
  import { buildWriteEffect, buildTokenGroup } from './builders.js';
5
+ import {
6
+ DoctypeTag,
7
+ OpenNodeTag,
8
+ CloseNodeTag,
9
+ ReferenceTag,
10
+ ShiftTag,
11
+ GapTag,
12
+ NullTag,
13
+ ArrayTag,
14
+ LiteralTag,
15
+ EmbeddedExpression,
16
+ TokenGroup,
17
+ } from './symbols.js';
18
+
5
19
  export * from './print.js';
6
20
 
7
21
  const getEmbeddedExpression = (obj) => {
8
- if (obj.type !== 'EmbeddedExpression') throw new Error();
22
+ if (obj.type !== EmbeddedExpression) throw new Error();
9
23
  return obj.value;
10
24
  };
11
25
 
@@ -89,10 +103,8 @@ export class AsyncGenerator {
89
103
  }
90
104
  }
91
105
 
92
- export const isIntrinsicToken = (terminal) => {
93
- return (
94
- terminal.type === 'OpenNodeTag' && terminal.value.flags.intrinsic && terminal.value.flags.token
95
- );
106
+ export const isIntrinsicToken = (tag) => {
107
+ return tag.type === OpenNodeTag && tag.value.flags.intrinsic && tag.value.flags.token;
96
108
  };
97
109
 
98
110
  export class StreamGenerator {
@@ -149,8 +161,8 @@ export const maybeWait = (maybePromise, callback) => {
149
161
  }
150
162
  };
151
163
 
152
- function* __generateStandardOutput(terminals) {
153
- const co = new Coroutine(getStreamIterator(terminals));
164
+ function* __generateStandardOutput(tags) {
165
+ const co = new Coroutine(getStreamIterator(tags));
154
166
 
155
167
  for (;;) {
156
168
  co.advance();
@@ -160,10 +172,10 @@ function* __generateStandardOutput(terminals) {
160
172
  }
161
173
  if (co.done) break;
162
174
 
163
- const terminal = co.value;
175
+ const tag = co.value;
164
176
 
165
- if (terminal.type === 'Effect') {
166
- const effect = getEmbeddedExpression(terminal.value);
177
+ if (tag.type === 'Effect') {
178
+ const effect = getEmbeddedExpression(tag.value);
167
179
  if (effect.verb === 'write') {
168
180
  const writeEffect = getEmbeddedExpression(effect.value);
169
181
  if (writeEffect.stream == null || writeEffect.stream === 1) {
@@ -174,11 +186,10 @@ function* __generateStandardOutput(terminals) {
174
186
  }
175
187
  }
176
188
 
177
- export const generateStandardOutput = (terminals) =>
178
- new StreamIterable(__generateStandardOutput(terminals));
189
+ export const generateStandardOutput = (tags) => new StreamIterable(__generateStandardOutput(tags));
179
190
 
180
- function* __generateAllOutput(terminals) {
181
- const co = new Coroutine(getStreamIterator(terminals));
191
+ function* __generateAllOutput(tags) {
192
+ const co = new Coroutine(getStreamIterator(tags));
182
193
 
183
194
  let currentStream = null;
184
195
 
@@ -190,10 +201,10 @@ function* __generateAllOutput(terminals) {
190
201
  }
191
202
  if (co.done) break;
192
203
 
193
- const terminal = co.value;
204
+ const tag = co.value;
194
205
 
195
- if (terminal.type === 'Effect') {
196
- const effect = getEmbeddedExpression(terminal.value);
206
+ if (tag.type === 'Effect') {
207
+ const effect = getEmbeddedExpression(tag.value);
197
208
  if (effect.verb === 'write') {
198
209
  const writeEffect = getEmbeddedExpression(effect.value);
199
210
  const prevStream = currentStream;
@@ -207,10 +218,10 @@ function* __generateAllOutput(terminals) {
207
218
  }
208
219
  }
209
220
 
210
- export const generateAllOutput = (terminals) => new StreamIterable(__generateAllOutput(terminals));
221
+ export const generateAllOutput = (tags) => new StreamIterable(__generateAllOutput(tags));
211
222
 
212
- export const printCSTML = (terminals) => {
213
- return stringFromStream(generateStandardOutput(generateCSTMLStrategy(terminals)));
223
+ export const printCSTML = (tags) => {
224
+ return stringFromStream(generateStandardOutput(generateCSTMLStrategy(tags)));
214
225
  };
215
226
 
216
227
  function* __emptyStreamIterator() {}
@@ -230,9 +241,9 @@ export const asyncStringFromStream = async (stream) => {
230
241
 
231
242
  if (co.done) break;
232
243
 
233
- const terminal = co.value;
244
+ const tag = co.value;
234
245
 
235
- str += printTerminal(terminal);
246
+ str += printTag(tag);
236
247
  }
237
248
 
238
249
  return str;
@@ -255,17 +266,17 @@ export const stringFromStream = (stream) => {
255
266
  return str;
256
267
  };
257
268
 
258
- function* __generateCSTMLStrategy(terminals, options) {
269
+ function* __generateCSTMLStrategy(tags, options) {
259
270
  let { emitEffects = false, inline: inlineOption = true } = options;
260
271
 
261
- if (!terminals) {
272
+ if (!tags) {
262
273
  yield buildWriteEffect('<//>');
263
274
  return;
264
275
  }
265
276
 
266
- let prevTerminal = null;
277
+ let prevTag = null;
267
278
 
268
- const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
279
+ const co = new Coroutine(getStreamIterator(prettyGroupTokens(tags)));
269
280
 
270
281
  for (;;) {
271
282
  co.advance();
@@ -275,43 +286,43 @@ function* __generateCSTMLStrategy(terminals, options) {
275
286
  }
276
287
  if (co.done) break;
277
288
 
278
- const terminal = co.value;
289
+ const tag = co.value;
279
290
 
280
- if (terminal.type === 'Reference' && prevTerminal.type === 'Null') {
291
+ if (tag.type === ReferenceTag && prevTag.type === NullTag) {
281
292
  yield buildWriteEffect(' ');
282
293
  }
283
294
 
284
- if (terminal.type === 'Effect') {
285
- const effect = terminal.value;
295
+ if (tag.type === 'Effect') {
296
+ const effect = tag.value;
286
297
  if (emitEffects && effect.verb === 'write') {
287
298
  yield buildWriteEffect(effect.value.text, effect.value.options);
288
299
  }
289
300
  continue;
290
301
  }
291
302
 
292
- if (terminal.type === 'TokenGroup') {
293
- const intrinsicValue = getCooked(terminal.value.slice(1, -1));
294
- yield buildWriteEffect(printSelfClosingNodeTag(terminal.value[0], intrinsicValue));
303
+ if (tag.type === TokenGroup) {
304
+ const intrinsicValue = getCooked(tag.value);
305
+ yield buildWriteEffect(printSelfClosingNodeTag(tag.value[0], intrinsicValue));
295
306
  } else {
296
- yield buildWriteEffect(printTerminal(terminal));
307
+ yield buildWriteEffect(printTag(tag));
297
308
  }
298
309
 
299
- prevTerminal = terminal;
310
+ prevTag = tag;
300
311
  }
301
312
 
302
313
  yield buildWriteEffect('\n');
303
314
  }
304
315
 
305
- export const generateCSTMLStrategy = (terminals, options = {}) =>
306
- new StreamIterable(__generateCSTMLStrategy(terminals, options));
316
+ export const generateCSTMLStrategy = (tags, options = {}) =>
317
+ new StreamIterable(__generateCSTMLStrategy(tags, options));
307
318
 
308
- export const prettyGroupTokens = (terminals) => new StreamIterable(__prettyGroupTokens(terminals));
319
+ export const prettyGroupTokens = (tags) => new StreamIterable(__prettyGroupTokens(tags));
309
320
 
310
- function* __prettyGroupTokens(terminals) {
321
+ function* __prettyGroupTokens(tags) {
311
322
  let states = emptyStack.push({ holding: [], broken: false, open: null });
312
323
  let state = states.value;
313
324
 
314
- const co = new Coroutine(getStreamIterator(terminals));
325
+ const co = new Coroutine(getStreamIterator(tags));
315
326
 
316
327
  for (;;) {
317
328
  co.advance();
@@ -322,18 +333,16 @@ function* __prettyGroupTokens(terminals) {
322
333
  co.current = yield co.current;
323
334
  }
324
335
 
325
- const terminal = co.value;
326
- const isOpenClose =
327
- terminal.type === 'CloseNodeTag' || (terminal.type === 'OpenNodeTag' && terminal.value.type);
336
+ const tag = co.value;
337
+ const isOpenClose = tag.type === CloseNodeTag || (tag.type === OpenNodeTag && tag.value.type);
328
338
 
329
339
  if (
330
- (terminal.type === 'Effect' && terminal.value.verb === 'write') ||
331
- ['Reference', 'DoctypeTag', 'Gap', 'Null'].includes(terminal.type) ||
332
- (terminal.type === 'OpenNodeTag' && !terminal.value.type) ||
340
+ (tag.type === 'Effect' && tag.value.verb === 'write') ||
341
+ [ReferenceTag, DoctypeTag, GapTag, NullTag, ArrayTag, ShiftTag].includes(tag.type) ||
342
+ (tag.type === OpenNodeTag && !tag.value.type) ||
333
343
  (state.open &&
334
344
  !isIntrinsicToken(state.open) &&
335
- (terminal.type === 'Literal' ||
336
- (terminal.type === 'OpenNodeTag' && terminal.value.flags.escape)))
345
+ (tag.type === LiteralTag || (tag.type === OpenNodeTag && tag.value.flags.escape)))
337
346
  ) {
338
347
  state.broken = true;
339
348
 
@@ -342,48 +351,48 @@ function* __prettyGroupTokens(terminals) {
342
351
  state.holding = [];
343
352
  }
344
353
 
345
- if (!isOpenClose && terminal.type !== 'Effect') {
346
- yield terminal;
354
+ if (!isOpenClose && tag.type !== 'Effect') {
355
+ yield tag;
347
356
  }
348
- } else if (!isOpenClose && terminal.type !== 'Effect') {
349
- state.holding.push(terminal);
357
+ } else if (!isOpenClose && tag.type !== 'Effect') {
358
+ state.holding.push(tag);
350
359
  }
351
360
 
352
- if (terminal.type === 'Effect') {
353
- yield terminal;
361
+ if (tag.type === 'Effect') {
362
+ yield tag;
354
363
  }
355
364
 
356
- if (terminal.type === 'CloseNodeTag') {
365
+ if (tag.type === CloseNodeTag) {
357
366
  if (!state.broken && (isIntrinsicToken(state.open) || state.holding.length === 1)) {
358
- state.holding.push(terminal);
367
+ state.holding.push(tag);
359
368
  yield buildTokenGroup(state.holding);
360
369
  } else {
361
370
  if (state.holding.length) {
362
371
  yield* state.holding;
363
372
  }
364
- yield terminal;
373
+ yield tag;
365
374
  }
366
375
 
367
376
  states = states.pop();
368
377
  state = states.value;
369
378
  }
370
379
 
371
- if (terminal.type === 'OpenNodeTag' && terminal.value.type) {
372
- states = states.push({ holding: [terminal], broken: false, open: terminal });
380
+ if (tag.type === OpenNodeTag && tag.value.type) {
381
+ states = states.push({ holding: [tag], broken: false, open: tag });
373
382
  state = states.value;
374
383
  }
375
384
  }
376
385
  }
377
386
 
378
- function* __generatePrettyCSTMLStrategy(terminals, options) {
387
+ function* __generatePrettyCSTMLStrategy(tags, options) {
379
388
  let { indent = ' ', emitEffects = false, inline: inlineOption = true } = options;
380
389
 
381
- if (!terminals) {
390
+ if (!tags) {
382
391
  yield buildWriteEffect('<//>');
383
392
  return;
384
393
  }
385
394
 
386
- const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
395
+ const co = new Coroutine(getStreamIterator(prettyGroupTokens(tags)));
387
396
  let indentLevel = 0;
388
397
  let first = true;
389
398
  let inline = false;
@@ -397,10 +406,10 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
397
406
  co.current = yield co.current;
398
407
  }
399
408
 
400
- const terminal = co.value;
409
+ const tag = co.value;
401
410
 
402
- if (terminal.type === 'Effect') {
403
- const effect = getEmbeddedExpression(terminal.value);
411
+ if (tag.type === 'Effect') {
412
+ const effect = getEmbeddedExpression(tag.value);
404
413
  if (emitEffects && effect.verb === 'write') {
405
414
  const writeEffect = getEmbeddedExpression(effect.value);
406
415
  yield buildWriteEffect(
@@ -417,13 +426,16 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
417
426
  inline =
418
427
  inlineOption &&
419
428
  inline &&
420
- (terminal.type === 'Null' || terminal.type === 'Gap' || terminal.type === 'TokenGroup');
429
+ (tag.type === NullTag ||
430
+ tag.type === GapTag ||
431
+ tag.type === ArrayTag ||
432
+ tag.type === TokenGroup);
421
433
 
422
434
  if (!first && !inline) {
423
435
  yield buildWriteEffect('\n');
424
436
  }
425
437
 
426
- if (terminal.type === 'CloseNodeTag') {
438
+ if (tag.type === CloseNodeTag) {
427
439
  if (indentLevel === 0) {
428
440
  throw new Error('imbalanced tag stack');
429
441
  }
@@ -437,18 +449,18 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
437
449
  yield buildWriteEffect(' ');
438
450
  }
439
451
 
440
- if (terminal.type === 'TokenGroup') {
441
- const intrinsicValue = getCooked(terminal.value.slice(1, -1));
442
- yield buildWriteEffect(printSelfClosingNodeTag(terminal.value[0], intrinsicValue));
452
+ if (tag.type === TokenGroup) {
453
+ const intrinsicValue = tag.value[0].value.flags.token ? getCooked(tag.value) : null;
454
+ yield buildWriteEffect(printSelfClosingNodeTag(tag.value[0], intrinsicValue));
443
455
  } else {
444
- yield buildWriteEffect(printTerminal(terminal));
456
+ yield buildWriteEffect(printTag(tag));
445
457
  }
446
458
 
447
- if (terminal.type === 'Reference') {
459
+ if (tag.type === ReferenceTag) {
448
460
  inline = true;
449
461
  }
450
462
 
451
- if (terminal.type === 'OpenNodeTag') {
463
+ if (tag.type === OpenNodeTag) {
452
464
  indentLevel++;
453
465
  }
454
466
 
@@ -458,32 +470,51 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
458
470
  yield buildWriteEffect('\n');
459
471
  }
460
472
 
461
- export const generatePrettyCSTMLStrategy = (terminals, options = {}) => {
462
- return new StreamIterable(__generatePrettyCSTMLStrategy(terminals, options));
473
+ export const generatePrettyCSTMLStrategy = (tags, options = {}) => {
474
+ return new StreamIterable(__generatePrettyCSTMLStrategy(tags, options));
463
475
  };
464
476
 
465
- export const printPrettyCSTML = (terminals, options = {}) => {
466
- return stringFromStream(generateStandardOutput(generatePrettyCSTMLStrategy(terminals, options)));
477
+ export const printPrettyCSTML = (tags, options = {}) => {
478
+ return stringFromStream(generateStandardOutput(generatePrettyCSTMLStrategy(tags, options)));
467
479
  };
468
480
 
469
- export const getCooked = (terminals) => {
481
+ export const getCooked = (tags) => {
470
482
  let cooked = '';
471
483
 
472
- for (const terminal of terminals) {
473
- switch (terminal.type) {
474
- case 'Reference': {
475
- throw new Error('cookable nodes must not contain other nodes');
484
+ let first = true;
485
+ let foundLast = false;
486
+ let depth = 0;
487
+
488
+ for (const tag of tags) {
489
+ if (foundLast) throw new Error();
490
+
491
+ switch (tag.type) {
492
+ case ReferenceTag: {
493
+ if (depth === 1) {
494
+ throw new Error('cookable nodes must not contain other nodes');
495
+ }
496
+ break;
476
497
  }
477
498
 
478
- case 'OpenNodeTag': {
479
- const { flags, attributes } = terminal.value;
499
+ case OpenNodeTag: {
500
+ const { flags, attributes } = tag.value;
501
+
502
+ depth++;
503
+
504
+ if (first) {
505
+ if (flags.token) {
506
+ break;
507
+ } else {
508
+ throw new Error(JSON.stringify(flags));
509
+ }
510
+ }
480
511
 
481
512
  if (!(flags.trivia || (flags.escape && attributes.cooked))) {
482
513
  throw new Error('cookable nodes must not contain other nodes');
483
514
  }
484
515
 
485
516
  if (flags.escape) {
486
- const { cooked: cookedValue } = terminal.value.attributes;
517
+ const { cooked: cookedValue } = tag.value.attributes;
487
518
 
488
519
  if (!cookedValue) throw new Error('cannot cook string: it contains uncooked escapes');
489
520
 
@@ -493,8 +524,18 @@ export const getCooked = (terminals) => {
493
524
  break;
494
525
  }
495
526
 
496
- case 'Literal': {
497
- cooked += terminal.value;
527
+ case CloseNodeTag: {
528
+ if (depth === 1) {
529
+ foundLast = true;
530
+ }
531
+ depth--;
532
+ break;
533
+ }
534
+
535
+ case LiteralTag: {
536
+ if (depth === 1) {
537
+ cooked += tag.value;
538
+ }
498
539
  break;
499
540
  }
500
541
 
@@ -502,29 +543,31 @@ export const getCooked = (terminals) => {
502
543
  throw new Error();
503
544
  }
504
545
  }
546
+
547
+ first = false;
505
548
  }
506
549
 
507
550
  return cooked;
508
551
  };
509
552
 
510
- export const printSource = (terminals) => {
553
+ export const printSource = (tags) => {
511
554
  let printed = '';
512
555
 
513
- if (!terminals) return printed;
556
+ if (!tags) return printed;
514
557
 
515
- for (const terminal of terminals) {
516
- if (terminal.type === 'Literal') {
517
- printed += terminal.value;
558
+ for (const tag of tags) {
559
+ if (tag.type === LiteralTag) {
560
+ printed += tag.value;
518
561
  }
519
562
  }
520
563
 
521
564
  return printed;
522
565
  };
523
566
 
524
- export function* generateSourceTextFor(terminals) {
525
- for (const terminal of terminals) {
526
- if (terminal.type === 'Literal') {
527
- yield* terminal.value;
567
+ export function* generateSourceTextFor(tags) {
568
+ for (const tag of tags) {
569
+ if (tag.type === LiteralTag) {
570
+ yield* tag.value;
528
571
  }
529
572
  }
530
573
  }
package/lib/symbols.js CHANGED
@@ -6,4 +6,20 @@ export const fragment = Symbol.for('@bablr/fragment');
6
6
 
7
7
  export const null_ = Symbol.for('@bablr/null');
8
8
 
9
+ export const DoctypeTag = Symbol.for('DoctypeTag');
10
+ export const OpenNodeTag = Symbol.for('OpenNodeTag');
11
+ export const CloseNodeTag = Symbol.for('CloseNodeTag');
12
+ export const ReferenceTag = Symbol.for('ReferenceTag');
13
+ export const ShiftTag = Symbol.for('ShiftTag');
14
+ export const GapTag = Symbol.for('GapTag');
15
+ export const NullTag = Symbol.for('NullTag');
16
+ export const ArrayTag = Symbol.for('ArrayTag');
17
+ export const LiteralTag = Symbol.for('LiteralTag');
18
+
19
+ export const EmbeddedNode = Symbol.for('EmbeddedNode');
20
+ export const EmbeddedTag = Symbol.for('EmbeddedTag');
21
+ export const EmbeddedExpression = Symbol.for('EmbeddedExpression');
22
+
23
+ export const TokenGroup = Symbol.for('TokenGroup');
24
+
9
25
  export { null_ as null };
package/lib/template.js CHANGED
@@ -1,11 +1,18 @@
1
1
  import * as t from './builders.js';
2
+ import { LiteralTag, EmbeddedNode } from './symbols.js';
3
+ import * as btree from './btree.js';
2
4
 
3
5
  const { isArray } = Array;
4
6
  const { freeze } = Object;
7
+ const { isFinite } = Number;
5
8
 
6
9
  export const interpolateArray = (value) => {
7
10
  if (isArray(value)) {
8
- return value;
11
+ if (isFinite(value[0])) {
12
+ return [...btree.traverse(value)];
13
+ } else {
14
+ return value;
15
+ }
9
16
  } else {
10
17
  return [value];
11
18
  }
@@ -27,28 +34,28 @@ export const interpolateArrayChildren = (value, ref, sep) => {
27
34
  }
28
35
  };
29
36
 
30
- const validateTerminal = (term) => {
31
- if (!term || (term.type !== 'Literal' && term.type !== 'EmbeddedNode')) {
32
- throw new Error('Invalid terminal');
37
+ const validateTag = (tag) => {
38
+ if (!tag || (tag.type !== LiteralTag && tag.type !== EmbeddedNode)) {
39
+ throw new Error('Invalid tag');
33
40
  }
34
- if (term.type === 'EmbeddedNode' && !term.value.flags.escape) {
41
+ if (tag.type === EmbeddedNode && !tag.value.flags.escape) {
35
42
  throw new Error();
36
43
  }
37
44
  };
38
45
 
39
46
  export const interpolateString = (value) => {
40
- const terminals = [];
47
+ const tags = [];
41
48
  if (isArray(value)) {
42
49
  for (const element of value) {
43
- validateTerminal(element);
50
+ validateTag(element);
44
51
 
45
- terminals.push(element);
52
+ tags.push(element);
46
53
  }
47
54
  } else {
48
55
  // we can't safely interpolate strings here, though I wish we could
49
- validateTerminal(value);
50
- terminals.push(value);
56
+ validateTag(value);
57
+ tags.push(value);
51
58
  }
52
59
 
53
- return t.buildNode('String', 'Content', terminals);
60
+ return t.buildNode('String', 'Content', tags);
54
61
  };