@bablr/agast-helpers 0.7.1 → 0.9.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
@@ -7,6 +7,16 @@ import {
7
7
  buildLiteralTag,
8
8
  buildCloseNodeTag,
9
9
  tokenFlags,
10
+ buildInitializerTag,
11
+ buildBindingTag,
12
+ buildProperty,
13
+ buildChild,
14
+ buildShiftTag,
15
+ buildGapTag,
16
+ buildBinding,
17
+ buildReference,
18
+ multiFragmentFlags,
19
+ buildPropertyWrapper,
10
20
  } from './builders.js';
11
21
  import {
12
22
  printPrettyCSTML as printPrettyCSTMLFromStream,
@@ -23,15 +33,21 @@ import {
23
33
  NullTag,
24
34
  InitializerTag,
25
35
  LiteralTag,
26
- EmbeddedNode,
36
+ AttributeDefinition,
37
+ BindingTag,
38
+ Property,
39
+ ShiftTag,
40
+ PropertyWrapper,
27
41
  } from './symbols.js';
28
- import * as btree from './btree.js';
29
- import * as sumtree from './sumtree.js';
42
+ import * as BTree from './btree.js';
43
+ import * as Tags from './tags.js';
30
44
  export * from './builders.js';
31
45
  export * from './print.js';
32
46
  import {
33
- add,
34
47
  get,
48
+ getOr,
49
+ has,
50
+ list,
35
51
  TagPath,
36
52
  Path,
37
53
  isFragmentNode,
@@ -39,14 +55,30 @@ import {
39
55
  isGapNode,
40
56
  getOpenTag,
41
57
  getCloseTag,
42
- getShifted,
58
+ getRoot,
59
+ getRootArray,
60
+ isStubTag,
61
+ wrapperIsFull,
43
62
  } from './path.js';
63
+ import { isPlainObject } from './object.js';
44
64
 
45
- export { add, get, isFragmentNode, isNullNode, isGapNode, getOpenTag, getCloseTag };
65
+ export {
66
+ get,
67
+ getOr,
68
+ has,
69
+ list,
70
+ isFragmentNode,
71
+ isNullNode,
72
+ isGapNode,
73
+ getOpenTag,
74
+ getCloseTag,
75
+ getRoot,
76
+ getRootArray,
77
+ };
46
78
 
47
- export const buildToken = (language, type, value, attributes = {}) => {
79
+ export const buildToken = (type, value, attributes = {}) => {
48
80
  return treeFromStreamSync([
49
- buildOpenNodeTag(tokenFlags, language, type, attributes),
81
+ buildOpenNodeTag(tokenFlags, type, attributes),
50
82
  buildLiteralTag(value),
51
83
  buildCloseNodeTag(),
52
84
  ]);
@@ -55,72 +87,36 @@ export const buildToken = (language, type, value, attributes = {}) => {
55
87
  const isString = (str) => typeof str === 'string';
56
88
 
57
89
  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
- };
73
-
74
- index = index ? parseInt(index, 10) : null;
75
- isArray = !!isArray;
76
- name = name || null;
77
-
78
- return buildReferenceTag(name, isArray, flags, index);
79
- };
90
+ const { freeze, hasOwn } = Object;
80
91
 
81
92
  export const mergeReferences = (outer, inner) => {
82
- let {
83
- name,
84
- isArray,
85
- index,
86
- flags: { expression, hasGap },
87
- } = outer.value;
93
+ let { type, name, isArray, index, flags: { expression, hasGap } = {} } = outer;
88
94
 
89
95
  if (
90
- name != null &&
91
- name !== '.' &&
92
- inner.value.name != null &&
93
- inner.value.name !== '.' &&
94
- name !== inner.value.name
96
+ (name != null && inner.name != null && name !== inner.name) ||
97
+ (type != null && inner.type != null && type !== inner.type && inner.type !== '.')
95
98
  ) {
96
99
  return inner;
97
100
  }
98
101
 
99
- isArray = isArray || inner.value.isArray;
100
- expression = !!(expression || inner.value.flags.expression);
101
- hasGap = !!(hasGap || inner.value.flags.hasGap);
102
- name = name === '.' ? inner.value.name : name;
103
-
104
- return buildReferenceTag(name, isArray, { expression, hasGap }, index);
105
- };
102
+ isArray = isArray || inner.isArray;
103
+ expression = !!(expression || inner.flags.expression);
104
+ hasGap = !!(hasGap || inner.flags.hasGap);
105
+ name = type === '.' ? inner.name : name;
106
+ type = type === '.' ? inner.type : type;
106
107
 
107
- export const isEmptyReference = (ref) => {
108
- let { name, isArray, flags } = ref.value;
109
- return name === '.' && !isArray && !(flags.expression || flags.hasGap);
108
+ return buildReferenceTag(type, name, isArray, { expression, hasGap }, index).value;
110
109
  };
111
110
 
112
- export const defineAttribute = (node, key, value) => {
113
- const openTag = getOpenTag(node);
114
-
115
- if (value != null) {
116
- const { flags, language, type } = openTag.value;
117
- const attributes = { ...openTag.value.attributes, [key]: value };
118
- const newOpenTag = buildOpenNodeTag(flags, language, type, attributes);
111
+ export const mergeReferenceTags = (outer, inner) => {
112
+ let value = mergeReferences(outer.value, inner.value);
119
113
 
120
- node.attributes = attributes;
114
+ return buildChild(ReferenceTag, value);
115
+ };
121
116
 
122
- node.children = sumtree.replaceAt(0, node.children, newOpenTag);
123
- }
117
+ export const isEmptyReference = (ref) => {
118
+ let { type, isArray, flags } = ref.value;
119
+ return type === '.' && !isArray && !(flags.expression || flags.hasGap);
124
120
  };
125
121
 
126
122
  function* __treeFromStream(tags, options) {
@@ -130,7 +126,8 @@ function* __treeFromStream(tags, options) {
130
126
  let doctype = null;
131
127
  const co = new Coroutine(getStreamIterator(tags));
132
128
  const expressionsCo = new Coroutine(getStreamIterator(options.expressions || []));
133
- let reference = null;
129
+ let referenceTag = null;
130
+ let bindingTag = null;
134
131
 
135
132
  for (;;) {
136
133
  co.advance();
@@ -160,20 +157,30 @@ function* __treeFromStream(tags, options) {
160
157
 
161
158
  switch (tag.type) {
162
159
  case LiteralTag:
160
+ break;
161
+
163
162
  case CloseNodeTag: {
163
+ let { node } = path;
164
+ if (!node.tags[1]) throw new Error();
164
165
  break;
165
166
  }
166
167
 
167
168
  case ReferenceTag: {
168
- reference = tag;
169
+ referenceTag = tag;
170
+ suppressTag = true;
171
+ break;
172
+ }
173
+
174
+ case BindingTag: {
175
+ bindingTag = tag;
169
176
  suppressTag = true;
170
177
  break;
171
178
  }
172
179
 
173
180
  case InitializerTag: {
174
- add(path.node, reference, []);
181
+ add(path.node, referenceTag, []);
175
182
  suppressTag = true;
176
- reference = null;
183
+ referenceTag = null;
177
184
  break;
178
185
  }
179
186
 
@@ -185,19 +192,20 @@ function* __treeFromStream(tags, options) {
185
192
 
186
193
  const isGap = tag.type === GapTag;
187
194
 
188
- if (path.parent && reference.type !== ReferenceTag) throw new Error();
195
+ if (path.parent && referenceTag.type !== ReferenceTag) throw new Error();
189
196
 
190
197
  let node = createNode(tag);
191
198
 
199
+ suppressTag = true;
200
+
192
201
  if (isGap) {
193
202
  if (held) {
194
203
  node = held;
195
- add(path.node, reference, node);
196
- suppressTag = true;
204
+ add(path.node, referenceTag, node, bindingTag);
197
205
  } else if (!expressionsCo.done) {
198
206
  expressionsCo.advance();
199
207
 
200
- let outerReference = reference;
208
+ let outerReference = referenceTag;
201
209
 
202
210
  if (!expressionsCo.done) {
203
211
  node =
@@ -206,20 +214,21 @@ function* __treeFromStream(tags, options) {
206
214
  : expressionsCo.value == null
207
215
  ? buildStubNode(buildNullTag())
208
216
  : expressionsCo.value;
209
- suppressTag = true;
210
217
 
211
218
  if (isFragmentNode(node)) {
212
219
  const parentNode = path.node;
213
220
 
214
- let reference;
221
+ let referenceTag;
222
+ let bindingTag;
215
223
 
216
- for (const tag of sumtree.traverse(node.children)) {
224
+ for (const tag of Tags.traverse(node.tags)) {
217
225
  switch (tag.type) {
218
226
  case DoctypeTag: {
219
227
  break;
220
228
  }
221
- case OpenNodeTag:
222
- case CloseNodeTag: {
229
+
230
+ case OpenNodeTag: {
231
+ referenceTag = bindingTag = null;
223
232
  if (!tag.value.type) {
224
233
  break;
225
234
  } else {
@@ -228,29 +237,28 @@ function* __treeFromStream(tags, options) {
228
237
  }
229
238
 
230
239
  case ReferenceTag:
231
- // combine tags for .
232
-
233
- reference = tag;
240
+ referenceTag = tag;
234
241
  break;
235
242
 
236
243
  case InitializerTag: {
237
- add(parentNode, mergeReferences(outerReference, reference), []);
244
+ add(parentNode, mergeReferenceTags(outerReference, referenceTag), []);
238
245
  break;
239
246
  }
240
247
 
241
- case EmbeddedNode: {
242
- add(parentNode, mergeReferences(outerReference, reference), tag.value);
243
- break;
244
- }
245
-
246
- case GapTag: {
247
- const resolvedNode = getShifted(shift, reference, node);
248
- add(parentNode, mergeReferences(outerReference, reference), resolvedNode);
248
+ case BindingTag: {
249
+ bindingTag = tag;
249
250
  break;
250
251
  }
251
252
 
253
+ case GapTag:
252
254
  case NullTag: {
253
- add(parentNode, mergeReferences(outerReference, reference), null);
255
+ add(
256
+ parentNode,
257
+ mergeReferenceTags(outerReference, referenceTag),
258
+ buildStubNode(tag),
259
+ bindingTag,
260
+ );
261
+ referenceTag = bindingTag = null;
254
262
  break;
255
263
  }
256
264
 
@@ -262,28 +270,28 @@ function* __treeFromStream(tags, options) {
262
270
  if (path.node.flags.token) {
263
271
  throw new Error('not implemented');
264
272
  }
265
- add(path.node, reference, node);
273
+ add(path.node, referenceTag, node, bindingTag ?? buildBindingTag());
266
274
  }
267
275
  } else {
268
276
  if (!path.node.flags.token) {
269
- add(path.node, reference, node);
277
+ add(path.node, referenceTag, node, bindingTag ?? buildBindingTag());
270
278
  }
271
279
  }
272
280
  }
273
281
  }
274
282
 
275
- reference = null;
283
+ referenceTag = null;
276
284
  held = isGap ? null : held;
277
285
 
278
286
  if (!path.node.flags.token) {
279
- path = { parent: path, node, depth: (path.depth || -1) + 1, arrays: new Set() };
287
+ path = { parent: path, node, depth: (path.depth ?? -1) + 1, arrays: new Set() };
280
288
  }
281
289
 
282
290
  break;
283
291
  }
284
292
 
285
293
  // case ShiftTag: {
286
- // const { children, properties } = path.node;
294
+ // const { tags, properties } = path.node;
287
295
 
288
296
  // let property = properties[ref.value.name];
289
297
  // let node;
@@ -301,35 +309,21 @@ function* __treeFromStream(tags, options) {
301
309
  // }
302
310
 
303
311
  case OpenNodeTag: {
312
+ const node = createNode(tag);
304
313
  if (path) {
305
- const node = createNode(tag);
306
-
307
314
  if (path) {
308
- add(path.node, reference, node);
309
- reference = null;
315
+ add(path.node, referenceTag, node, bindingTag ?? buildBindingTag());
316
+ referenceTag = null;
310
317
  }
311
318
 
312
319
  path = { parent: path, node, depth: (path ? path.depth : -1) + 1, arrays: new Set() };
313
320
  } else {
314
- const { language, type, flags, attributes } = tag.value;
315
-
316
- const attributes_ = doctype?.value.attributes ?? attributes;
317
- const language_ = attributes?.['bablrLanguage'] ?? language;
318
-
319
- const node = {
320
- flags,
321
- language: language_,
322
- type,
323
- children: [],
324
- properties: {},
325
- attributes: attributes_,
326
- };
327
-
328
321
  path = { parent: null, node, depth: 0, arrays: new Set() };
329
322
 
330
323
  rootPath = path;
331
324
  }
332
325
 
326
+ suppressTag = true;
333
327
  break;
334
328
  }
335
329
 
@@ -339,7 +333,7 @@ function* __treeFromStream(tags, options) {
339
333
  }
340
334
 
341
335
  if (!suppressTag) {
342
- path.node.children = sumtree.push(path.node.children, tag);
336
+ path.node.tags = Tags.push(path.node.tags, tag);
343
337
  }
344
338
 
345
339
  switch (tag.type) {
@@ -403,41 +397,22 @@ export const evaluateReturnAsync = async (generator) => {
403
397
  return co.value;
404
398
  };
405
399
 
406
- export const streamFromTree = (rootNode, options = {}) => __streamFromTree(rootNode, options);
407
-
408
400
  export const isEmpty = (node) => {
409
- const { properties } = node;
410
-
411
- let ref = null;
412
-
413
- for (const tag of sumtree.traverse(node.children)) {
401
+ for (const tag of Tags.traverseInner(node.tags)) {
414
402
  switch (tag.type) {
415
- case ReferenceTag: {
416
- const { name } = tag.value;
417
-
418
- ref = tag;
419
-
420
- if (properties[name]) {
421
- const property = properties[name];
403
+ case Property: {
404
+ if (tag.value.reference.type === '@') {
405
+ return false;
406
+ } else {
407
+ const property = tag.value;
422
408
 
423
- if (
424
- property != null ||
425
- (isArray(property) && property.length) ||
426
- !isNullNode(property.node)
427
- ) {
409
+ if (!isNullNode(property.node)) {
428
410
  return false;
429
411
  }
430
412
  }
431
413
  break;
432
414
  }
433
415
 
434
- case EmbeddedNode: {
435
- if (ref.value.name === '@') {
436
- return false;
437
- }
438
- break;
439
- }
440
-
441
416
  case LiteralTag:
442
417
  case GapTag:
443
418
  return false;
@@ -446,67 +421,193 @@ export const isEmpty = (node) => {
446
421
  return true;
447
422
  };
448
423
 
424
+ let buildGapProperty = () =>
425
+ buildProperty(buildReference('.'), buildBinding(), buildStubNode(buildGapTag()));
426
+
427
+ export const buildBounds = (
428
+ openBoundary = BTree.fromValues([buildGapProperty()]),
429
+ closeBoundary = openBoundary,
430
+ ) => {
431
+ return freeze([openBoundary, closeBoundary]);
432
+ };
433
+
449
434
  export const buildStubNode = (tag) => {
435
+ if (!isStubTag(tag)) throw new Error();
450
436
  return freeze({
451
437
  flags: nodeFlags,
452
- language: null,
453
438
  type: null,
454
- children: freeze([tag]),
455
- properties: freeze({}),
439
+ bounds: buildBounds(null, null),
440
+ tags: Tags.from(tag),
456
441
  attributes: freeze({}),
457
442
  });
458
443
  };
459
444
 
460
- function* __streamFromTree(rootNode, options) {
445
+ export const streamFromTree = (tree, options = {}) => {
446
+ let rootNode = isPlainObject(tree) ? tree : tree.node;
447
+
448
+ return __streamFromTree(null, rootNode, options);
449
+ };
450
+
451
+ function* __streamFromTree(doctypeTag, rootNode, options) {
461
452
  const { unshift = false } = options;
462
- if (!rootNode || !sumtree.getSize(rootNode.children)) return;
453
+ if (!rootNode || !Tags.getSize(rootNode.tags)) return;
463
454
 
464
455
  let tagPath = TagPath.fromNode(rootNode, 0);
465
456
 
466
457
  let count = 0;
467
458
 
459
+ if (doctypeTag) {
460
+ yield doctypeTag;
461
+ }
462
+
468
463
  do {
469
- if (tagPath.tag.type === OpenNodeTag) count++;
464
+ if (tagPath.tag.type === OpenNodeTag && !tagPath.tag.value.literalValue) count++;
470
465
  if (tagPath.tag.type === CloseNodeTag) count--;
471
466
 
472
- yield tagPath.tag;
467
+ if (
468
+ !(
469
+ tagPath.tag.type === AttributeDefinition ||
470
+ (tagPath.tag.type === BindingTag && !tagPath.tag.value.languagePath?.length)
471
+ )
472
+ ) {
473
+ yield tagPath.tag;
474
+ }
473
475
  } while ((tagPath = unshift ? tagPath.nextUnshifted : tagPath.next));
474
476
 
475
477
  if (count !== 0) throw new Error();
476
478
  }
477
479
 
480
+ export const vcsStreamFromTree = (rootNode) => {
481
+ let rootNode_ = isPlainObject(rootNode) ? rootNode : rootNode.node;
482
+
483
+ return __vcsStreamFromTree(rootNode_);
484
+ };
485
+
486
+ function* __vcsStreamFromTree(rootNode) {
487
+ let stack = null;
488
+ let agastNode = rootNode;
489
+ let depth = 0;
490
+ let nodeShifted = false;
491
+ let i = 0;
492
+ let seenFirstProperty = false;
493
+ let node = Tags.getValues(rootNode.tags);
494
+
495
+ outer: while (node) {
496
+ while (i >= node.length) {
497
+ if (stack) {
498
+ let oldAgastNode = agastNode;
499
+ let hadSeenFirst = seenFirstProperty;
500
+ ({ stack, agastNode, node, depth, i, nodeShifted, seenFirstProperty } = stack);
501
+ if (oldAgastNode === agastNode) {
502
+ seenFirstProperty = hadSeenFirst;
503
+ yield buildCloseNodeTag();
504
+ }
505
+
506
+ i++;
507
+ } else {
508
+ return;
509
+ }
510
+ }
511
+
512
+ let child = node[i];
513
+
514
+ if (isArray(child)) {
515
+ stack = { stack, agastNode, node, depth, i, nodeShifted, seenFirstProperty };
516
+
517
+ yield buildOpenNodeTag(multiFragmentFlags);
518
+
519
+ node = Tags.getValues(child);
520
+ depth++;
521
+ i = 0;
522
+ } else {
523
+ let wrappedTag = child;
524
+
525
+ if (wrappedTag.type === PropertyWrapper) {
526
+ for (let tag of wrappedTag.value.tags) {
527
+ switch (tag.type) {
528
+ case Property: {
529
+ let replaceWithGap = nodeShifted && !seenFirstProperty;
530
+
531
+ if (replaceWithGap) {
532
+ yield buildGapTag();
533
+
534
+ seenFirstProperty = true;
535
+
536
+ break;
537
+ } else {
538
+ stack = { stack, agastNode, node, depth, i, nodeShifted, seenFirstProperty: true };
539
+ node = Tags.getValues(tag.value.node.tags);
540
+
541
+ depth++;
542
+ i = 0;
543
+ agastNode = tag.value.node;
544
+ nodeShifted = wrappedTag.value.tags[0].type === ShiftTag;
545
+ seenFirstProperty = false;
546
+ continue outer;
547
+ }
548
+ }
549
+
550
+ default: {
551
+ yield tag;
552
+
553
+ if (tag.type === OpenNodeTag && tag.value.literalValue) {
554
+ ({ stack, agastNode, node, depth, i, nodeShifted } = stack);
555
+ seenFirstProperty = true;
556
+ }
557
+
558
+ break;
559
+ }
560
+ }
561
+ }
562
+ } else {
563
+ yield wrappedTag;
564
+
565
+ if (wrappedTag.type === OpenNodeTag && wrappedTag.value.literalValue) {
566
+ ({ stack, agastNode, node, depth, i, nodeShifted } = stack);
567
+ seenFirstProperty = true;
568
+ }
569
+ }
570
+ i++;
571
+ }
572
+ }
573
+ }
574
+
478
575
  export const getCooked = (cookable) => {
479
576
  if (!cookable || isGapNode(cookable.type)) {
480
577
  return '';
481
578
  }
482
579
 
483
- const children = cookable.children || cookable;
580
+ const tags = cookable.tags || cookable;
484
581
 
485
582
  let cooked = '';
486
583
 
487
584
  // const openTag = getOpenTag(cookable);
488
585
  // const closeTag = getCloseTag(cookable);
489
586
 
490
- let reference = null;
587
+ let referenceTag = null;
491
588
 
492
- for (const tag of sumtree.traverse(children)) {
589
+ for (let tag of Tags.traverse(tags)) {
493
590
  switch (tag.type) {
494
591
  case ReferenceTag: {
495
- const { name } = tag.value;
592
+ let { type } = tag.value;
496
593
 
497
- if (!(name === '#' || name === '@')) {
594
+ if (!(type === '#' || type === '@')) {
498
595
  throw new Error('cookable nodes must not contain other nodes');
499
596
  }
500
597
 
501
- reference = tag;
598
+ referenceTag = tag;
502
599
  break;
503
600
  }
504
601
 
505
- case EmbeddedNode: {
506
- const { attributes } = tag.value;
602
+ case BindingTag:
603
+ break;
604
+
605
+ case PropertyWrapper: {
606
+ let { node, reference } = tag.value.property;
607
+ let { attributes } = node;
507
608
 
508
- if (reference.value.name === '@') {
509
- const { cooked: cookedValue } = attributes;
609
+ if (reference.type === '@') {
610
+ let { cooked: cookedValue } = attributes;
510
611
 
511
612
  if (!isString(cookedValue))
512
613
  throw new Error('cannot cook string: it contains uncooked escapes');
@@ -539,45 +640,44 @@ export const getCooked = (cookable) => {
539
640
  return cooked;
540
641
  };
541
642
 
542
- export const printCSTML = (rootNode) => {
543
- return printCSTMLFromStream(streamFromTree(rootNode));
643
+ export const printCSTML = (tree) => {
644
+ return printCSTMLFromStream(streamFromTree(tree));
544
645
  };
545
646
 
546
- export const printPrettyCSTML = (rootNode, options = {}) => {
547
- return printPrettyCSTMLFromStream(streamFromTree(rootNode), options);
647
+ export const printPrettyCSTML = (tree, options = {}) => {
648
+ return printPrettyCSTMLFromStream(streamFromTree(tree), options);
548
649
  };
549
650
 
550
- export const printSource = (rootNode) => {
551
- return printSourceFromStream(streamFromTree(rootNode, { unshift: true }));
651
+ export const printSource = (tree) => {
652
+ return printSourceFromStream(streamFromTree(tree, { unshift: true }));
552
653
  };
553
654
 
554
655
  export const sourceTextFor = printSource;
555
656
 
556
657
  export const getRange = (node) => {
557
- const { children } = node;
658
+ const { tags } = node;
558
659
  let path = Path.from(node);
559
- return sumtree.getSize(children) ? [TagPath.from(path, 0), TagPath.from(path, -1)] : null;
660
+ return Tags.getSize(tags) ? [TagPath.from(path, 0), TagPath.from(path, -1)] : null;
560
661
  };
561
662
 
562
663
  export const createNode = (openTag) => {
664
+ let bounds = buildBounds();
665
+ let literalTags = [];
666
+ let tags = openTag ? Tags.fromValues([openTag, ...literalTags]) : Tags.fromValues(literalTags);
667
+ let flags, type, attributes;
668
+
563
669
  if (!openTag || openTag.type === GapTag || openTag.type === NullTag) {
564
- return {
565
- flags: nodeFlags,
566
- language: openTag?.language,
567
- type: openTag && ([NullTag, GapTag].includes(openTag.type) ? null : openTag.type),
568
- children: [],
569
- properties: {},
570
- attributes: openTag?.attributes || {},
571
- };
670
+ flags = nodeFlags;
671
+ type = openTag && ([NullTag, GapTag].includes(openTag.type) ? null : openTag.type);
672
+ attributes = openTag?.attributes || {};
572
673
  } else {
573
- const { flags, language, type, attributes = {} } = openTag.value || {};
574
- return { flags, language, type, children: [], properties: {}, attributes };
674
+ ({ flags, type, attributes = {} } = openTag.value || {});
575
675
  }
676
+ return { flags, type, bounds, tags, attributes };
576
677
  };
577
678
 
578
679
  export const finalizeNode = (node) => {
579
680
  freeze(node);
580
- freeze(node.properties);
581
681
  freeze(node.attributes);
582
682
  return node;
583
683
  };
@@ -590,54 +690,132 @@ export const isNull = (node) => {
590
690
  return node == null || isNullNode(node);
591
691
  };
592
692
 
593
- export const branchProperties = (properties) => {
594
- const copy = { ...properties };
693
+ export const addProperty = (node, property) => {
694
+ if (!node || !property) throw new Error();
695
+ if (!Object.isFrozen(property)) throw new Error();
595
696
 
596
- for (const { 0: key, 1: value } of Object.entries(copy)) {
597
- if (isArray(value)) {
598
- copy[key] = btree.treeFromValues(value);
599
- }
697
+ if (property.node === null) {
698
+ throw new Error();
600
699
  }
601
700
 
602
- return copy;
603
- };
701
+ let { node: value, binding, reference } = property;
702
+
703
+ let isArrayInitializer = reference.type !== '_' && isArray(value);
704
+ let isInitializer = value === undefined || isArrayInitializer;
705
+
706
+ if (isArrayInitializer && value.length) throw new Error();
707
+
708
+ if (!isInitializer && property.node.type && !property.binding) {
709
+ throw new Error();
710
+ }
711
+
712
+ if (reference.type === '_') {
713
+ node.tags = Tags.push(node.tags, value);
714
+ return node;
715
+ }
716
+
717
+ let referenceTag = buildChild(ReferenceTag, reference);
718
+ let propertyWrapper;
719
+
720
+ if (isInitializer) {
721
+ let tags = freeze([referenceTag, buildInitializerTag(isArrayInitializer)]);
722
+
723
+ propertyWrapper = buildPropertyWrapper(
724
+ tags,
725
+ buildProperty(reference, null, isArrayInitializer ? [] : null),
726
+ );
727
+ } else {
728
+ let tags = freeze([
729
+ referenceTag,
730
+ buildChild(BindingTag, binding),
731
+ buildChild(Property, property),
732
+ ]);
733
+
734
+ propertyWrapper = buildPropertyWrapper(tags, property);
735
+ }
604
736
 
605
- export const branchNode = (node) => {
606
- const { flags, language, type, children, properties, attributes } = node;
607
- return {
608
- flags,
609
- language,
610
- type,
611
- children,
612
- properties: branchProperties(properties),
613
- attributes: { ...attributes },
614
- };
737
+ let lastTag = Tags.getAt(-1, node.tags);
738
+ let unboundProperty = lastTag.type === PropertyWrapper && lastTag.value.tags.length < 3;
739
+
740
+ if (unboundProperty) {
741
+ node.tags = Tags.replaceAt(-1, node.tags, buildChild(PropertyWrapper, propertyWrapper));
742
+ } else {
743
+ node.tags = Tags.push(node.tags, buildChild(PropertyWrapper, propertyWrapper));
744
+ }
745
+
746
+ return node;
615
747
  };
616
748
 
617
- export const acceptNode = (node, accepted) => {
618
- const { children, properties, attributes } = accepted;
619
- node.children = children;
620
- node.properties = properties;
621
- node.attributes = attributes;
749
+ export const shiftProperty = (node, property) => {
750
+ if (!node || !property) throw new Error();
751
+ if (!property.reference) throw new Error();
752
+ if (!Object.isFrozen(property)) throw new Error();
753
+
754
+ if (property.node === null) {
755
+ property.node = buildNullNode();
756
+ }
757
+
758
+ let { node: value } = property;
759
+
760
+ let isArrayInitializer = isArray(value);
761
+ let isInitializer = value === undefined || isArrayInitializer;
762
+
763
+ if (isInitializer || !property.binding) {
764
+ throw new Error();
765
+ }
766
+
767
+ let existingProperty = Tags.getAt(-1, node.tags);
768
+
769
+ if (!wrapperIsFull(existingProperty)) {
770
+ existingProperty = Tags.getAt(-2, node.tags);
771
+ }
772
+
773
+ let existingRef = existingProperty?.value.tags[0];
774
+
775
+ let bindingTag = buildChild(BindingTag, property.binding);
776
+
777
+ let shiftStack = existingProperty?.value.property.node.bounds[0] || BTree.fromValues([]);
778
+ let stackSize = shiftStack && property.reference.flags.expression ? BTree.getSize(shiftStack) : 0;
779
+
780
+ let index = existingRef.type === ReferenceTag ? 1 : existingRef.value.index + 1;
781
+ let height = BTree.getSize(shiftStack) + 1;
782
+
783
+ let shiftTag = stackSize ? buildShiftTag(index, height) : existingRef;
784
+
785
+ let tags = freeze([shiftTag, bindingTag, buildChild(Property, property)]);
786
+
787
+ let lastTag = Tags.getAt(-1, node.tags);
788
+ let unboundProperty = lastTag.type === PropertyWrapper && lastTag.value.tags.length < 3;
789
+
790
+ let propertyWrapper = buildPropertyWrapper(tags, property);
791
+
792
+ if (unboundProperty) {
793
+ node.tags = Tags.replaceAt(-1, node.tags, buildChild(PropertyWrapper, propertyWrapper));
794
+ } else {
795
+ node.tags = Tags.push(node.tags, buildChild(PropertyWrapper, propertyWrapper));
796
+ }
797
+
622
798
  return node;
623
799
  };
624
800
 
625
- export const getRoot = (node) => {
626
- return node == null ? node : isFragmentNode(node) ? node.properties['.'].node : node;
801
+ export const add = (node, referenceTag, value, bindingTag) => {
802
+ let lastChild = Tags.getAt(-1, node.tags);
803
+ let reference = referenceTag.value;
804
+ let binding = bindingTag
805
+ ? bindingTag.value
806
+ : lastChild?.type === BindingTag
807
+ ? lastChild.value
808
+ : buildBinding();
809
+ return addProperty(node, buildProperty(reference, binding, value));
627
810
  };
628
811
 
629
- export function* traverseProperties(properties) {
630
- for (const value of Object.values(properties)) {
631
- if (isArray(value)) {
632
- for (let item of btree.traverse(value)) {
633
- if (isArray(item.node)) {
634
- yield btree.getAt(-1, item.node);
635
- } else {
636
- yield item.node;
637
- }
638
- }
639
- } else {
640
- yield value.node;
641
- }
642
- }
643
- }
812
+ export const shift = (node, referenceTag, value, bindingTag) => {
813
+ let lastChild = Tags.getAt(-1, node.tags);
814
+ let reference = referenceTag.value;
815
+ let binding = bindingTag
816
+ ? bindingTag.value
817
+ : lastChild.type === BindingTag
818
+ ? lastChild.value
819
+ : buildBinding();
820
+ return shiftProperty(node, buildProperty(reference, binding, value));
821
+ };