@bablr/helpers 0.20.6 → 0.21.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,117 +1,130 @@
1
1
  // import { i } from '@bablr/boot/shorthand.macro';
2
- import { interpolateFragmentChildren, interpolateString } from '@bablr/agast-helpers/template';
3
- import { getRoot, isNull } from '@bablr/agast-helpers/tree';
2
+ import { interpolateFragment, buildFilledGapFunction } from '@bablr/agast-helpers/template';
3
+ import {
4
+ buildNullNode,
5
+ isNull,
6
+ treeFromStreamSync as treeFromStream,
7
+ } from '@bablr/agast-helpers/tree';
4
8
  import { buildLiteralTag as agastBuildLiteralTag } from '@bablr/agast-helpers/builders';
5
9
  import * as t from '@bablr/agast-helpers/shorthand';
6
- import * as btree from '@bablr/agast-helpers/btree';
7
10
  import * as l from '@bablr/agast-vm-helpers/languages';
11
+ import { concat } from '@bablr/agast-vm-helpers/iterable';
8
12
 
9
- const { getPrototypeOf, freeze } = Object;
13
+ const { getPrototypeOf, freeze, hasOwn } = Object;
10
14
  const { isArray } = Array;
11
15
 
12
16
  const when = (condition, value) => (condition ? value : { *[Symbol.iterator]() {} });
13
17
 
14
18
  const isString = (val) => typeof val === 'string';
15
- const isBoolean = (val) => typeof val === 'boolean';
16
19
 
17
- function* repeat(times, ...values) {
18
- for (let i = 0; i < times; i++) for (const value of values) yield value;
19
- }
20
-
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) => {
39
- return t.node(
40
- l.CSTML,
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
- ),
50
- {
51
- name: buildIdentifier(name),
52
- arrayOperatorToken: isArray ? t.s_node(l.CSTML, 'Punctuator', '[]') : t.null_node(),
53
- hasGapToken: hasGap ? t.s_node(l.CSTML, 'Punctuator', '$') : t.null_node(),
54
- sigilToken: t.s_node(l.CSTML, 'Punctuator', ':'),
55
- },
20
+ export const buildReferenceTag = (
21
+ name,
22
+ isArray = false,
23
+ flags = t.referenceFlags,
24
+ index = null,
25
+ ) => {
26
+ let expressions = [];
27
+ const gap = buildFilledGapFunction(expressions);
28
+
29
+ return treeFromStream(
30
+ [
31
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'ReferenceTag'),
32
+ t.ref`name`,
33
+ gap(name ? buildIdentifier(name) : buildNullNode()),
34
+ t.ref`openIndexToken`,
35
+ gap(isArray ? buildToken(l.CSTML, 'Punctuator', '[') : buildNullNode()),
36
+ t.ref`index`,
37
+ gap(index || buildNullNode()),
38
+ t.ref`closeIndexToken`,
39
+ gap(isArray ? buildToken(l.CSTML, 'Punctuator', ']') : buildNullNode()),
40
+ t.ref`flags`,
41
+ gap(flags ? buildReferenceFlags(flags) : buildNullNode()),
42
+ t.ref`sigilToken`,
43
+ gap(buildToken(l.CSTML, 'Punctuator', ':')),
44
+ t.nodeClose(),
45
+ ],
46
+ { expressions },
56
47
  );
57
48
  };
58
49
 
59
50
  export const buildGapTag = () => {
60
- return t.node(l.CSTML, 'GapTag', [t.ref`sigilToken`], {
61
- sigilToken: t.s_node(l.CSTML, 'Punctuator', '<//>'),
62
- });
51
+ let expressions = [];
52
+ const gap = buildFilledGapFunction(expressions);
53
+
54
+ return treeFromStream(
55
+ [
56
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'ShiftTag'),
57
+ t.ref`sigilToken`,
58
+ gap(buildToken(l.CSTML, 'Punctuator', '<//>')),
59
+ t.nodeClose(),
60
+ ],
61
+ { expressions },
62
+ );
63
63
  };
64
64
 
65
65
  export const buildShiftTag = () => {
66
- return t.node(l.CSTML, 'ShiftTag', [t.ref`sigilToken`], {
67
- sigilToken: t.s_node(l.CSTML, 'Punctuator', '^^^'),
68
- });
66
+ let expressions = [];
67
+ const gap = buildFilledGapFunction(expressions);
68
+
69
+ return treeFromStream(
70
+ [
71
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'ShiftTag'),
72
+ t.ref`sigilToken`,
73
+ gap(buildToken(l.CSTML, 'Punctuator', '^^^')),
74
+ t.nodeClose(),
75
+ ],
76
+ { expressions },
77
+ );
69
78
  };
70
79
 
71
- export const buildFlags = (flags = {}) => {
72
- const { token = null, escape = null, trivia = null, expression = null, hasGap = null } = flags;
73
-
74
- if ((trivia && escape) || (expression && (trivia || escape))) {
75
- throw new Error('invalid flags');
76
- }
80
+ export const buildReferenceFlags = (flags = t.referenceFlags) => {
81
+ const { expression = null, hasGap = null } = flags;
82
+ let expressions = [];
83
+ const gap = buildFilledGapFunction(expressions);
84
+
85
+ return treeFromStream(
86
+ [
87
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'ReferenceFlags'),
88
+ t.ref`expressionToken`,
89
+ gap(expression ? buildToken(l.CSTML, 'Punctuator', '+') : buildNullNode()),
90
+ t.ref`hasGapToken`,
91
+ gap(hasGap ? buildToken(l.CSTML, 'Punctuator', '$') : buildNullNode()),
92
+ t.nodeClose(),
93
+ ],
94
+ { expressions },
95
+ );
96
+ };
77
97
 
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
- {
91
- triviaToken: trivia ? t.s_node(l.CSTML, 'Punctuator', '#') : t.null_node(),
92
- tokenToken: token ? t.s_node(l.CSTML, 'Punctuator', '*') : t.null_node(),
93
- escapeToken: escape ? t.s_node(l.CSTML, 'Punctuator', '@') : t.null_node(),
94
- expressionToken: expression ? t.s_node(l.CSTML, 'Punctuator', '+') : t.null_node(),
95
- hasGapToken: hasGap ? t.s_node(l.CSTML, 'Punctuator', '$') : t.null_node(),
96
- },
98
+ export const buildNodeFlags = (flags = t.nodeFlags) => {
99
+ const { token = null, hasGap = null } = flags;
100
+ let expressions = [];
101
+ const gap = buildFilledGapFunction(expressions);
102
+
103
+ return treeFromStream(
104
+ [
105
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'NodeFlags'),
106
+ t.ref`triviaToken`,
107
+ gap(token ? buildToken(l.CSTML, 'Punctuator', '*') : buildNullNode()),
108
+ t.ref`hasGapToken`,
109
+ gap(hasGap ? buildToken(l.CSTML, 'Punctuator', '$') : buildNullNode()),
110
+ t.nodeClose(),
111
+ ],
112
+ { expressions },
97
113
  );
