@bablr/agast-helpers 0.3.1 → 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,21 +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 (emitEffects) {
262
- throw new Error('You must use generatePrettyCSTML with emitEffects');
263
- }
264
-
265
- if (!terminals) {
272
+ if (!tags) {
266
273
  yield buildWriteEffect('<//>');
267
274
  return;
268
275
  }
269
276
 
270
- let prevTerminal = null;
277
+ let prevTag = null;
271
278
 
272
- const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
279
+ const co = new Coroutine(getStreamIterator(prettyGroupTokens(tags)));
273
280
 
274
281
  for (;;) {
275
282
  co.advance();
@@ -279,40 +286,43 @@ function* __generateCSTMLStrategy(terminals, options) {
279
286
  }
280
287
  if (co.done) break;
281
288
 
282
- const terminal = co.value;
289
+ const tag = co.value;
283
290
 
284
- if (terminal.type === 'Reference' && prevTerminal.type === 'Null') {
291
+ if (tag.type === ReferenceTag && prevTag.type === NullTag) {
285
292
  yield buildWriteEffect(' ');
286
293
  }
287
294
 
288
- if (terminal.type === 'Effect') {
289
- const effect = terminal.value;
295
+ if (tag.type === 'Effect') {
296
+ const effect = tag.value;
290
297
  if (emitEffects && effect.verb === 'write') {
291
298
  yield buildWriteEffect(effect.value.text, effect.value.options);
292
299
  }
293
300
  continue;
294
301
  }
295
302
 
296
- if (terminal.type === 'TokenGroup') {
297
- const intrinsicValue = getCooked(terminal.value.slice(1, -1));
298
- 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));
299
306
  } else {
300
- yield buildWriteEffect(printTerminal(terminal));
307
+ yield buildWriteEffect(printTag(tag));
301
308
  }
302
309
 
303
- prevTerminal = terminal;
310
+ prevTag = tag;
304
311
  }
305
312
 
306
313
  yield buildWriteEffect('\n');
307
314
  }
308
315
 
309
- export const prettyGroupTokens = (terminals) => new StreamIterable(__prettyGroupTokens(terminals));
316
+ export const generateCSTMLStrategy = (tags, options = {}) =>
317
+ new StreamIterable(__generateCSTMLStrategy(tags, options));
310
318
 
311
- function* __prettyGroupTokens(terminals) {
319
+ export const prettyGroupTokens = (tags) => new StreamIterable(__prettyGroupTokens(tags));
320
+
321
+ function* __prettyGroupTokens(tags) {
312
322
  let states = emptyStack.push({ holding: [], broken: false, open: null });
313
323
  let state = states.value;
314
324
 
315
- const co = new Coroutine(getStreamIterator(terminals));
325
+ const co = new Coroutine(getStreamIterator(tags));
316
326
 
317
327
  for (;;) {
318
328
  co.advance();
@@ -323,18 +333,16 @@ function* __prettyGroupTokens(terminals) {
323
333
  co.current = yield co.current;
324
334
  }
325
335
 
326
- const terminal = co.value;
327
- const isOpenClose =
328
- 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);
329
338
 
330
339
  if (
331
- (terminal.type === 'Effect' && terminal.value.verb === 'write') ||
332
- ['Reference', 'DoctypeTag', 'Gap', 'Null'].includes(terminal.type) ||
333
- (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) ||
334
343
  (state.open &&
335
344
  !isIntrinsicToken(state.open) &&
336
- (terminal.type === 'Literal' ||
337
- (terminal.type === 'OpenNodeTag' && terminal.value.flags.escape)))
345
+ (tag.type === LiteralTag || (tag.type === OpenNodeTag && tag.value.flags.escape)))
338
346
  ) {
339
347
  state.broken = true;
340
348
 
@@ -343,51 +351,48 @@ function* __prettyGroupTokens(terminals) {
343
351
  state.holding = [];
344
352
  }
345
353
 
346
- if (!isOpenClose && terminal.type !== 'Effect') {
347
- yield terminal;
354
+ if (!isOpenClose && tag.type !== 'Effect') {
355
+ yield tag;
348
356
  }
349
- } else if (!isOpenClose && terminal.type !== 'Effect') {
350
- state.holding.push(terminal);
357
+ } else if (!isOpenClose && tag.type !== 'Effect') {
358
+ state.holding.push(tag);
351
359
  }
352
360
 
353
- if (terminal.type === 'Effect') {
354
- yield terminal;
361
+ if (tag.type === 'Effect') {
362
+ yield tag;
355
363
  }
356
364
 
357
- if (terminal.type === 'CloseNodeTag') {
365
+ if (tag.type === CloseNodeTag) {
358
366
  if (!state.broken && (isIntrinsicToken(state.open) || state.holding.length === 1)) {
359
- state.holding.push(terminal);
367
+ state.holding.push(tag);
360
368
  yield buildTokenGroup(state.holding);
361
369
  } else {
362
370
  if (state.holding.length) {
363
371
  yield* state.holding;
364
372
  }
365
- yield terminal;
373
+ yield tag;
366
374
  }
367
375
 
368
376
  states = states.pop();
369
377
  state = states.value;
370
378
  }
371
379
 
372
- if (terminal.type === 'OpenNodeTag' && terminal.value.type) {
373
- 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 });
374
382
  state = states.value;
375
383
  }
376
384
  }
377
385
  }
378
386
 
