@bablr/agast-helpers 0.1.5 → 0.2.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,5 +1,9 @@
1
1
  const { freeze } = Object;
2
2
 
3
+ export const buildBeginningOfStreamToken = () => {
4
+ return freeze({ type: Symbol.for('@bablr/beginning-of-stream'), value: undefined });
5
+ };
6
+
3
7
  export const buildReference = (name, isArray) => {
4
8
  return freeze({ type: 'Reference', value: freeze({ name, isArray }) });
5
9
  };
@@ -12,6 +16,10 @@ export const buildGap = () => {
12
16
  return freeze({ type: 'Gap', value: undefined });
13
17
  };
14
18
 
19
+ export const buildShift = () => {
20
+ return freeze({ type: 'Shift', value: undefined });
21
+ };
22
+
15
23
  export const buildEmbedded = (node) => {
16
24
  return freeze({ type: 'Embedded', value: node });
17
25
  };
@@ -23,11 +31,15 @@ export const buildDoctypeTag = (attributes) => {
23
31
  });
24
32
  };
25
33
 
26
- export const buildNodeOpenTag = (flags, language, type, intrinsicValue, attributes = {}) => {
34
+ export const buildNodeOpenTag = (
35
+ flags = {},
36
+ language = null,
37
+ type = null,
38
+ intrinsicValue = null,
39
+ attributes = {},
40
+ ) => {
27
41
  let { token, trivia, escape, expression, intrinsic } = flags;
28
42
 
29
- if (!type) throw new Error();
30
-
31
43
  token = !!token;
32
44
  trivia = !!trivia;
33
45
  escape = !!escape;
@@ -46,27 +58,10 @@ export const buildNodeOpenTag = (flags, language, type, intrinsicValue, attribut
46
58
  });
47
59
  };
48
60
 
49
- export const buildFragmentOpenTag = (flags = nodeFlags, language) => {
50
- let { token, trivia, escape } = flags;
51
-
52
- token = !!token;
53
- trivia = !!trivia;
54
- escape = !!escape;
55
-
56
- return freeze({
57
- type: 'OpenFragmentTag',
58
- value: freeze({ flags: freeze({ token, trivia, escape }) }),
59
- });
60
- };
61
-
62
61
  export const buildNodeCloseTag = (type = null, language = null) => {
63
62
  return freeze({ type: 'CloseNodeTag', value: freeze({ language, type }) });
64
63
  };
65
64
 
66
- export const buildFragmentCloseTag = () => {
67
- return freeze({ type: 'CloseFragmentTag', value: freeze({}) });
68
- };
69
-
70
65
  const isString = (val) => typeof val === 'string';
71
66
 
72
67
  export const buildLiteral = (value) => {
@@ -227,3 +222,21 @@ export const buildTriviaNode = (language, type, children = [], properties = {},
227
222
  properties: freeze(properties),
228
223
  attributes: freeze(attributes),
229
224
  });
225
+
226
+ export const buildEffect = (value) => {
227
+ return freeze({ type: 'Effect', value });
228
+ };
229
+
230
+ export const buildWriteEffect = (text, options = {}) => {
231
+ return buildEffect(freeze({ verb: 'write', value: freeze({ text, options }) }));
232
+ };
233
+
234
+ export const buildAnsiPushEffect = (spans = '') => {
235
+ return buildEffect(
236
+ freeze({ verb: 'ansi-push', value: { spans: spans === '' ? [] : spans.split(' ') } }),
237
+ );
238
+ };
239
+
240
+ export const buildAnsiPopEffect = () => {
241
+ return buildEffect(freeze({ verb: 'ansi-pop', value: undefined }));
242
+ };
package/lib/path.js CHANGED
@@ -2,7 +2,7 @@ export const parsePath = (str) => {
2
2
  const isArray = str.endsWith('[]');
3
3
  const name = isArray ? str.slice(0, -2) : str;
4
4
 
5
- if (!/^\w+$/.test(name)) throw new Error();
5
+ if (!/^[a-zA-Z]+$/.test(name)) throw new Error();
6
6
 
7
7
  return { isArray, name };
8
8
  };
package/lib/print.js CHANGED
@@ -3,6 +3,10 @@ const { isArray } = Array;
3
3
  const isString = (val) => typeof val === 'string';
4
4
  const isNumber = (val) => typeof val === 'number';