98
114
  };
99
115
 
100
- export const buildSpamMatcher = (type = null, value = null, attributes = {}) => {
101
- return buildFullyQualifiedSpamMatcher({}, null, type, value, attributes);
116
+ export const buildSpamMatcher = (type = null, value = null, attributes = null) => {
117
+ return buildOpenNodeMatcher(t.nodeFlags, null, type, value, attributes);
102
118
  };
103
119
 
104
- export const buildFullyQualifiedSpamMatcher = (
105
- flags,
106
- language,
107
- type,
108
- intrinsicValue,
109
- attributes = {},
110
- ) => {
111
- const attributes_ = buildAttributes(attributes);
120
+ export const buildOpenNodeMatcher = (flags, language, type, intrinsicValue, attributes = null) => {
121
+ const expressions = [];
122
+ const gap = buildFilledGapFunction(expressions);
112
123
 
113
124
  let language_;
114
125
 
126
+ if (!type) throw new Error();
127
+
115
128
  if (isString(language)) {
116
129
  language_ = language;
117
130
  } else {
@@ -120,115 +133,195 @@ export const buildFullyQualifiedSpamMatcher = (
120
133
  language_ = lArr.length === 0 ? null : lArr;
121
134
  }
122
135
 
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
- });
136
+ return treeFromStream(
137
+ (function* () {
138
+ yield t.nodeOpen(t.nodeFlags, l.Spamex, 'OpenNodeMatcher');
139
+ yield t.ref`openToken`;
140
+ yield gap(buildToken(l.CSTML, 'Punctuator', '<'));
141
+ yield t.ref`flags`;
142
+ yield gap(buildNodeFlags(flags));
143
+ yield t.ref`language`;
144
+ yield gap(language_ ? buildLanguage(language_) : buildNullNode());
145
+ yield t.ref`languageSeparator`;
146
+ yield gap(language_ && type ? buildToken(l.CSTML, 'Punctuator', ':') : buildNullNode());
147
+ yield t.ref`type`;
148
+ yield gap(typeof type === 'string' ? buildIdentifier(type) : type);
149
+
150
+ yield* when(intrinsicValue, [t.ref`#`, ...buildSpace().children]);
151
+
152
+ yield t.ref`intrinsicValue`;
153
+ yield gap(intrinsicValue ? buildString(intrinsicValue) : buildNullNode());
154
+
155
+ yield* when(attributes?.properties['.'].length, [t.ref`#`, ...buildSpace().children]);
156
+ if (attributes?.properties['.'].length) {
157
+ yield* interpolateFragment(attributes, t.ref`attributes[]`, expressions);
158
+ }
159
+
160
+ yield t.ref`selfClosingTagToken`;
161
+ yield gap(buildToken(l.CSTML, 'Punctuator', '/'));
162
+ yield t.ref`closeToken`;
163
+ yield gap(buildToken(l.CSTML, 'Punctuator', '>'));
164
+ yield t.nodeClose();
165
+ })(),
166
+ { expressions },
167
+ );
152
168
  };
153
169
 
154
- export const buildNodeOpenTag = (flags, language, type = null, attributes = {}) => {
155
- const attributes_ = buildAttributes(attributes);
170
+ export const buildBasicNodeMatcher = (open) => {
171
+ const expressions = [];
172
+ const gap = buildFilledGapFunction(expressions);
173
+
174
+ return treeFromStream(
175
+ [t.nodeOpen(t.nodeFlags, l.Spamex, 'BasicNodeMatcher'), t.ref`open`, gap(open), t.nodeClose()],
176
+ { expressions },
177
+ );
178
+ };
179
+
180
+ export const buildReferenceMatcher = (name, isArray, flags) => {
181
+ const expressions = [];
182
+ const gap = buildFilledGapFunction(expressions);
183
+
184
+ return treeFromStream(
185
+ (function* () {
186
+ yield t.nodeOpen(t.nodeFlags, l.Spamex, 'ReferenceMatcher');
187
+ yield t.ref`name`;
188
+ yield gap(buildToken(l.CSTML, 'Identifier', name));
189
+ yield* (function* () {
190
+ if (isArray) {
191
+ yield t.ref`openIndexToken`;
192
+ yield gap(buildToken(l.CSTML, 'Punctuator', '['));
193
+ yield t.ref`closeIndexToken`;
194
+ yield gap(buildToken(l.CSTML, 'Punctuator', ']'));
195
+ }
196
+ })();
197
+ yield t.ref`flags`;
198
+ yield gap(flags);
199
+ yield t.ref`sigilToken`;
200
+ yield gap(buildToken(l.CSTML, 'Punctuator', ':'));
201
+ yield t.ref`#`;
202
+ yield* buildSpace().children;
203
+ yield t.nodeClose();
204
+ })(),
205
+ { expressions },
206
+ );
207
+ };
208
+
209
+ export const buildFragmentMatcher = (flags) => {
210
+ const expressions = [];
211
+ const gap = buildFilledGapFunction(expressions);
212
+
213
+ return treeFromStream(
214
+ (function* () {
215
+ yield t.nodeOpen(t.nodeFlags, l.Spamex, 'FragmentMatcher');
216
+ yield t.ref`openToken`;
217
+ yield gap(buildToken(l.CSTML, 'Punctuator', '<'));
218
+ yield t.ref`flags`;
219
+ yield gap(flags);
220
+ yield t.ref`#`;
221
+ yield* buildSpace().children;
222
+ yield t.ref`closeToken`;
223
+ yield gap(buildToken(l.CSTML, 'Punctuator', '/>'));
224
+ yield t.nodeClose();
225
+ })(),
226
+ { expressions },
227
+ );
228
+ };
229
+
230
+ export const buildToken = (language, type, value, attributes = {}) => {
231
+ return treeFromStream([
232
+ t.nodeOpen(t.tokenFlags, language, type, attributes),
233
+ t.lit(value),
234
+ t.nodeClose(),
235
+ ]);
236
+ };
237
+
238
+ export const buildPunctuator = (language, value, attributes = {}) => {
239
+ return buildToken(language, 'Punctuator', value, attributes);
240
+ };
241
+
242
+ export const buildOpenNodeTag = (flags, language, type = null, attributes) => {
243
+ const expressions = [];
244
+ const gap = buildFilledGapFunction(expressions);
156
245
 
157
246
  let language_ = !language || language.length === 0 ? null : language;
158
247
 
159
- return t.node(
160
- l.CSTML,
161
- 'OpenNodeTag',
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
- ),
172
- {
173
- openToken: t.s_node(l.CSTML, 'Punctuator', '<'),
174
- flags: buildFlags(flags),
175
- language: language_ && type ? buildLanguage(language_) : t.null_node(),
176
- languageSeparator: language_ && type ? t.s_node(l.CSTML, 'Punctuator', ':') : t.null_node(),
177
- type: type ? buildIdentifier(type) : t.null_node(),
178
- attributes: attributes_.properties['.'],
179
- closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
180
- },
248
+ return treeFromStream(
249
+ (function* () {
250
+ yield t.ref`openToken`;
251
+ yield gap(buildPunctuator(l.CSTML, '<'));
252
+ yield t.ref`flags`;
253
+ yield gap(buildNodeFlags(flags));
254
+ yield t.ref`language`;
255
+ yield gap(language_ && type ? buildLanguage(language_) : buildNullNode());
256
+ yield t.ref`languageSeparator`;
257
+ yield gap(language_ && type ? buildPunctuator(l.CSTML, ':') : buildNullNode());
258
+ yield t.ref`type`;
259
+ yield gap(type ? buildIdentifier(type) : buildNullNode());
260
+ yield* when(attributes.properties['.'].length, [t.ref`#`, gap(buildSpace())]);
261
+ yield* interpolateFragment(attributes, t.ref`attributes[]`, expressions);
262
+ yield t.ref`closeToken`;
263
+ yield gap(buildPunctuator(l.CSTML, '>'));
264
+ })(),
265
+ { expressions },
181
266
  );
182
267
  };
183
268
 
184
269
  export const buildDoctypeTag = (attributes) => {
185
- const attributes_ = buildAttributes(attributes);
186
-
187
- return t.node(
188
- l.CSTML,
189
- 'DoctypeTag',
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
- ),
198
- {
199
- openToken: t.s_node(l.CSTML, 'Punctuator', '<!'),
200
- version: t.s_node(l.CSTML, 'PositiveInteger', '0'),
201
- versionSeparator: t.s_node(l.CSTML, 'Punctuator', ':'),
202
- doctype: t.s_node(l.CSTML, 'Keyword', 'cstml'),
203
- attributes: attributes_.properties['.'],
204
- closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
205
- },
270
+ const expressions = [];
271
+ const gap = buildFilledGapFunction(expressions);
272
+
273
+ return treeFromStream(
274
+ (function* () {
275
+ yield t.nodeOpen(t.nodeFlags, l.CSTML, 'DoctypeTag');
276
+ yield t.ref`openToken`;
277
+ yield gap(buildPunctuator(l.CSTML, 'Punctuator', '<!'));
278
+ yield t.ref`version`;
279
+ yield gap(buildToken(l.CSTML, 'PositiveInteger', '0'));
280
+ yield t.ref`versionSeparator`;
281
+ yield gap(buildPunctuator(l.CSTML, 'Punctuator', ':'));
282
+ yield t.ref`doctype`;
283
+ yield gap(buildKeyword(l.CSTML, 'cstml'));
284
+ yield t.nodeClose();
285
+
286
+ yield* when(attributes.properties['.'].length, [t.ref`#`, ...buildSpace().children]);
287
+ yield* interpolateFragment(attributes, t.ref`attributes[]`, expressions);
288
+
289
+ yield t.ref`closeToken`;
290
+ yield gap(buildToken(l.CSTML, 'Punctuator', '>'));
291
+ })(),
292
+ { expressions },
206
293
  );
207
294
  };
208
295
 
209
296
  export const buildIdentifierPath = (path) => {
210
297
  const path_ = isString(path) ? [path] : [...path];
211
- const segments = path_.map((name) => buildIdentifier(name));
212
- const separators = path_.slice(0, -1).map((_) => t.s_node(l.CSTML, 'Punctuator', '.'));
298
+ const expressions = [];
299
+ const gap = buildFilledGapFunction(expressions);
213
300
 
214
301
  if (!path_.length) {
215
302
  return null;
216
303
  }
217
304
 
218
- return t.node(
219
- l.CSTML,
220
- 'IdentifierPath',
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
- ),
228
- {
229
- segments,
230
- separators,
231
- },
305
+ return treeFromStream(
306
+ (function* () {
307
+ yield t.nodeOpen(t.nodeFlags, l.CSTML, 'IdentifierPath');
308
+ yield t.ref`segments[]`;
309
+ yield t.arr();
310
+ yield t.ref`separatorTokens[]`;
311
+ yield t.arr();
312
+
313
+ yield* path_
314
+ .flatMap((name) => [
315
+ t.ref`segments[]`,
316
+ gap(buildIdentifier(name)),
317
+ t.ref`separatorTokens[]`,
318
+ gap(buildToken(l.CSTML, 'Punctuator', '.')),
319
+ ])
320
+ .slice(0, -1);
321
+
322
+ yield t.nodeClose();
323
+ })(),
324
+ { expressions },
232
325
  );
233
326
  };
234
327
 
@@ -238,31 +331,36 @@ export const buildLanguage = (language) => {
238
331
  : buildIdentifierPath(language);
239
332
  };
240
333
 
241
- export const buildNodeCloseTag = (type, language) => {
242
- return t.node(
243
- l.CSTML,
244
- 'CloseNodeTag',
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
- ),
254
- {
255
- openToken: t.s_node(l.CSTML, 'Punctuator', '</'),
256
- language: language ? buildLanguage(language) : t.null_node(),
257
- languageSeparator: language && type ? t.s_node(l.CSTML, 'Punctuator', ':') : t.null_node(),
258
- type: type ? buildIdentifier(type) : t.null_node(),
259
- closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
260
- },
334
+ export const buildCloseNodeTag = (type, language) => {
335
+ const expressions = [];
336
+ const gap = buildFilledGapFunction(expressions);
337
+
338
+ return treeFromStream(
339
+ [
340
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'CloseNodeTag'),
341
+ t.ref`openToken`,
342
+ gap(buildToken(l.CSTML, 'Punctuator', '</')),
343
+ t.ref`language`,
344
+ t.gap(language ? buildLanguage(language) : buildNullNode()),
345
+ t.ref`languageSeparator`,
346
+ gap(language && type ? buildToken(l.CSTML, 'Punctuator', ':') : buildNullNode()),
347
+ t.ref`type`,
348
+ gap(type ? buildIdentifier(type) : buildNullNode()),
349
+ t.ref`closeToken`,
350
+ gap(buildToken(l.CSTML, 'Punctuator', '>')),
351
+ t.nodeClose(),
352
+ ],
353
+ { expressions },
261
354
  );
262
355
  };
263
356
 
264
357
  export const buildLiteralTag = (value) => {
265
- return t.node(l.CSTML, 'LiteralTag', [t.ref`value`], { value });
358
+ return treeFromStream([
359
+ t.nodeOpen(t.nodeFlags, l.Instruction, 'LiteralTag'),
360
+ t.ref`value`,
361
+ t.lit(value),
362
+ t.nodeClose(),
363
+ ]);
266
364
  };
267
365
 
268
366
  export const buildTerminalProps = (matcher) => {
@@ -272,34 +370,54 @@ export const buildTerminalProps = (matcher) => {
272
370
  };
273
371
 
274
372
  export const buildSpace = () => {
275
- return t.t_node(l.Comment, null, [t.embedded(t.t_node(l.Space, 'Space', [t.lit(' ')]))]);
373
+ return buildToken(l.Space, 'Space', ' ');
276
374
  };
277
375
 
278
376
  export const buildIdentifier = (name) => {
279
- return t.s_node(l.Instruction, 'Identifier', name);
280
- };
377
+ if (!/^[a-zA-Z]+$/.test(name)) throw new Error();
281
378
 
282
- export const buildKeyword = (name) => {
283
- return t.s_node(l.Instruction, 'Identifier', name);
379
+ return buildToken(l.Instruction, 'Identifier', name);
284
380
  };
285
381
 
286
- export const buildCall = (verb, ...args) => {
287
- return t.node(l.Instruction, 'Call', [t.ref`verb`, t.ref`arguments`], {
288
- verb: buildIdentifier(verb),
289
- arguments: buildTuple(args),
290
- });
382
+ export const buildKeyword = (name) => {
383
+ return buildToken(l.Instruction, 'Keyword', name);
384
+ };
385
+
386
+ export const buildCall = (verb, args) => {
387
+ const expressions = [];
388
+ const gap = buildFilledGapFunction(expressions);
389
+
390
+ return treeFromStream(
391
+ [
392
+ t.nodeOpen(t.nodeFlags, l.Instruction, 'Call'),
393
+ t.ref`verb`,
394
+ gap(verb),
395
+ t.ref`arguments`,
396
+ gap(args),
397
+ t.nodeClose(),
398
+ ],
399
+ { expressions },
400
+ );
291
401
  };
292
402
 
293
403
  export const buildProperty = (key, value) => {
294
- return t.node(
295
- l.Instruction,
296
- 'Property',
297
- btree.from(t.ref`key`, t.ref`mapOperator`, t.embedded(buildSpace()), t.ref`value`),
298
- {
299
- key: buildIdentifier(key),
300
- mapOperator: t.s_node(l.Instruction, 'Punctuator', ':'),
301
- value: buildExpression(value),
302
- },
404
+ const expressions = [];
405
+ const gap = buildFilledGapFunction(expressions);
406
+
407
+ return treeFromStream(
408
+ (function* () {
409
+ yield t.nodeOpen(t.nodeFlags, l.Instruction, 'Property');
410
+ yield t.ref`key`;
411
+ yield gap(key);
412
+ yield t.ref`mapOperator`;
413
+ yield gap(buildToken(l.Instruction, 'Punctuator', ':'));
414
+ yield t.ref`#`;
415
+ yield gap(buildSpace());
416
+ yield t.ref`value`;
417
+ yield gap(value);
418
+ yield t.nodeClose();
419
+ })(),
420
+ { expressions },
303
421
  );
304
422
  };
305
423
 
@@ -311,24 +429,23 @@ const escapables = {
311
429
  };
312
430
 
313
431
  export const buildDigit = (value) => {
314
- return t.s_node(l.CSTML, 'Digit', value);
432
+ return buildToken(l.CSTML, 'Digit', value);
315
433
  };
316
434
 
317
435
  export const buildInteger = (value, base = 10) => {
436
+ const expressions = [];
437
+ const gap = buildFilledGapFunction(expressions);
438
+
318
439
  const digits = value.toString(base).split('');
319
440
 
320
- return t.node(
321
- l.CSTML,
322
- 'Integer',
323
- btree.fromValues(
324
- concat(
325
- [t.ref`digits[]`, t.arr()],
326
- digits.map(() => t.ref`digits[]`),
327
- ),
441
+ return treeFromStream(
442
+ concat(
443
+ [t.nodeOpen(t.nodeFlags, l.CSTML, 'Integer'), t.ref`digits[]`, t.arr()],
444
+ digits.flatMap((digit) => [t.ref`digits[]`, gap(buildDigit(digit))]),
445
+ [t.nodeClose()],
328
446
  ),
329
- {
330
- digits: digits.map((digit) => buildDigit(digit)),
331
- },
447
+
448
+ { expressions },
332
449
  );
333
450
  };
334
451
 
@@ -342,10 +459,20 @@ export const buildInfinity = (value) => {
342
459
  throw new Error();
343
460
  }
344
461
 
345
- return t.node(l.CSTML, 'Infinity', [t.ref`sign`, t.ref`value`], {
346
- sign: t.s_node(l.CSTML, 'Punctuator', sign),
347
- value: t.s_node(l.CSTML, 'Keyword', 'Infinity'),
348
- });
462
+ const expressions = [];
463
+ const gap = buildFilledGapFunction(expressions);
464
+
465
+ return treeFromStream(
466
+ [
467
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'Infinity'),
468
+ t.ref`sign`,
469
+ gap(buildToken(l.CSTML, 'Punctuator', sign)),
470
+ t.ref`value`,
471
+ gap(buildToken(l.CSTML, 'Keyword', 'Infinity')),
472
+ t.nodeClose(),
473
+ ],
474
+ { expressions },
475
+ );
349
476
  };
350
477
 
351
478
  export const buildNumber = (value) => {
@@ -358,297 +485,390 @@ export const buildNumber = (value) => {
358
485
 
359
486
  export const buildString = (value) => {
360
487
  const pieces = isArray(value) ? value : [value];
361
- const tags = [];
362
488
  let lit = '';
363
489
 
364
490
  if (pieces.length === 1 && pieces[0] === "'") {
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
- },
491
+ const expressions = [];
492
+ const gap = buildFilledGapFunction(expressions);
493
+ return treeFromStream(
494
+ [
495
+ t.nodeOpen(t.nodeFlags, l.JSON, 'String'),
496
+ t.ref`openToken`,
497
+ gap(buildToken(l.JSON, 'Punctuator', '"')),
498
+ t.ref`content`,
499
+ gap(buildToken(l.JSON, 'StringContent', value)),
500
+ t.ref`closeToken`,
501
+ gap(buildToken(l.JSON, 'Punctuator', '"')),
502
+ t.nodeClose(),
503
+ ],
504
+ { expressions },
374
505
  );
375
506
  }
376
507
 
377
- for (const piece of pieces) {
378
- if (isString(piece)) {
379
- const value = piece;
380
-
381
- for (const chr of value) {
382
- if (
383
- chr === '\\' ||
384
- chr === "'" ||
385
- chr === '\n' ||
386
- chr === '\r' ||
387
- chr === '\t' ||
388
- chr === '\0' ||
389
- chr.charCodeAt(0) < 32
390
- ) {
391
- if (lit) {
392
- tags.push(agastBuildLiteralTag(lit));
393
- lit = '';
508
+ const expressions = [];
509
+ const gap = buildFilledGapFunction(expressions);
510
+
511
+ return treeFromStream(
512
+ (function* () {
513
+ yield t.nodeOpen(t.nodeFlags, l.JSON, 'String');
514
+ yield t.ref`openToken`;
515
+ const tok = buildToken(l.JSON, 'Punctuator', "'");
516
+ yield gap(tok);
517
+ yield t.ref`content`;
518
+ yield t.nodeOpen(t.tokenFlags, l.JSON, 'StringContent');
519
+
520
+ for (const piece of pieces) {
521
+ if (isString(piece)) {
522
+ const value = piece;
523
+
524
+ for (const chr of value) {
525
+ if (
526
+ chr === '\\' ||
527
+ chr === "'" ||
528
+ chr === '\n' ||
529
+ chr === '\r' ||
530
+ chr === '\t' ||
531
+ chr === '\0' ||
532
+ chr.charCodeAt(0) < 32
533
+ ) {
534
+ if (lit) {
535
+ yield agastBuildLiteralTag(lit);
536
+ lit = '';
537
+ }
538
+
539
+ let value;
540
+
541
+ if (escapables[chr]) {
542
+ const expressions = [];
543
+ const gap = buildFilledGapFunction(expressions);
544
+
545
+ value = treeFromStream(
546
+ [
547
+ t.nodeOpen(t.nodeFlags, l.JSON, 'EscapeCode'),
548
+ t.ref`sigilToken`,
549
+ gap(buildKeyword(escapables[chr])),
550
+ t.ref`digits[]`,
551
+ t.arr(),
552
+ t.nodeClose(),
553
+ ],
554
+ { expressions },
555
+ );
556
+ } else if (chr.charCodeAt(0) < 32) {
557
+ const hexDigits = chr.charCodeAt(0).toString(16).padStart(4, '0');
558
+ const expressions = [];
559
+ const gap = buildFilledGapFunction(expressions);
560
+
561
+ value = treeFromStream(
562
+ [
563
+ t.nodeOpen(t.nodeFlags, l.JSON, 'EscapeCode'),
564
+ t.ref`sigilToken`,
565
+ gap(buildKeyword('u')),
566
+ t.ref`digits[]`,
567
+ t.arr(),
568
+ [...hexDigits].flatMap((digit) => [t.ref`digits[]`, gap(buildDigit(digit))]),
569
+ t.nodeClose(),
570
+ ],
571
+ { expressions },
572
+ );
573
+ } else {
574
+ value = buildKeyword(chr);
575
+ }
576
+
577
+ yield t.ref`@`;
578
+ yield t.nodeOpen(t.nodeFlags, l.JSON, 'EscapeSequence', { cooked: chr });
579
+ yield t.ref`escape`;
580
+ yield gap(buildToken(l.JSON, 'Punctuator', '\\'));
581
+ yield t.ref`value`;
582
+ yield gap(value);
583
+ yield t.nodeClose();
584
+ } else {
585
+ lit += chr;
586
+ }
394
587
  }
588
+ } else {
589
+ yield agastBuildLiteralTag(lit);
590
+ lit = '';
395
591
 
396
- let value;
397
-
398
- if (escapables[chr]) {
399
- value = t.node(l.CSTML, 'EscapeCode', [t.ref`sigilToken`], {
400
- sigilToken: buildKeyword(escapables[chr]),
401
- digits: t.null_node(),
402
- });
403
- } else if (chr.charCodeAt(0) < 32) {
404
- const hexDigits = chr.charCodeAt(0).toString(16).padStart(4, '0');
405
- value = t.node(
406
- l.CSTML,
407
- 'EscapeCode',
408
- btree.fromValues(
409
- concat(
410
- [t.ref`sigilToken`, t.ref`digits[]`, t.arr()],
411
- [...hexDigits].map((d) => t.ref`digits[]`),
412
- ),
413
- ),
414
- {
415
- sigilToken: buildKeyword('u'),
416
- digits: [...hexDigits].map((digit) => buildDigit(digit)),
417
- },
418
- );
592
+ if (piece == null) {
593
+ throw new Error('not implemented');
594
+ } else if (isString(piece.type)) {
595
+ yield piece;
419
596
  } else {
420
- value = buildKeyword(chr);
597
+ throw new Error();
421
598
  }
422
-
423
- tags.push(
424
- t.buildEmbeddedNode(
425
- t.e_node(
426
- l.CSTML,
427
- 'EscapeSequence',
428
- [t.ref`escape`, t.ref`value`],
429
- {
430
- escape: t.s_node(l.CSTML, 'Punctuator', '\\'),
431
- value,
432
- },
433
- { cooked: chr },
434
- ),
435
- ),
436
- );
437
- } else {
438
- lit += chr;
439
599
  }
440
600
  }
441
- } else {
442
- tags.push(agastBuildLiteralTag(lit));
443
- lit = '';
444
-
445
- if (piece == null) {
446
- throw new Error('not implemented');
447
- } else if (isString(piece.type)) {
448
- tags.push(piece);
449
- } else {
450
- throw new Error();
451
- }
452
- }
453
- }
454
601
 
455
- if (lit) tags.push(agastBuildLiteralTag(lit));
456
- lit = '';
602
+ if (lit) yield agastBuildLiteralTag(lit);
603
+ lit = '';
457
604
 
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
- },
605
+ yield t.nodeClose();
606
+ yield t.ref`closeToken`;
607
+ yield gap(buildToken(l.JSON, 'Punctuator', "'"));
608
+ yield t.nodeClose();
609
+ })(),
610
+ { expressions },
467
611
  );
468
612
  };
469
613
 
470
614
  export const buildBoolean = (value) => {
471
- return t.node(l.Instruction, 'Boolean', [t.ref`sigilToken`], {
472
- sigilToken: t.s_node(l.Instruction, 'Keyword', value ? 'true' : 'false'),
473
- });
615
+ const expressions = [];
616
+ const gap = buildFilledGapFunction(expressions);
617
+
618
+ return treeFromStream(
619
+ [
620
+ t.nodeOpen(t.nodeFlags, l.Instruction, 'Boolean'),
621
+ t.ref`sigilToken`,
622
+ gap(buildToken(l.Instruction, 'Keyword', value ? 'true' : 'false')),
623
+ t.nodeClose(),
624
+ ],
625
+ { expressions },
626
+ );
627
+ };
628
+
629
+ export const buildNull = () => {
630
+ const expressions = [];
631
+ const gap = buildFilledGapFunction(expressions);
632
+
633
+ return treeFromStream(
634
+ [
635
+ t.nodeOpen(t.nodeFlags, l.Instruction, 'Null'),
636
+ t.ref`sigilToken`,
637
+ gap(buildToken(l.Instruction, 'Keyword', 'null')),
638
+ t.nodeClose(),
639
+ ],
640
+ { expressions },
641
+ );
474
642
  };
475
643
 
476
644
  export const buildNullTag = () => {
477
- return t.node(l.Instruction, 'Null', [t.ref`sigilToken`], {
478
- sigilToken: t.s_node(l.Instruction, 'Keyword', 'null'),
479
- });
645
+ const expressions = [];
646
+ const gap = buildFilledGapFunction(expressions);
647
+
648
+ return treeFromStream(
649
+ [
650
+ t.nodeOpen(t.nodeFlags, l.CSTML, 'NullTag'),
651
+ t.ref`sigilToken`,
652
+ gap(buildToken(l.CSTML, 'Keyword', 'null')),
653
+ t.nodeClose(),
654
+ ],
655
+ { expressions },
656
+ );
480
657
  };
481
658
 
482
659
  export const buildArray = (elements) => {
483
- const elements_ = buildArrayElements(elements);
484
- return t.node(
485
- l.Instruction,
486
- 'Array',
487
- btree.fromValues(
488
- concat([t.ref`openToken`], interpolateFragmentChildren(elements_, t.ref`elements[]`), [
489
- t.ref`closeToken`,
490
- ]),
491
- ),
492
- {
493
- openToken: t.s_node(l.Instruction, 'Punctuator', '['),
494
- elements: elements_.properties['.'],
495
- closeToken: t.s_node(l.Instruction, 'Punctuator', ']'),
496
- },
660
+ const expressions = [];
661
+ const gap = buildFilledGapFunction(expressions);
662
+
663
+ return treeFromStream(
664
+ (function* () {
665
+ yield t.nodeOpen(t.nodeFlags, l.Instruction, 'Array');
666
+ yield t.ref`openToken`;
667
+ yield gap(buildToken(l.Instruction, 'Punctuator', '['));
668
+ yield* interpolateFragment(elements, t.ref`elements[]`, expressions);
669
+ yield t.ref`closeToken`;
670
+ yield gap(buildToken(l.Instruction, 'Punctuator', ']'));
671
+ yield t.nodeClose();
672
+ })(),
673
+ { expressions },
497
674
  );
498
675
  };
499
676
 
500
677
  export const buildArrayElements = (values) => {
501
- return buildSpaceSeparatedList(values.map((value) => buildExpression(value)));
678
+ const expressions = [];
679
+ return treeFromStream(
680
+ (function* () {
681
+ yield t.doctype({ bablrLanguage: l.Instruction });
682
+ yield t.nodeOpen(t.nodeFlags);
683
+ yield* buildSpaceSeparatedList(values, t.ref`.[]`, expressions);
684
+ yield t.nodeClose();
685
+ })(),
686
+ { expressions },
687
+ );
502
688
  };
503
689
 
504
- export const buildTupleValues = buildArrayElements;
690
+ export function* buildSpaceSeparatedList(values, ref, expressions) {
691
+ const gap = buildFilledGapFunction(expressions);
505
692
 
506
- export const buildTuple = (values) => {
507
- const values_ = buildTupleValues(values);
508
- return t.node(
509
- l.Instruction,
510
- 'Tuple',
511
- btree.fromValues(
512
- concat([t.ref`openToken`], interpolateFragmentChildren(values_, t.ref`values[]`), [
513
- t.ref`closeToken`,
514
- ]),
515
- ),
516
- {
517
- openToken: t.s_node(l.Instruction, 'Punctuator', '('),
518
- values: values_.properties['.'],
519
- closeToken: t.s_node(l.Instruction, 'Punctuator', ')'),
520
- },
521
- );
522
- };
693
+ if (!ref.value.isArray) throw new Error();
523
694
 
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
- };
695
+ yield freeze({ ...ref });
696
+ yield t.arr();
697
+
698
+ let first = true;
699
+ for (const value of values) {
700
+ if (!first) {
701
+ yield t.buildReferenceTag('#', false);
702
+ yield gap(buildSpace());
703
+ }
704
+ yield freeze({ ...ref });
705
+ yield gap(value || buildNullNode());
706
+ first = false;
707
+ }
708
+ }
537
709
 
538
710
  export const buildObjectProperties = (properties) => {
539
- return buildSpaceSeparatedList(
540
- Object.entries(properties).map(({ 0: key, 1: value }) => buildProperty(key, value)),
711
+ const expressions = [];
712
+
713
+ return treeFromStream(
714
+ concat(
715
+ [t.doctype({ bablrLanguage: l.Instruction }), t.nodeOpen(t.nodeFlags)],
716
+ buildSpaceSeparatedList(properties, t.ref`properties[]`, expressions),
717
+ [t.nodeClose()],
718
+ ),
719
+ { expressions },
541
720
  );
542
721
  };
543
722
 
544
723
  export const buildObject = (properties) => {
545
- const properties_ = buildObjectProperties(properties);
546
- return t.node(
547
- l.Instruction,
548
- 'Object',
549
- btree.fromValues(
550
- concat([t.ref`openToken`], interpolateFragmentChildren(properties_, t.ref`properties[]`), [
551
- t.ref`closeToken`,
552
- ]),
553
- ),
554
- {
555
- openToken: t.s_node(l.Instruction, 'Punctuator', '{'),
556
- properties: properties_.properties['.'],
557
- closeToken: t.s_node(l.Instruction, 'Punctuator', '}'),
558
- },
559
- {},
724
+ const expressions = [];
725
+ const gap = buildFilledGapFunction(expressions);
726
+
727
+ return treeFromStream(
728
+ (function* () {
729
+ yield t.nodeOpen(t.nodeFlags, l.Instruction, 'Object');
730
+ yield t.ref`openToken`;
731
+ yield gap(buildToken(l.Instruction, 'Punctuator', '{'));
732
+
733
+ yield* interpolateFragment(properties, t.ref`properties[]`, expressions);
734
+
735
+ yield t.ref`closeToken`;
736
+ yield gap(buildToken(l.Instruction, 'Punctuator', '}'));
737
+ yield t.nodeClose();
738
+ })(),
739
+ { expressions },
560
740
  );
561
741
  };
562
742
 
563
- export const buildMappingAttribute = (key, value) => {
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
- },
743
+ export const buildPattern = (alternatives, flags) => {
744
+ const expressions = [];
745
+ const gap = buildFilledGapFunction(expressions);
746
+
747
+ return treeFromStream(
748
+ (function* () {
749
+ yield t.nodeOpen(t.nodeFlags, l.Regex, 'Pattern');
750
+ yield t.ref`openToken`;
751
+ yield gap(buildToken(l.Regex, 'Punctuator', '/'));
752
+
753
+ yield* interpolateFragment(alternatives, t.ref`alternatives[]`, expressions);
754
+
755
+ yield t.ref`closeToken`;
756
+ yield gap(buildToken(l.Regex, 'Punctuator', '/'));
757
+ yield t.ref`flags`;
758
+ yield gap(flags || buildReferenceFlags());
759
+ yield t.nodeClose();
760
+ })(),
761
+ { expressions },
573
762
  );
574
763
  };
575
764
 
576
- export const buildBooleanAttribute = (key, value) => {
577
- return t.node(l.CSTML, 'BooleanAttribute', [...when(!value, [t.ref`negateToken`]), t.ref`key`], {
578
- negateToken: !value ? t.s_node(l.CSTML, 'Puncutator', '!') : t.null_node(),
579
- key: buildIdentifier(key),
580
- });
765
+ const flagCharacters = {
766
+ global: 'g',
767
+ ignoreCase: 'i',
768
+ multiline: 'm',
769
+ dotAll: 's',
770
+ unicode: 'u',
771
+ sticky: 'y',
581
772
  };
582
773
 
583
- export const buildAttribute = (key, value) => {
584
- return isBoolean(value) ? buildBooleanAttribute(key, value) : buildMappingAttribute(key, value);
585
- };
774
+ export const buildRegexFlags = (flags = '') => {
775
+ let expressions = [];
776
+ const gap = buildFilledGapFunction(expressions);
586
777
 
587
- export const buildPattern = (alternatives, flags) => {
588
- return t.node(
589
- l.Regex,
590
- 'Pattern',
591
- btree.fromValues(
592
- concat([t.ref`openToken`], interpolateFragmentChildren(alternatives, t.ref`alternatives[]`), [
593
- t.ref`closeToken`,
594
- t.ref`flags`,
595
- ]),
596
- ),
597
- {
598
- openToken: t.s_node(l.Regex, 'Punctuator', '/'),
599
- separators: alternatives.properties.separators,
600
- alternatives: alternatives.properties['.'],
601
- closeToken: t.s_node(l.Regex, 'Punctuator', '/'),
602
- flags: buildFlags(flags),
603
- },
778
+ return treeFromStream(
779
+ (function* () {
780
+ yield t.nodeOpen(t.nodeFlags, l.Regex, 'Flags');
781
+
782
+ for (const { 0: name, 1: chr } of Object.entries(flagCharacters)) {
783
+ yield t.buildReferenceTag(name + 'Token');
784
+
785
+ yield gap(flags.includes(chr) ? buildToken(l.CSTML, 'Punctuator', chr) : buildNullNode());
786
+ }
787
+ yield t.nodeClose();
788
+ })(),
789
+ { expressions },
604
790
  );
605
791
  };
606
792
 
607
793
  export const buildAlternative = (elements) => {
608
- const elementsArray = getRoot(elements);
609
- return t.node(
610
- l.Regex,
611
- 'Alternative',
612
- btree.fromValues(interpolateFragmentChildren(elements, t.ref`elements[]`), {
613
- elements: elementsArray,
614
- }),
615
- { elements: elementsArray },
616
- );
617
- };
618
-
619
- export const buildAlternatives = (alternatives = {}) => {
620
- return t.frag(
621
- btree.fromValues(
622
- concat(
623
- [t.ref`.[]`, t.arr(), t.ref`separators[]`, t.arr()],
624
- buildSeparatedListChildren(alternatives, t.ref`.[]`, t.ref`separators[]`),
625
- ),
794
+ const expressions = [];
795
+
796
+ return treeFromStream(
797
+ concat(
798
+ [t.nodeOpen(t.nodeFlags, l.Regex, 'Alternative')],
799
+ interpolateFragment(elements, t.ref`elements[]+`, expressions),
800
+ [t.nodeClose()],
626
801
  ),
627
- {
628
- ['.']: alternatives,
629
- separators: alternatives.slice(0, -1).map((alt) => t.s_node(l.Regex, 'Punctuator', '|')),
630
- },
802
+ { expressions },
803
+ );
804
+ };
805
+
806
+ export const buildAlternatives = (alternatives = []) => {
807
+ const expressions = [];
808
+ const gap = buildFilledGapFunction(expressions);
809
+
810
+ return treeFromStream(
811
+ (function* () {
812
+ yield t.doctype({ bablrLanguage: l.Regex });
813
+ yield t.nodeOpen(t.nodeFlags);
814
+ yield t.ref`.[]`;
815
+ yield t.arr();
816
+ yield t.ref`separatorTokens[]`;
817
+ yield t.arr();
818
+
819
+ yield* alternatives
820
+ .flatMap((alt) => [
821
+ t.ref`.[]`,
822
+ gap(alt),
823
+ t.ref`separatorTokens[]`,
824
+ gap(buildPunctuator(l.Regex, '|')),
825
+ ])
826
+ .slice(0, -2);
827
+
828
+ yield t.nodeClose();
829
+ })(),
830
+ { expressions },
831
+ );
832
+ };
833
+
834
+ export const buildRegexGap = () => {
835
+ const expressions = [];
836
+ const gap = buildFilledGapFunction(expressions);
837
+
838
+ return treeFromStream(
839
+ [
840
+ t.nodeOpen(t.nodeFlags, l.Regex, 'Gap'),
841
+ t.ref`escapeToken`,
842
+ gap(buildToken(l.Regex, 'Punctuator', '\\')),
843
+ t.ref`value`,
844
+ gap(buildToken(l.Regex, 'Keyword', 'g')),
845
+ t.nodeClose(),
846
+ ],
847
+ { expressions },
631
848
  );
632
849
  };
633
850
 
634
851
  export const buildElements = (elements) => {
635
- return t.frag(
636
- btree.fromValues(
637
- concat(
638
- [t.ref`.[]`, t.arr()],
639
- elements.map((el) => t.ref`.[]`),
640
- ),
852
+ const expressions = [];
853
+ const gap = buildFilledGapFunction(expressions);
854
+
855
+ return treeFromStream(
856
+ concat(
857
+ [t.doctype({ bablrLanguage: l.Regex }), t.nodeOpen(t.nodeFlags), t.ref`.[]+`, t.arr()],
858
+ elements.flatMap((el) => [t.ref`.[]+`, gap(el)]),
859
+ [t.nodeClose()],
641
860
  ),
642
- {
643
- ['.']: elements,
644
- },
861
+ { expressions },
645
862
  );
646
863
  };
647
864
 
648
865
  export const buildExpression = (expr) => {
866
+ throw new Error('unimplemented');
867
+
649
868
  if (isNull(expr)) return buildNullTag();
650
869
 
651
870
  switch (typeof expr) {
871
+ case 'symbol':
652
872
  case 'boolean':
653
873
  return buildBoolean(expr);
654
874
  case 'string':
@@ -658,12 +878,21 @@ export const buildExpression = (expr) => {
658
878
  case 'object': {
659
879
  switch (getPrototypeOf(expr)) {
660
880
  case Array.prototype:
661
- return buildArray(expr);
881
+ return buildArray(buildArrayElements(expr));
662
882
  case Object.prototype:
663
- if (expr.type && expr.language && expr.children && expr.properties) {
883
+ if (
884
+ hasOwn(expr, 'type') &&
885
+ hasOwn(expr, 'language') &&
886
+ hasOwn(expr, 'children') &&
887
+ hasOwn(expr, 'properties')
888
+ ) {
664
889
  return expr;
665
890
  }
666
- return buildObject(expr);
891
+ return buildObject(
892
+ buildObjectProperties(
893
+ Object.entries(expr).map((e) => buildProperty(buildIdentifier(e[0]), e[1])),
894
+ ),
895
+ );
667
896
  default:
668
897
  throw new Error();
669
898
  }
@@ -673,49 +902,52 @@ export const buildExpression = (expr) => {
673
902
  }
674
903
  };
675
904
 
676
- export const buildAttributes = (attributes = {}) => {
677
- const attributes_ = Object.entries(attributes).map(({ 0: key, 1: value }) =>
678
- buildAttribute(key, value),
905
+ export const buildTaggedString = (tag, content) => {
906
+ const expressions = [];
907
+ const gap = buildFilledGapFunction(expressions);
908
+
909
+ return treeFromStream(
910
+ [
911
+ t.buildOpenNodeTag(t.nodeFlags, l.Spamex, 'SpamexString'),
912
+ t.buildReferenceTag('sigilToken'),
913
+ gap(buildToken(l.Instruction, 'Keyword', tag)),
914
+ t.buildReferenceTag('openToken'),
915
+ gap(buildToken(l.Instruction, 'Punctuator', "'")),
916
+ t.buildReferenceTag('content'),
917
+ gap(content),
918
+ t.buildReferenceTag('closeToken'),
919
+ gap(buildToken(l.Instruction, 'Punctuator', "'")),
920
+ t.buildCloseNodeTag(),
921
+ ],
922
+ { expressions },
679
923
  );
924
+ };
680
925
 
681
- return t.frag(
682
- btree.fromValues(
683
- [t.ref`.[]`, t.arr()],
684
- buildSeparatedListChildren(attributes_, t.ref`.[]`, t.embedded(buildSpace())),
685
- ),
686
- {
687
- ['.']: attributes_,
688
- },
689
- );
926
+ export const buildSpamexString = (content) => {
927
+ return buildTaggedString('m', content);
690
928
  };
691
929
 
692
- export const buildNodeMatcher = (flags, language, type, attributes) => {
693
- let language_ = !language || language.length === 0 ? null : language;
930
+ export const buildRegexString = (content) => {
931
+ return buildTaggedString('re', content);
932
+ };
694
933
 
695
- const flags_ = buildFlags(flags);
696
-
697
- return t.node(
698
- l.Spamex,
699
- 'NodeMatcher',
700
- btree.fromValues(
701
- concat(
702
- [t.ref`openToken`],
703
- when(flags_, [t.ref`flags`]),
704
- when(language_, [t.ref`language`, t.ref`languageSeparator`]),
705
- [t.ref`type`],
706
- when(attributes.length, [t.embedded(buildSpace())]),
707
- [...btree.traverse(attributes.children)].slice(1, -1),
708
- [t.ref`closeToken`],
709
- ),
710
- ),
711
- {
712
- openToken: t.s_node(l.CSTML, 'Punctuator', '<'),
713
- language: buildLanguage(language_),
714
- languageSeparator: language_ && type ? t.s_node(l.CSTML, 'Punctuator', ':') : t.null_node(),
715
- flags: flags_,
716
- type: buildIdentifier(type),
717
- attributes: attributes.properties.attributes,
718
- closeToken: t.s_node(l.CSTML, 'Punctuator', '>'),
719
- },
934
+ export const buildPropertyMatcher = (refMatcher, nodeMatcher) => {
935
+ const expressions = [];
936
+ const gap = buildFilledGapFunction(expressions);
937
+
938
+ return treeFromStream(
939
+ [
940
+ t.nodeOpen(t.nodeFlags, l.Spamex, 'PropertyMatcher'),
941
+ t.ref`refMatcher`,
942
+ gap(refMatcher || buildNullNode()),
943
+ t.ref`nodeMatcher`,
944
+ gap(nodeMatcher),
945
+ t.nodeClose(),
946
+ ],
947
+ { expressions },
720
948
  );
721
949
  };
950
+
951
+ export const buildGapNodeMatcher = () => {
952
+ return buildToken(l.Spamex, 'GapNodeMatcher', '<//>');
953
+ };