@bablr/agast-helpers 0.8.0 → 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
@@ -13,6 +13,10 @@ import {
13
13
  buildChild,
14
14
  buildShiftTag,
15
15
  buildGapTag,
16
+ buildBinding,
17
+ buildReference,
18
+ multiFragmentFlags,
19
+ buildPropertyWrapper,
16
20
  } from './builders.js';
17
21
  import {
18
22
  printPrettyCSTML as printPrettyCSTMLFromStream,
@@ -33,13 +37,17 @@ import {
33
37
  BindingTag,
34
38
  Property,
35
39
  ShiftTag,
40
+ PropertyWrapper,
36
41
  } from './symbols.js';
37
- import * as btree from './btree.js';
38
- import * as Children from './children.js';
42
+ import * as BTree from './btree.js';
43
+ import * as Tags from './tags.js';
39
44
  export * from './builders.js';
40
45
  export * from './print.js';
41
46
  import {
42
47
  get,
48
+ getOr,
49
+ has,
50
+ list,
43
51
  TagPath,
44
52
  Path,
45
53
  isFragmentNode,
@@ -49,12 +57,16 @@ import {
49
57
  getCloseTag,
50
58
  getRoot,
51
59
  getRootArray,
52
- getFirstNodeShiftStack,
60
+ isStubTag,
61
+ wrapperIsFull,
53
62
  } from './path.js';
54
63
  import { isPlainObject } from './object.js';
55
64
 
56
65
  export {
57
66
  get,
67
+ getOr,
68
+ has,
69
+ list,
58
70
  isFragmentNode,
59
71
  isNullNode,
60
72
  isGapNode,
@@ -82,7 +94,7 @@ export const mergeReferences = (outer, inner) => {
82
94
 
83
95
  if (
84
96
  (name != null && inner.name != null && name !== inner.name) ||
85
- (type != null && inner.type != null && type !== inner.type)
97
+ (type != null && inner.type != null && type !== inner.type && inner.type !== '.')
86
98
  ) {
87
99
  return inner;
88
100
  }
@@ -145,7 +157,11 @@ function* __treeFromStream(tags, options) {
145
157
 
146
158
  switch (tag.type) {
147
159
  case LiteralTag:
160
+ break;
161
+
148
162
  case CloseNodeTag: {
163
+ let { node } = path;
164
+ if (!node.tags[1]) throw new Error();
149
165
  break;
150
166
  }
151
167
 
@@ -180,11 +196,12 @@ function* __treeFromStream(tags, options) {
180
196
 
181
197
  let node = createNode(tag);
182
198
 
199
+ suppressTag = true;
200
+
183
201
  if (isGap) {
184
202
  if (held) {
185
203
  node = held;
186
204
  add(path.node, referenceTag, node, bindingTag);
187
- suppressTag = true;
188
205
  } else if (!expressionsCo.done) {
189
206
  expressionsCo.advance();
190
207
 
@@ -197,7 +214,6 @@ function* __treeFromStream(tags, options) {
197
214
  : expressionsCo.value == null
198
215
  ? buildStubNode(buildNullTag())
199
216
  : expressionsCo.value;
200
- suppressTag = true;
201
217
 
202
218
  if (isFragmentNode(node)) {
203
219
  const parentNode = path.node;
@@ -205,7 +221,7 @@ function* __treeFromStream(tags, options) {
205
221
  let referenceTag;
206
222
  let bindingTag;
207
223
 
208
- for (const tag of Children.traverse(node.children)) {
224
+ for (const tag of Tags.traverse(node.tags)) {
209
225
  switch (tag.type) {
210
226
  case DoctypeTag: {
211
227
  break;
@@ -275,7 +291,7 @@ function* __treeFromStream(tags, options) {
275
291
  }
276
292
 
277
293
  // case ShiftTag: {
278
- // const { children, properties } = path.node;
294
+ // const { tags, properties } = path.node;
279
295
 
280
296
  // let property = properties[ref.value.name];
281
297
  // let node;
@@ -293,9 +309,8 @@ function* __treeFromStream(tags, options) {
293
309
  // }
294
310
 
295
311
  case OpenNodeTag: {
312
+ const node = createNode(tag);
296
313
  if (path) {
297
- const node = createNode(tag);
298
-
299
314
  if (path) {
300
315
  add(path.node, referenceTag, node, bindingTag ?? buildBindingTag());
301
316
  referenceTag = null;
@@ -303,23 +318,12 @@ function* __treeFromStream(tags, options) {
303
318
 
304
319
  path = { parent: path, node, depth: (path ? path.depth : -1) + 1, arrays: new Set() };
305
320
  } else {
306
- const { type, flags, attributes } = tag.value;
307
-
308
- const attributes_ = doctype?.value.attributes ?? attributes;
309
-
310
- const node = {
311
- flags,
312
- type,
313
- children: [],
314
- properties: {},
315
- attributes: attributes_,
316
- };
317
-
318
321
  path = { parent: null, node, depth: 0, arrays: new Set() };
319
322
 
320
323
  rootPath = path;
321
324
  }
322
325
 
326
+ suppressTag = true;
323
327
  break;
324
328
  }
325
329
 
@@ -329,7 +333,7 @@ function* __treeFromStream(tags, options) {
329
333
  }
330
334
 
331
335
  if (!suppressTag) {
332
- path.node.children = Children.push(path.node.children, tag);
336
+ path.node.tags = Tags.push(path.node.tags, tag);
333
337
  }
334
338
 
335
339
  switch (tag.type) {
@@ -394,34 +398,17 @@ export const evaluateReturnAsync = async (generator) => {
394
398
  };
395
399
 
396
400
  export const isEmpty = (node) => {
397
- const { properties } = node;
398
-
399
- let ref = null;
400
-
401
- for (const tag of Children.traverse(node.children)) {
401
+ for (const tag of Tags.traverseInner(node.tags)) {
402
402
  switch (tag.type) {
403
- case ReferenceTag: {
404
- const { name } = tag.value;
405
-
406
- ref = tag;
407
-
408
- if (properties[name]) {
409
- const property = properties[name];
410
-
411
- if (
412
- property != null ||
413
- (isArray(property) && property.length) ||
414
- !isNullNode(property.node)
415
- ) {
416
- return false;
417
- }
418
- }
419
- break;
420
- }
421
-
422
403
  case Property: {
423
404
  if (tag.value.reference.type === '@') {
424
405
  return false;
406
+ } else {
407
+ const property = tag.value;
408
+
409
+ if (!isNullNode(property.node)) {
410
+ return false;
411
+ }
425
412
  }
426
413
  break;
427
414
  }
@@ -434,32 +421,47 @@ export const isEmpty = (node) => {
434
421
  return true;
435
422
  };
436
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
+
437
434
  export const buildStubNode = (tag) => {
435
+ if (!isStubTag(tag)) throw new Error();
438
436
  return freeze({
439
437
  flags: nodeFlags,
440
438
  type: null,
441
- children: freeze([tag]),
442
- properties: freeze({}),
439
+ bounds: buildBounds(null, null),
440
+ tags: Tags.from(tag),
443
441
  attributes: freeze({}),
444
442
  });
445
443
  };
446
444
 
447
- export const streamFromTree = (rootNode, options = {}) => {
448
- let rootNode_ = isPlainObject(rootNode) ? rootNode : rootNode.node;
445
+ export const streamFromTree = (tree, options = {}) => {
446
+ let rootNode = isPlainObject(tree) ? tree : tree.node;
449
447
 
450
- return __streamFromTree(rootNode_, options);
448
+ return __streamFromTree(null, rootNode, options);
451
449
  };
452
450
 
453
- function* __streamFromTree(rootNode, options) {
451
+ function* __streamFromTree(doctypeTag, rootNode, options) {
454
452
  const { unshift = false } = options;
455
- if (!rootNode || !Children.getSize(rootNode.children)) return;
453
+ if (!rootNode || !Tags.getSize(rootNode.tags)) return;
456
454
 
457
455
  let tagPath = TagPath.fromNode(rootNode, 0);
458
456
 
459
457
  let count = 0;
460
458
 
459
+ if (doctypeTag) {
460
+ yield doctypeTag;
461
+ }
462
+
461
463
  do {
462
- if (tagPath.tag.type === OpenNodeTag) count++;
464
+ if (tagPath.tag.type === OpenNodeTag && !tagPath.tag.value.literalValue) count++;
463
465
  if (tagPath.tag.type === CloseNodeTag) count--;
464
466
 
465
467
  if (
@@ -475,12 +477,107 @@ function* __streamFromTree(rootNode, options) {
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
 
@@ -489,7 +586,7 @@ export const getCooked = (cookable) => {
489
586
 
490
587
  let referenceTag = null;
491
588
 
492
- for (let tag of Children.traverse(children)) {
589
+ for (let tag of Tags.traverse(tags)) {
493
590
  switch (tag.type) {
494
591
  case ReferenceTag: {
495
592
  let { type } = tag.value;
@@ -505,8 +602,8 @@ export const getCooked = (cookable) => {
505
602
  case BindingTag:
506
603
  break;
507
604
 
508
- case Property: {
509
- let { node, reference } = tag.value;
605
+ case PropertyWrapper: {
606
+ let { node, reference } = tag.value.property;
510
607
  let { attributes } = node;
511
608
 
512
609
  if (reference.type === '@') {
@@ -543,50 +640,44 @@ export const getCooked = (cookable) => {
543
640
  return cooked;
544
641
  };
545
642
 
546
- export const printCSTML = (rootNode) => {
547
- return printCSTMLFromStream(streamFromTree(rootNode));
643
+ export const printCSTML = (tree) => {
644
+ return printCSTMLFromStream(streamFromTree(tree));
548
645
  };
549
646
 
550
- export const printPrettyCSTML = (rootNode, options = {}) => {
551
- return printPrettyCSTMLFromStream(streamFromTree(rootNode), options);
647
+ export const printPrettyCSTML = (tree, options = {}) => {
648
+ return printPrettyCSTMLFromStream(streamFromTree(tree), options);
552
649
  };
553
650
 
554
- export const printSource = (rootNode) => {
555
- return printSourceFromStream(streamFromTree(rootNode, { unshift: true }));
651
+ export const printSource = (tree) => {
652
+ return printSourceFromStream(streamFromTree(tree, { unshift: true }));
556
653
  };
557
654
 
558
655
  export const sourceTextFor = printSource;
559
656
 
560
657
  export const getRange = (node) => {
561
- const { children } = node;
658
+ const { tags } = node;
562
659
  let path = Path.from(node);
563
- return Children.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;
564
661
  };
565
662
 
566
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
+
567
669
  if (!openTag || openTag.type === GapTag || openTag.type === NullTag) {
568
- return {
569
- flags: nodeFlags,
570
- type: openTag && ([NullTag, GapTag].includes(openTag.type) ? null : openTag.type),
571
- children: [],
572
- properties: {},
573
- attributes: openTag?.attributes || {},
574
- };
670
+ flags = nodeFlags;
671
+ type = openTag && ([NullTag, GapTag].includes(openTag.type) ? null : openTag.type);
672
+ attributes = openTag?.attributes || {};
575
673
  } else {
576
- const { flags, type, attributes = {} } = openTag.value || {};
577
- return {
578
- flags,
579
- type,
580
- children: [],
581
- properties: {},
582
- attributes,
583
- };
674
+ ({ flags, type, attributes = {} } = openTag.value || {});
584
675
  }
676
+ return { flags, type, bounds, tags, attributes };
585
677
  };
586
678
 
587
679
  export const finalizeNode = (node) => {
588
680
  freeze(node);
589
- freeze(node.properties);
590
681
  freeze(node.attributes);
591
682
  return node;
592
683
  };
@@ -599,145 +690,17 @@ export const isNull = (node) => {
599
690
  return node == null || isNullNode(node);
600
691
  };
601
692
 
602
- export const branchProperties = (properties) => {
603
- const copy = { ...properties };
604
-
605
- for (const { 0: key, 1: value } of Object.entries(copy)) {
606
- if (isArray(value)) {
607
- copy[key] = btree.fromValues(value);
608
- }
609
- }
610
-
611
- return copy;
612
- };
613
-
614
- export const branchNode = (node) => {
615
- const { flags, type, children, properties, attributes } = node;
616
- return {
617
- flags,
618
- type,
619
- children,
620
- properties: branchProperties(properties),
621
- attributes: { ...attributes },
622
- };
623
- };
624
-
625
- export const acceptNode = (node, accepted) => {
626
- const { children, properties, attributes } = accepted;
627
- node.children = children;
628
- node.properties = properties;
629
- node.attributes = attributes;
630
- return node;
631
- };
632
-
633
- export function* traverseProperties(properties) {
634
- for (const value of Object.values(properties)) {
635
- if (isArray(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
- }
643
- } else {
644
- yield value.node;
645
- }
646
- }
647
- }
648
-
649
- export const addToProperties = (properties, property) => {
650
- let { reference, node: value } = property;
651
- let { flags, name, isArray } = reference;
652
-
653
- if (!name) throw new Error();
654
- if (value === null) throw new Error();
655
- if (!hasOwn(property, 'binding')) throw new Error();
656
-
657
- if (Object.isFrozen(properties)) {
658
- throw new Error('not implemented');
659
- }
660
-
661
- let isInitializer = value === undefined || Array.isArray(value);
662
-
663
- let outerProperty = property;
664
-
665
- if (flags.expression && !isInitializer) {
666
- let shiftStack =
667
- getFirstNodeShiftStack(value) ||
668
- btree.fromValues([buildProperty(reference, null, buildStubNode(buildGapTag()))]);
669
-
670
- shiftStack = btree.push(shiftStack, property);
671
- outerProperty = buildProperty(reference, null, shiftStack);
672
- }
673
-
674
- if (isArray && !isInitializer) {
675
- properties[name] = buildProperty(
676
- reference,
677
- null,
678
- btree.push(properties[name]?.node || btree.fromValues([]), outerProperty),
679
- );
680
- } else {
681
- properties[name] = outerProperty;
682
- }
683
-
684
- return properties;
685
- };
686
-
687
- export const shiftToProperties = (properties, property) => {
688
- let { reference, node: value } = property;
689
- let { flags, name, isArray } = reference;
690
-
691
- if (!name) throw new Error();
692
-
693
- if (Object.isFrozen(properties)) {
694
- throw new Error('not implemented');
695
- }
696
-
697
- if (!flags.expression) {
698
- return addToProperties(properties, property);
699
- }
700
- let isInitializer = value === undefined || Array.isArray(value);
701
-
702
- let outerProperty;
703
- if (flags.expression && !isInitializer) {
704
- let shiftStack =
705
- getFirstNodeShiftStack(value) ||
706
- btree.fromValues([buildProperty(reference, null, buildStubNode(buildGapTag()))]);
707
-
708
- shiftStack = btree.push(shiftStack, property);
709
- outerProperty = buildProperty(reference, null, shiftStack);
710
- }
711
-
712
- if (isArray) {
713
- properties[name] = buildProperty(
714
- reference,
715
- null,
716
- btree.replaceAt(-1, properties[name].node, outerProperty),
717
- );
718
- } else {
719
- properties[name] = outerProperty;
720
- }
721
- return properties;
722
- };
723
-
724
693
  export const addProperty = (node, property) => {
725
694
  if (!node || !property) throw new Error();
726
695
  if (!Object.isFrozen(property)) throw new Error();
727
696
 
728
- let back1 = Children.getAt(-1, node.children);
729
- let back2 = Children.getAt(-2, node.children);
730
-
731
- let referenceTag;
732
- let bindingTag = null;
733
-
734
697
  if (property.node === null) {
735
698
  throw new Error();
736
699
  }
737
700
 
738
- let { node: value } = property;
701
+ let { node: value, binding, reference } = property;
739
702
 
740
- let isArrayInitializer = isArray(value);
703
+ let isArrayInitializer = reference.type !== '_' && isArray(value);
741
704
  let isInitializer = value === undefined || isArrayInitializer;
742
705
 
743
706
  if (isArrayInitializer && value.length) throw new Error();
@@ -746,47 +709,39 @@ export const addProperty = (node, property) => {
746
709
  throw new Error();
747
710
  }
748
711
 
749
- let foundShift = false;
750
- let foundBinding = false;
751
- if (back1.type === BindingTag) {
752
- bindingTag = property.binding && back1;
753
- referenceTag = back2;
754
- foundShift = foundBinding = true;
755
- } else if (back1.type === ReferenceTag) {
756
- bindingTag = property.binding && buildChild(BindingTag, property.binding);
757
- referenceTag = back1;
758
- foundShift = true;
759
- } else {
760
- bindingTag = property.binding && buildChild(BindingTag, property.binding);
761
- referenceTag = buildChild(ReferenceTag, property.reference);
712
+ if (reference.type === '_') {
713
+ node.tags = Tags.push(node.tags, value);
714
+ return node;
762
715
  }
763
716
 
764
- if (!referenceTag) throw new Error();
717
+ let referenceTag = buildChild(ReferenceTag, reference);
718
+ let propertyWrapper;
765
719
 
766
720
  if (isInitializer) {
767
- if (property.reference.name) {
768
- addToProperties(node.properties, property);
769
- }
721
+ let tags = freeze([referenceTag, buildInitializerTag(isArrayInitializer)]);
770
722
 
771
- if (!foundShift) {
772
- node.children = Children.push(node.children, referenceTag);
773
- }
774
- node.children = Children.push(node.children, buildInitializerTag(isArrayInitializer));
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
+ ]);
775
733
 
776
- return;
734
+ propertyWrapper = buildPropertyWrapper(tags, property);
777
735
  }
778
736
 
779
- if (property.reference.name) {
780
- addToProperties(node.properties, property);
781
- }
737
+ let lastTag = Tags.getAt(-1, node.tags);
738
+ let unboundProperty = lastTag.type === PropertyWrapper && lastTag.value.tags.length < 3;
782
739
 
783
- if (!foundShift) {
784
- node.children = Children.push(node.children, referenceTag);
785
- }
786
- if (!foundBinding && bindingTag) {
787
- node.children = Children.push(node.children, bindingTag);
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));
788
744
  }
789
- node.children = Children.push(node.children, buildChild(Property, property));
790
745
 
791
746
  return node;
792
747
  };
@@ -796,12 +751,6 @@ export const shiftProperty = (node, property) => {
796
751
  if (!property.reference) throw new Error();
797
752
  if (!Object.isFrozen(property)) throw new Error();
798
753
 
799
- let back1 = Children.getAt(-1, node.children);
800
- let back2 = Children.getAt(-2, node.children);
801
-
802
- let shiftTag;
803
- let bindingTag = null;
804
-
805
754
  if (property.node === null) {
806
755
  property.node = buildNullNode();
807
756
  }
@@ -811,79 +760,62 @@ export const shiftProperty = (node, property) => {
811
760
  let isArrayInitializer = isArray(value);
812
761
  let isInitializer = value === undefined || isArrayInitializer;
813
762
 
814
- if (!isInitializer && !property.binding) {
763
+ if (isInitializer || !property.binding) {
815
764
  throw new Error();
816
765
  }
817
766
 
818
- let foundShift = false;
819
- let foundBinding = false;
820
- if (back1.type === BindingTag) {
821
- bindingTag = back1;
822
- shiftTag = back2;
823
- foundShift = foundBinding = true;
824
-
825
- if (bindingTag.value !== property.binding) throw new Error();
826
- } else if ([ShiftTag, ReferenceTag].includes(back1.type)) {
827
- bindingTag = buildChild(BindingTag, property.binding);
828
- shiftTag = back1;
829
- foundShift = true;
830
- } else {
831
- bindingTag = buildChild(BindingTag, property.binding);
832
- shiftTag = buildChild(ReferenceTag, property.reference);
767
+ let existingProperty = Tags.getAt(-1, node.tags);
768
+
769
+ if (!wrapperIsFull(existingProperty)) {
770
+ existingProperty = Tags.getAt(-2, node.tags);
833
771
  }
834
772
 
835
- if (!shiftTag) throw new Error();
773
+ let existingRef = existingProperty?.value.tags[0];
836
774
 
837
- if (isInitializer) {
838
- shiftToProperties(node.properties, property);
775
+ let bindingTag = buildChild(BindingTag, property.binding);
839
776
 
840
- if (!foundShift) {
841
- node.children = Children.push(node.children, shiftTag);
842
- }
843
- node.children = Children.push(node.children, buildInitializerTag(isArrayInitializer));
777
+ let shiftStack = existingProperty?.value.property.node.bounds[0] || BTree.fromValues([]);
778
+ let stackSize = shiftStack && property.reference.flags.expression ? BTree.getSize(shiftStack) : 0;
844
779
 
845
- return;
846
- }
780
+ let index = existingRef.type === ReferenceTag ? 1 : existingRef.value.index + 1;
781
+ let height = BTree.getSize(shiftStack) + 1;
847
782
 
848
- let shiftStack = node.properties[property.reference.name]?.node || btree.fromValues([]);
849
- let stackSize = shiftStack && property.reference.flags.expression ? btree.getSize(shiftStack) : 0;
783
+ let shiftTag = stackSize ? buildShiftTag(index, height) : existingRef;
850
784
 
851
- shiftToProperties(node.properties, property);
785
+ let tags = freeze([shiftTag, bindingTag, buildChild(Property, property)]);
852
786
 
853
- let index = shiftTag.type === ReferenceTag ? 1 : shiftTag.value.index + 1;
854
- let height = btree.getSize(shiftStack) + 1;
787
+ let lastTag = Tags.getAt(-1, node.tags);
788
+ let unboundProperty = lastTag.type === PropertyWrapper && lastTag.value.tags.length < 3;
855
789
 
856
- let newShiftTag = stackSize ? buildShiftTag(index, height) : shiftTag;
790
+ let propertyWrapper = buildPropertyWrapper(tags, property);
857
791
 
858
- if (!foundShift) {
859
- node.children = Children.push(node.children, newShiftTag);
860
- }
861
- if (!foundBinding) {
862
- node.children = Children.push(node.children, bindingTag);
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));
863
796
  }
864
- node.children = Children.push(node.children, buildChild(Property, property));
865
797
 
866
798
  return node;
867
799
  };
868
800
 
869
801
  export const add = (node, referenceTag, value, bindingTag) => {
870
- let lastChild = Children.getAt(-1, node.children);
802
+ let lastChild = Tags.getAt(-1, node.tags);
871
803
  let reference = referenceTag.value;
872
804
  let binding = bindingTag
873
805
  ? bindingTag.value
874
- : lastChild.type === BindingTag
806
+ : lastChild?.type === BindingTag
875
807
  ? lastChild.value
876
- : buildBindingTag().value;
808
+ : buildBinding();
877
809
  return addProperty(node, buildProperty(reference, binding, value));
878
810
  };
879
811
 
880
812
  export const shift = (node, referenceTag, value, bindingTag) => {
881
- let lastChild = Children.getAt(-1, node.children);
813
+ let lastChild = Tags.getAt(-1, node.tags);
882
814
  let reference = referenceTag.value;
883
815
  let binding = bindingTag
884
816
  ? bindingTag.value
885
817
  : lastChild.type === BindingTag
886
818
  ? lastChild.value
887
- : buildBindingTag().value;
819
+ : buildBinding();
888
820
  return shiftProperty(node, buildProperty(reference, binding, value));
889
821
  };