5
5
  const isObject = (val) => val && typeof val === 'object' && !isArray(val);
6
+ const isFunction = (val) => typeof val === 'function';
7
+
8
+ const when = (condition, value) =>
9
+ condition ? (isFunction(value) ? value() : value) : { *[Symbol.iterator]() {} };
6
10
 
7
11
  export const printExpression = (expr) => {
8
12
  if (isString(expr)) {
@@ -11,6 +15,7 @@ export const printExpression = (expr) => {
11
15
  return String(expr);
12
16
  } else if (isNumber(expr)) {
13
17
  if (!isFinite(expr)) {
18
+ if (isNaN(expr)) throw new Error();
14
19
  return expr === -Infinity ? '-Infinity' : '+Infinity';
15
20
  } else if (isInteger(expr)) {
16
21
  return String(expr);
@@ -41,7 +46,9 @@ export const printLanguage = (language) => {
41
46
  };
42
47
 
43
48
  export const printTagPath = (language, type) => {
44
- return language?.length ? `${printLanguage(language)}:${type}` : type;
49
+ return [...when(language?.length, () => [printLanguage(language)]), ...when(type, [type])].join(
50
+ ':',
51
+ );
45
52
  };
46
53
 
47
54
  const escapeReplacer = (esc) => {
@@ -78,6 +85,12 @@ export const printGap = (terminal) => {
78
85
  return `<//>`;
79
86
  };
80
87
 
88
+ export const printShift = (terminal) => {
89
+ if (terminal?.type !== 'Shift') throw new Error();
90
+
91
+ return `^^^`;
92
+ };
93
+
81
94
  export const printReference = (terminal) => {
82
95
  if (terminal?.type !== 'Reference') throw new Error();
83
96
 
@@ -147,26 +160,12 @@ export const printOpenNodeTag = (terminal) => {
147
160
  )}${intrinsicFrag}${attributesFrag}${selfCloser}>`;
148
161
  };
149
162
 
150
- export const printOpenFragmentTag = (terminal) => {
151
- if (terminal?.type !== 'OpenFragmentTag') throw new Error();
152
-
153
- let { flags } = terminal.value;
154
-
155
- return `<${printFlags(flags)}>`;
156
- };
157
-
158
163
  export const printCloseNodeTag = (terminal) => {
159
164
  if (terminal?.type !== 'CloseNodeTag') throw new Error();
160
165
 
161
166
  return `</>`;
162
167
  };
163
168
 
164
- export const printCloseFragmentTag = (terminal) => {
165
- if (terminal?.type !== 'CloseFragmentTag') throw new Error();
166
-
167
- return `</>`;
168
- };
169
-
170
169
  export const printTerminal = (terminal) => {
171
170
  if (!isObject(terminal)) throw new Error();
172
171
 
@@ -177,6 +176,9 @@ export const printTerminal = (terminal) => {
177
176
  case 'Gap':
178
177
  return printGap(terminal);
179
178
 
179
+ case 'Shift':
180
+ return printShift(terminal);
181
+
180
182
  case 'Literal':
181
183
  return printLiteral(terminal);
182
184
 
@@ -189,15 +191,9 @@ export const printTerminal = (terminal) => {
189
191
  case 'OpenNodeTag':
190
192
  return printOpenNodeTag(terminal);
191
193
 
192
- case 'OpenFragmentTag':
193
- return printOpenFragmentTag(terminal);
194
-
195
194
  case 'CloseNodeTag':
196
195
  return printCloseNodeTag(terminal);
197
196
 
198
- case 'CloseFragmentTag':
199
- return printCloseFragmentTag(terminal);
200
-
201
197
  default:
202
198
  throw new Error();
203
199
  }
package/lib/shorthand.js CHANGED
@@ -3,9 +3,7 @@ import {
3
3
  buildGap,
4
4
  buildEmbedded,
5
5
  buildNodeOpenTag,
6
- buildFragmentOpenTag,
7
6
  buildNodeCloseTag,
8
- buildFragmentCloseTag,
9
7
  buildLiteral,
10
8
  buildNode,
11
9
  buildSyntacticNode,
@@ -47,9 +45,7 @@ export const lit = (str) => buildLiteral(stripArray(str));
47
45
  export const gap = buildGap;
48
46
  export const embedded = buildEmbedded;
49
47
  export const nodeOpen = buildNodeOpenTag;
50
- export const fragOpen = buildFragmentOpenTag;
51
48
  export const nodeClose = buildNodeCloseTag;
52
- export const fragClose = buildFragmentCloseTag;
53
49
  export const node = buildNode;
54
50
  export const s_node = buildSyntacticNode;
55
51
  export const s_i_node = buildSyntacticIntrinsicNode;
package/lib/stream.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Coroutine } from '@bablr/coroutine';
2
2
  import { printTerminal } from './print.js';
3
+ import { buildWriteEffect } from './builders.js';
3
4
  export * from './print.js';
4
5
 
5
6
  export const getStreamIterator = (obj) => {
@@ -21,12 +22,11 @@ export class SyncGenerator {
21
22
  }
22
23
 
23
24
  if (step.done) {
24
- return { value: undefined, done: true };
25
+ return step;
25
26
  } else if (step.value instanceof Promise) {
26
27
  throw new Error('sync generators cannot resolve promises');
27
28
  } else {
28
- const { value } = step;
29
- return { value, done: false };
29
+ return step;
30
30
  }
31
31
  }
32
32
 
@@ -60,14 +60,13 @@ export class AsyncGenerator {
60
60
  }
61
61
 
62
62
  if (step.done) {
63
- return Promise.resolve({ value: undefined, done: true });
63
+ return Promise.resolve(step);
64
64
  } else if (step.value instanceof Promise) {
65
65
  return step.value.then((value) => {
66
66
  return this.next(value);
67
67
  });
68
68
  } else {
69
- const { value } = step;
70
- return Promise.resolve({ value, done: false });
69
+ return Promise.resolve(step);
71
70
  }
72
71
  }
73
72
 
@@ -93,14 +92,13 @@ export class StreamGenerator {
93
92
  const step = this.generator.next(value);
94
93
 
95
94
  if (step.done) {
96
- return { value: undefined, done: true };
95
+ return step;
97
96
  } else if (step.value instanceof Promise) {
98
97
  return step.value.then((value) => {
99
98
  return this.next(value);
100
99
  });
101
100
  } else {
102
- const { value } = step;
103
- return { value, done: false };
101
+ return step;
104
102
  }
105
103
  }
106
104
 
@@ -139,9 +137,9 @@ export const maybeWait = (maybePromise, callback) => {
139
137
  }
140
138
  };
141
139
 
142
- function* __generateCSTML(terminals) {
140
+ function* __generateCSTMLStrategy(terminals) {
143
141
  if (!terminals) {
144
- yield* '<//>';
142
+ yield buildWriteEffect('<//>');
145
143
  return;
146
144
  }
147
145
 
@@ -160,23 +158,88 @@ function* __generateCSTML(terminals) {
160
158
  const terminal = co.value;
161
159
 
162
160
  if (terminal.type === 'Reference' && prevTerminal.type === 'Null') {
163
- yield* ' ';
161
+ yield buildWriteEffect(' ');
164
162
  }
165
163
 
166
- yield* printTerminal(terminal);
164
+ if (terminal.type !== 'Effect') {
165
+ buildWriteEffect(printTerminal(terminal));
167
166
 
168
- prevTerminal = terminal;
167
+ prevTerminal = terminal;
168
+ } else {
169
+ yield terminal;
170
+ }
169
171
  }
170
172
 
171
- yield* '\n';
173
+ yield buildWriteEffect('\n');
174
+ }
175
+
176
+ export const generateCSTMLStrategy = (terminals) =>
177
+ new StreamIterable(__generateCSTMLStrategy(terminals));
178
+
179
+ function* __generateStandardOutput(terminals) {
180
+ const co = new Coroutine(getStreamIterator(terminals));
181
+
182
+ for (;;) {
183
+ co.advance();
184
+
185
+ if (co.current instanceof Promise) {
186
+ co.current = yield co.current;
187
+ }
188
+ if (co.done) break;
189
+
190
+ const terminal = co.value;
191
+
192
+ if (terminal.type === 'Effect') {
193
+ const effect = terminal.value;
194
+ if (effect.verb === 'write' && (effect.value.stream == null || effect.value.stream === 1)) {
195
+ yield* effect.value.text;
196
+ }
197
+ }
198
+ }
172
199
  }
173
200
 
174
- export const generateCSTML = (terminals) => new StreamIterable(__generateCSTML(terminals));
201
+ export const generateStandardOutput = (terminals) =>
202
+ new StreamIterable(__generateStandardOutput(terminals));
203
+
204
+ function* __generateAllOutput(terminals) {
205
+ const co = new Coroutine(getStreamIterator(terminals));
206
+
207
+ let currentStream = null;
208
+
209
+ for (;;) {
210
+ co.advance();
211
+
212
+ if (co.current instanceof Promise) {
213
+ co.current = yield co.current;
214
+ }
215
+ if (co.done) break;
216
+
217
+ const terminal = co.value;
218
+
219
+ if (terminal.type === 'Effect') {
220
+ const effect = terminal.value;
221
+ if (effect.verb === 'write') {
222
+ const prevStream = currentStream;
223
+ currentStream = effect.value.options.stream || 1;
224
+ if (prevStream && prevStream !== currentStream && !effect.value.text.startsWith('\n')) {
225
+ yield* '\n';
226
+ }
227
+ yield* effect.value.text;
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ export const generateAllOutput = (terminals) => new StreamIterable(__generateAllOutput(terminals));
175
234
 
176
235
  export const printCSTML = (terminals) => {
177
- return stringFromStream(generateCSTML(terminals));
236
+ return stringFromStream(generateStandardOutput(generateCSTMLStrategy(terminals)));
178
237
  };
179
238
 
239
+ function* __emptyStreamIterator() {}
240
+
241
+ export const emptyStreamIterator = () => new StreamIterable(__emptyStreamIterator());
242
+
180
243
  export const asyncStringFromStream = async (stream) => {
181
244
  const co = new Coroutine(getStreamIterator(stream));
182
245
  let str = '';
@@ -215,9 +278,11 @@ export const stringFromStream = (stream) => {
215
278
  return str;
216
279
  };
217
280
 
218
- function* __generatePrettyCSTML(terminals, indent) {
281
+ function* __generatePrettyCSTMLStrategy(terminals, options) {
282
+ const { indent = ' ', emitEffects = false, inline: inlineOption = true } = options;
283
+
219
284
  if (!terminals) {
220
- yield* '<//>';
285
+ yield buildWriteEffect('<//>');
221
286
  return;
222
287
  }
223
288
 
@@ -236,18 +301,29 @@ function* __generatePrettyCSTML(terminals, indent) {
236
301
 
237
302
  const terminal = co.value;
238
303
 
304
+ if (terminal.type === 'Effect') {
305
+ const effect = terminal.value;
306
+ if (emitEffects && effect.verb === 'write') {
307
+ yield buildWriteEffect((first ? '' : '\n') + effect.value.text, effect.value.options);
308
+
309
+ first = false;
310
+ }
311
+ continue;
312
+ }
313
+
239
314
  const inline =
240
- terminal.type === 'Null' ||
241
- terminal.type === 'Gap' ||
242
- (terminal.type === 'OpenNodeTag' &&
243
- terminal.value.intrinsicValue &&
244
- terminal.value.flags.intrinsic);
315
+ inlineOption &&
316
+ (terminal.type === 'Null' ||
317
+ terminal.type === 'Gap' ||
318
+ (terminal.type === 'OpenNodeTag' &&
319
+ terminal.value.intrinsicValue &&
320
+ terminal.value.flags.intrinsic));
245
321
 
246
322
  if (!first && !inline) {
247
- yield* '\n';
323
+ yield buildWriteEffect('\n');
248
324
  }
249
325
 
250
- if (['CloseNodeTag', 'CloseFragmentTag'].includes(terminal.type)) {
326
+ if (terminal.type === 'CloseNodeTag') {
251
327
  if (indentLevel === 0) {
252
328
  throw new Error('imbalanced tag stack');
253
329
  }
@@ -256,15 +332,15 @@ function* __generatePrettyCSTML(terminals, indent) {
256
332
  }
257
333
 
258
334
  if (!inline) {
259
- yield* indent.repeat(indentLevel);
335
+ yield buildWriteEffect(indent.repeat(indentLevel));
260
336
  } else {
261
- yield* ' ';
337
+ yield buildWriteEffect(' ');
262
338
  }
263
- yield* printTerminal(terminal);
339
+ yield buildWriteEffect(printTerminal(terminal));
264
340
 
265
341
  if (
266
- terminal.type === 'OpenFragmentTag' ||
267
- (terminal.type === 'OpenNodeTag' && !terminal.value.intrinsicValue)
342
+ terminal.type === 'OpenNodeTag' &&
343
+ (!terminal.value.intrinsicValue || !terminal.value.type)
268
344
  ) {
269
345
  indentLevel++;
270
346
  }
@@ -272,15 +348,15 @@ function* __generatePrettyCSTML(terminals, indent) {
272
348
  first = false;
273
349
  }
274
350
 
275
- yield* '\n';
351
+ yield buildWriteEffect('\n');
276
352
  }
277
353
 
278
- export const generatePrettyCSTML = (terminals, indent = ' ') => {
279
- return new StreamIterable(__generatePrettyCSTML(terminals, indent));
354
+ export const generatePrettyCSTMLStrategy = (terminals, options = {}) => {
355
+ return new StreamIterable(__generatePrettyCSTMLStrategy(terminals, options));
280
356
  };
281
357
 
282
- export const printPrettyCSTML = (terminals) => {
283
- return stringFromStream(generatePrettyCSTML(terminals));
358
+ export const printPrettyCSTML = (terminals, options = {}) => {
359
+ return stringFromStream(generateStandardOutput(generatePrettyCSTMLStrategy(terminals, options)));
284
360
  };
285
361
 
286
362
  export const getCooked = (terminals) => {
@@ -353,11 +429,11 @@ export function* generateSourceTextFor(terminals) {
353
429
  export const sourceTextFor = printSource;
354
430
 
355
431
  export const startsDocument = (terminal) => {
356
- const { type } = terminal;
357
- if (type === 'OpenFragmentTag' || type === 'DoctypeTag') {
432
+ const { type, value } = terminal;
433
+ if ((type === 'OpenNodeTag' && !value.type) || type === 'DoctypeTag') {
358
434
  return true;
359
435
  } else if (type === 'OpenNodeTag') {
360
- const { flags } = terminal.value;
436
+ const { flags } = value;
361
437
 
362
438
  return flags.trivia || flags.escape;
363
439
  }
package/lib/tree.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import emptyStack from '@iter-tools/imm-stack';
2
+ import { Coroutine } from '@bablr/coroutine';
2
3
  import {
3
4
  buildNodeCloseTag,
4
5
  buildNodeOpenTag,
@@ -6,13 +7,12 @@ import {
6
7
  buildEmbedded,
7
8
  nodeFlags,
8
9
  buildDoctypeTag,
9
- buildFragmentOpenTag,
10
- buildFragmentCloseTag,
11
- buildReference,
12
10
  } from './builders.js';
13
11
  import {
14
12
  printPrettyCSTML as printPrettyCSTMLFromStream,
15
13
  printCSTML as printCSTMLFromStream,
14
+ getStreamIterator,
15
+ StreamIterable,
16
16
  } from './stream.js';
17
17
  export * from './builders.js';
18
18
  export * from './print.js';
@@ -38,134 +38,201 @@ const get = (node, path) => {
38
38
  }
39
39
  };
40
40
 
41
- const reduceTokenToNodes = (nodes, token) => {
42
- switch (token.type) {
43
- case 'OpenFragmentTag':
44
- case 'Null': {
45
- return nodes;
46
- }
41
+ function* __treeFromStream(tokens) {
42
+ let nodes = emptyStack;
43
+ let rootNode;
44
+ let held = null;
45
+ const co = new Coroutine(getStreamIterator(tokens));
47
46
 
48
- case 'DoctypeTag': {
49
- const { attributes } = token.value;
47
+ for (;;) {
48
+ co.advance();
50
49
 
51
- return nodes.push(
52
- freeze({
53
- flags: nodeFlags,
54
- type: Symbol.for('@bablr/fragment'),
55
- children: [],
56
- properties: {},
57
- attributes: freeze(attributes),
58
- }),
59
- );
50
+ if (co.current instanceof Promise) {
51
+ co.current = yield co.current;
60
52
  }
61
53
 
62
- case 'CloseFragmentTag': {
63
- freeze(nodes.value.properties);
64
- freeze(nodes.value.children);
54
+ if (co.done) break;
65
55
 
66
- return nodes.pop();
67
- }
56
+ const token = co.value;
68
57
 
69
- case 'Literal':
70
- case 'Gap':
71
- case 'Reference': {
72
- nodes.value.children.push(token);
73
- return nodes;
58
+ if (token.type === 'Effect') {
59
+ continue;
74
60
  }
75
61
 
76
- case 'OpenNodeTag': {
77
- const buildStringTerminals = (str) => {
78
- // do better
79
- return [{ type: 'Literal', value: str }];
80
- };
81
-
82
- const { flags, language, type, intrinsicValue, attributes } = token.value;
83
- const node = nodes.value;
84
- const newNode = freeze({
85
- flags,
86
- language,
87
- type,
88
- children: intrinsicValue && flags.intrinsic ? buildStringTerminals(intrinsicValue) : [],
62
+ if (token.type === 'DoctypeTag') {
63
+ const { attributes } = token.value;
64
+
65
+ rootNode = freeze({
66
+ flags: nodeFlags,
67
+ type: null,
68
+ children: [],
89
69
  properties: {},
90
70
  attributes: freeze(attributes),
91
71
  });
72
+ nodes = nodes.push(rootNode);
73
+ continue;
74
+ }
92
75
 
93
- if (node && !(flags.escape || flags.trivia)) {
94
- if (nodes.size === 1) {
95
- node.children.push(buildReference('root', false));
96
- }
76
+ if (!nodes.size) {
77
+ throw new Error('imbalanced tag stack');
78
+ }
97
79
 
98
- if (!node.children.length) {
99
- throw new Error('Nodes must follow references');
100
- }
80
+ switch (token.type) {
81
+ case 'Null': {
82
+ break;
83
+ }
84
+
85
+ case 'Literal':
86
+ case 'Reference': {
87
+ nodes.value.children.push(token);
88
+ break;
89
+ }
101
90
 
102
- const { name, isArray } = arrayLast(node.children).value;
91
+ case 'Gap': {
92
+ if (held) {
93
+ const { children, properties } = nodes.value;
94
+ const ref = arrayLast(children);
103
95
 
104
- if (isArray) {
105
- if (!hasOwn(node.properties, name)) {
106
- node.properties[name] = [];
96
+ if (ref.type !== 'Reference') throw new Error();
97
+
98
+ if (ref.value.isArray) {
99
+ if (!properties[ref.value.name]) {
100
+ properties[ref.value.name] = [];
101
+ }
102
+ properties[ref.value.name].push(held);
103
+ } else {
104
+ properties[ref.value.name] = held;
107
105
  }
108
- const array = node.properties[name];
109
106
 
110
- array.push(newNode);
111
- } else {
112
- node.properties[name] = newNode;
107
+ held = null;
113
108
  }
109
+ break;
114
110
  }
115
111
 
116
- if (intrinsicValue && flags.intrinsic) {
117
- freeze(newNode.children);
118
- freeze(newNode.properties);
119
- return nodes;
112
+ case 'Shift': {
113
+ const { children, properties } = nodes.value;
114
+
115
+ const ref = arrayLast(children);
116
+ let node = properties[ref.value.name];
117
+
118
+ if (ref.value.isArray) {
119
+ node = arrayLast(node);
120
+ properties[ref.value.name].pop();
121
+ } else {
122
+ properties[ref.value.name] = null;
123
+ }
124
+
125
+ held = node;
126
+ break;
120
127
  }
121
128
 
122
- return nodes.push(newNode);
123
- }
129
+ case 'OpenNodeTag': {
130
+ const buildStringTerminals = (str) => {
131
+ // do better
132
+ return [{ type: 'Literal', value: str }];
133
+ };
134
+
135
+ const { flags, language, type, intrinsicValue, attributes } = token.value;
136
+ const node = nodes.value;
124
137
 
125
- case 'CloseNodeTag': {
126
- const completedNode = nodes.value;
127
- const { flags } = completedNode;
138
+ if (!type) {
139
+ break;
140
+ }
128
141
 
129
- if (flags.escape || flags.trivia) {
130
- const parentChildren = nodes.prev.value.children;
142
+ const newNode = freeze({
143
+ flags,
144
+ language,
145
+ type,
146
+ children: intrinsicValue && flags.intrinsic ? buildStringTerminals(intrinsicValue) : [],
147
+ properties: {},
148
+ attributes: freeze(attributes),
149
+ });
131
150
 
132
- parentChildren.push(buildEmbedded(completedNode));
151
+ if (node && !(flags.escape || flags.trivia)) {
152
+ if (!node.children.length) {
153
+ throw new Error('Nodes must follow references');
154
+ }
155
+
156
+ const { name, isArray } = arrayLast(node.children).value;
157
+
158
+ if (isArray) {
159
+ if (!hasOwn(node.properties, name)) {
160
+ node.properties[name] = [];
161
+ }
162
+ const array = node.properties[name];
163
+
164
+ array.push(newNode);
165
+ } else {
166
+ node.properties[name] = newNode;
167
+ }
168
+ }
169
+
170
+ if (intrinsicValue && flags.intrinsic) {
171
+ freeze(newNode.children);
172
+ freeze(newNode.properties);
173
+ break;
174
+ }
175
+
176
+ nodes = nodes.push(newNode);
177
+ break;
133
178
  }
134
179
 
135
- freeze(completedNode.properties);
136
- freeze(completedNode.children);
180
+ case 'CloseNodeTag': {
181
+ const completedNode = nodes.value;
182
+ const { flags } = completedNode;
137
183
 
138
- return nodes.pop();
139
- }
184
+ if (flags.escape || flags.trivia) {
185
+ const parentChildren = nodes.prev.value.children;
140
186
 
141
- default: {
142
- throw new Error();
143
- }
144
- }
145
- };
187
+ parentChildren.push(buildEmbedded(completedNode));
188
+ }
146
189
 
147
- export const treeFromStreamSync = (tokens) => {
148
- let nodes = emptyStack;
149
- let rootNode;
190
+ freeze(completedNode.properties);
191
+ freeze(completedNode.children);
150
192
 
151
- for (const token of tokens) {
152
- nodes = reduceTokenToNodes(nodes, token);
153
- rootNode = nodes.value || rootNode;
193
+ if (!completedNode.type && nodes.size !== 1) {
194
+ throw new Error('imbalanced tag stack');
195
+ }
196
+
197
+ nodes = nodes.pop();
198
+ break;
199
+ }
200
+
201
+ default: {
202
+ throw new Error();
203
+ }
204
+ }
154
205
  }
155
206
 
156
207
  return rootNode;
208
+ }
209
+
210
+ export const treeFromStream = (terminals) => new StreamIterable(__treeFromStream(terminals));
211
+
212
+ export const treeFromStreamSync = (tokens) => {
213
+ return evaluateReturnSync(treeFromStream(tokens));
157
214
  };
158
215
 
159
216
  export const treeFromStreamAsync = async (tokens) => {
160
- let nodes = emptyStack;
161
- let rootNode;
217
+ return evaluateReturnAsync(treeFromStream(tokens));
218
+ };
162
219
 
163
- for await (const token of tokens) {
164
- nodes = reduceTokenToNodes(nodes, token);
165
- rootNode = nodes.value || rootNode;
166
- }
220
+ export const evaluateReturnSync = (generator) => {
221
+ const co = new Coroutine(generator[Symbol.iterator]());
222
+ while (!co.done) co.advance();
223
+ return co.value;
224
+ };
167
225
 
168
- return rootNode;
226
+ export const evaluateReturnAsync = async (generator) => {
227
+ const co = new Coroutine(getStreamIterator(generator));
228
+ while (!co.done) {
229
+ co.advance();
230
+
231
+ if (co.current instanceof Promise) {
232
+ co.current = await co.current;
233
+ }
234
+ }
235
+ return co.value;
169
236
  };
170
237
 
171
238
  export function* streamFromTree(rootNode) {
@@ -174,7 +241,7 @@ export function* streamFromTree(rootNode) {
174
241
  }
175
242
 
176
243
  yield buildDoctypeTag(rootNode.attributes);
177
- yield buildFragmentOpenTag();
244
+ yield buildNodeOpenTag();
178
245
 
179
246
  let stack = emptyStack.push(buildFrame(rootNode));
180
247
 
@@ -236,7 +303,7 @@ export function* streamFromTree(rootNode) {
236
303
 
237
304
  stack = stack.pop();
238
305
  }
239
- yield buildFragmentCloseTag();
306
+ yield buildNodeCloseTag();
240
307
  }
241
308
 
242
309
  export const getCooked = (cookable) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bablr/agast-helpers",
3
3
  "description": "Helper functions for working with agAST trees",
4
- "version": "0.1.5",
4
+ "version": "0.2.0",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [