@bablr/agast-helpers 0.5.3 → 0.6.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/tree.js CHANGED
@@ -1,74 +1,121 @@
1
1
  import { Coroutine } from '@bablr/coroutine';
2
2
  import emptyStack from '@iter-tools/imm-stack';
3
- import { nodeFlags, buildEmbeddedNode } from './builders.js';
3
+ import {
4
+ nodeFlags,
5
+ buildReferenceTag,
6
+ buildNullTag,
7
+ buildOpenNodeTag,
8
+ buildLiteralTag,
9
+ buildCloseNodeTag,
10
+ tokenFlags,
11
+ } from './builders.js';
4
12
  import {
5
13
  printPrettyCSTML as printPrettyCSTMLFromStream,
6
14
  printCSTML as printCSTMLFromStream,
15
+ printSource as printSourceFromStream,
7
16
  getStreamIterator,
8
- StreamIterable,
9
17
  } from './stream.js';
10
18
  import {
11
19
  DoctypeTag,
12
20
  OpenNodeTag,
13
21
  CloseNodeTag,
14
- OpenFragmentTag,
15
- CloseFragmentTag,
16
22
  ReferenceTag,
17
23
  ShiftTag,
18
24
  GapTag,
19
25
  NullTag,
20
- ArrayTag,
26
+ ArrayInitializerTag,
21
27
  LiteralTag,
22
28
  EmbeddedNode,
23
29
  } from './symbols.js';
24
30
  import * as btree from './btree.js';
25
- import * as sym from './symbols.js';
26
31
  export * from './builders.js';
27
32
  export * from './print.js';
28
-
29
- const arrayLast = (arr) => arr[arr.length - 1];
33
+ import {
34
+ add,
35
+ get,
36
+ TagPath,
37
+ Path,
38
+ isFragmentNode,
39
+ isNullNode,
40
+ isGapNode,
41
+ getOpenTag,
42
+ getCloseTag,
43
+ } from './path.js';
44
+
45
+ export { add, get, isFragmentNode, isNullNode, isGapNode, getOpenTag, getCloseTag };
46
+
47
+ export const buildToken = (language, type, value, attributes = {}) => {
48
+ return treeFromStreamSync([
49
+ buildOpenNodeTag(tokenFlags, language, type, attributes),
50
+ buildLiteralTag(value),
51
+ buildCloseNodeTag(),
52
+ ]);
53
+ };
30
54
 
31
55
  const isString = (str) => typeof str === 'string';
32
56
 
33
57
  const { isArray } = Array;
58
+ const { freeze } = Object;
59
+
60
+ export const parseReference = (str) => {
61
+ let {
62
+ 1: name,
63
+ 2: isArray,
64
+ 3: index,
65
+ 4: expressionToken,
66
+ 5: hasGapToken,
67
+ } = /^\s*([.#@]|[a-zA-Z]+)\s*(\[\s*(\d+\s*)?\])?\s*(\+)?(\$)?\s*$/.exec(str);
68
+
69
+ let flags = {
70
+ expression: !!expressionToken,
71
+ hasGap: !!hasGapToken,
72
+ };
34
73
 
35
- const { hasOwn, freeze } = Object;
36
-
37
- export const get = (node, path) => {
38
- const { properties } = node;
39
- const { 1: name, 2: index } = /^([^\.]+|\.)(?:\.(\d+))?/.exec(path) || [];
40
-
41
- if (!hasOwn(properties, name)) {
42
- return null;
43
- }
74
+ index = index ? parseInt(index, 10) : null;
75
+ isArray = !!isArray;
76
+ name = name || null;
44
77
 
45
- if (index != null) {
46
- return btree.getAt(parseInt(index, 10), properties[name]);
47
- } else {
48
- return properties[name];
49
- }
78
+ return buildReferenceTag(name, isArray, flags, index);
50
79
  };
51
80
 
52
- export const add = (node, ref, value) => {
53
- const { name, isArray } = ref.value;
54
-
55
- if (isArray) {
56
- if (!hasOwn(node.properties, name)) {
57
- node.properties[name] = [];
58
- }
81
+ export const mergeReferences = (outer, inner) => {
82
+ let {
83
+ name,
84
+ isArray,
85
+ index,
86
+ flags: { expression, hasGap },
87
+ } = outer.value;
88
+
89
+ if (
90
+ name != null &&
91
+ name !== '.' &&
92
+ inner.value.name != null &&
93
+ inner.value.name !== '.' &&
94
+ name !== inner.value.name
95
+ )
96
+ throw new Error();
97
+
98
+ isArray = isArray || inner.value.isArray;
99
+ expression = !!(expression || inner.value.flags.expression);
100
+ hasGap = !!(hasGap || inner.value.flags.hasGap);
101
+ name = name === '.' ? inner.value.name : name;
102
+
103
+ return buildReferenceTag(name, isArray, { expression, hasGap }, index);
104
+ };
59
105
 
60
- node.properties[name] = btree.push(node.properties[name], value);
61
- } else {
62
- node.properties[name] = value;
63
- }
106
+ export const isEmptyReference = (ref) => {
107
+ let { name, isArray, flags } = ref.value;
108
+ return name === '.' && !isArray && !(flags.expression || flags.hasGap);
64
109
  };
65
110
 
66
- function* __treeFromStream(tokens) {
111
+ function* __treeFromStream(tags, options) {
67
112
  let path = null;
68
113
  let rootPath = null;
69
114
  let held = null;
70
115
  let doctype = null;
71
- const co = new Coroutine(getStreamIterator(tokens));
116
+ const co = new Coroutine(getStreamIterator(tags));
117
+ const expressionsCo = new Coroutine(getStreamIterator(options.expressions || []));
118
+ let reference = null;
72
119
 
73
120
  for (;;) {
74
121
  co.advance();
@@ -94,95 +141,179 @@ function* __treeFromStream(tokens) {
94
141
  throw new Error('cannot eat this type of tag while holding');
95
142
  }
96
143
 
144
+ let suppressTag = false;
145
+
97
146
  switch (tag.type) {
98
147
  case LiteralTag:
99
- case ReferenceTag:
100
- case CloseNodeTag:
101
- case CloseFragmentTag: {
148
+ case CloseNodeTag: {
149
+ break;
150
+ }
151
+
152
+ case ReferenceTag: {
153
+ reference = tag;
154
+ suppressTag = true;
155
+ break;
156
+ }
157
+
158
+ case ArrayInitializerTag: {
159
+ add(path.node, reference, []);
160
+ suppressTag = true;
161
+ reference = null;
102
162
  break;
103
163
  }
104
164
 
105
165
  case NullTag:
106
166
  case GapTag: {
107
- const parentNode = path.parent.node;
108
- const ref = arrayLast(parentNode.children);
109
- const isGap = tag.type === GapTag;
167
+ if (!path) {
168
+ return buildStubNode(tag);
169
+ }
110
170
 
111
- if (ref.type !== ReferenceTag) throw new Error();
171
+ const isGap = tag.type === GapTag;
112
172
 
113
- const node = (isGap && held) || createNode(nodeFlags, null, sym.null);
173
+ if (path.parent && reference.type !== ReferenceTag) throw new Error();
174
+
175
+ let node = createNode(tag);
176
+
177
+ if (isGap) {
178
+ if (held) {
179
+ node = held;
180
+ add(path.node, reference, node);
181
+ suppressTag = true;
182
+ } else if (!expressionsCo.done) {
183
+ expressionsCo.advance();
184
+
185
+ let outerReference = reference;
186
+
187
+ if (!expressionsCo.done) {
188
+ node =
189
+ node == null
190
+ ? buildStubNode(buildNullTag())
191
+ : expressionsCo.value == null
192
+ ? buildStubNode(buildNullTag())
193
+ : expressionsCo.value;
194
+ suppressTag = true;
195
+
196
+ if (isFragmentNode(node)) {
197
+ const parentNode = path.node;
198
+
199
+ let reference;
200
+
201
+ for (const tag of btree.traverse(node.children)) {
202
+ switch (tag.type) {
203
+ case DoctypeTag: {
204
+ break;
205
+ }
206
+ case OpenNodeTag:
207
+ case CloseNodeTag: {
208
+ if (!tag.value.type) {
209
+ break;
210
+ } else {
211
+ throw new Error();
212
+ }
213
+ }
214
+
215
+ case ReferenceTag:
216
+ // combine tags for .
217
+
218
+ reference = tag;
219
+ break;
220
+
221
+ case ArrayInitializerTag: {
222
+ add(parentNode, mergeReferences(outerReference, reference), []);
223
+ break;
224
+ }
225
+
226
+ case EmbeddedNode: {
227
+ add(parentNode, mergeReferences(outerReference, reference), tag.value);
228
+ break;
229
+ }
230
+
231
+ case GapTag: {
232
+ const resolvedNode = get(reference, node);
233
+ add(parentNode, mergeReferences(outerReference, reference), resolvedNode);
234
+ break;
235
+ }
236
+
237
+ case NullTag: {
238
+ add(parentNode, mergeReferences(outerReference, reference), null);
239
+ break;
240
+ }
241
+
242
+ default:
243
+ throw new Error();
244
+ }
245
+ }
246
+ } else {
247
+ if (path.node.flags.token) {
248
+ throw new Error('not implemented');
249
+ }
250
+ add(path.node, reference, node);
251
+ }
252
+ } else {
253
+ if (!path.node.flags.token) {
254
+ add(path.node, reference, node);
255
+ }
256
+ }
257
+ }
258
+ }
114
259
 
260
+ reference = null;
115
261
  held = isGap ? null : held;
116
- path = { parent: path, node, depth: (path.depth || -1) + 1 };
117
-
118
- add(parentNode, ref, node);
119
- break;
120
- }
121
-
122
- case ShiftTag: {
123
- const { children, properties } = path.node;
124
-
125
- const ref = arrayLast(children);
126
- let node = properties[ref.value.name];
127
262
 
128
- if (ref.value.isArray) {
129
- node = arrayLast(node);
130
- properties[ref.value.name].pop();
131
- } else {
132
- properties[ref.value.name] = null;
263
+ if (!path.node.flags.token) {
264
+ path = { parent: path, node, depth: (path.depth || -1) + 1, arrays: new Set() };
133
265
  }
134
266
 
135
- held = node;
136
267
  break;
137
268
  }
138
269
 
139
- case OpenNodeTag: {
140
- const { flags, type, language, attributes } = tag.value;
270
+ // case ShiftTag: {
271
+ // const { children, properties } = path.node;
141
272
 
142
- const node = createNode(flags, language, type, [], {}, attributes);
273
+ // let property = properties[ref.value.name];
274
+ // let node;
143
275
 
144
- const parentPath = path;
276
+ // if (ref.value.isArray) {
277
+ // ({ node } = btree.getAt(-1, property));
278
+ // properties[ref.value.name].pop();
279
+ // } else {
280
+ // ({ node } = property);
281
+ // properties[ref.value.name] = null;
282
+ // }
145
283
 
146
- path = { parent: path, node, depth: (path.depth || -1) + 1 };
284
+ // held = node;
285
+ // break;
286
+ // }
147
287
 
148
- if (!parentPath) throw new Error();
288
+ case OpenNodeTag: {
289
+ if (path) {
290
+ const node = createNode(tag);
149
291
 
150
- const { node: parentNode } = path;
151
- if (!(flags.escape || flags.trivia)) {
152
- if (!parentNode.children.length) {
153
- throw new Error('Nodes must follow references');
292
+ if (path) {
293
+ add(path.node, reference, node);
294
+ reference = null;
154
295
  }
155
296
 
156
- const ref = arrayLast(parentNode.children);
157
-
158
- add(parentNode, ref, node);
297
+ path = { parent: path, node, depth: (path ? path.depth : -1) + 1, arrays: new Set() };
159
298
  } else {
160
- parentNode.children.push(buildEmbeddedNode(node));
161
- }
162
-
163
- break;
164
- }
165
-
166
- case OpenFragmentTag: {
167
- const { flags } = tag.value;
168
-
169
- const { attributes } = doctype.value;
170
- const language = attributes['bablr-language'];
299
+ const { language, type, flags, attributes } = tag.value;
171
300
 
172
- const node = freeze({
173
- flags,
174
- language,
175
- type: null,
176
- children: [],
177
- properties: {},
178
- attributes,
179
- });
301
+ const attributes_ = doctype?.value.attributes ?? attributes;
302
+ const language_ = attributes?.['bablrLanguage'] ?? language;
180
303
 
181
- if (path) throw new Error();
304
+ const node = {
305
+ flags,
306
+ language: language_,
307
+ type,
308
+ children: [],
309
+ properties: {},
310
+ attributes: attributes_,
311
+ };
182
312
 
183
- path = { parent: null, node, depth: 0 };
313
+ path = { parent: null, node, depth: 0, arrays: new Set() };
184
314
 
185
- rootPath = path;
315
+ rootPath = path;
316
+ }
186
317
 
187
318
  break;
188
319
  }
@@ -192,7 +323,9 @@ function* __treeFromStream(tokens) {
192
323
  }
193
324
  }
194
325
 
195
- path.node.children.push(tag);
326
+ if (!suppressTag) {
327
+ path.node.children = btree.push(path.node.children, tag);
328
+ }
196
329
 
197
330
  switch (tag.type) {
198
331
  case NullTag:
@@ -200,10 +333,14 @@ function* __treeFromStream(tokens) {
200
333
  case CloseNodeTag: {
201
334
  const completedNode = path.node;
202
335
 
203
- finalizeNode(completedNode);
336
+ if (!(tag.type === GapTag && completedNode.flags.token)) {
337
+ finalizeNode(completedNode);
338
+ }
204
339
 
205
- if (!completedNode.type && path.depth !== 1) {
206
- throw new Error('imbalanced tag stack');
340
+ if (tag.type === GapTag) {
341
+ if (path && completedNode.type === null && completedNode.flags.token) {
342
+ break;
343
+ }
207
344
  }
208
345
 
209
346
  path = path.parent;
@@ -212,17 +349,25 @@ function* __treeFromStream(tokens) {
212
349
  }
213
350
  }
214
351
 
352
+ if (path && path.node.type) {
353
+ throw new Error('imbalanced tag stack');
354
+ }
355
+
215
356
  return rootPath.node;
216
357
  }
217
358
 
218
- export const treeFromStream = (tags) => __treeFromStream(tags);
359
+ export const buildNullNode = () => {
360
+ return treeFromStreamSync([buildNullTag()]);
361
+ };
362
+
363
+ export const treeFromStream = (tags, options = {}) => __treeFromStream(tags, options);
219
364
 
220
- export const treeFromStreamSync = (tokens) => {
221
- return evaluateReturnSync(treeFromStream(tokens));
365
+ export const treeFromStreamSync = (tokens, options = {}) => {
366
+ return evaluateReturnSync(treeFromStream(tokens, options));
222
367
  };
223
368
 
224
- export const treeFromStreamAsync = async (tokens) => {
225
- return evaluateReturnAsync(treeFromStream(tokens));
369
+ export const treeFromStreamAsync = async (tokens, options = {}) => {
370
+ return evaluateReturnAsync(treeFromStream(tokens, options));
226
371
  };
227
372
 
228
373
  export const evaluateReturnSync = (generator) => {
@@ -243,20 +388,28 @@ export const evaluateReturnAsync = async (generator) => {
243
388
  return co.value;
244
389
  };
245
390
 
246
- export const streamFromTree = (rootNode) => __streamFromTree(rootNode);
391
+ export const streamFromTree = (rootNode, options = {}) => __streamFromTree(rootNode, options);
247
392
 
248
393
  export const isEmpty = (node) => {
249
394
  const { properties } = node;
250
395
 
251
- for (const child of btree.traverse(node.children)) {
252
- switch (child.type) {
396
+ let ref = null;
397
+
398
+ for (const tag of btree.traverse(node.children)) {
399
+ switch (tag.type) {
253
400
  case ReferenceTag: {
254
- const { name } = child.value;
401
+ const { name } = tag.value;
402
+
403
+ ref = tag;
255
404
 
256
405
  if (properties[name]) {
257
- const value = properties[name];
406
+ const property = properties[name];
258
407
 
259
- if (value != null || value.type !== sym.null || (isArray(value) && value.length)) {
408
+ if (
409
+ property != null ||
410
+ (isArray(property) && property.length) ||
411
+ !isNullNode(property.node)
412
+ ) {
260
413
  return false;
261
414
  }
262
415
  }
@@ -264,7 +417,7 @@ export const isEmpty = (node) => {
264
417
  }
265
418
 
266
419
  case EmbeddedNode: {
267
- if (node.value.flags.escape) {
420
+ if (ref.value.name === '@') {
268
421
  return false;
269
422
  }
270
423
  break;
@@ -278,93 +431,37 @@ export const isEmpty = (node) => {
278
431
  return true;
279
432
  };
280
433
 
281
- const symbolTypeFor = (type) => {
282
- // prettier-ignore
283
- switch (type) {
284
- case NullTag: return sym.null;
285
- case GapTag: return sym.gap;
286
- default: throw new Error();
287
- }
288
- };
289
-
290
434
  export const buildStubNode = (tag) => {
291
435
  return freeze({
292
436
  flags: nodeFlags,
293
437
  language: null,
294
- type: symbolTypeFor(tag.type),
438
+ type: null,
295
439
  children: freeze([tag]),
296
440
  properties: freeze({}),
297
441
  attributes: freeze({}),
298
442
  });
299
443
  };
300
444
 
301
- function* __streamFromTree(rootNode) {
302
- if (!rootNode || rootNode.type === GapTag) {
303
- return rootNode;
304
- }
305
-
306
- let stack = emptyStack.push(rootNode);
307
- const resolver = new Resolver();
308
-
309
- stack: while (stack.size) {
310
- const node = stack.value;
311
- const { children } = node;
312
-
313
- while (true) {
314
- const tag = btree.getAt(resolver.idx, children);
315
-
316
- if (isArray(tag)) {
317
- throw new Error();
318
- }
319
-
320
- switch (tag.type) {
321
- case EmbeddedNode: {
322
- stack = stack.push(tag.value);
323
-
324
- resolver.advance(tag);
325
-
326
- continue stack;
327
- }
445
+ function* __streamFromTree(rootNode, options) {
446
+ const { unshift = false } = options;
447
+ if (!rootNode || !btree.getSum(rootNode.children)) return;
328
448
 
329
- case ReferenceTag: {
330
- const resolvedPath = resolver.resolve(tag);
331
- const resolved = get(stack.value, resolvedPath);
332
- const { isArray: refIsArray } = tag.value;
449
+ let tagPath = TagPath.from(Path.from(rootNode), 0);
333
450
 
334
- if (!resolved) throw new Error();
451
+ let count = 0;
335
452
 
336
- yield tag;
453
+ do {
454
+ if (tagPath.tag.type === OpenNodeTag) count++;
455
+ if (tagPath.tag.type === CloseNodeTag) count--;
337
456
 
338
- resolver.advance(tag);
457
+ yield tagPath.tag;
458
+ } while ((tagPath = unshift ? tagPath.nextUnshifted : tagPath.next));
339
459
 
340
- if (!refIsArray || !isArray(resolved)) {
341
- if (isArray(resolved)) throw new Error();
342
- stack = stack.push(resolved);
343
- }
344
- continue stack;
345
- }
346
-
347
- case GapTag:
348
- case NullTag:
349
- case CloseNodeTag:
350
- case CloseFragmentTag: {
351
- stack = stack.pop();
352
- resolver.advance(tag);
353
- yield tag;
354
- continue stack;
355
- }
356
-
357
- default:
358
- resolver.advance(tag);
359
- yield tag;
360
- break;
361
- }
362
- }
363
- }
460
+ if (count !== 0) throw new Error();
364
461
  }
365
462
 
366
463
  export const getCooked = (cookable) => {
367
- if (!cookable || cookable.type === GapTag) {
464
+ if (!cookable || isGapNode(cookable.type)) {
368
465
  return '';
369
466
  }
370
467
 
@@ -372,26 +469,31 @@ export const getCooked = (cookable) => {
372
469
 
373
470
  let cooked = '';
374
471
 
375
- const openTag = getOpenTag(cookable);
376
- const closeTag = getCloseTag(cookable);
472
+ // const openTag = getOpenTag(cookable);
473
+ // const closeTag = getCloseTag(cookable);
474
+
475
+ let reference = null;
377
476
 
378
477
  for (const tag of btree.traverse(children)) {
379
478
  switch (tag.type) {
380
479
  case ReferenceTag: {
381
- throw new Error('cookable nodes must not contain other nodes');
382
- }
383
-
384
- case EmbeddedNode: {
385
- const { flags, attributes } = tag.value;
480
+ const { name } = tag.value;
386
481
 
387
- if (!(flags.trivia || (flags.escape && attributes.cooked))) {
482
+ if (!(name === '#' || name === '@')) {
388
483
  throw new Error('cookable nodes must not contain other nodes');
389
484
  }
390
485
 
391
- if (flags.escape) {
486
+ reference = tag;
487
+ break;
488
+ }
489
+
490
+ case EmbeddedNode: {
491
+ const { attributes } = tag.value;
492
+
493
+ if (reference.value.name === '@') {
392
494
  const { cooked: cookedValue } = attributes;
393
495
 
394
- if (!cookedValue && isString(cookedValue))
496
+ if (!isString(cookedValue))
395
497
  throw new Error('cannot cook string: it contains uncooked escapes');
396
498
 
397
499
  cooked += cookedValue;
@@ -430,74 +532,49 @@ export const printPrettyCSTML = (rootNode, options = {}) => {
430
532
  return printPrettyCSTMLFromStream(streamFromTree(rootNode), options);
431
533
  };
432
534
 
433
- const __printSource = (rootNode, resolver = new Resolver()) => {
434
- let printed = '';
435
-
436
- if (!rootNode) return '';
437
-
438
- let node = rootNode;
439
-
440
- if (node instanceof Promise) {
441
- printed += '$Promise';
442
- } else {
443
- for (const child of btree.traverse(node.children)) {
444
- if (child.type === LiteralTag) {
445
- printed += child.value;
446
- resolver.advance(child);
447
- } else if (child.type === EmbeddedNode) {
448
- resolver.advance(child);
449
- printed += __printSource(child.value, resolver);
450
- } else if (child.type === ReferenceTag) {
451
- const resolvedPath = resolver.resolve(child);
452
- const resolvedNode = get(node, resolvedPath);
453
-
454
- resolver.advance(child);
455
- if (resolvedNode) {
456
- if (!isArray(resolvedNode)) {
457
- printed += __printSource(resolvedNode, resolver);
458
- }
459
- }
460
- } else {
461
- resolver.advance(child);
462
- }
463
- }
464
- }
465
-
466
- return printed;
535
+ export const printSource = (rootNode) => {
536
+ return printSourceFromStream(streamFromTree(rootNode, { unshift: true }));
467
537
  };
468
538
 
469
- export const printSource = (rootNode) => __printSource(rootNode);
470
-
471
539
  export const sourceTextFor = printSource;
472
540
 
473
- export const getOpenTag = (node) => {
474
- const tag = btree.getAt(node.type ? 0 : 1, node.children);
475
- if (tag.type === NullTag) return null;
476
- if (tag && tag.type !== OpenNodeTag && tag.type !== OpenFragmentTag) throw new Error();
477
- return tag;
478
- };
479
-
480
- export const getCloseTag = (node) => {
481
- const { children } = node;
482
- const tag = btree.getAt(-1, children);
483
- if (tag.type !== CloseNodeTag) return null;
484
- return tag;
485
- };
486
-
487
541
  export const getRange = (node) => {
488
542
  const { children } = node;
489
- return children.length ? [children[1], children[children.length - 1]] : null;
543
+ return btree.getSum(children) ? [btree.getAt(0, children), btree.getAt(-1, children)] : null;
490
544
  };
491
545
 
492
546
  export const createNode = (openTag) => {
493
- const { flags, language, type, attributes } = openTag.value;
494
- return { flags, language, type, children: [], properties: {}, attributes };
547
+ if (!openTag || openTag.type === GapTag || openTag.type === NullTag) {
548
+ return {
549
+ flags: nodeFlags,
550
+ language: openTag?.language,
551
+ type: openTag && ([NullTag, GapTag].includes(openTag.type) ? null : openTag.type),
552
+ children: [],
553
+ properties: {},
554
+ attributes: openTag?.attributes || {},
555
+ };
556
+ } else {
557
+ const { flags, language, type, attributes = {} } = openTag.value || {};
558
+ return { flags, language, type, children: [], properties: {}, attributes };
559
+ }
495
560
  };
496
561
 
497
562
  export const finalizeNode = (node) => {
498
- for (const propertyValue of Object.values(node.properties)) {
499
- if (isArray(propertyValue)) {
500
- btree.freeze(propertyValue);
563
+ for (const property of Object.values(node.properties)) {
564
+ if (isArray(property)) {
565
+ btree.freeze(property);
566
+ for (const childProperty of btree.traverse(property)) {
567
+ freeze(childProperty);
568
+ if (childProperty.reference.value.flags.expression) {
569
+ btree.freeze(childProperty.node);
570
+ }
571
+ }
572
+ } else {
573
+ freeze(property);
574
+
575
+ if (property.reference.value.flags.expression) {
576
+ btree.freeze(property.node);
577
+ }
501
578
  }
502
579
  }
503
580
 
@@ -509,11 +586,11 @@ export const finalizeNode = (node) => {
509
586
  };
510
587
 
511
588
  export const notNull = (node) => {
512
- return node && node.type !== sym.null;
589
+ return node != null && !isNullNode(node);
513
590
  };
514
591
 
515
592
  export const isNull = (node) => {
516
- return !node || node.type === sym.null;
593
+ return node == null || isNullNode(node);
517
594
  };
518
595
 
519
596
  export const branchProperties = (properties) => {
@@ -521,7 +598,7 @@ export const branchProperties = (properties) => {
521
598
 
522
599
  for (const { 0: key, 1: value } of Object.entries(copy)) {
523
600
  if (isArray(value)) {
524
- copy[key] = [...value];
601
+ copy[key] = btree.fromValues(value);
525
602
  }
526
603
  }
527
604
 
@@ -534,7 +611,8 @@ export const branchNode = (node) => {
534
611
  flags,
535
612
  language,
536
613
  type,
537
- children: [...children],
614
+ // if we always use immutable trees we won't need to do this
615
+ children: btree.fromValues(btree.traverse(children)),
538
616
  properties: branchProperties(properties),
539
617
  attributes: { ...attributes },
540
618
  };
@@ -549,15 +627,21 @@ export const acceptNode = (node, accepted) => {
549
627
  };
550
628
 
551
629
  export const getRoot = (node) => {
552
- return node.type == null ? node.properties['.'] : node;
630
+ return node == null ? node : isFragmentNode(node) ? node.properties['.'].node : node;
553
631
  };
554
632
 
555
633
  export function* traverseProperties(properties) {
556
634
  for (const value of Object.values(properties)) {
557
635
  if (isArray(value)) {
558
- yield* btree.traverse(value);
636
+ for (let item of btree.traverse(value)) {
637
+ if (isArray(item.node)) {
638
+ yield btree.getAt(-1, item.node);
639
+ } else {
640
+ yield item.node;
641
+ }
642
+ }
559
643
  } else {
560
- yield value;
644
+ yield value.node;
561
645
  }
562
646
  }
563
647
  }
@@ -591,6 +675,8 @@ export class Resolver {
591
675
 
592
676
  this.popped = false;
593
677
 
678
+ let hadReference = this.reference;
679
+
594
680
  switch (tag.type) {
595
681
  case ReferenceTag: {
596
682
  const { name, isArray } = tag.value;
@@ -600,62 +686,65 @@ export class Resolver {
600
686
 
601
687
  this.reference = tag;
602
688
 
603
- let state = properties.get(name);
604
-
605
- if (isArray) {
606
- if (state && !state.isArray) throw new Error();
689
+ if (name && name !== '#' && name !== '@') {
690
+ let state = properties.get(name);
607
691
 
608
- const { count = -1 } = state || {};
692
+ if (isArray) {
693
+ if (state && !state.isArray) throw new Error();
609
694
 
610
- state = { count: count + 1, isArray };
611
- } else if (state) {
612
- throw new Error(`attempted to consume property {name: ${name}} twice`);
613
- } else {
614
- state = { count: 1, isArray: false };
615
- }
695
+ const { count = -1 } = state || {};
616
696
 
617
- properties.set(name, state);
697
+ state = { count: count + 1, isArray };
698
+ } else if (state) {
699
+ throw new Error(`attempted to consume property {name: ${name}} twice`);
700
+ } else {
701
+ state = { count: 1, isArray: false };
702
+ }
618
703
 
619
- if (!isArray || state.count > 0) {
620
- this.states = states.push({ properties: new Map(), idx: 0 });
704
+ properties.set(name, state);
621
705
  }
622
706
 
623
707
  break;
624
708
  }
625
709
 
626
710
  case EmbeddedNode: {
627
- if (this.reference) throw new Error();
711
+ if (!this.reference || !['#', '@'].includes(this.reference.value.name)) throw new Error();
628
712
 
629
- this.reference = tag;
630
-
631
- this.states = states.push({ properties: new Map(), idx: 0 });
713
+ // this.states = states.push({ properties: new Map(), idx: 0 });
632
714
  break;
633
715
  }
634
716
 
635
- case OpenNodeTag:
636
- case OpenFragmentTag: {
717
+ case OpenNodeTag: {
718
+ const { reference } = this;
637
719
  const { flags } = tag.value;
638
720
  const isRootNode = states.size === 1;
639
721
 
640
- if (tag.type === OpenFragmentTag && (!isRootNode || this.reference)) throw new Error();
722
+ if (tag.value.type) {
723
+ this.states = states.push({ properties: new Map(), idx: 0 });
724
+ }
641
725
 
642
- if (flags.trivia || flags.escape) {
643
- if (this.reference?.type === ReferenceTag)
644
- throw new Error('embedded nodes cannot follow references');
645
- if (this.reference?.type !== EmbeddedNode) {
646
- this.states = states.push({ properties: new Map(), idx: 0 });
647
- }
648
- } else {
649
- if (!isRootNode && !this.reference) {
650
- throw new Error();
651
- }
726
+ if (!tag.value.type && (!isRootNode || this.reference)) throw new Error();
727
+
728
+ if (
729
+ tag.type === OpenNodeTag &&
730
+ ((!reference && !isRootNode) ||
731
+ (reference &&
732
+ reference.type !== ReferenceTag &&
733
+ reference.type !== ShiftTag &&
734
+ reference.type !== OpenNodeTag))
735
+ ) {
736
+ throw new Error('Invalid location for OpenNodeTag');
737
+ }
738
+
739
+ if (!isRootNode && !reference) {
740
+ throw new Error();
652
741
  }
653
742
 
654
743
  this.reference = null;
655
744
  break;
656
745
  }
657
746
 
658
- case ArrayTag: {
747
+ case ArrayInitializerTag: {
659
748
  if (!this.reference) throw new Error();
660
749
 
661
750
  const { name } = this.reference.value;
@@ -679,14 +768,15 @@ export class Resolver {
679
768
  }
680
769
 
681
770
  case NullTag: {
682
- this.states = states.pop();
771
+ if (!this.reference) throw new Error();
772
+
683
773
  this.popped = true;
684
774
  this.reference = null;
685
775
  break;
686
776
  }
687
777
 
688
778
  case GapTag: {
689
- this.states = states.pop();
779
+ // if (!this.reference) throw new Error();
690
780
 
691
781
  if (this.held) {
692
782
  // this.states = this.states.push(this.held);
@@ -698,8 +788,9 @@ export class Resolver {
698
788
  break;
699
789
  }
700
790
 
701
- case CloseNodeTag:
702
- case CloseFragmentTag: {
791
+ case CloseNodeTag: {
792
+ if (this.reference) throw new Error();
793
+
703
794
  this.states = states.pop();
704
795
  this.popped = true;
705
796
  break;
@@ -708,6 +799,7 @@ export class Resolver {
708
799
  case DoctypeTag:
709
800
  this.doctype = tag;
710
801
  break;
802
+
711
803
  case LiteralTag:
712
804
  break;
713
805
 
@@ -715,23 +807,24 @@ export class Resolver {
715
807
  throw new Error();
716
808
  }
717
809
 
810
+ if (hadReference && this.reference) throw new Error();
811
+
718
812
  return this;
719
813
  }
720
814
 
721
815
  resolve(reference) {
722
- let { name, isArray } = reference.value;
816
+ let { name, isArray, flags } = reference.value;
723
817
  const { states } = this;
724
818
  const state = states.value.properties.get(name);
725
- let path = name;
819
+ let index = null;
726
820
 
727
- if (isArray) {
728
- if (state) {
729
- const count = state?.count || 0;
730
- path += '.' + count;
731
- }
821
+ if (name === '@' || name === '#') return reference;
822
+
823
+ if (isArray && state) {
824
+ index = state?.count || 0;
732
825
  }
733
826
 
734
- return path;
827
+ return buildReferenceTag(name, isArray, flags, index);
735
828
  }
736
829
 
737
830
  branch() {