@bablr/agast-vm-helpers 0.3.2 → 0.5.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/builders.js CHANGED
@@ -1,11 +1,9 @@
1
1
  // import { i } from '@bablr/boot/shorthand.macro';
2
- import {
3
- interpolateArray,
4
- interpolateArrayChildren,
5
- interpolateString,
6
- } from '@bablr/agast-helpers/template';
2
+ import { interpolateFragmentChildren, interpolateString } from '@bablr/agast-helpers/template';
7
3
  import { isNull } from '@bablr/agast-helpers/tree';
4
+ import { buildLiteralTag as agastBuildLiteralTag } from '@bablr/agast-helpers/builders';
8
5
  import * as t from '@bablr/agast-helpers/shorthand';
6
+ import * as btree from '@bablr/agast-helpers/btree';
9
7
  import * as l from './languages.js';
10
8
 
11
9
  const { getPrototypeOf, freeze } = Object;
@@ -20,54 +18,83 @@ function* repeat(times, ...values) {
20
18
  for (let i = 0; i < times; i++) for (const value of values) yield value;
21
19
  }
22
20
 
23
- export const buildReference = (name, isArray) => {
21
+ function* concat(...iterables) {
22
+ for (const iterable of iterables) yield* iterable;
23
+ }
24
+
25
+ export const buildSeparatedListChildren = (list, ref, sep) => {
26
+ const children = [];
27
+ let first = true;
28
+ for (const _ of list) {
29
+ if (!first && sep) {
30
+ children.push(freeze({ ...sep }));
31
+ }
32
+ children.push(freeze({ ...ref }));
33
+ first = false;
34
+ }
35
+ return children;
36
+ };
37
+
38
+ export const buildReferenceTag = (name, isArray, hasGap) => {
24
39
  return t.node(
25
40
  l.CSTML,
26
- 'Reference',
27
- [t.ref`name`, ...when(isArray, [t.ref`arrayOperatorToken`]), t.ref`sigilToken`],
41
+ 'ReferenceTag',
42
+ btree.fromValues(
43
+ concat(
44
+ [t.ref`name`],
45
+ when(isArray, [t.ref`arrayOperatorToken`]),
46
+ when(hasGap, [t.ref`hasGapToken`]),
47
+ [t.ref`sigilToken`],
48
+ ),
49
+ ),
28
50
  {
29
51
  name: buildIdentifier(name),
30
52
  arrayOperatorToken: isArray ? t.s_node(l.CSTML, 'Punctuator', '[]') : t.null_node(),
53
+ hasGapToken: hasGap ? t.s_node(l.CSTML, 'Punctuator', '$') : t.null_node(),
31
54
  sigilToken: t.s_node(l.CSTML, 'Punctuator', ':'),
32
55
  },
33
56
  );
34
57
  };
35
58
 
36
- export const buildGap = () => {
37
- return t.node(l.CSTML, 'Gap', [t.ref`sigilToken`], {
59
+ export const buildGapTag = () => {
60
+ return t.node(l.CSTML, 'GapTag', [t.ref`sigilToken`], {
38
61
  sigilToken: t.s_node(l.CSTML, 'Punctuator', '<//>'),
39
62
  });
40
63
  };
41
64
 
42
- export const buildShift = () => {
43
- return t.node(l.CSTML, 'Shift', [t.ref`sigilToken`], {
65
+ export const buildShiftTag = () => {
66
+ return t.node(l.CSTML, 'ShiftTag', [t.ref`sigilToken`], {
44
67
  sigilToken: t.s_node(l.CSTML, 'Punctuator', '^^^'),
45
68
  });
46
69
  };
47
70
 
48
71
  export const buildFlags = (flags = {}) => {
49
- const { intrinsic = null, token = null, escape = null, trivia = null, expression = null } = flags;
72
+ const { token = null, escape = null, trivia = null, expression = null, hasGap = null } = flags;
50
73
 
51
74
  if ((trivia && escape) || (expression && (trivia || escape))) {
52
75
  throw new Error('invalid flags');
53
76
  }
54
77
 
55
- return {
56
- children: [
57
- ...when(trivia, [t.ref`triviaToken`]),
58
- ...when(intrinsic, [t.ref`intrinsicToken`]),
59
- ...when(token, [t.ref`tokenToken`]),
60
- ...when(escape, [t.ref`escapeToken`]),
61
- ...when(expression, [t.ref`expressionToken`]),
62
- ],
63
- properties: {
78
+ return t.node(
79
+ l.CSTML,
80
+ 'Flags',
81
+ btree.fromValues(
82
+ concat(
83
+ when(trivia, [t.ref`triviaToken`]),
84
+ when(token, [t.ref`tokenToken`]),
85
+ when(escape, [t.ref`escapeToken`]),
86
+ when(expression, [t.ref`expressionToken`]),
87
+ when(hasGap, [t.ref`hasGapToken`]),
88
+ ),
89
+ ),
90
+ {
64
91
  triviaToken: trivia ? t.s_node(l.CSTML, 'Punctuator', '#') : t.null_node(),
65
- intrinsicToken: intrinsic ? t.s_node(l.CSTML, 'Punctuator', '~') : t.null_node(),
66
92
  tokenToken: token ? t.s_node(l.CSTML, 'Punctuator', '*') : t.null_node(),
67
93
  escapeToken: escape ? t.s_node(l.CSTML, 'Punctuator', '@') : t.null_node(),
68
94
  expressionToken: expression ? t.s_node(l.CSTML, 'Punctuator', '+') : t.null_node(),
95
+ hasGapToken: hasGap ? t.s_node(l.CSTML, 'Punctuator', '$') : t.null_node(),
69
96
  },
70
- };
97
+ );
71
98
  };
72
99
 
73
100
  export const buildSpamMatcher = (type = null, value = null, attributes = {}) => {
@@ -93,30 +120,35 @@ export const buildFullyQualifiedSpamMatcher = (
93
120
  language_ = lArr.length === 0 ? null : lArr;
94
121
  }
95
122
 
96
- return t.node(
97
- l.Spamex,
98
- 'NodeMatcher',
99
- [
100
- t.ref`openToken`,
101
- t.ref`flags`,
102
- ...when(language_, [t.ref`language`, t.ref`languageSeparator`]),
103
- ...when(type, [t.ref`type`]),
104
- ...when(intrinsicValue, [t.embedded(buildSpace()), t.ref`intrinsicValue`]),
105
- ...when(attributes_.length, [t.embedded(buildSpace())]),
106
- ...interpolateArrayChildren(attributes, t.ref`attributes[]`, t.embedded(buildSpace())),
107
- t.ref`closeToken`,
108
- ],
109
- {
110
- openToken: t.s_node(l.CSTML, 'Punctuator', '<'),
111
- flags: buildFlags(flags),
112
- language: language_ ? buildLanguage(language_) : t.null_node(),
113
- languageSeparator: language_ && type ? t.s_node(l.CSTML, 'Punctuator', ':') : t.null_node(),
114
- type: type ? buildIdentifier(type) : t.null_node(),
115
- intrinsicValue: intrinsicValue ? buildString(intrinsicValue) : t.null_node(),
116
- attributes: attributes_,
117
- closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
118
- },
119
- );
123
+ return t.node(l.Spamex, 'NodeMatcher', [t.ref`open`], {
124
+ open: t.node(
125
+ l.Spamex,
126
+ 'NodeMatcher',
127
+ btree.fromValues(
128
+ concat(
129
+ [t.ref`openToken`, t.ref`flags`],
130
+ when(language_, [t.ref`language`, t.ref`languageSeparator`]),
131
+ when(type, [t.ref`type`]),
132
+ when(intrinsicValue, [t.embedded(buildSpace()), t.ref`intrinsicValue`]),
133
+ when(attributes_.length, [t.embedded(buildSpace())]),
134
+ interpolateFragmentChildren(attributes_, t.ref`attributes[]`),
135
+ when(!type, [t.embedded(buildSpace())]),
136
+ [t.ref`selfClosingTagToken`, t.ref`closeToken`],
137
+ ),
138
+ ),
139
+ {
140
+ openToken: t.s_node(l.CSTML, 'Punctuator', '<'),
141
+ flags: buildFlags(flags),
142
+ language: language_ ? buildLanguage(language_) : t.null_node(),
143
+ languageSeparator: language_ && type ? t.s_node(l.CSTML, 'Punctuator', ':') : t.null_node(),
144
+ type: type ? buildIdentifier(type) : t.null_node(),
145
+ intrinsicValue: intrinsicValue ? buildString(intrinsicValue) : t.null_node(),
146
+ attributes: attributes_.properties['.'],
147
+ selfClosingTagToken: t.s_node(l.CSTML, 'Punctuator', '/'),
148
+ closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
149
+ },
150
+ ),
151
+ });
120
152
  };
121
153
 
122
154
  export const buildNodeOpenTag = (flags, language, type = null, attributes = {}) => {
@@ -127,22 +159,23 @@ export const buildNodeOpenTag = (flags, language, type = null, attributes = {})
127
159
  return t.node(
128
160
  l.CSTML,
129
161
  'OpenNodeTag',
130
- [
131
- t.ref`openToken`,
132
- t.ref`flags`,
133
- ...when(language_, [t.ref`language`, t.ref`languageSeparator`]),
134
- ...when(type, [t.ref`type`]),
135
- ...when(attributes_.length, [t.embedded(buildSpace())]),
136
- ...interpolateArrayChildren(attributes_, t.ref`attributes[]`, t.embedded(buildSpace())),
137
- t.ref`closeToken`,
138
- ],
162
+ btree.fromValues(
163
+ concat(
164
+ [t.ref`openToken`, t.ref`flags`],
165
+ when(language_, [t.ref`language`, t.ref`languageSeparator`]),
166
+ when(type, [t.ref`type`]),
167
+ when(attributes_.length, [t.embedded(buildSpace())]),
168
+ interpolateFragmentChildren(attributes_, t.ref`attributes[]`),
169
+ [t.ref`closeToken`],
170
+ ),
171
+ ),
139
172
  {
140
173
  openToken: t.s_node(l.CSTML, 'Punctuator', '<'),
141
174
  flags: buildFlags(flags),
142
175
  language: language_ && type ? buildLanguage(language_) : t.null_node(),
143
176
  languageSeparator: language_ && type ? t.s_node(l.CSTML, 'Punctuator', ':') : t.null_node(),
144
177
  type: type ? buildIdentifier(type) : t.null_node(),
145
- attributes: attributes_,
178
+ attributes: attributes_.properties['.'],
146
179
  closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
147
180
  },
148
181
  );
@@ -154,21 +187,20 @@ export const buildDoctypeTag = (attributes) => {
154
187
  return t.node(
155
188
  l.CSTML,
156
189
  'DoctypeTag',
157
- [
158
- t.ref`openToken`,
159
- t.ref`version`,
160
- t.ref`versionSeparator`,
161
- t.ref`doctype`,
162
- ...when(attributes_.length, [t.embedded(buildSpace())]),
163
- ...interpolateArrayChildren(attributes_, t.ref`attributes[]`, t.embedded(buildSpace())),
164
- t.ref`closeToken`,
165
- ],
190
+ btree.fromValues(
191
+ concat(
192
+ [t.ref`openToken`, t.ref`version`, t.ref`versionSeparator`, t.ref`doctype`],
193
+ when(attributes_.length, [t.embedded(buildSpace())]),
194
+ interpolateFragmentChildren(attributes_, t.ref`attributes[]`),
195
+ [t.ref`closeToken`],
196
+ ),
197
+ ),
166
198
  {
167
199
  openToken: t.s_node(l.CSTML, 'Punctuator', '<!'),
168
200
  version: t.s_node(l.CSTML, 'PositiveInteger', '0'),
169
201
  versionSeparator: t.s_node(l.CSTML, 'Punctuator', ':'),
170
202
  doctype: t.s_node(l.CSTML, 'Keyword', 'cstml'),
171
- attributes: attributes_,
203
+ attributes: attributes_.properties['.'],
172
204
  closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
173
205
  },
174
206
  );
@@ -186,7 +218,13 @@ export const buildIdentifierPath = (path) => {
186
218
  return t.node(
187
219
  l.CSTML,
188
220
  'IdentifierPath',
189
- [...repeat(segments.length, t.ref`segments[]`, t.ref`separators[]`)].slice(0, -1),
221
+ btree.fromValues(
222
+ concat(
223
+ [t.ref`segments[]`, t.arr()],
224
+ repeat(segments.length - 1, t.ref`segments[]`, t.ref`separators[]`),
225
+ [t.ref`segments[]`],
226
+ ),
227
+ ),
190
228
  {
191
229
  segments,
192
230
  separators,
@@ -204,13 +242,15 @@ export const buildNodeCloseTag = (type, language) => {
204
242
  return t.node(
205
243
  l.CSTML,
206
244
  'CloseNodeTag',
207
- [
208
- t.ref`openToken`,
209
- ...when(language, [t.ref`language`]),
210
- ...when(type && language, [t.ref`languageSeparator`]),
211
- ...when(type, [t.ref`type`]),
212
- t.ref`closeToken`,
213
- ],
245
+ btree.fromValues(
246
+ concat(
247
+ [t.ref`openToken`],
248
+ when(language, [t.ref`language`]),
249
+ when(type && language, [t.ref`languageSeparator`]),
250
+ when(type, [t.ref`type`]),
251
+ [t.ref`closeToken`],
252
+ ),
253
+ ),
214
254
  {
215
255
  openToken: t.s_node(l.CSTML, 'Punctuator', '</'),
216
256
  language: language ? buildLanguage(language) : t.null_node(),
@@ -221,8 +261,8 @@ export const buildNodeCloseTag = (type, language) => {
221
261
  );
222
262
  };
223
263
 
224
- export const buildLiteral = (value) => {
225
- return t.node(l.CSTML, 'Literal', [t.ref`value`], { value });
264
+ export const buildLiteralTag = (value) => {
265
+ return t.node(l.CSTML, 'LiteralTag', [t.ref`value`], { value });
226
266
  };
227
267
 
228
268
  export const buildTerminalProps = (matcher) => {
@@ -254,7 +294,7 @@ export const buildProperty = (key, value) => {
254
294
  return t.node(
255
295
  l.Instruction,
256
296
  'Property',
257
- [t.ref`key`, t.ref`mapOperator`, t.embedded(buildSpace()), t.ref`value`],
297
+ btree.from(t.ref`key`, t.ref`mapOperator`, t.embedded(buildSpace()), t.ref`value`),
258
298
  {
259
299
  key: buildIdentifier(key),
260
300
  mapOperator: t.s_node(l.Instruction, 'Punctuator', ':'),
@@ -280,8 +320,15 @@ export const buildInteger = (value, base = 10) => {
280
320
  return t.node(
281
321
  l.CSTML,
282
322
  'Integer',
283
- digits.map((d) => t.ref`digits[]`),
284
- { digits: digits.map((digit) => buildDigit(digit)) },
323
+ btree.fromValues(
324
+ concat(
325
+ [t.ref`digits[]`, t.arr()],
326
+ digits.map(() => t.ref`digits[]`),
327
+ ),
328
+ ),
329
+ {
330
+ digits: digits.map((digit) => buildDigit(digit)),
331
+ },
285
332
  );
286
333
  };
287
334
 
@@ -309,24 +356,22 @@ export const buildNumber = (value) => {
309
356
  }
310
357
  };
311
358
 
312
- const buildLiteralTerminal = (lit) => freeze({ type: 'Literal', value: lit });
313
-
314
359
  export const buildString = (value) => {
315
360
  const pieces = isArray(value) ? value : [value];
316
- const terminals = [];
361
+ const tags = [];
317
362
  let lit = '';
318
363
 
319
364
  if (pieces.length === 1 && pieces[0] === "'") {
320
- return t.node(l.CSTML, 'String', [t.ref`openToken`, t.ref`content`, t.ref`closeToken`], {
321
- openToken: t.s_node(l.CSTML, 'Punctuator', '"'),
322
- content: interpolateString(
323
- freeze({
324
- type: 'Literal',
325
- value,
326
- }),
327
- ),
328
- closeToken: t.s_node(l.CSTML, 'Punctuator', '"'),
329
- });
365
+ return t.node(
366
+ l.CSTML,
367
+ 'String',
368
+ btree.from(t.ref`openToken`, t.ref`content`, t.ref`closeToken`),
369
+ {
370
+ openToken: t.s_node(l.CSTML, 'Punctuator', '"'),
371
+ content: interpolateString(agastBuildLiteralTag(value)),
372
+ closeToken: t.s_node(l.CSTML, 'Punctuator', '"'),
373
+ },
374
+ );
330
375
  }
331
376
 
332
377
  for (const piece of pieces) {
@@ -344,7 +389,7 @@ export const buildString = (value) => {
344
389
  chr.charCodeAt(0) < 32
345
390
  ) {
346
391
  if (lit) {
347
- terminals.push(buildLiteralTerminal(lit));
392
+ tags.push(agastBuildLiteralTag(lit));
348
393
  lit = '';
349
394
  }
350
395
 
@@ -360,7 +405,12 @@ export const buildString = (value) => {
360
405
  value = t.node(
361
406
  l.CSTML,
362
407
  'EscapeCode',
363
- [t.ref`sigilToken`, ...[...hexDigits].map((d) => t.ref`digits[]`)],
408
+ btree.fromValues(
409
+ concat(
410
+ [t.ref`sigilToken`, t.ref`digits[]`, t.arr()],
411
+ [...hexDigits].map((d) => t.ref`digits[]`),
412
+ ),
413
+ ),
364
414
  {
365
415
  sigilToken: buildKeyword('u'),
366
416
  digits: [...hexDigits].map((digit) => buildDigit(digit)),
@@ -370,7 +420,7 @@ export const buildString = (value) => {
370
420
  value = buildKeyword(chr);
371
421
  }
372
422
 
373
- terminals.push(
423
+ tags.push(
374
424
  t.buildEmbeddedNode(
375
425
  t.e_node(
376
426
  l.CSTML,
@@ -389,27 +439,32 @@ export const buildString = (value) => {
389
439
  }
390
440
  }
391
441
  } else {
392
- terminals.push(buildLiteralTerminal(lit));
442
+ tags.push(agastBuildLiteralTag(lit));
393
443
  lit = '';
394
444
 
395
445
  if (piece == null) {
396
446
  throw new Error('not implemented');
397
447
  } else if (isString(piece.type)) {
398
- terminals.push(piece);
448
+ tags.push(piece);
399
449
  } else {
400
450
  throw new Error();
401
451
  }
402
452
  }
403
453
  }
404
454
 
405
- if (lit) terminals.push(buildLiteralTerminal(lit));
455
+ if (lit) tags.push(agastBuildLiteralTag(lit));
406
456
  lit = '';
407
457
 
408
- return t.node(l.CSTML, 'String', [t.ref`openToken`, t.ref`content`, t.ref`closeToken`], {
409
- openToken: t.s_node(l.CSTML, 'Punctuator', "'"),
410
- content: interpolateString(terminals),
411
- closeToken: t.s_node(l.CSTML, 'Punctuator', "'"),
412
- });
458
+ return t.node(
459
+ l.CSTML,
460
+ 'String',
461
+ btree.from(t.ref`openToken`, t.ref`content`, t.ref`closeToken`),
462
+ {
463
+ openToken: t.s_node(l.CSTML, 'Punctuator', "'"),
464
+ content: interpolateString(tags),
465
+ closeToken: t.s_node(l.CSTML, 'Punctuator', "'"),
466
+ },
467
+ );
413
468
  };
414
469
 
415
470
  export const buildBoolean = (value) => {
@@ -418,80 +473,87 @@ export const buildBoolean = (value) => {
418
473
  });
419
474
  };
420
475
 
421
- export const buildNull = () => {
476
+ export const buildNullTag = () => {
422
477
  return t.node(l.Instruction, 'Null', [t.ref`sigilToken`], {
423
478
  sigilToken: t.s_node(l.Instruction, 'Keyword', 'null'),
424
479
  });
425
480
  };
426
481
 
427
482
  export const buildArray = (elements) => {
483
+ const elements_ = buildArrayElements(elements);
428
484
  return t.node(
429
485
  l.Instruction,
430
486
  'Array',
431
- [
432
- t.ref`openToken`,
433
- ...interpolateArrayChildren(
434
- elements,
435
- t.ref`elements[]`,
436
- t.embedded(
437
- t.t_node(l.Comment, null, [t.embedded(t.t_node('Space', 'Space', [t.lit(' ')]))]),
438
- ),
439
- ),
440
- t.ref`closeToken`,
441
- ],
487
+ btree.fromValues(
488
+ concat([t.ref`openToken`], interpolateFragmentChildren(elements_, t.ref`elements[]`), [
489
+ t.ref`closeToken`,
490
+ ]),
491
+ ),
442
492
  {
443
493
  openToken: t.s_node(l.Instruction, 'Punctuator', '['),
444
- elements: [...interpolateArray(elements)],
494
+ elements: elements_.properties['.'],
445
495
  closeToken: t.s_node(l.Instruction, 'Punctuator', ']'),
446
496
  },
447
497
  );
448
498
  };
449
499
 
500
+ export const buildArrayElements = (values) => {
501
+ return buildSpaceSeparatedList(values.map((value) => buildExpression(value)));
502
+ };
503
+
504
+ export const buildTupleValues = buildArrayElements;
505
+
450
506
  export const buildTuple = (values) => {
507
+ const values_ = buildTupleValues(values);
451
508
  return t.node(
452
509
  l.Instruction,
453
510
  'Tuple',
454
- [
455
- t.ref`openToken`,
456
- ...interpolateArrayChildren(
457
- values,
458
- t.ref`values[]`,
459
- t.embedded(
460
- t.t_node(l.Comment, null, [t.embedded(t.t_node('Space', 'Space', [t.lit(' ')]))]),
461
- ),
462
- ),
463
- t.ref`closeToken`,
464
- ],
511
+ btree.fromValues(
512
+ concat([t.ref`openToken`], interpolateFragmentChildren(values_, t.ref`values[]`), [
513
+ t.ref`closeToken`,
514
+ ]),
515
+ ),
465
516
  {
466
517
  openToken: t.s_node(l.Instruction, 'Punctuator', '('),
467
- values: [...interpolateArray(values)],
518
+ values: values_.properties['.'],
468
519
  closeToken: t.s_node(l.Instruction, 'Punctuator', ')'),
469
520
  },
470
521
  );
471
522
  };
472
523
 
524
+ export const buildSpaceSeparatedList = (values) => {
525
+ return t.frag(
526
+ btree.fromValues(
527
+ concat(
528
+ [t.ref`.`, t.arr()],
529
+ buildSeparatedListChildren(values, t.ref`.`, t.embedded(buildSpace())),
530
+ ),
531
+ ),
532
+ {
533
+ ['.']: values,
534
+ },
535
+ );
536
+ };
537
+
538
+ export const buildObjectProperties = (properties) => {
539
+ return buildSpaceSeparatedList(
540
+ Object.entries(properties).map(({ 0: key, 1: value }) => buildProperty(key, value)),
541
+ );
542
+ };
543
+
473
544
  export const buildObject = (properties) => {
545
+ const properties_ = buildObjectProperties(properties);
474
546
  return t.node(
475
547
  l.Instruction,
476
548
  'Object',
477
- [
478
- t.ref`openToken`,
479
- ...interpolateArrayChildren(
480
- Object.entries(properties).map(([key, value]) => buildProperty(key, value)),
481
- t.ref`properties[]`,
482
- t.embedded(
483
- t.t_node(l.Comment, null, [t.embedded(t.t_node('Space', 'Space', [t.lit(' ')]))]),
484
- ),
485
- ),
486
- t.ref`closeToken`,
487
- ],
549
+ btree.fromValues(
550
+ concat([t.ref`openToken`], interpolateFragmentChildren(properties_, t.ref`properties[]`), [
551
+ t.ref`closeToken`,
552
+ ]),
553
+ ),
488
554
  {
489
555
  openToken: t.s_node(l.Instruction, 'Punctuator', '{'),
490
- properties: [
491
- ...interpolateArray(
492
- Object.entries(properties).map(([key, value]) => buildProperty(key, value)),
493
- ),
494
- ],
556
+ properties: properties_.properties['.'],
495
557
  closeToken: t.s_node(l.Instruction, 'Punctuator', '}'),
496
558
  },
497
559
  {},
@@ -499,11 +561,16 @@ export const buildObject = (properties) => {
499
561
  };
500
562
 
501
563
  export const buildMappingAttribute = (key, value) => {
502
- return t.node(l.CSTML, 'MappingAttribute', [t.ref`key`, t.ref`mapOperator`, t.ref`value`], {
503
- key: buildIdentifier(key),
504
- mapOperator: t.s_node(l.CSTML, 'Punctuator', '='),
505
- value: buildExpression(value),
506
- });
564
+ return t.node(
565
+ l.CSTML,
566
+ 'MappingAttribute',
567
+ btree.from(t.ref`key`, t.ref`mapOperator`, t.ref`value`),
568
+ {
569
+ key: buildIdentifier(key),
570
+ mapOperator: t.s_node(l.CSTML, 'Punctuator', '='),
571
+ value: buildExpression(value),
572
+ },
573
+ );
507
574
  };
508
575
 
509
576
  export const buildBooleanAttribute = (key, value) => {
@@ -517,8 +584,44 @@ export const buildAttribute = (key, value) => {
517
584
  return isBoolean(value) ? buildBooleanAttribute(key, value) : buildMappingAttribute(key, value);
518
585
  };
519
586
 
587
+ export const buildPattern = (alternatives, flags) => {
588
+ return t.node(
589
+ l.Regex,
590
+ 'Pattern',
591
+ btree.fromValues(
592
+ concat(
593
+ [t.ref`openToken`],
594
+ buildSeparatedListChildren(alternatives, t.ref`alternatives[]`, t.ref`separators[]`),
595
+ [t.ref`closeToken`, t.ref`flags`],
596
+ ),
597
+ ),
598
+ {
599
+ openToken: t.s_node(l.Regex, 'Punctuator', '/'),
600
+ alternatives,
601
+ separators: alternatives.slice(0, -1).map((alt) => t.s_node(l.Regex, 'Punctuator', '|')),
602
+ closeToken: t.s_node(l.Regex, 'Punctuator', '/'),
603
+ flags: buildFlags(flags),
604
+ },
605
+ );
606
+ };
607
+
608
+ export const buildAlternative = (elements) => {
609
+ return t.node(
610
+ l.Regex,
611
+ 'Pattern',
612
+ btree.fromValues(
613
+ concat([t.ref`openToken`], buildSeparatedListChildren(elements, t.ref`elements[]`), [
614
+ t.ref`closeToken`,
615
+ ]),
616
+ ),
617
+ {
618
+ elements,
619
+ },
620
+ );
621
+ };
622
+
520
623
  export const buildExpression = (expr) => {
521
- if (isNull(expr)) return buildNull();
624
+ if (isNull(expr)) return buildNullTag();
522
625
 
523
626
  switch (typeof expr) {
524
627
  case 'boolean':
@@ -546,12 +649,22 @@ export const buildExpression = (expr) => {
546
649
  };
547
650
 
548
651
  export const buildAttributes = (attributes = {}) => {
549
- return Object.entries(attributes).map(({ 0: key, 1: value }) => buildAttribute(key, value));
550
- };
652
+ const attributes_ = Object.entries(attributes).map(({ 0: key, 1: value }) =>
653
+ buildAttribute(key, value),
654
+ );
551
655
 
552
- export const buildNodeMatcher = (flags, language, type, attributes = {}) => {
553
- const attributes_ = buildAttributes(attributes);
656
+ return t.frag(
657
+ btree.fromValues(
658
+ [t.ref`.[]`, t.arr()],
659
+ buildSeparatedListChildren(attributes_, t.ref`.[]`, t.embedded(buildSpace())),
660
+ ),
661
+ {
662
+ ['.']: attributes_,
663
+ },
664
+ );
665
+ };
554
666
 
667
+ export const buildNodeMatcher = (flags, language, type, attributes) => {
555
668
  let language_ = !language || language.length === 0 ? null : language;
556
669
 
557
670
  const flags_ = buildFlags(flags);
@@ -559,22 +672,24 @@ export const buildNodeMatcher = (flags, language, type, attributes = {}) => {
559
672
  return t.node(
560
673
  l.Spamex,
561
674
  'NodeMatcher',
562
- [
563
- t.ref`openToken`,
564
- ...when(flags_, [t.ref`flags`]),
565
- ...when(language_, [t.ref`language`, t.ref`languageSeparator`]),
566
- t.ref`type`,
567
- ...when(attributes_.length, [t.embedded(buildSpace())]),
568
- ...interpolateArrayChildren(attributes_, t.ref`attributes[]`, t.embedded(buildSpace())),
569
- t.ref`closeToken`,
570
- ],
675
+ btree.fromValues(
676
+ concat(
677
+ [t.ref`openToken`],
678
+ when(flags_, [t.ref`flags`]),
679
+ when(language_, [t.ref`language`, t.ref`languageSeparator`]),
680
+ [t.ref`type`],
681
+ when(attributes.length, [t.embedded(buildSpace())]),
682
+ [...btree.traverse(attributes.children)].slice(1, -1),
683
+ [t.ref`closeToken`],
684
+ ),
685
+ ),
571
686
  {
572
687
  openToken: t.s_node(l.CSTML, 'Punctuator', '<'),
573
688
  language: buildLanguage(language_),
574
689
  languageSeparator: language_ && type ? t.s_node(l.CSTML, 'Punctuator', ':') : t.null_node(),
575
690
  flags: flags_,
576
691
  type: buildIdentifier(type),
577
- attributes: attributes_,
692
+ attributes: attributes.properties.attributes,
578
693
  closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
579
694
  },
580
695
  );
package/lib/deembed.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { EmbeddedExpression } from '@bablr/agast-helpers/symbols';
2
+
1
3
  const { isArray } = Array;
2
4
  const isString = (val) => typeof val === 'string';
3
5
  const isNumber = (val) => typeof val === 'number';
@@ -18,6 +20,6 @@ export const deembedExpression = (expr) => {
18
20
 
19
21
  export const getEmbeddedExpression = (expr) => {
20
22
  if (!expr) return expr;
21
- if (expr.type !== 'EmbeddedExpression') throw new Error();
23
+ if (expr.type !== EmbeddedExpression) throw new Error();
22
24
  return expr.value;
23
25
  };
package/lib/index.js CHANGED
@@ -1,5 +1,18 @@
1
- import { getCooked, isNull } from '@bablr/agast-helpers/tree';
1
+ import { sourceTextFor, getCooked, isNull, nodeFlags } from '@bablr/agast-helpers/tree';
2
+ import * as btree from '@bablr/agast-helpers/btree';
2
3
  import * as sym from '@bablr/agast-helpers/symbols';
4
+ import { buildNodeCloseTag, buildLiteralTag, buildDoctypeTag } from '@bablr/agast-helpers/builders';
5
+ import {
6
+ DoctypeTag,
7
+ OpenNodeTag,
8
+ CloseNodeTag,
9
+ ReferenceTag,
10
+ ShiftTag,
11
+ GapTag,
12
+ NullTag,
13
+ ArrayTag,
14
+ LiteralTag,
15
+ } from '@bablr/agast-helpers/symbols';
3
16
 
4
17
  export * from './builders.js';
5
18
 
@@ -29,14 +42,14 @@ export const shouldBranch = (effects) => {
29
42
  };
30
43
 
31
44
  const reifyFlags = (flags) => {
32
- let { triviaToken, escapeToken, tokenToken, expressionToken, intrinsicToken } = flags.properties;
45
+ let { triviaToken, escapeToken, tokenToken, expressionToken, hasGapToken } = flags.properties;
33
46
 
34
47
  return {
35
- token: reifyExpression(tokenToken),
36
- escape: reifyExpression(escapeToken),
37
- trivia: reifyExpression(triviaToken),
38
- intrinsic: reifyExpression(intrinsicToken),
39
- expression: reifyExpression(expressionToken),
48
+ token: !!reifyExpression(tokenToken),
49
+ escape: !!reifyExpression(escapeToken),
50
+ trivia: !!reifyExpression(triviaToken),
51
+ expression: !!reifyExpression(expressionToken),
52
+ hasGap: !!reifyExpression(hasGapToken),
40
53
  };
41
54
  };
42
55
 
@@ -46,39 +59,149 @@ export const reifyLanguage = (language) => {
46
59
  return typeof value === 'string' && !value.startsWith('https://') ? [value] : value;
47
60
  };
48
61
 
62
+ export const reifyProperties = (properties = []) => {
63
+ const built = {};
64
+ for (const property of btree.traverse(properties)) {
65
+ switch (property.type) {
66
+ case 'Property': {
67
+ let { reference, value } = property.properties;
68
+
69
+ reference = reifyExpression(reference);
70
+ value = reifyExpression(value);
71
+
72
+ if (reference.value.isArray) {
73
+ built[reference.value.name] ||= [];
74
+ built[reference.value.name].push(value);
75
+ } else {
76
+ built[reference.value.name] = value;
77
+ }
78
+ break;
79
+ }
80
+ default:
81
+ throw new Error();
82
+ }
83
+ }
84
+ return built;
85
+ };
86
+
87
+ export const buildChildren = (node) => {
88
+ let { open, children = [], close } = node.properties;
89
+
90
+ const selfClosing = !!open.properties.selfClosingTagToken;
91
+ const { intrinsicValue } = open.properties;
92
+ let built = [];
93
+
94
+ open = reifyExpression(open);
95
+ close = reifyExpression(close);
96
+
97
+ if (selfClosing) {
98
+ built = btree.push(built, open);
99
+ if (intrinsicValue) {
100
+ built = btree.push(built, buildLiteralTag(intrinsicValue));
101
+ }
102
+ built = btree.push(built, buildNodeCloseTag());
103
+ } else {
104
+ built = btree.push(built, open);
105
+ for (const child of btree.traverse(children)) {
106
+ if (child.type !== 'Property') throw new Error('umimplemented');
107
+
108
+ let { reference } = child.properties;
109
+
110
+ reference = reifyExpression(reference);
111
+
112
+ built = btree.push(built, reference);
113
+ }
114
+
115
+ built = btree.push(built, close);
116
+ }
117
+
118
+ return built;
119
+ };
120
+
49
121
  export const reifyExpression = (node) => {
50
122
  if (node instanceof Promise) throw new Error();
51
123
 
52
124
  if (!node || node.type === sym.null) return null;
53
125
 
126
+ if (!node.type) {
127
+ node = node.properties['.'];
128
+ }
129
+
54
130
  if (node.language === 'https://bablr.org/languages/core/en/cstml') {
55
- switch (node.type) {
131
+ switch (node.type?.description || node.type) {
132
+ case 'Document': {
133
+ let { doctype, tree } = node.properties;
134
+
135
+ doctype = reifyExpression(doctype);
136
+ tree = reifyExpression(tree);
137
+
138
+ let { attributes } = doctype.value;
139
+ let { properties } = tree;
140
+
141
+ return {
142
+ flags: nodeFlags,
143
+ language: attributes['bablr-language'],
144
+ type: null,
145
+ children: btree.addAt(
146
+ 0,
147
+ buildChildren(node.properties.tree),
148
+ buildDoctypeTag(attributes),
149
+ ),
150
+ properties,
151
+ attributes,
152
+ };
153
+ }
154
+
155
+ case 'Node': {
156
+ let { open, children } = node.properties;
157
+
158
+ open = reifyExpression(open);
159
+
160
+ let { flags, language, type, attributes } = open.value;
161
+
162
+ const properties = reifyProperties(children);
163
+
164
+ return {
165
+ flags,
166
+ language,
167
+ type,
168
+ children: buildChildren(node),
169
+ properties,
170
+ attributes,
171
+ };
172
+ }
173
+
56
174
  case 'DoctypeTag': {
57
175
  let { doctype, version, attributes } = node.properties;
58
176
  return {
59
- type: 'DoctypeTag',
177
+ type: DoctypeTag,
60
178
  value: {
61
179
  doctype: getCooked(doctype),
62
- version: parseInt(getCooked(version), 10),
180
+ version: parseInt(sourceTextFor(version), 10),
63
181
  attributes: reifyAttributes(attributes),
64
182
  },
65
183
  };
66
184
  }
67
185
 
68
- case 'Reference': {
69
- let { name, arrayOperatorToken } = node.properties;
186
+ case 'ReferenceTag': {
187
+ let { name, arrayOperatorToken, hasGapToken } = node.properties;
70
188
 
71
189
  name = reifyExpression(name);
72
190
 
73
- return { type: 'Reference', value: { name, isArray: !isNull(arrayOperatorToken) } };
191
+ return {
192
+ type: ReferenceTag,
193
+ value: { name, isArray: !isNull(arrayOperatorToken), hasGap: !isNull(hasGapToken) },
194
+ };
74
195
  }
75
196
 
76
- case 'Literal': {
197
+ case 'LiteralTag': {
77
198
  let { value } = node.properties;
78
199
 
79
- return { type: 'Literal', value: getCooked(value.properties.content) };
200
+ return { type: LiteralTag, value: getCooked(value.properties.content) };
201
+ }
202
+ case 'Identifier': {
203
+ return getCooked(node);
80
204
  }
81
-
82
205
  case 'IdentifierPath': {
83
206
  return node.properties.segments.map((segment) => reifyExpression(segment));
84
207
  }
@@ -92,7 +215,7 @@ export const reifyExpression = (node) => {
92
215
  attributes = reifyAttributes(attributes);
93
216
 
94
217
  return {
95
- type: 'OpenNodeTag',
218
+ type: OpenNodeTag,
96
219
  value: { flags, language, type, attributes },
97
220
  };
98
221
  }
@@ -103,7 +226,7 @@ export const reifyExpression = (node) => {
103
226
  language = reifyLanguage(language);
104
227
  type = reifyExpression(type);
105
228
 
106
- return { type: 'CloseNodeTag', value: { language, type } };
229
+ return { type: CloseNodeTag, value: { language, type } };
107
230
  }
108
231
 
109
232
  case 'Integer': {
@@ -122,11 +245,17 @@ export const reifyExpression = (node) => {
122
245
  case 'String':
123
246
  return node.properties.content ? getCooked(node.properties.content) : '';
124
247
 
125
- case 'Gap':
126
- return { type: 'Gap', value: undefined };
248
+ case 'GapTag':
249
+ return { type: GapTag, value: undefined };
250
+
251
+ case 'ArrayTag':
252
+ return { type: ArrayTag, value: undefined };
253
+
254
+ case 'NullTag':
255
+ return { type: NullTag, value: undefined };
127
256
 
128
- case 'Shift':
129
- return { type: 'Shift', value: undefined };
257
+ case 'ShiftTag':
258
+ return { type: ShiftTag, value: undefined };
130
259
 
131
260
  default:
132
261
  throw new Error();
@@ -145,7 +274,7 @@ export const reifyExpression = (node) => {
145
274
 
146
275
  switch (node.type) {
147
276
  case 'NodeMatcher':
148
- let { flags, language, type, attributes, intrinsicValue } = node.properties;
277
+ let { flags, language, type, attributes, intrinsicValue } = node.properties.open.properties;
149
278
 
150
279
  flags = reifyFlags(flags);
151
280
  language = reifyLanguage(language);
@@ -164,7 +293,7 @@ export const reifyExpression = (node) => {
164
293
  const { properties } = node.properties;
165
294
 
166
295
  return Object.fromEntries(
167
- properties.map(({ properties: { key, value } }) => [
296
+ [...btree.traverse(properties)].map(({ properties: { key, value } }) => [
168
297
  getCooked(key),
169
298
  reifyExpression(value),
170
299
  ]),
@@ -174,16 +303,16 @@ export const reifyExpression = (node) => {
174
303
  case 'Tuple': {
175
304
  const { values = [] } = node.properties;
176
305
 
177
- return [...values.map((el) => reifyExpression(el))];
306
+ return [...btree.traverse(values)].map((el) => reifyExpression(el));
178
307
  }
179
308
 
180
309
  case 'Array': {
181
- const { elements } = node.properties;
310
+ const { elements = [] } = node.properties;
182
311
 
183
- return [...elements.map((el) => reifyExpression(el))];
312
+ return [...btree.traverse(elements)].map((el) => reifyExpression(el));
184
313
  }
185
314
 
186
- case 'Literal':
315
+ case 'LiteralTag':
187
316
  case 'Identifier':
188
317
  return getCooked(node);
189
318
 
@@ -221,15 +350,18 @@ export const reifyExpressionShallow = (node) => {
221
350
  const { properties } = node.properties;
222
351
 
223
352
  return Object.fromEntries(
224
- properties.map(({ properties: { key, value } }) => [getCooked(key), value]),
353
+ [...btree.traverse(properties)].map(({ properties: { key, value } }) => [
354
+ getCooked(key),
355
+ value,
356
+ ]),
225
357
  );
226
358
  }
227
359
 
228
360
  case 'Array':
229
- return [...node.properties.elements];
361
+ return [...btree.traverse(node.properties.elements)];
230
362
 
231
363
  case 'Tuple':
232
- return [...node.properties.values];
364
+ return [...btree.traverse(node.properties.values)];
233
365
 
234
366
  default:
235
367
  return reifyExpression(node);
@@ -240,7 +372,7 @@ export const reifyAttributes = (attributes) => {
240
372
  if (attributes == null) return {};
241
373
 
242
374
  return Object.fromEntries(
243
- attributes.map((attr) => {
375
+ [...btree.traverse(attributes)].map((attr) => {
244
376
  if (attr.type === 'MappingAttribute') {
245
377
  return [reifyExpression(attr.properties.key), reifyExpression(attr.properties.value)];
246
378
  } else if (attr.type === 'BooleanAttribute') {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bablr/agast-vm-helpers",
3
3
  "description": "Helper functions for working with the BABLR VM",
4
- "version": "0.3.2",
4
+ "version": "0.5.0",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "sideEffects": false,
20
20
  "dependencies": {
21
- "@bablr/agast-helpers": "0.3.2"
21
+ "@bablr/agast-helpers": "^0.5.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#49f5952efed27f94ee9b94340eb1563c440bf64e",