379
- export const generateCSTMLStrategy = (terminals, options = {}) =>
380
- new StreamIterable(__generateCSTMLStrategy(terminals, options));
381
-
382
- function* __generatePrettyCSTMLStrategy(terminals, options) {
387
+ function* __generatePrettyCSTMLStrategy(tags, options) {
383
388
  let { indent = ' ', emitEffects = false, inline: inlineOption = true } = options;
384
389
 
385
- if (!terminals) {
390
+ if (!tags) {
386
391
  yield buildWriteEffect('<//>');
387
392
  return;
388
393
  }
389
394
 
390
- const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
395
+ const co = new Coroutine(getStreamIterator(prettyGroupTokens(tags)));
391
396
  let indentLevel = 0;
392
397
  let first = true;
393
398
  let inline = false;
@@ -401,10 +406,10 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
401
406
  co.current = yield co.current;
402
407
  }
403
408
 
404
- const terminal = co.value;
409
+ const tag = co.value;
405
410
 
406
- if (terminal.type === 'Effect') {
407
- const effect = getEmbeddedExpression(terminal.value);
411
+ if (tag.type === 'Effect') {
412
+ const effect = getEmbeddedExpression(tag.value);
408
413
  if (emitEffects && effect.verb === 'write') {
409
414
  const writeEffect = getEmbeddedExpression(effect.value);
410
415
  yield buildWriteEffect(
@@ -421,13 +426,16 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
421
426
  inline =
422
427
  inlineOption &&
423
428
  inline &&
424
- (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);
425
433
 
426
434
  if (!first && !inline) {
427
435
  yield buildWriteEffect('\n');
428
436
  }
429
437
 
430
- if (terminal.type === 'CloseNodeTag') {
438
+ if (tag.type === CloseNodeTag) {
431
439
  if (indentLevel === 0) {
432
440
  throw new Error('imbalanced tag stack');
433
441
  }
@@ -441,18 +449,18 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
441
449
  yield buildWriteEffect(' ');
442
450
  }
443
451
 
444
- if (terminal.type === 'TokenGroup') {
445
- const intrinsicValue = getCooked(terminal.value.slice(1, -1));
446
- 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));
447
455
  } else {
448
- yield buildWriteEffect(printTerminal(terminal));
456
+ yield buildWriteEffect(printTag(tag));
449
457
  }
450
458
 
451
- if (terminal.type === 'Reference') {
459
+ if (tag.type === ReferenceTag) {
452
460
  inline = true;
453
461
  }
454
462
 
455
- if (terminal.type === 'OpenNodeTag') {
463
+ if (tag.type === OpenNodeTag) {
456
464
  indentLevel++;
457
465
  }
458
466
 
@@ -462,32 +470,51 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
462
470
  yield buildWriteEffect('\n');
463
471
  }
464
472
 
465
- export const generatePrettyCSTMLStrategy = (terminals, options = {}) => {
466
- return new StreamIterable(__generatePrettyCSTMLStrategy(terminals, options));
473
+ export const generatePrettyCSTMLStrategy = (tags, options = {}) => {
474
+ return new StreamIterable(__generatePrettyCSTMLStrategy(tags, options));
467
475
  };
468
476
 
469
- export const printPrettyCSTML = (terminals, options = {}) => {
470
- return stringFromStream(generateStandardOutput(generatePrettyCSTMLStrategy(terminals, options)));
477
+ export const printPrettyCSTML = (tags, options = {}) => {
478
+ return stringFromStream(generateStandardOutput(generatePrettyCSTMLStrategy(tags, options)));
471
479
  };
472
480
 
473
- export const getCooked = (terminals) => {
481
+ export const getCooked = (tags) => {
474
482
  let cooked = '';
475
483
 
476
- for (const terminal of terminals) {
477
- switch (terminal.type) {
478
- case 'Reference': {
479
- 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;
480
497
  }
481
498
 
482
- case 'OpenNodeTag': {
483
- 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
+ }
484
511
 
485
512
  if (!(flags.trivia || (flags.escape && attributes.cooked))) {
486
513
  throw new Error('cookable nodes must not contain other nodes');
487
514
  }
488
515
 
489
516
  if (flags.escape) {
490
- const { cooked: cookedValue } = terminal.value.attributes;
517
+ const { cooked: cookedValue } = tag.value.attributes;
491
518
 
492
519
  if (!cookedValue) throw new Error('cannot cook string: it contains uncooked escapes');
493
520
 
@@ -497,8 +524,18 @@ export const getCooked = (terminals) => {
497
524
  break;
498
525
  }
499
526
 
500
- case 'Literal': {
501
- 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
+ }
502
539
  break;
503
540
  }
504
541
 
@@ -506,29 +543,31 @@ export const getCooked = (terminals) => {
506
543
  throw new Error();
507
544
  }
508
545
  }
546
+
547
+ first = false;
509
548
  }
510
549
 
511
550
  return cooked;
512
551
  };
513
552
 
514
- export const printSource = (terminals) => {
553
+ export const printSource = (tags) => {
515
554
  let printed = '';
516
555
 
517
- if (!terminals) return printed;
556
+ if (!tags) return printed;
518
557
 
519
- for (const terminal of terminals) {
520
- if (terminal.type === 'Literal') {
521
- printed += terminal.value;
558
+ for (const tag of tags) {
559
+ if (tag.type === LiteralTag) {
560
+ printed += tag.value;
522
561
  }
523
562
  }
524
563
 
525
564
  return printed;
526
565
  };
527
566
 
528
- export function* generateSourceTextFor(terminals) {
529
- for (const terminal of terminals) {
530
- if (terminal.type === 'Literal') {
531
- yield* terminal.value;
567
+ export function* generateSourceTextFor(tags) {
568
+ for (const tag of tags) {
569
+ if (tag.type === LiteralTag) {
570
+ yield* tag.value;
532
571
  }
533
572
  }
534
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
  };