@bablr/agast-helpers 0.8.0 → 0.10.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/path.js CHANGED
@@ -1,19 +1,230 @@
1
1
  import { WeakStackFrame } from '@bablr/weak-stack';
2
- import * as btree from '@bablr/agast-helpers/btree';
3
- import * as Children from './children.js';
2
+ import * as BTree from '@bablr/agast-helpers/btree';
3
+ import * as Tags from './tags.js';
4
4
  import {
5
5
  ReferenceTag,
6
- InitializerTag,
7
- DoctypeTag,
8
- OpenNodeTag,
9
6
  CloseNodeTag,
10
7
  GapTag,
11
8
  NullTag,
12
9
  ShiftTag,
13
10
  BindingTag,
14
11
  Property,
12
+ OpenNodeTag,
13
+ AttributeDefinition,
14
+ LiteralTag,
15
+ TreeNode,
16
+ NullNode,
17
+ GapNode,
18
+ Document,
15
19
  } from './symbols.js';
16
- import { isString } from './object.js';
20
+ import {
21
+ immSet,
22
+ isPlainObject,
23
+ isString,
24
+ has as objHas,
25
+ get as objGet,
26
+ isObject,
27
+ isSymbol,
28
+ } from './object.js';
29
+ import {
30
+ buildReferenceTag,
31
+ buildChild,
32
+ buildProperty,
33
+ buildGapTag,
34
+ buildNullTag,
35
+ buildOpenNodeTag,
36
+ buildShift,
37
+ buildPropertyTag,
38
+ } from './builders.js';
39
+
40
+ export const incrementShift = (shift) => {
41
+ if (!shift) return buildShift(1, 3);
42
+
43
+ return buildShift(shift.index + 1, shift.height + 1);
44
+ };
45
+
46
+ export const finalizeNode = (node) => {
47
+ freeze(node);
48
+ freeze(node.value);
49
+
50
+ return node;
51
+ };
52
+
53
+ export const buildNode = (tags) => {
54
+ if (isArray(tags) && !Number.isFinite(tags[0])) throw new Error();
55
+ if (!isArray(tags) && !isSymbol(tags.type)) throw new Error();
56
+ let tags_ = isArray(tags) ? tags : Tags.fromValues([tags]);
57
+
58
+ if (!Number.isFinite(tags_[0])) throw new Error();
59
+ if (!isArray(tags_[1]) || !tags_[1].length) throw new Error();
60
+
61
+ let openTag = tags_[1][0];
62
+ if (![GapTag, NullTag, OpenNodeTag].includes(openTag.type)) throw new Error();
63
+ // let tags = openTag ? Tags.fromValues([openTag]) : Tags.fromValues([]);
64
+
65
+ if (openTag.type === GapTag) {
66
+ return finalizeNode({
67
+ type: GapNode,
68
+ value: {
69
+ tags: Tags.fromValues([openTag]),
70
+ },
71
+ });
72
+ } else if (openTag.type === NullTag) {
73
+ return finalizeNode({
74
+ type: NullNode,
75
+ value: { tags: Tags.fromValues([openTag]) },
76
+ });
77
+ } else if (openTag.type === OpenNodeTag) {
78
+ return finalizeNode({
79
+ type: TreeNode,
80
+ value: {
81
+ flags: openTag.value.flags,
82
+ type: openTag.value.type,
83
+ name: openTag.value.name,
84
+ attributes: openTag.value.attributes,
85
+ tags: tags_,
86
+ children: tags_[1][1] || Tags.fromValues([]),
87
+ bounds: buildBoundsFromTags(tags_),
88
+ },
89
+ });
90
+ } else {
91
+ throw new Error();
92
+ }
93
+ };
94
+
95
+ export const buildBoundsFromTags = (tags) => {
96
+ let gapProperty = buildProperty([buildReferenceTag(), [], buildNode(buildGapTag())]);
97
+ let bounds = [BTree.fromValues([gapProperty]), BTree.fromValues([gapProperty])];
98
+ let firstProperty, lastProperty;
99
+
100
+ if (Tags.getAt(-1, tags)?.type !== CloseNodeTag) {
101
+ return freeze(bounds);
102
+ }
103
+
104
+ for (let i = 1; i < Tags.getSize(tags); i++) {
105
+ let tag = Tags.getAt(i, tags);
106
+
107
+ if (tag.type === Property && !isNullNode(tag.value.node)) {
108
+ firstProperty = tag;
109
+ break;
110
+ }
111
+ }
112
+
113
+ for (let i = Tags.getSize(tags) - 2; i >= 0; i--) {
114
+ let tag = Tags.getAt(i, tags);
115
+
116
+ if (tag.type === Property && !isNullNode(tag.value.node)) {
117
+ lastProperty = tag;
118
+ break;
119
+ }
120
+ }
121
+
122
+ if (firstProperty && firstProperty.value.node.type === TreeNode) {
123
+ let openStack = firstProperty.value.node.value.bounds[0];
124
+
125
+ openStack = BTree.pop(openStack);
126
+ openStack = BTree.push(openStack, firstProperty);
127
+ openStack = BTree.concat(openStack, BTree.pop(bounds[0]));
128
+ openStack = BTree.push(
129
+ openStack,
130
+ buildPropertyTag([buildReferenceTag(), [], buildNode(buildGapTag())]),
131
+ );
132
+
133
+ bounds[0] = openStack;
134
+ }
135
+
136
+ let closeStack = bounds[1];
137
+
138
+ if (lastProperty && lastProperty.value.type === TreeNode) {
139
+ if (!lastProperty.value.node) throw new Error();
140
+
141
+ closeStack = lastProperty.value.node.value.bounds[1];
142
+
143
+ closeStack = closeStack
144
+ ? BTree.replaceAt(-1, closeStack, lastProperty)
145
+ : BTree.fromValues([lastProperty]);
146
+
147
+ closeStack = BTree.push(
148
+ closeStack,
149
+ buildPropertyTag([buildReferenceTag(), [], buildNode(buildGapTag())]),
150
+ );
151
+ bounds[1] = closeStack;
152
+ }
153
+
154
+ return freeze(bounds);
155
+ };
156
+
157
+ export const isNode = (value) => {
158
+ return [TreeNode, NullNode, GapNode].includes(value?.type);
159
+ };
160
+
161
+ export const endsNode = (tag) => {
162
+ return (
163
+ [CloseNodeTag, NullTag, GapTag].includes(tag.type) ||
164
+ (tag.type === OpenNodeTag && tag.value.selfClosing)
165
+ );
166
+ };
167
+
168
+ export const propertyIsFull = (tag) => {
169
+ if (tag.type !== Property) return true;
170
+
171
+ let { tags } = tag.value;
172
+
173
+ return tags.length === 3;
174
+ };
175
+
176
+ export const nodeIsComplete = (node) => {
177
+ let sigilTag = getOpenTag(node);
178
+ return sigilTag.type !== OpenNodeTag || sigilTag.value.selfClosing ? true : !!getCloseTag(node);
179
+ };
180
+
181
+ export const getTags = (node) => {
182
+ switch (node.type) {
183
+ case TreeNode:
184
+ return node.value.tags;
185
+ case NullNode:
186
+ return node.value.tags;
187
+ case GapNode:
188
+ return node.value.tags;
189
+ case Document:
190
+ return getTags(node.value.tree);
191
+ default:
192
+ throw new Error();
193
+ }
194
+ };
195
+
196
+ export const offsetForTag = (tag) => {
197
+ switch (tag.type) {
198
+ case ReferenceTag:
199
+ case ShiftTag:
200
+ return [0];
201
+ case BindingTag:
202
+ return [1, -1];
203
+ default:
204
+ return [];
205
+ }
206
+ };
207
+
208
+ export const isNodeTag = (tag) => {
209
+ switch (tag.type) {
210
+ case TreeNode:
211
+ case NullNode:
212
+ case GapNode:
213
+ return true;
214
+ default:
215
+ return false;
216
+ }
217
+ };
218
+
219
+ export const arraysEqual = (a, b) => {
220
+ if (a.length !== b.length) return false;
221
+
222
+ for (let i = 0; i < a.length; i++) {
223
+ if (a[i] !== b[i]) return false;
224
+ }
225
+
226
+ return true;
227
+ };
17
228
 
18
229
  export const buildPathSegment = (name, index = null, shiftIndex = null) => {
19
230
  if (!name) throw new Error();
@@ -29,90 +240,114 @@ export const buildFullPathSegment = (type, name, index = null, shiftIndex = null
29
240
  return { type, name, index, shiftIndex };
30
241
  };
31
242
 
32
- export const getRoot = (node, index = 0) => {
33
- if (node == null || !isFragmentNode(node)) {
34
- return node;
243
+ export const getRootProperty = (node) => {
244
+ if (node == null || !isCover(node)) {
245
+ return null;
35
246
  }
36
- let idx = getPropertyChildrenIndex(node, '.', null, index);
247
+ let idx = getPropertyTagsIndex(node, '_', null);
37
248
 
38
249
  if (idx == null) return null;
39
250
 
40
- let tag = Children.getAt(idx + 2, node.children);
251
+ let tag = Tags.getAt(idx, getTags(node));
41
252
 
42
253
  if (tag.type !== Property) throw new Error();
43
- return tag.value.node;
254
+ return tag;
44
255
  };
45
256
 
46
- export const getRootArray = (node) => {
47
- let arr = [];
48
- for (let i = 0; ; i++) {
49
- let root = getRoot(node, i);
50
- if (root) {
51
- arr.push(root);
52
- } else {
53
- break;
54
- }
257
+ // TODO prevent infinite recursion
258
+ export const getRoot = (node, depth = Infinity) => {
259
+ let node_ = node;
260
+ let i = 0;
261
+
262
+ while (isCover(node_) && i < depth) {
263
+ node_ = getRootProperty(node_)?.value.node;
264
+ i++;
55
265
  }
56
- return arr;
266
+
267
+ return node_;
268
+ };
269
+
270
+ export const getSigilTag = (node) => {
271
+ return Tags.getValues(getTags(node))[0];
57
272
  };
58
273
 
59
274
  export const getOpenTag = (node) => {
60
- if (!node.children.length) return null;
61
- let tag = Children.getAt(0, node.children);
62
- if (tag.type === NullTag || tag.type === GapTag) return null;
63
- if (tag.type === DoctypeTag) {
64
- tag = Children.getAt(1, node.children);
65
- }
66
- if (tag && tag.type !== OpenNodeTag) throw new Error();
67
- return tag;
275
+ let tag = Tags.getValues(getTags(node))[0];
276
+ return tag.type === OpenNodeTag ? tag : null;
68
277
  };
69
278
 
70
279
  export const getCloseTag = (node) => {
71
- const { children } = node;
72
- const tag = Children.getAt(-1, children);
73
- if (tag.type !== CloseNodeTag) return null;
74
- return tag;
280
+ return Tags.getValues(getTags(node))[2];
75
281
  };
76
282
 
77
283
  export const isNullNode = (node) => {
78
- return node && node.type === null && Children.getAt(0, node.children).type === NullTag;
284
+ return node && Tags.getAt(0, getTags(node)).type === NullTag;
285
+ };
286
+
287
+ export const isCover = (node) => {
288
+ return node?.type === TreeNode && node.value.type === Symbol.for('_');
289
+ };
290
+
291
+ export const getChildren = (node) => {
292
+ return node?.type === TreeNode ? node.value.children : [];
293
+ };
294
+
295
+ export const isFragment = (node) => {
296
+ return node?.type === TreeNode && [Symbol.for('__'), Symbol.for('_')].includes(node.value.type);
79
297
  };
80
298
 
81
- export const isFragmentNode = (node) => {
82
- return node && node.type === null && getOpenTag(node)?.value.type === null;
299
+ export const isMultiFragment = (node) => {
300
+ return node?.type === TreeNode && node.value.type === Symbol.for('__');
83
301
  };
84
302
 
85
303
  export const isGapNode = (node) => {
86
- return node && node.type === null && Children.getAt(0, node.children).type === GapTag;
304
+ return node && Tags.getAt(0, getTags(node)).type === GapTag;
87
305
  };
88
306
 
89
307
  export const isStubNode = (node) => {
90
- return (
91
- node && node.type === null && [GapTag, NullTag].includes(Children.getAt(0, node.children).type)
92
- );
308
+ return node && [GapTag, NullTag].includes(Tags.getAt(0, getTags(node)).type);
309
+ };
310
+
311
+ export const isStubTag = (tag) => {
312
+ return [GapTag, NullTag].includes(tag.type);
93
313
  };
94
314
 
95
- export const getChildPropertyIndex = (agAstNode, childrenIndex) => {
96
- let child = Children.getAt(childrenIndex, agAstNode.children);
315
+ export const getChildPropertyIndex = (agAstNode, tagsIndex) => {
316
+ let child = Tags.getAt(tagsIndex, agAstNode.value.tags);
317
+
318
+ if (child.type !== Property) return null;
97
319
 
98
- let refIndex = child.type === ShiftTag ? childrenIndex - child.value.index * 3 : childrenIndex;
320
+ let tag = child.value.tags[0];
99
321
 
100
- let stack = Children.findPath(refIndex, agAstNode.children);
322
+ let refIndex = tag.type === ShiftTag ? tagsIndex - tag.value.index : tagsIndex;
323
+
324
+ let stack = Tags.findPath(refIndex, agAstNode.value.tags);
101
325
  let { node, index: leafIdx } = stack.value;
102
- let leaf = Children.getAt(leafIdx, node);
326
+ let leaf = Tags.getAt(leafIdx, node);
327
+
328
+ if (leaf.type !== Property) {
329
+ return null;
330
+ }
103
331
 
104
- if (leaf.type !== ReferenceTag) return null;
332
+ let leafRefTag = leaf.value.tags[0];
105
333
 
106
- let { name, isArray } = leaf.value;
334
+ if (leafRefTag.type !== ReferenceTag) return null;
335
+
336
+ let { name, flags } = leafRefTag.value;
107
337
  let count = -1;
108
338
 
109
- if (!isArray) return null;
339
+ if (!name) throw new Error();
340
+
341
+ if (!flags.array) return null;
110
342
 
111
343
  for (let i = leafIdx; i >= 0; i--) {
112
- let value = Children.getAt(i, node);
344
+ let value = Tags.getAt(i, node);
345
+ if (value.type === Property) {
346
+ let firstTag = value.value.tags[0];
113
347
 
114
- if (value.type === ReferenceTag && value.value.name === name) {
115
- count++;
348
+ if (firstTag.type === ReferenceTag && firstTag.value.name === name) {
349
+ count++;
350
+ }
116
351
  }
117
352
  }
118
353
  stack = stack.pop();
@@ -123,11 +358,16 @@ export const getChildPropertyIndex = (agAstNode, childrenIndex) => {
123
358
 
124
359
  do {
125
360
  for (let i = leafIdx - 1; i >= 0; i--) {
126
- let childNode = Children.getValues(node)[i];
127
- let { references } = Children.getSums(childNode);
361
+ let value = Tags.getValues(node)[i];
128
362
 
129
- if (hasOwn(references, name)) {
130
- count += references[name];
363
+ if (Array.isArray(value)) {
364
+ let childNode = value;
365
+ let { references } = Tags.getSums(childNode);
366
+
367
+ let res;
368
+ if ((res = Tags.getAtName(name, references))) {
369
+ count += res;
370
+ }
131
371
  }
132
372
  }
133
373
  stack = stack.pop();
@@ -141,69 +381,106 @@ export const getChildPropertyIndex = (agAstNode, childrenIndex) => {
141
381
  return count - 1;
142
382
  };
143
383
 
144
- export const getPropertyChildrenIndex = (agAstNode, type, name, index) => {
145
- let firstRefIndex = __getPropertyChildrenIndex(agAstNode, type, name, 0);
146
- let nextTag = Children.getAt(firstRefIndex + 1, agAstNode.children);
147
- let initializerOffset = nextTag.type === InitializerTag ? 1 : 0;
384
+ export const getPropertyTagsIndex = (agAstNode, type, name, index, shiftIndex) => {
385
+ let firstPropsIndex = __getPropertyTagsIndex(agAstNode, type, name, 0);
386
+ let prop = firstPropsIndex == null ? null : Tags.getAt(firstPropsIndex, getTags(agAstNode));
387
+ let ref = prop?.value.reference;
388
+ let sums = Tags.getSums(agAstNode.value.tags);
389
+ let counts = name ? sums.references : sums.specialTypes;
148
390
 
149
- if (isArray) {
150
- if (index == null) {
151
- if (type) {
152
- index = btree.getSums(agAstNode.children).specialTypes[type] - 1 - initializerOffset;
153
- } else {
154
- index = btree.getSums(agAstNode.children).references[name] - 1 - initializerOffset;
155
- }
391
+ if (ref?.flags.array) {
392
+ if (index < 0) {
393
+ index = Tags.getAtName(type, counts) + index;
394
+ } else if (index == null) {
395
+ index = Tags.getAtName(name, counts) - 1;
396
+
397
+ if (index < 0) return null;
156
398
  }
157
399
  }
158
400
 
159
- return __getPropertyChildrenIndex(agAstNode, type, name, index + initializerOffset);
160
- };
401
+ let parentIndex = __getPropertyTagsIndex(agAstNode, type, name, index ?? 0);
402
+
403
+ if (parentIndex != null && ref?.flags.expression) {
404
+ let shiftIndex_ = shiftIndex == null ? -1 : shiftIndex;
405
+ if (shiftIndex_ < 0) {
406
+ let shifts = 0;
407
+ // TODO speed this up for deeply nested shifts
408
+ // algorithm: make big jump forward, then look at shift index to see if we overshot completely
409
+ // works because we know the size of the thing and a bigger thing doesn't fit in a smaller one
410
+ while (Tags.getAt(parentIndex + shifts, agAstNode.value.children)?.value.shift) {
411
+ shifts++;
412
+ }
413
+
414
+ if (-shiftIndex_ > shifts + 1) return null;
415
+
416
+ return parentIndex + shifts + shiftIndex_ + 1;
417
+ } else {
418
+ if (parentIndex + shiftIndex_ >= Tags.getSize(agAstNode.value.tags)) {
419
+ return null;
420
+ }
421
+
422
+ return parentIndex + shiftIndex_;
423
+ }
424
+ }
161
425
 
162
- export const getInitializerChildrenIndex = (agAstNode, reference) => {
163
- let { type, name } = reference.value;
164
- return __getPropertyChildrenIndex(agAstNode, type, name, 0);
426
+ return parentIndex;
165
427
  };
166
428
 
167
- const __getPropertyChildrenIndex = (agAstNode, type, name, index) => {
429
+ const __getPropertyTagsIndex = (agAstNode, type, name, index) => {
168
430
  let nameCount = 0;
169
- let node = agAstNode.children;
431
+ let node = Tags.getValues(agAstNode.value.tags)[1];
170
432
  let idx = -1;
171
433
 
172
434
  // drill into subtrees, passing over subtrees with too few references of the desired name
173
435
  outer: while (node) {
174
- let isLeaf = Children.isLeafNode(node);
175
- let sums = Children.getSums(node);
176
- let valueNameCount = type == null ? sums.references[name] : sums.specialTypes[type];
436
+ let sums = Tags.getSums(node);
437
+
438
+ if (!sums) return null;
439
+
440
+ let valueNameCount =
441
+ name != null
442
+ ? Tags.getAtName(name, sums.references)
443
+ : Tags.getAtName(type, sums.specialTypes);
177
444
 
178
445
  if (nameCount + valueNameCount < index) {
179
446
  return null;
180
447
  }
181
448
 
182
- for (const value of Children.getValues(node)) {
183
- if (isLeaf) {
449
+ for (const value of Tags.getValues(node)) {
450
+ if (!isArray(value)) {
184
451
  idx++;
185
452
  let tag = value;
186
- if (
187
- tag.type === ReferenceTag &&
188
- ((name != null && tag.value.name === name) || (type != null && tag.value.type === type))
189
- ) {
190
- nameCount += 1;
191
- if (nameCount > index) {
192
- return idx;
453
+ if (tag.type === Property) {
454
+ let { tags, reference } = tag.value;
455
+ if (tags[0].type === ReferenceTag) {
456
+ if (
457
+ (name != null && reference.name === name) ||
458
+ (type != null && reference.type === type)
459
+ ) {
460
+ nameCount += 1;
461
+ if (nameCount > index) {
462
+ return idx + 1;
463
+ }
464
+ }
193
465
  }
194
466
  }
195
467
  } else {
196
- let valueSums = Children.getSums(value);
468
+ let valueSums = Tags.getSums(value);
197
469
  if (
198
- nameCount + (type == null ? valueSums.references[name] : valueSums.specialTypes[type]) >
470
+ nameCount +
471
+ (name != null
472
+ ? Tags.getAtName(name, valueSums.references)
473
+ : Tags.getAtName(type, valueSums.specialTypes)) >
199
474
  index
200
475
  ) {
201
476
  node = value;
202
477
  continue outer;
203
478
  } else {
204
479
  nameCount +=
205
- (type == null ? valueSums.references[name] : valueSums.specialTypes[type]) ?? 0;
206
- idx += Children.getSize(value);
480
+ (name != null
481
+ ? Tags.getAtName(name, valueSums.references)
482
+ : Tags.getAtName(type, valueSums.specialTypes)) ?? 0;
483
+ idx += Tags.getSize(value);
207
484
  }
208
485
  }
209
486
  }
@@ -217,11 +494,12 @@ const __getPropertyChildrenIndex = (agAstNode, type, name, index) => {
217
494
  const { hasOwn, freeze } = Object;
218
495
  const { isArray } = Array;
219
496
 
220
- export const getOriginalFirstNode = (node, height = 1) => {
221
- for (let child of Children.traverse(node.children)) {
497
+ export const getOriginalFirstNode = (node) => {
498
+ if (node.type !== TreeNode) return;
499
+
500
+ for (let child of Tags.traverse(getTags(node))) {
222
501
  if (child.type === Property) {
223
- return getProperties(buildPathSegment(child.value.reference.name, 0, height), node.properties)
224
- .node;
502
+ return child.value.node;
225
503
  }
226
504
  }
227
505
  return null;
@@ -232,112 +510,135 @@ export const getFirstNode = (node) => {
232
510
  };
233
511
 
234
512
  export const getFirstNodeProperty = (node) => {
235
- for (let child of Children.traverse(node.children)) {
513
+ for (let child of Tags.traverse(getTags(node))) {
236
514
  if (child.type === Property) {
237
- return child.value;
515
+ let ref = child.value.reference;
516
+ if (!ref.flags.expression) {
517
+ return child;
518
+ } else {
519
+ let tagsIndex = getPropertyTagsIndex(node, ref.type, ref.name, 0, -1);
520
+ return Tags.getAt(tagsIndex, getTags(node), 2)?.value;
521
+ }
522
+ // is it shifted?
238
523
  }
239
524
  }
240
525
  return null;
241
526
  };
242
527
 
243
- export const getFirstNodeShiftStack = (node) => {
244
- for (let child of Children.traverse(node.children)) {
245
- if (child.type === Property) {
246
- let { reference } = child.value;
247
-
248
- if (!reference.flags.expression) return null;
249
-
250
- let property = node.properties[reference.name];
251
- if (reference.isArray) {
252
- property = btree.getAt(-1, property.node);
253
- }
254
- return property.node;
255
- }
256
- }
257
- return null;
528
+ export const referenceIsSingular = (ref) => {
529
+ return (ref.name && !['#', '@'].includes(ref.type)) || ref.type === '_';
258
530
  };
259
531
 
260
532
  export const referencesAreEqual = (a, b) => {
261
533
  return (
262
534
  a === b ||
263
- (a.value.name === b.value.name &&
264
- a.value.isArray === b.value.isArray &&
265
- a.value.flags.hasGap === b.value.flags.hasGap &&
266
- a.value.flags.expression === b.value.flags.expression)
535
+ (a.type === b.type &&
536
+ a.name === b.name &&
537
+ a.flags.array === b.flags.array &&
538
+ a.flags.intrinsic === b.flags.intrinsic &&
539
+ a.flags.hasGap === b.flags.hasGap &&
540
+ a.flags.expression === b.flags.expression)
267
541
  );
268
542
  };
269
543
 
270
- export const getProperties = (pathSegment, properties) => {
271
- let pathSegment_ = typeof pathSegment === 'string' ? buildPathSegment(pathSegment) : pathSegment;
272
- if (!hasOwn(pathSegment_, 'shiftIndex')) throw new Error();
273
- let { name, index, shiftIndex } = pathSegment_;
274
-
275
- if (hasOwn(properties, name)) {
276
- let prop = properties[name];
544
+ export const isDefined = (obj, key) => hasOwn(obj, key) && obj[key] !== undefined;
545
+ export const isUndefined = (obj, key) => !hasOwn(obj, key) || obj[key] === undefined;
277
546
 
278
- if (prop.reference.isArray) {
279
- prop = btree.getAt(index ?? -1, prop.node);
547
+ export function* relatedNodes(node) {
548
+ for (const child of Tags.traverse(getTags(node))) {
549
+ if (child.type === Property) {
550
+ yield child.node;
280
551
  }
552
+ }
553
+ }
281
554
 
282
- if (!prop) return null;
555
+ export const getProperty = (pathSegment, node) => {
556
+ if (!node || node.type !== TreeNode) return null;
283
557
 
284
- if (prop.reference.flags.expression) {
285
- prop = btree.getAt(shiftIndex ?? -1, prop.node);
286
- }
558
+ if (pathSegment == null) throw new Error('Bad path segment');
287
559
 
288
- return prop;
289
- }
290
- return null;
560
+ let { name, type, index, shiftIndex } =
561
+ typeof pathSegment === 'string' ? buildPathSegment(pathSegment) : pathSegment;
562
+
563
+ let propIndex = getPropertyTagsIndex(node, type, name, index, shiftIndex);
564
+
565
+ if (propIndex == null) return null;
566
+
567
+ return Tags.getAt(propIndex, getTags(node));
291
568
  };
292
569
 
293
- export const isDefined = (obj, key) => hasOwn(obj, key) && obj[key] !== undefined;
294
- export const isUndefined = (obj, key) => !hasOwn(obj, key) || obj[key] === undefined;
570
+ export const has = (path, node) => {
571
+ if (!isArray(path)) {
572
+ path = [path];
573
+ }
295
574
 
296
- export const get = (path, node) => {
297
- if (!node) throw new Error();
575
+ let pathArr = [...path];
576
+ let node_ = get(pathArr.slice(0, -1), node);
577
+ let seg = pathArr[pathArr.length - 1];
298
578
 
579
+ return !!getProperty(seg, node_);
580
+ };
581
+
582
+ export const get = (path, node) => getOr(null, path, node);
583
+
584
+ export const getOr = (defaultValue, path, node) => {
299
585
  if (path == null) throw new Error('Bad path');
586
+ if (!node) return null;
300
587
 
301
588
  if (!isArray(path)) {
302
589
  path = [path];
303
590
  }
304
591
 
305
- let node_ = node;
306
- for (let i = 0; i < path.length; i++) {
592
+ let root = getRoot(node);
593
+ let result = root || node;
594
+
595
+ let start = 0;
596
+ // TODO allow dot dot at all levels
597
+ if (path[0]?.type === '_') {
598
+ if (!root) return null;
599
+ start = 1;
600
+ }
601
+
602
+ for (let i = start; i < path.length; i++) {
307
603
  let nameOrSeg = path[i];
308
604
  let isName = typeof nameOrSeg === 'string';
309
605
  let property;
606
+ let index = path[i + 1];
310
607
 
311
- if (!isName && (!nameOrSeg || !hasOwn(nameOrSeg, 'shiftIndex'))) throw new Error('Bad path');
608
+ if (typeof index === 'number') {
609
+ i++;
610
+ } else {
611
+ index = undefined;
612
+ }
312
613
 
313
- let name = isName ? nameOrSeg : nameOrSeg.name;
614
+ if (!isName && (!nameOrSeg || !hasOwn(nameOrSeg, 'shiftIndex'))) throw new Error('Bad path');
314
615
 
315
- if (name) {
316
- let { index } = nameOrSeg;
616
+ let seg = isName ? buildPathSegment(nameOrSeg, index) : nameOrSeg;
317
617
 
318
- property = getProperties(
319
- isName ? buildPathSegment(name, index) : nameOrSeg,
320
- node_.properties,
321
- );
618
+ property = getProperty(seg, result);
322
619
 
323
- ({ node: node_ } = property || {});
324
- } else if (!isName) {
325
- let { type } = nameOrSeg;
326
- if (type !== '.') throw new Error('not implemented');
620
+ if (!property) return defaultValue;
327
621
 
328
- let refIndex = getPropertyChildrenIndex(node_, '.');
622
+ result = getRoot(property.value.node);
623
+ }
329
624
 
330
- if (refIndex == null) return null;
625
+ return isNullNode(result) ? null : result;
626
+ };
331
627
 
332
- node_ = Children.getAt(refIndex + 2, node_.children).value.node;
333
- } else {
334
- throw new Error();
335
- }
628
+ export function* list(name, node) {
629
+ let count = countList(name, node);
336
630
 
337
- if (!node_) return null;
631
+ for (let i = 0; i < count; i++) {
632
+ yield get(buildPathSegment(name, i, -1), node);
338
633
  }
634
+ }
339
635
 
340
- return node_;
636
+ export const countList = (name, node) => {
637
+ if (!node) throw new Error();
638
+
639
+ if (name == null) throw new Error('Bad path');
640
+
641
+ return Tags.getAtName(name, Tags.getSums(getTags(node)).references);
341
642
  };
342
643
 
343
644
  export function* allTagPathsFor(range, options = {}) {
@@ -353,7 +654,7 @@ export function* allTagPathsFor(range, options = {}) {
353
654
 
354
655
  while (path) {
355
656
  if (path.inner && path.previousSibling.tag.type === ReferenceTag) {
356
- path = new TagPath(path.inner, 0);
657
+ path = TagPath.from(path.inner, 0);
357
658
  }
358
659
 
359
660
  if (path.path.depth < startPath.path.depth) {
@@ -362,21 +663,17 @@ export function* allTagPathsFor(range, options = {}) {
362
663
 
363
664
  yield path;
364
665
 
365
- if (
366
- endPath &&
367
- path.childrenIndex === endPath.childrenIndex &&
368
- path.path.node === endPath.path.node
369
- ) {
666
+ if (endPath && path.tagsIndex === endPath.tagsIndex && path.path.node === endPath.path.node) {
370
667
  return;
371
668
  }
372
669
 
373
- let gapPath = path.path.parent && TagPath.from(path.path.parent, path.path.referenceIndex + 1);
670
+ let gapPath = path.path.parent && TagPath.from(path.path.parent, path.path.parentIndex, 1);
374
671
 
375
672
  if (
376
673
  endPath &&
377
674
  path.tag.type === CloseNodeTag &&
378
675
  gapPath &&
379
- gapPath.childrenIndex === endPath.childrenIndex &&
676
+ gapPath.tagsIndex === endPath.tagsIndex &&
380
677
  gapPath.path.node === endPath.path.node
381
678
  ) {
382
679
  return;
@@ -392,7 +689,7 @@ export function* allTagsFor(range, options = {}) {
392
689
  }
393
690
 
394
691
  export const buildFullRange = (node) => {
395
- let sum = Children.getSize(node.children);
692
+ let sum = Tags.getSize(getTags(node));
396
693
  return sum ? [0, sum - 1] : null;
397
694
  };
398
695
 
@@ -404,13 +701,17 @@ export function* ownTagPathsFor(range) {
404
701
 
405
702
  if (startPath.parent.node !== endPath.parent.node) throw new Error();
406
703
 
407
- let { children } = startPath.parent.node;
704
+ let { tags } = startPath.parent.node;
408
705
 
409
- for (let i = startPath.childrenIndex; i < endPath.childrenIndex; i++) {
410
- yield children[i];
706
+ for (let i = startPath.tagsIndex; i < endPath.tagsIndex; i++) {
707
+ yield tags[i];
411
708
  }
412
709
  }
413
710
 
711
+ export const buildNullNode = () => {
712
+ return buildNode(buildNullTag());
713
+ };
714
+
414
715
  const findRight = (arr, predicate) => {
415
716
  for (let i = arr.length - 1; i >= 0; i--) {
416
717
  let value = arr[i];
@@ -457,57 +758,127 @@ const skipToDepth = (depth, frame) => {
457
758
  return parent;
458
759
  };
459
760
 
761
+ export const getRange = (node) => {
762
+ const { tags } = node;
763
+ let path = Path.from(node);
764
+ return Tags.getSize(tags) ? [TagPath.from(path, 0), TagPath.from(path, -1)] : null;
765
+ };
766
+
767
+ export const getFlags = (node) => {
768
+ return node.type === TreeNode ? node.value.flags : null;
769
+ };
770
+
771
+ export const getAttributes = (node) => {
772
+ return node.type === TreeNode ? node.value.attributes : freeze({});
773
+ };
774
+
775
+ export const isSelfClosingTag = (tag) => {
776
+ return tag.type === OpenNodeTag
777
+ ? tag.value.selfClosing
778
+ : [NullTag, GapTag].includes(tag.type)
779
+ ? true
780
+ : false;
781
+ };
782
+
460
783
  export let pathForTagPath = (tagPath) => {
461
784
  let refTagPath = tagPath;
462
785
  let isShift = tagPath.tag.type === ShiftTag;
463
786
 
464
787
  if (isShift) {
465
- refTagPath = tagPath.siblingAt(tagPath.childrenIndex - tagPath.tag.value.index * 3);
788
+ refTagPath = tagPath.siblingAt(tagPath.tagsIndex - tagPath.tag.value.index);
466
789
  }
467
790
 
468
791
  let { type, name } = refTagPath.tag.value;
469
792
 
470
793
  if (refTagPath.tag.type !== ReferenceTag) throw new Error();
471
794
 
472
- let index = getChildPropertyIndex(tagPath.node, refTagPath.childrenIndex);
795
+ let index = getChildPropertyIndex(tagPath.node, refTagPath.tagsIndex);
473
796
  let shiftIndex = isShift ? (tagPath.tag.value.index ?? 0) + 1 : null;
474
797
 
475
798
  return [{ type, name, index, shiftIndex }];
476
799
  };
477
800
 
801
+ let getSigilTag_ = (tag) => {
802
+ switch (tag.type) {
803
+ case Property:
804
+ return getTags(tag.value.property.node)[1][0];
805
+ case TreeNode:
806
+ case NullNode:
807
+ case GapNode:
808
+ return getTags(tag)[1][0];
809
+ case OpenNodeTag:
810
+ case GapTag:
811
+ case NullTag:
812
+ return tag;
813
+ }
814
+ };
815
+
478
816
  export const Path = class AgastPath extends WeakStackFrame {
817
+ static create(node) {
818
+ return node && new Path(node);
819
+ }
820
+
479
821
  static from(node) {
480
- return node && this.create(node);
822
+ return node && new Path(node);
481
823
  }
482
824
 
483
- constructor(parent, node, referenceIndex = null) {
484
- super(parent);
825
+ static fromTag(openTag) {
826
+ return Path.from(buildNode(openTag));
827
+ }
828
+
829
+ static set(node, path, value) {
830
+ return Path.from(node).replaceAt(path, value).node;
831
+ }
832
+
833
+ static get(node, path) {
834
+ return Path.from(node).get(path).node;
835
+ }
836
+
837
+ constructor(nodeOrParent, parentIndex = null, isGap = false) {
838
+ let isRoot = !(nodeOrParent instanceof Path);
839
+ let parent = isRoot ? null : nodeOrParent;
840
+ let node = isRoot ? nodeOrParent : parent.getChild(parentIndex - 1).value.node;
841
+
842
+ if (isGap) {
843
+ ({ node } = BTree.getAt(-1, node.value.bounds[0]).value);
844
+ }
485
845
 
486
- if (!(hasOwn(node, 'type') && hasOwn(node, 'properties'))) throw new Error();
846
+ if (!node || ![TreeNode, NullNode, GapNode].includes(node.type)) throw new Error();
487
847
 
488
- if (parent && referenceIndex == null) throw new Error();
489
848
  if (!node) throw new Error();
849
+ if (!Object.isFrozen(node)) throw new Error();
490
850
  if (isArray(node)) throw new Error();
491
851
 
852
+ super(parent);
853
+
492
854
  this.node = node;
493
- this.referenceIndex = referenceIndex; // in the parent
855
+ this.parentIndex = parentIndex;
856
+ this.isGap = isGap;
857
+
858
+ this.name = node.type === TreeNode ? node.value.name : null;
859
+ this.type = node.type === TreeNode ? node.value.type : null;
860
+ this.flags = node.type === TreeNode ? node.value.flags : null;
861
+ this.attributes = node.type === TreeNode ? node.value.attributes : null;
862
+ this.tags = getTags(node);
494
863
 
495
- let referenceTag =
496
- referenceIndex != null && Children.getAt(referenceIndex, parent.node.children);
864
+ let parentTag = parentIndex == null ? null : Tags.getAt(parentIndex, getTags(parent.node));
497
865
 
498
- if (referenceTag && ![ReferenceTag, ShiftTag].includes(referenceTag.type)) {
866
+ if (parentTag && parentTag.type !== Property) {
499
867
  throw new Error();
500
868
  }
501
869
 
502
- if (parent && (!this.reference || ![ReferenceTag, ShiftTag].includes(this.reference.type))) {
870
+ if (
871
+ parentIndex != null &&
872
+ (!this.referenceTag || ![ReferenceTag, ShiftTag].includes(this.referenceTag.type))
873
+ ) {
503
874
  throw new Error();
504
875
  }
505
876
 
506
877
  // if (
507
878
  // parent &&
508
- // (['#', '@'].includes(this.reference.value.type)
509
- // ? parent.tagPathAt(referenceIndex + 1).tag.value
510
- // : get(pathForTagPath(parent.tagPathAt(referenceIndex)), parent.node)) !== node
879
+ // (['#', '@'].includes(this.referenceTag.value.type)
880
+ // ? parent.tagPathAt(parentIndex, 1).tag.value
881
+ // : get(pathForTagPath(parent.tagPathAt(parentIndex)), parent.node)) !== node
511
882
  // ) {
512
883
  // throw new Error('Path not reachable');
513
884
  // }
@@ -519,240 +890,1042 @@ export const Path = class AgastPath extends WeakStackFrame {
519
890
  freeze(this);
520
891
  }
521
892
 
893
+ getChild(childrenIndex) {
894
+ let { node } = this;
895
+ if (isStubNode(node)) return null;
896
+ return Tags.getAt(childrenIndex, getChildren(node)) || null;
897
+ }
898
+
899
+ getSigilTagPath() {
900
+ return TagPath.from(this, 0);
901
+ }
902
+
903
+ tagPathAt(tagsIndex, propertyIndex) {
904
+ return TagPath.from(this, tagsIndex, propertyIndex);
905
+ }
906
+
907
+ getPropertyTagPath(childrenIndex) {
908
+ let tagPath = this.tagPathAt(childrenIndex + 1);
909
+ return tagPath && tagPath.tag.type === Property ? tagPath : null;
910
+ }
911
+
912
+ getReferenceTagPath(childrenIndex) {
913
+ return this.tagPathAt(childrenIndex + 1, [0]);
914
+ }
915
+
916
+ // getBindings(childrenIndex) {
917
+ // let property = this.getPropertyTagPath(childrenIndex);
918
+ // return property && Tags.getAt([1], property.value.tags);
919
+ // }
920
+
921
+ getBindingTagPath(childrenIndex, bindingIndex) {
922
+ return this.tagPathAt(childrenIndex + 1, [1, bindingIndex]);
923
+ }
924
+
925
+ getNodeTagPath(childrenIndex) {
926
+ return this.tagPathAt(childrenIndex + 1, [2]);
927
+ }
928
+
929
+ getHashTagPath(childrenIndex) {
930
+ return this.tagPathAt(childrenIndex + 1, [3]);
931
+ }
932
+
522
933
  get openTagPath() {
523
934
  let tagPath = TagPath.from(this, 0);
524
- if (tagPath.tag.type === DoctypeTag) {
525
- tagPath = tagPath.nextSibling;
526
- }
527
- return tagPath;
935
+ return tagPath.tag.type === OpenNodeTag ? tagPath : null;
936
+ }
937
+
938
+ get closeTagPath() {
939
+ let tagPath = TagPath.from(this, -1);
940
+ return tagPath.tag.type === CloseNodeTag ? tagPath : null;
528
941
  }
529
942
 
530
943
  get openTag() {
531
944
  return this.openTagPath.tag;
532
945
  }
533
946
 
534
- get closeTagPath() {
535
- let tagPath = TagPath.from(this, -1);
536
- if (tagPath.tag.type !== CloseNodeTag) return null;
537
- return tagPath;
947
+ get open() {
948
+ return this.openTag.value;
538
949
  }
539
950
 
540
951
  get closeTag() {
541
952
  return this.closeTagPath.tag;
542
953
  }
543
954
 
544
- tagPathAt(idx) {
545
- return TagPath.from(this, idx);
955
+ get bindings() {
956
+ return this.parentPropertyPath?.tag.value.bindings;
957
+ }
958
+
959
+ get bindingTags() {
960
+ return this.parentPropertyPath?.tag.value.tags[1];
546
961
  }
547
962
 
548
963
  get reference() {
549
- return (
550
- (this.parent?.node ?? null) && Children.getAt(this.referenceIndex, this.parent.node.children)
551
- );
964
+ return this.referenceTag?.value ?? null;
552
965
  }
553
966
 
554
- get referenceTagPath() {
555
- return (this.parent?.node ?? null) && new TagPath(this.parent, this.referenceIndex);
967
+ get referenceTag() {
968
+ return this.referenceTagPath?.tag ?? null;
556
969
  }
557
970
 
558
- get(path) {
559
- let path_ = path;
560
- if (typeof path_ === 'string') {
561
- path_ = [path_];
971
+ get referenceTagPath() {
972
+ if (!this.parent?.node) {
973
+ return null;
562
974
  }
563
- if (!isArray(path_)) throw new Error();
564
-
565
- let pathInst = this;
566
- for (let i = 0; i < path_.length; i++) {
567
- let nameOrSeg = path_[i];
568
-
569
- if (isString(nameOrSeg)) {
570
- nameOrSeg = { type: null, name: nameOrSeg, index: null, shiftIndex: null };
571
- }
975
+ return TagPath.from(this.parent, this.parentIndex, 0).referenceTagPath;
976
+ }
572
977
 
573
- let property = getProperties(nameOrSeg, pathInst.node.properties);
978
+ get propertyKeyTagPath() {
979
+ if (!this.parent?.node) {
980
+ return null;
981
+ }
982
+ return TagPath.from(this.parent, this.parentIndex, 0);
983
+ }
574
984
 
575
- if (!property) return null;
985
+ get parentPropertyPath() {
986
+ if (!this.parent?.node || this.parentIndex == null) {
987
+ return null;
988
+ }
989
+ let path = TagPath.from(this.parent, this.parentIndex);
990
+ if (path.tag.type === ShiftTag) {
991
+ path = TagPath.from(this.parent, this.parentIndex - path.tag.value.index);
992
+ }
993
+ return path;
994
+ }
576
995
 
577
- let { node, reference: ref } = property;
996
+ get parentProperty() {
997
+ return this.parentPropertyPath?.tag ?? null;
998
+ }
578
999
 
579
- let shiftIndex_ = nameOrSeg.shiftIndex;
1000
+ get parentNode() {
1001
+ return this.parent.node;
1002
+ }
580
1003
 
581
- if (shiftIndex_ && shiftIndex_ < 0) {
582
- shiftIndex_ = btree.getSize(node) + shiftIndex_;
583
- } else if (shiftIndex_ == null && ref.flags.expression) {
584
- shiftIndex_ = 0;
1004
+ get firstPropertyTagPath() {
1005
+ let tagPath = this.tagPathAt(0);
1006
+ while (tagPath && (!tagPath.propertyPath || isNullNode(tagPath.propertyPath.tag.value.node))) {
1007
+ tagPath = tagPath.nextSibling;
1008
+ if (tagPath?.propertyPath) {
1009
+ tagPath = tagPath.propertyPath;
585
1010
  }
586
-
587
- let shiftOffset = shiftIndex_ ? (shiftIndex_ - 1) * 3 : 0;
588
-
589
- pathInst = pathInst.push(
590
- node,
591
- getPropertyChildrenIndex(pathInst.node, nameOrSeg.type, nameOrSeg.name, nameOrSeg.index) +
592
- shiftOffset,
593
- );
1011
+ }
1012
+ return tagPath;
1013
+ }
1014
+
1015
+ get lastPropertyTagPath() {
1016
+ let tagPath = this.tagPathAt(-1);
1017
+ while (tagPath && (!tagPath.propertyPath || isNullNode(tagPath.propertyPath.tag.value.node))) {
1018
+ tagPath = tagPath.previousSibling;
1019
+ if (tagPath?.propertyPath) {
1020
+ tagPath = tagPath.propertyPath;
1021
+ }
1022
+ }
1023
+ return tagPath;
1024
+ }
1025
+
1026
+ get coverBoundary() {
1027
+ let path = this;
1028
+
1029
+ if (path.parent && !isFragment(this.node) && isCover(path.parent.node)) {
1030
+ path = path.parent;
1031
+ }
1032
+
1033
+ while (path && isCover(path.node) && path.parent && isCover(path.parent.node)) {
1034
+ path = path.parent;
1035
+ }
1036
+
1037
+ return path;
1038
+ }
1039
+
1040
+ get(path) {
1041
+ let path_ = typeof path === 'string' ? [path] : [...path];
1042
+ let pathInst = this;
1043
+
1044
+ let skippedRootFragment = false;
1045
+
1046
+ if (!(path.length && isObject(path[1]) && path[0].type === '_')) {
1047
+ while (!pathInst.node.value.name && pathInst.node.value.type === Symbol.for('_')) {
1048
+ if (pathInst.depth) throw new Error();
1049
+ skippedRootFragment = true;
1050
+ pathInst = pathInst.push(getPropertyTagsIndex(pathInst.node, '_', null, 0));
1051
+ }
1052
+ }
1053
+
1054
+ for (let i = 0; i < path_.length; i++) {
1055
+ let nameOrSeg = path_[i];
1056
+
1057
+ let seg = nameOrSeg;
1058
+ if (isString(nameOrSeg)) {
1059
+ let index = path_[i + 1];
1060
+ if (typeof index === 'number') {
1061
+ i++;
1062
+ } else {
1063
+ index = undefined;
1064
+ }
1065
+ seg = buildPathSegment(nameOrSeg, index);
1066
+ }
1067
+
1068
+ if (i === 0 && seg.type === '_' && skippedRootFragment) continue;
1069
+
1070
+ let tagsIndex = getPropertyTagsIndex(
1071
+ pathInst.node,
1072
+ seg.type,
1073
+ seg.name,
1074
+ seg.index,
1075
+ seg.shiftIndex,
1076
+ );
1077
+
1078
+ if (tagsIndex == null) return null;
1079
+
1080
+ pathInst = pathInst.push(tagsIndex);
594
1081
  }
595
1082
 
596
1083
  return pathInst;
597
1084
  }
598
1085
 
1086
+ replaceWith(node, bindingTags) {
1087
+ let bindings = bindingTags?.map((tag) => tag.value); // TODO improve here
1088
+ if (bindings != null && !isArray(bindings)) throw new Error();
1089
+ if (!isPlainObject(node)) throw new Error();
1090
+
1091
+ if (!node) throw new Error();
1092
+
1093
+ if (!this.parent) {
1094
+ return Path.from(node);
1095
+ }
1096
+
1097
+ let built = node;
1098
+ let path = this.parent;
1099
+ let replacementPath = [];
1100
+
1101
+ let targetShift_ = this.parent?.parentProperty?.value.tags[0];
1102
+ let targetShift = targetShift_ && (targetShift_.type === ReferenceTag ? null : targetShift_);
1103
+ let targetReference = this.reference;
1104
+ let targetBindings = bindings == null ? this.bindings : bindings;
1105
+ let targetParentIndex = this.parentIndex;
1106
+ let held = null;
1107
+ let heldCount = 0;
1108
+ let value;
1109
+
1110
+ while (path) {
1111
+ value = built;
1112
+ built = buildNode(getTags(path.node));
1113
+
1114
+ let { tags, shift } = Tags.getAt(targetParentIndex, getTags(path.node)).value;
1115
+
1116
+ let firstTag = tags[0];
1117
+ let newFirstTag = firstTag;
1118
+ if (shift) {
1119
+ newFirstTag = buildChild(ReferenceTag, targetReference);
1120
+ }
1121
+
1122
+ let tags_ = [
1123
+ firstTag,
1124
+ targetBindings.map((binding) => buildChild(BindingTag, binding)),
1125
+ value,
1126
+ ];
1127
+
1128
+ if (!held && !shift) {
1129
+ built = buildNode(
1130
+ Tags.replaceAt(
1131
+ targetParentIndex,
1132
+ built.value.tags,
1133
+ buildChild(Property, buildProperty(tags_)),
1134
+ ),
1135
+ );
1136
+ replacementPath.push(targetParentIndex);
1137
+ } else {
1138
+ let i = targetParentIndex + 1;
1139
+ let nextProperty = Tags.getAt(i, built.value.tags);
1140
+
1141
+ let nextShift = nextProperty?.type === Property && nextProperty.value.shift;
1142
+
1143
+ if (nextShift) {
1144
+ let { reference, bindings } = BTree.getAt(0, nextProperty.value.node.value.bounds[0]);
1145
+
1146
+ path = path.push(i);
1147
+
1148
+ built = buildNode(
1149
+ Tags.replaceAt(
1150
+ targetParentIndex - shift.index,
1151
+ built.value.tags,
1152
+ buildChild(Property, buildProperty(tags_)),
1153
+ ),
1154
+ );
1155
+
1156
+ targetParentIndex -= shift.index;
1157
+ for (let i = shift.index - 1; i >= 0; i--) {
1158
+ built = buildNode(Tags.removeAt(targetParentIndex + 1, built.value.tags));
1159
+ }
1160
+
1161
+ tags_ = [newFirstTag, tags_[1], tags_[2]];
1162
+
1163
+ targetShift = null;
1164
+ targetReference = reference;
1165
+ targetBindings = bindings;
1166
+ targetParentIndex = getPropertyTagsIndex(
1167
+ nextProperty.value.node,
1168
+ reference.type,
1169
+ reference.name,
1170
+ 0,
1171
+ 0,
1172
+ );
1173
+ held = null;
1174
+
1175
+ replacementPath.push(targetParentIndex);
1176
+ continue;
1177
+ } else {
1178
+ built = buildNode(
1179
+ Tags.replaceAt(
1180
+ targetParentIndex,
1181
+ getTags(built),
1182
+ buildChild(Property, buildProperty(tags_, shift)),
1183
+ ),
1184
+ );
1185
+ replacementPath.push(targetParentIndex);
1186
+ }
1187
+ }
1188
+
1189
+ held = targetShift ? value : null;
1190
+ heldCount = held ? heldCount + 1 : 0;
1191
+
1192
+ targetShift_ = path?.parentProperty?.value.tags[0];
1193
+ targetShift = targetShift_ && (targetShift_.type === ReferenceTag ? null : targetShift_);
1194
+ targetReference = path.reference;
1195
+ targetBindings = path.bindings;
1196
+ targetParentIndex = path.parentIndex;
1197
+ path = path.parent;
1198
+ }
1199
+
1200
+ return this.mapOnto(built);
1201
+ }
1202
+
1203
+ replaceAt(path, node, bindings) {
1204
+ return this.get(path).replaceWith(node, bindings).atDepth(0);
1205
+ }
1206
+
1207
+ mapOnto(rootNode) {
1208
+ let path = Path.from(rootNode);
1209
+
1210
+ for (let i = 1; i <= this.depth; i++) {
1211
+ let { parentIndex, isGap } = this.atDepth(i);
1212
+ path = path.push(parentIndex, isGap);
1213
+ }
1214
+
1215
+ return path;
1216
+ }
1217
+
599
1218
  atDepth(depth) {
600
1219
  return skipToDepth(depth, this);
601
1220
  }
1221
+
1222
+ get rootNode() {
1223
+ return this.atDepth(0).node;
1224
+ }
1225
+
1226
+ get done() {
1227
+ let { parent, node } = this;
1228
+ let sigilTag = getSigilTag(node);
1229
+
1230
+ return !parent && sigilTag && !!(isSelfClosingTag(sigilTag) || getCloseTag(node));
1231
+ }
1232
+
1233
+ get held() {
1234
+ let tagPath = this.tagPathAt(-1);
1235
+
1236
+ // TODO is this safe
1237
+ if (tagPath.tag.type !== Property) return null;
1238
+
1239
+ let { tags } = tagPath.tag.value;
1240
+
1241
+ if (tags[0]?.type === ShiftTag && !tags[2]) {
1242
+ return TagPath.from(tagPath.path, tagPath.tagsIndex - 1).tag.value;
1243
+ }
1244
+
1245
+ // if we're not at the left side, we aren't held
1246
+ if (
1247
+ !tagPath.equalTo(this.firstPropertyTagPath) ||
1248
+ propertyIsFull(tagPath.tag) ||
1249
+ (tagPath.inner && nodeIsComplete(tagPath.inner.node))
1250
+ ) {
1251
+ return null;
1252
+ }
1253
+
1254
+ let coverParent = this;
1255
+
1256
+ for (;;) {
1257
+ let refPath = coverParent.propertyKeyTagPath;
1258
+
1259
+ if (refPath?.tag.type === ShiftTag) {
1260
+ return TagPath.from(refPath.path, refPath.tagsIndex - 1).tag.value;
1261
+ }
1262
+
1263
+ coverParent = coverParent.parent;
1264
+ if (coverParent?.node.type !== Symbol.for('_')) break;
1265
+ }
1266
+
1267
+ return null;
1268
+ }
1269
+
1270
+ advance(tag) {
1271
+ if (!tag) throw new Error();
1272
+
1273
+ let tagPath = this.tagPathAt(-1, 2);
1274
+
1275
+ if (tagPath?.tag.type === Property ? tagPath?.nextSibling : tagPath?.next) throw new Error();
1276
+
1277
+ if (this.done) throw new Error();
1278
+
1279
+ let resultPath = this;
1280
+
1281
+ switch (tag.type) {
1282
+ case BindingTag: {
1283
+ if (!this.lastPropertyTagPath) {
1284
+ resultPath = this.advanceUnwrapped(buildReferenceTag()).path;
1285
+ }
1286
+ break;
1287
+ }
1288
+
1289
+ case TreeNode:
1290
+ case NullNode:
1291
+ case GapNode:
1292
+ case NullTag:
1293
+ case GapTag:
1294
+ case OpenNodeTag: {
1295
+ let sigilTag = getSigilTag_(tag);
1296
+
1297
+ if (sigilTag.type === OpenNodeTag && sigilTag.value.type === Symbol.for('__')) {
1298
+ break;
1299
+ }
1300
+
1301
+ let lastTagPath = this.lastPropertyTagPath;
1302
+ let newProperty = !lastTagPath || propertyIsFull(lastTagPath.tag);
1303
+ let reference = lastTagPath?.referenceTagPath.tag.value;
1304
+
1305
+ // TODO revisit this condition
1306
+ if (!getFlags(this.node)?.token || reference?.type === '@') {
1307
+ if (newProperty || !lastTagPath.tag.value.tags.length) {
1308
+ resultPath = this.advanceUnwrapped(buildReferenceTag()).path;
1309
+ }
1310
+ }
1311
+
1312
+ break;
1313
+ }
1314
+ }
1315
+
1316
+ if (tag.type === Property) {
1317
+ resultPath = Path.from(buildNode(Tags.push(getTags(resultPath.node), tag)));
1318
+ } else {
1319
+ resultPath = resultPath.advanceUnwrapped(tag).path;
1320
+ }
1321
+
1322
+ if (
1323
+ (tag.type === CloseNodeTag || (tag.type === OpenNodeTag && tag.value.selfClosing)) &&
1324
+ resultPath.depth
1325
+ ) {
1326
+ return resultPath.parent;
1327
+ }
1328
+
1329
+ return resultPath;
1330
+ }
1331
+
1332
+ advanceUnwrapped(tag) {
1333
+ if (!tag) throw new Error();
1334
+
1335
+ let tagPath = this.tagPathAt(-1, [-1, -1]);
1336
+
1337
+ if (
1338
+ [TreeNode, NullNode, GapNode].includes(tagPath?.tag.type)
1339
+ ? tagPath?.nextSibling
1340
+ : tagPath?.next
1341
+ )
1342
+ throw new Error();
1343
+
1344
+ if (this.done) throw new Error();
1345
+
1346
+ let targetPath = tagPath && endsNode(tagPath.tag) ? this.parent : this;
1347
+
1348
+ switch (tag.type) {
1349
+ case ReferenceTag: {
1350
+ let { type, name, flags } = tag.value;
1351
+
1352
+ if ([ReferenceTag, ShiftTag, BindingTag].includes(targetPath.tagPathAt(-1, -1)?.tag.type))
1353
+ throw new Error('invalid location for reference');
1354
+ if (!name && !type) throw new Error();
1355
+
1356
+ if (flags.hasGap && flags.intrinsic) throw new Error();
1357
+ if (type && !['_', '.', '#', '@'].includes(type)) throw new Error();
1358
+ if (flags.array && ['_', '#', '@'].includes(type)) throw new Error();
1359
+ if (getFlags(targetPath.node).token && type !== '@') throw new Error();
1360
+ if (targetPath.open.type === Symbol.for('_') && name != null) throw new Error();
1361
+ if (targetPath.open.type === Symbol.for('_') && !['_', '#'].includes(type))
1362
+ throw new Error();
1363
+
1364
+ if (tag.value.name) {
1365
+ let property = getProperty(tag.value.name, targetPath.node);
1366
+ if (
1367
+ getOr(null, tag.value.name, targetPath.node) &&
1368
+ !property.value.shift &&
1369
+ !referencesAreEqual(tag.value, property.value.reference)
1370
+ ) {
1371
+ throw new Error('mismatched references');
1372
+ }
1373
+ } else if (tag.value.type === '_') {
1374
+ let rootIdx = getPropertyTagsIndex(targetPath.node, '_', null, 0);
1375
+
1376
+ if (
1377
+ rootIdx != null &&
1378
+ !referencesAreEqual(
1379
+ tag.value,
1380
+ Tags.getAt(rootIdx, getTags(targetPath.node)).value.property.reference,
1381
+ )
1382
+ ) {
1383
+ throw new Error('mismatched references');
1384
+ }
1385
+ }
1386
+
1387
+ let property = buildChild(Property, buildProperty([tag]));
1388
+
1389
+ targetPath = targetPath.replaceWith(
1390
+ buildNode(Tags.push(getTags(targetPath.node), property)),
1391
+ );
1392
+ break;
1393
+ }
1394
+
1395
+ case BindingTag: {
1396
+ if (![ReferenceTag, ShiftTag].includes(this.tagPathAt(-1, -1).tag.type)) {
1397
+ throw new Error('Invalid location for BindingTag');
1398
+ }
1399
+ if (!tag.value.segments) throw new Error();
1400
+
1401
+ let refPath = this.tagPathAt(-1, 0);
1402
+ let propPath = TagPath.from(refPath.path, refPath.tagsIndex);
1403
+
1404
+ let { shift } = propPath.tag.value;
1405
+
1406
+ if (![ReferenceTag, ShiftTag].includes(refPath.tag.type)) throw new Error();
1407
+
1408
+ if (refPath.tag.type === ShiftTag) {
1409
+ refPath = TagPath.from(refPath.path, refPath.tagsIndex - shift.index, 0);
1410
+ }
1411
+
1412
+ let { 0: firstTag, 1: bindingTags = [] } = propPath.tag.value.tags;
1413
+ let tags = [firstTag, [...bindingTags, tag]];
1414
+ let property = buildChild(Property, buildProperty(tags, shift));
1415
+
1416
+ targetPath = this.replaceWith(
1417
+ buildNode(Tags.replaceAt(propPath.tagsIndex, getTags(this.node), property)),
1418
+ );
1419
+ break;
1420
+ }
1421
+
1422
+ case OpenNodeTag: {
1423
+ let { literalValue, flags, type } = tag.value;
1424
+
1425
+ if (this.held && flags.token) throw new Error();
1426
+
1427
+ if (type === Symbol.for('__')) {
1428
+ throw new Error('not implemented');
1429
+ }
1430
+
1431
+ let refTag = this.node && Tags.getAt(-1, getChildren(this.node)).value.tags[0];
1432
+
1433
+ if (![ReferenceTag, ShiftTag].includes(refTag.type)) throw new Error();
1434
+
1435
+ let node = buildNode(tag);
1436
+
1437
+ if (literalValue && !flags.token) {
1438
+ throw new Error();
1439
+ }
1440
+
1441
+ targetPath = this.advanceUnwrapped(node).path;
1442
+
1443
+ targetPath = targetPath.tagPathAt(-1).inner;
1444
+
1445
+ break;
1446
+ }
1447
+
1448
+ case GapTag:
1449
+ case NullTag: {
1450
+ // if (getSigilTag(this.node)) throw new Error();
1451
+ if (this.tagPathAt(-1, 0).tag.type !== ReferenceTag) {
1452
+ throw new Error('Invalid location for NullTag');
1453
+ }
1454
+
1455
+ let node = buildNode(tag);
1456
+
1457
+ targetPath = this.advanceUnwrapped(node).path;
1458
+ // targetPath = targetPath.tagPathAt(-1).inner; // ?
1459
+
1460
+ break;
1461
+ }
1462
+
1463
+ case CloseNodeTag: {
1464
+ let { node } = targetPath;
1465
+
1466
+ let lastChild = targetPath.getChild(-1);
1467
+
1468
+ if (lastChild?.type === Property && !propertyIsFull(lastChild)) throw new Error();
1469
+
1470
+ let openTag = getOpenTag(node);
1471
+
1472
+ if (!openTag) throw new Error();
1473
+ if (openTag.value.selfClosing) throw new Error();
1474
+
1475
+ targetPath = targetPath.replaceWith(buildNode(Tags.push(getTags(node), tag)));
1476
+ break;
1477
+ }
1478
+
1479
+ case NullNode:
1480
+ case GapNode:
1481
+ case TreeNode: {
1482
+ let node = tag;
1483
+ let parentPath = targetPath;
1484
+ let { node: parentNode } = parentPath;
1485
+
1486
+ let tagPath = TagPath.from(parentPath, -1);
1487
+
1488
+ if (isMultiFragment(node)) {
1489
+ let existingProperty = tagPath;
1490
+
1491
+ while (existingProperty?.tag.type === AttributeDefinition) {
1492
+ existingProperty = existingProperty.previousSibling.propertyPath;
1493
+ }
1494
+
1495
+ if (
1496
+ existingProperty.tag.type !== OpenNodeTag &&
1497
+ existingProperty.tag &&
1498
+ !propertyIsFull(existingProperty.tag)
1499
+ ) {
1500
+ throw new Error();
1501
+ }
1502
+
1503
+ let newPath = Path.from(buildNode(this.node.value.tags));
1504
+
1505
+ for (let tag of Tags.traverse(node.value.children)) {
1506
+ if (tag.type === Property) {
1507
+ newPath = newPath.advance(tag);
1508
+ } else {
1509
+ newPath = newPath.advanceUnwrapped(tag).path.atDepth(0);
1510
+ }
1511
+ }
1512
+
1513
+ targetPath = this.replaceWith(newPath.node);
1514
+ break;
1515
+ }
1516
+
1517
+ let shift = tagPath.tag.type === Property ? tagPath.tag.value.shift : undefined;
1518
+ let { held } = this;
1519
+
1520
+ if (held) {
1521
+ // let heldProperty = this.tagPathAt(-2, 2).tag.value;
1522
+ let matchesHeld = isGapNode(node);
1523
+ if (getRoot(node)) {
1524
+ matchesHeld =
1525
+ matchesHeld ||
1526
+ isGapNode(node) ||
1527
+ held.node === node ||
1528
+ held.node === (getOriginalFirstNode(getRoot(node)) || held.node);
1529
+ if (!matchesHeld) {
1530
+ // TODO We're passing by the node that shifted if it's a cover!
1531
+ throw new Error();
1532
+ }
1533
+ }
1534
+ }
1535
+
1536
+ if (propertyIsFull(tagPath.tag)) {
1537
+ let tags = [null, [], node];
1538
+ let property = buildChild(Property, buildProperty(tags, shift));
1539
+
1540
+ targetPath = this.replaceWith(buildNode(Tags.push(parentNode.value.tags, property)));
1541
+ } else {
1542
+ let { 0: firstTag, 1: bindingTags = [] } = tagPath.tag.value.tags;
1543
+ let tags = [firstTag, bindingTags, node];
1544
+ let property = buildChild(Property, buildProperty(tags, shift));
1545
+
1546
+ targetPath = this.replaceWith(
1547
+ buildNode(Tags.replaceAt(tagPath.tagsIndex, parentNode.value.tags, property)),
1548
+ );
1549
+ }
1550
+
1551
+ break;
1552
+ }
1553
+
1554
+ case AttributeDefinition: {
1555
+ if (this.held) throw new Error('invalid place for an attribute binding');
1556
+
1557
+ let lastChild = this.getChild(-1);
1558
+
1559
+ if (lastChild?.type === Property && !propertyIsFull(lastChild)) throw new Error();
1560
+
1561
+ // add undefined attributes from value
1562
+ let { node } = this;
1563
+ if (tag.type !== AttributeDefinition) throw new Error();
1564
+
1565
+ let { path, value } = tag.value;
1566
+ let openTag = getOpenTag(node);
1567
+ let { attributes } = node.value;
1568
+
1569
+ if (!objHas(attributes, path) && objGet(attributes, path) !== undefined)
1570
+ throw new Error('Can only define undefined attributes');
1571
+
1572
+ if (value === undefined) throw new Error('cannot define attribute to undefined');
1573
+
1574
+ let { flags, name } = openTag.value;
1575
+ attributes = immSet(attributes, path, value);
1576
+ let newOpenTag = buildOpenNodeTag(flags, name, null, attributes);
1577
+
1578
+ targetPath = this.replaceWith(
1579
+ buildNode(Tags.push(Tags.replaceAt(0, getTags(node), newOpenTag), tag)),
1580
+ );
1581
+ break;
1582
+ }
1583
+
1584
+ case ShiftTag: {
1585
+ let lastPropertyTag = this.tagPathAt(-1).tag;
1586
+
1587
+ if (lastPropertyTag.type !== Property) throw new Error();
1588
+
1589
+ let { reference, shift: heldShift } = lastPropertyTag.value;
1590
+
1591
+ if (!reference) {
1592
+ ({ reference } = this.tagPathAt(-1 - (heldShift?.index ?? 0)).tag.value);
1593
+ }
1594
+
1595
+ if (!reference.flags.expression && reference.type !== '_') throw new Error();
1596
+
1597
+ let shift = heldShift
1598
+ ? buildShift(heldShift.index + 1, heldShift.height + 1)
1599
+ : buildShift(1, 3);
1600
+
1601
+ let property = buildChild(Property, buildProperty([tag], shift));
1602
+
1603
+ targetPath = this.replaceWith(buildNode(Tags.push(this.node.value.tags, property)));
1604
+ break;
1605
+ }
1606
+
1607
+ case LiteralTag:
1608
+ if (typeof tag.value !== 'string') throw new Error();
1609
+
1610
+ targetPath = this.replaceWith(buildNode(Tags.push(this.node.value.tags, tag)));
1611
+ break;
1612
+
1613
+ default:
1614
+ throw new Error();
1615
+ }
1616
+
1617
+ return targetPath && TagPath.from(targetPath, -1, isNodeTag(tag) ? -1 : offsetForTag(tag));
1618
+ }
602
1619
  };
603
1620
 
1621
+ Object.freeze(Path.prototype);
1622
+
604
1623
  export const tagPathsAreEqual = (a, b) => {
605
1624
  if (a == null || b == null) return b == a;
606
- return a.path.node === b.path.node && a.childrenIndex === b.childrenIndex;
1625
+ return a.path.node === b.path.node && a.tagsIndex === b.tagsIndex;
607
1626
  };
608
1627
 
609
1628
  export class TagPath {
610
- constructor(path, childrenIndex) {
611
- if (path == null || childrenIndex == null) throw new Error();
1629
+ static from(path, tagsIndex, propertyIndex) {
1630
+ let tags = getTags(path.node);
1631
+ let size = Tags.getSize(tags);
1632
+ let index = Number.isFinite(tagsIndex) && tagsIndex < 0 ? size + tagsIndex : tagsIndex;
1633
+
1634
+ let propertyIndex_ = typeof propertyIndex === 'number' ? [propertyIndex] : propertyIndex;
1635
+
1636
+ let propTag = Tags.getAt(index, tags);
1637
+
1638
+ if (
1639
+ [OpenNodeTag, CloseNodeTag, LiteralTag, AttributeDefinition, GapTag, NullTag].includes(
1640
+ propTag?.type,
1641
+ )
1642
+ ) {
1643
+ propertyIndex_ = [];
1644
+ }
1645
+
1646
+ let resolvedPropertyIndex =
1647
+ propTag && (propertyIndex_?.length ? Tags.findPath(propertyIndex_, propTag.value.tags) : []);
1648
+
1649
+ return resolvedPropertyIndex && new TagPath(path, index, resolvedPropertyIndex);
1650
+ }
1651
+
1652
+ static fromNode(node, tagsIndex, propertyIndex) {
1653
+ return TagPath.from(Path.from(node), tagsIndex, propertyIndex);
1654
+ }
1655
+
1656
+ static fromTag(openTag) {
1657
+ return TagPath.fromNode(buildNode(openTag), 0);
1658
+ }
1659
+
1660
+ constructor(path, tagsIndex, propertyIndex) {
1661
+ if (path == null) throw new Error();
1662
+ if (path.tag) throw new Error();
1663
+ if (!Number.isFinite(tagsIndex) || tagsIndex < 0) throw new Error();
1664
+ if (!isArray(propertyIndex)) throw new Error();
1665
+
1666
+ let propertyIndex_ = Object.freeze([...propertyIndex]);
612
1667
 
613
- let tag = Children.getAt(childrenIndex, path.node.children);
1668
+ let tag = Tags.getAt(tagsIndex, getTags(path.node));
614
1669
 
615
- if (tag == null) throw new Error();
1670
+ if (propertyIndex_.length) {
1671
+ tag = Tags.getAt(propertyIndex_, tag.value.tags);
1672
+ }
1673
+
1674
+ if (tag == null || isArray(tag)) throw new Error();
1675
+
1676
+ for (let segment of propertyIndex_) {
1677
+ if (!isPlainObject(segment) || !(segment.index === 0 || segment.index > 0)) throw new Error();
1678
+ }
616
1679
 
617
1680
  this.path = path;
1681
+ this.tagsIndex = tagsIndex;
1682
+ this.propertyIndex = propertyIndex_;
1683
+
618
1684
  this.node = path.node;
619
1685
  this.tag = tag;
620
- this.childrenIndex = childrenIndex;
1686
+
1687
+ if (tag.type === Property && propertyIndex_.length) throw new Error();
621
1688
 
622
1689
  freeze(this);
623
1690
  }
624
1691
 
625
- static from(path, childrenIndex) {
626
- let size = Children.getSize(path.node.children);
627
- let index = childrenIndex < 0 ? size + childrenIndex : childrenIndex;
628
-
629
- return index >= 0 && index < size ? new TagPath(path, index) : null;
1692
+ get child() {
1693
+ return this.tag;
630
1694
  }
631
1695
 
632
- static fromNode(node, childrenIndex) {
633
- return TagPath.from(Path.from(node), childrenIndex);
1696
+ get parentNode() {
1697
+ return this.path.parentNode;
634
1698
  }
635
1699
 
636
- get child() {
637
- return this.tag;
1700
+ get depth() {
1701
+ return this.path.depth;
638
1702
  }
639
1703
 
640
1704
  siblingAt(index) {
641
1705
  return TagPath.from(this.path, index);
642
1706
  }
643
1707
 
1708
+ mapOnto(rootNode) {
1709
+ let path = this.path.mapOnto(rootNode);
1710
+
1711
+ return TagPath.from(path, this.tagsIndex, this.propertyIndex);
1712
+ }
1713
+
1714
+ get referenceTagPath() {
1715
+ let path = TagPath.from(this.path, this.tagsIndex, 0);
1716
+ if (path && path.propertyPath) {
1717
+ let { shift } = path.propertyPath.tag.value;
1718
+ if (shift) {
1719
+ path = TagPath.from(this.path, this.tagsIndex - shift.index, 0);
1720
+ }
1721
+ }
1722
+ return path?.tag.type === ReferenceTag ? path : null;
1723
+ }
1724
+
1725
+ get nextProperty() {
1726
+ let { path, tagsIndex } = this;
1727
+
1728
+ let nextChild = TagPath.from(path, tagsIndex + 1);
1729
+
1730
+ return nextChild?.tag.type === Property ? nextChild : null;
1731
+ }
1732
+
644
1733
  get nextSibling() {
645
- const { path, childrenIndex } = this;
1734
+ let { path, tagsIndex, propertyIndex } = this;
1735
+
1736
+ let propertyIndex0 = propertyIndex[0]?.index;
646
1737
 
647
- const child =
648
- childrenIndex + 1 >= Children.getSize(path.node.children)
649
- ? null
650
- : Children.getAt(childrenIndex + 1, path.node.children);
1738
+ let nextChildIndex = tagsIndex === 0 ? 0 : tagsIndex - 1 + 1;
1739
+ let nextChild = path.getChild(nextChildIndex);
651
1740
 
652
- return child && new TagPath(path, childrenIndex + 1);
1741
+ if (propertyIndex0 == null && !nextChild) {
1742
+ return this.tag.type === CloseNodeTag ? null : path.closeTagPath;
1743
+ }
1744
+
1745
+ switch (propertyIndex0) {
1746
+ case 0:
1747
+ case 1: {
1748
+ let childIndex = tagsIndex - 1;
1749
+ let bindingIndex = propertyIndex[1]?.index ?? -1;
1750
+ let binding = path.getBindingTagPath(childIndex, bindingIndex + 1);
1751
+ if (binding) return binding;
1752
+ return path.getNodeTagPath(childIndex);
1753
+ }
1754
+ case 2:
1755
+ default: {
1756
+ if (!nextChild || nextChild.type !== Property) {
1757
+ return path.tagPathAt(tagsIndex + 1);
1758
+ } else {
1759
+ let ref = path.getReferenceTagPath(nextChildIndex);
1760
+ if (ref) return ref;
1761
+ let binding = path.getBindingTagPath(nextChildIndex, 0);
1762
+ if (binding) return binding;
1763
+ return path.getNodeTagPath(nextChildIndex);
1764
+ }
1765
+ }
1766
+ }
1767
+ }
1768
+
1769
+ get previousSibling() {
1770
+ let { path, tagsIndex, propertyIndex } = this;
1771
+
1772
+ let propertyIndex0 = propertyIndex[0]?.index;
1773
+
1774
+ if (!tagsIndex) return null;
1775
+
1776
+ let prevChildIndex = tagsIndex === 0 ? 0 : tagsIndex - 1 - 1;
1777
+ let prevChild = path.getChild(prevChildIndex);
1778
+
1779
+ if (!prevChild) {
1780
+ return path.getOpenTagPath();
1781
+ }
1782
+
1783
+ if (propertyIndex0) {
1784
+ if (propertyIndex0 > 0) {
1785
+ let property = path.getChild(tagsIndex - 1);
1786
+ let bindingIndex = propertyIndex[1]?.index ?? property.value.bindings.length;
1787
+ let binding = path.getBindingTagPath(tagsIndex - 1, bindingIndex - 1);
1788
+ if (binding) return binding;
1789
+ }
1790
+
1791
+ let ref = path.getReferenceTagPath(tagsIndex - 1);
1792
+ if (ref) return ref;
1793
+ }
1794
+
1795
+ return prevChild && prevChild.type === Property
1796
+ ? path.getNodeTagPath(tagsIndex - 1 - 1)
1797
+ : prevChild;
653
1798
  }
654
1799
 
655
1800
  get next() {
656
- let { path, childrenIndex } = this;
1801
+ let { path, tagsIndex, propertyIndex } = this;
1802
+
1803
+ propertyIndex = [...propertyIndex];
657
1804
 
658
1805
  let leaving = false;
659
1806
 
660
1807
  for (;;) {
661
- let prevTag = Children.getAt(childrenIndex - 1, path.node.children);
662
- let tag = Children.getAt(childrenIndex, path.node.children);
663
- let isInitialTag = path.node === this.path.node && childrenIndex === this.childrenIndex;
664
- let wasLeaving = leaving;
1808
+ let tag = Tags.getAt(tagsIndex, getTags(path.node));
1809
+ let propTag = tag;
1810
+ let lastRef = null;
665
1811
  leaving = false;
666
1812
 
667
1813
  if (!tag) return null;
668
1814
 
669
- // done
670
- if (!isInitialTag && tag.type !== Property) {
671
- return new TagPath(path, childrenIndex);
672
- }
673
-
674
- // in
675
- if (tag.type === Property && !wasLeaving) {
676
- // jump to gap tag?
677
- if (
678
- path.parent &&
679
- tag.value.reference.flags.expression &&
680
- (childrenIndex === 3 ||
681
- (childrenIndex === 5 &&
682
- Children.getAt(2, path.node.children).type === InitializerTag)) &&
683
- Children.getAt(path.referenceIndex, path.parent.node.children)?.type === ShiftTag
684
- ) {
685
- let { reference: ref } = tag.value;
686
- let shiftStack = path.node.properties[ref.name].node;
687
-
688
- if (ref.isArray) {
689
- shiftStack = btree.getAt(-1, shiftStack).node;
690
- }
691
- let gapNode = btree.getAt(0, shiftStack).node;
1815
+ if (tag.type === Property) {
1816
+ if (propertyIndex.length === 0 && !leaving) {
1817
+ propertyIndex.push({ index: 0, node: tag.value.tags });
1818
+ }
692
1819
 
693
- path = new Path(path, gapNode, childrenIndex - 2);
694
- childrenIndex = 0;
695
- continue;
1820
+ if (propertyIndex != null) {
1821
+ propTag = Tags.getAt(propertyIndex, tag.value.tags);
1822
+ lastRef = Tags.getAt(0, tag.value.tags);
696
1823
  }
1824
+ }
697
1825
 
698
- path = path.push(tag.value.node, childrenIndex - 2);
699
- childrenIndex = 0;
1826
+ if (isArray(propTag)) {
1827
+ if (propTag.length) {
1828
+ // enter bindings array
1829
+ propertyIndex.push({ index: 0, node: propTag });
1830
+ propTag = propTag[propertyIndex[1].index];
1831
+ } else {
1832
+ propertyIndex = [{ index: propertyIndex[0].index + 1, node: tag }];
1833
+ }
700
1834
  continue;
701
1835
  }
702
1836
 
1837
+ let isInitialTag =
1838
+ path.node === this.path.node &&
1839
+ tagsIndex === this.tagsIndex &&
1840
+ arraysEqual(propertyIndex, this.propertyIndex);
1841
+
1842
+ // done
1843
+ if (!isInitialTag && !isNodeTag(propTag)) {
1844
+ return TagPath.from(path, tagsIndex, propertyIndex);
1845
+ }
1846
+
703
1847
  // shift
704
- if (tag.type === BindingTag && prevTag.type === ShiftTag) {
705
- let refIndex = childrenIndex - 1 - prevTag.value.index * 3;
1848
+ if (propTag.type === BindingTag && lastRef.type === ShiftTag) {
1849
+ let lastProp = Tags.getAt(tagsIndex, path.tags);
1850
+ let { shift } = lastProp.value;
1851
+ let refIndex = tagsIndex - shift.index;
706
1852
  if (refIndex < 0) throw new Error();
707
- let refTag = Children.getAt(refIndex, path.node.children);
708
-
709
- const { name, isArray } = refTag.value;
710
- let pathDesc = buildPathSegment(name);
711
- let index;
712
- if (isArray) {
713
- index = getChildPropertyIndex(path.node, refIndex);
714
- pathDesc = buildPathSegment(name, index);
715
- }
1853
+ let refTag = Tags.getAt(refIndex, path.tags, 0);
716
1854
 
717
- // HMM
718
- pathDesc.shiftIndex = prevTag.value.height;
1855
+ if (refTag.type !== ReferenceTag) throw new Error();
719
1856
 
720
- if (index !== -1) {
721
- path = path.get([pathDesc]);
1857
+ let { node } = tag.value;
1858
+ if (!node) return null;
722
1859
 
723
- if (!path) return null;
1860
+ path = new Path(path, tagsIndex);
724
1861
 
725
- childrenIndex = 0;
726
- continue;
727
- }
1862
+ if (!path) return null;
1863
+
1864
+ tagsIndex = 0;
1865
+ propertyIndex = [];
1866
+ continue;
728
1867
  }
729
1868
 
1869
+ let { nextSibling } = this;
1870
+
730
1871
  // over
731
- if (path.node && childrenIndex + 1 < Children.getSize(path.node.children)) {
732
- childrenIndex++;
733
- continue;
1872
+ if (nextSibling) {
1873
+ ({ tagsIndex, propertyIndex } = nextSibling);
1874
+ // in
1875
+ if (isNodeTag(nextSibling.tag)) {
1876
+ let shiftPath = path;
1877
+
1878
+ while (
1879
+ shiftPath.parent &&
1880
+ !Tags.getAt(shiftPath.parentIndex - 1, shiftPath.parent.node.value.children).value.shift
1881
+ ) {
1882
+ shiftPath = shiftPath.parent;
1883
+ }
1884
+
1885
+ let isGap =
1886
+ path.parent &&
1887
+ tagsIndex === 1 &&
1888
+ shiftPath.parent &&
1889
+ Tags.getAt(shiftPath.parentIndex - 1, shiftPath.parent.node.value.children).value
1890
+ .shift &&
1891
+ tag.value.node ===
1892
+ Tags.getAt(shiftPath.parentIndex - 2, shiftPath.parent.node.value.children).value
1893
+ .node;
1894
+
1895
+ path = new Path(path, tagsIndex, isGap);
1896
+ tagsIndex = 0;
1897
+ propertyIndex = [];
1898
+ continue;
1899
+ } else {
1900
+ return nextSibling;
1901
+ }
734
1902
  }
735
1903
 
736
1904
  // out
737
- if (path.referenceIndex != null && path.parent) {
1905
+ if (path.parentIndex != null && path.parent) {
738
1906
  do {
739
1907
  if (
740
- path.parent &&
741
- Children.getAt(path.referenceIndex, path.parent.node.children)?.type === ShiftTag
1908
+ path.coverBoundary.parent &&
1909
+ Tags.getAt(
1910
+ [path.coverBoundary.parentIndex, 0],
1911
+ path.coverBoundary.parent.node.value.tags,
1912
+ )?.type === ShiftTag
742
1913
  ) {
743
- childrenIndex =
744
- Children.getSize(path.parent.node.children) > path.referenceIndex + 2
745
- ? path.referenceIndex + 2
1914
+ // while
1915
+ tagsIndex =
1916
+ Tags.getSize(path.parent.node.value.tags) > path.parentIndex + 1
1917
+ ? path.parentIndex + 1
746
1918
  : null;
747
1919
  } else {
748
- childrenIndex = path.referenceIndex + 2;
1920
+ tagsIndex = path.parentIndex + 1;
749
1921
  }
750
1922
 
1923
+ propertyIndex = [];
751
1924
  path = path.parent;
752
1925
  leaving = true;
753
1926
 
754
1927
  if (!path) return null;
755
- } while (childrenIndex == null);
1928
+ } while (tagsIndex == null);
756
1929
 
757
1930
  leaving = true;
758
1931
  continue;
@@ -763,124 +1936,91 @@ export class TagPath {
763
1936
  }
764
1937
 
765
1938
  get nextUnshifted() {
766
- let { path, childrenIndex } = this;
1939
+ let { path, tagsIndex, propertyIndex } = this;
1940
+
1941
+ propertyIndex = [...propertyIndex];
767
1942
 
768
1943
  let leaving = false;
769
1944
 
770
1945
  for (;;) {
771
- let tag = Children.getAt(childrenIndex, path.node.children);
772
- let isInitialTag = path.node === this.path.node && childrenIndex === this.childrenIndex;
1946
+ let tag = Tags.getAt(tagsIndex, getTags(path.node));
1947
+ let propTag = tag;
773
1948
  let wasLeaving = leaving;
774
1949
  leaving = false;
775
1950
 
776
1951
  if (!tag) return null;
777
1952
 
778
- if (tag.type === ReferenceTag && tag.value.flags.expression) {
779
- // look forward for another reference
780
- let offset = 1;
781
- let tag_ = tag;
782
- do {
783
- tag_ = Children.getAt(childrenIndex + offset, path.node.children);
784
- offset++;
785
-
786
- if (tag_?.type === InitializerTag) break;
787
- } while (tag_ && [BindingTag, ShiftTag].includes(tag_.type));
788
- if (!tag_) {
789
- return null;
1953
+ if (tag.type === Property) {
1954
+ if (propertyIndex.length === 0 && !leaving) {
1955
+ propertyIndex.push({ index: 0, node: tag.value.tags });
790
1956
  }
791
- }
792
1957
 
793
- // done
794
- if (!isInitialTag && tag.type !== Property) {
795
- return new TagPath(path, childrenIndex);
796
- }
797
-
798
- // in
799
- if (
800
- tag.type === Property &&
801
- !wasLeaving &&
802
- !(Children.getAt(childrenIndex + 1, path.node.children)?.type === ShiftTag)
803
- ) {
804
- // TODO use properties to skip over a lot of tag walking
805
- path = path.push(tag.value.node, childrenIndex - 2);
806
- childrenIndex = 0;
807
- continue;
1958
+ if (propertyIndex != null) {
1959
+ propTag = Tags.getAt(propertyIndex, tag.value.tags);
1960
+ }
808
1961
  }
809
1962
 
810
- // over
811
- if (path.node && childrenIndex + 1 < Children.getSize(path.node.children)) {
812
- childrenIndex++;
1963
+ if (isArray(propTag)) {
1964
+ if (propTag.length) {
1965
+ // enter bindings array
1966
+ propertyIndex.push({ index: 0, node: propTag });
1967
+ propTag = propTag[propertyIndex[1].index];
1968
+ } else {
1969
+ propertyIndex = [{ index: propertyIndex[0].index + 1, node: tag }];
1970
+ }
813
1971
  continue;
814
- }
815
-
816
- // out
817
- if (path.referenceIndex != null) {
818
- do {
819
- childrenIndex = path.referenceIndex + 2;
820
-
821
- path = path.parent;
822
- leaving = true;
823
- } while (childrenIndex == null);
824
-
825
- leaving = true;
1972
+ } else if (isNodeTag(propTag) && !leaving) {
1973
+ path = path.push(tagsIndex);
1974
+ tagsIndex = 0;
1975
+ propertyIndex = [];
826
1976
  continue;
827
1977
  }
828
1978
 
829
- return null;
830
- }
831
- }
832
-
833
- get previousSibling() {
834
- const { path, childrenIndex } = this;
835
-
836
- const child = childrenIndex - 1 < 0 ? null : btree.getAt(childrenIndex - 1, path.node.children);
837
-
838
- return child && new TagPath(path, childrenIndex - 1);
839
- }
840
-
841
- get previous() {
842
- throw new Error('not implemented');
843
- }
1979
+ let isInitialTag =
1980
+ path.node === this.path.node &&
1981
+ tagsIndex === this.tagsIndex &&
1982
+ arraysEqual(propertyIndex, this.propertyIndex);
844
1983
 
845
- get previousUnshifted() {
846
- let { path, childrenIndex } = this;
1984
+ if (!wasLeaving && propTag.type === ReferenceTag && propTag.value.flags.expression) {
1985
+ // move past shifts
847
1986
 
848
- let leaving = false;
1987
+ let shifts = 0;
1988
+ let shiftedTag = Tags.getAt(tagsIndex + 1, getTags(path.node));
849
1989
 
850
- for (;;) {
851
- let tag = Children.getAt(childrenIndex, path.node.children);
852
- let isInitialTag = path.node === this.path.node && childrenIndex === this.childrenIndex;
853
- let wasLeaving = leaving;
854
- leaving = false;
1990
+ while (shiftedTag?.type === Property && shiftedTag.value.shift) {
1991
+ shifts++;
1992
+ propertyIndex = [{ index: 1, node: shiftedTag.value.tags }];
1993
+ shiftedTag = Tags.getAt(tagsIndex + 1 + shifts, path.tags);
1994
+ }
855
1995
 
856
- if (!tag) return null;
1996
+ if (shifts) {
1997
+ tagsIndex += shifts;
1998
+ continue;
1999
+ }
2000
+ }
857
2001
 
858
2002
  // done
859
- if (!isInitialTag && tag.type !== ShiftTag && tag.type !== Property) {
860
- return new TagPath(path, childrenIndex);
2003
+ if (!isInitialTag && !isNodeTag(propTag) && !isArray(propTag)) {
2004
+ return TagPath.from(path, tagsIndex, propertyIndex);
861
2005
  }
862
2006
 
863
- // in
864
- if (tag.type === Property && !wasLeaving) {
865
- path = path.push(tag.value.node, childrenIndex - 1);
866
- childrenIndex = Children.getSize(tag.value.children) - 1;
867
- continue;
868
- }
2007
+ let { nextSibling } = TagPath.from(path, tagsIndex, propertyIndex);
869
2008
 
870
2009
  // over
871
- if (path.node && childrenIndex + 1 < Children.getSize(path.node.children)) {
872
- childrenIndex--;
2010
+ if (nextSibling) {
2011
+ ({ tagsIndex, propertyIndex } = nextSibling);
873
2012
  continue;
874
2013
  }
875
2014
 
876
2015
  // out
877
- if (path.referenceIndex != null) {
2016
+ if (path.parentIndex != null) {
878
2017
  do {
879
- childrenIndex = path.referenceIndex;
2018
+ tagsIndex = path.parentIndex + 1;
880
2019
 
881
2020
  path = path.parent;
882
2021
  leaving = true;
883
- } while (childrenIndex == null);
2022
+ if (!path) return null;
2023
+ } while (tagsIndex == null);
884
2024
 
885
2025
  leaving = true;
886
2026
  continue;
@@ -890,40 +2030,52 @@ export class TagPath {
890
2030
  }
891
2031
  }
892
2032
 
893
- get innerNode() {
894
- return this.inner?.node;
2033
+ get previous() {
2034
+ throw new Error('not implemented');
895
2035
  }
896
2036
 
897
- get inner() {
898
- let { tag, previousSibling } = this;
2037
+ get previousUnshifted() {
2038
+ throw new Error('not implemented');
2039
+ }
899
2040
 
900
- let refPath = previousSibling?.previousSibling;
2041
+ get propertyPath() {
2042
+ let { path, tagsIndex } = this;
901
2043
 
902
- if (tag.type !== Property) {
903
- return null;
904
- }
2044
+ let tag = Tags.getAt(tagsIndex, getTags(path.node));
905
2045
 
906
- let shiftPath = null;
907
- if (refPath.tag.type === ShiftTag) {
908
- shiftPath = refPath;
909
- refPath = TagPath.from(this.path, this.childrenIndex - refPath.tag.value.index * 3 - 2);
910
- }
2046
+ return tag.type === Property ? path.tagPathAt(tagsIndex) : null;
2047
+ }
911
2048
 
912
- if (refPath.tag.type !== ReferenceTag) throw new Error();
2049
+ get innerNode() {
2050
+ return this.inner?.node;
2051
+ }
913
2052
 
914
- const { type, name } = refPath.tag.value;
2053
+ get inner() {
2054
+ let { tagsIndex, tag } = this;
915
2055
 
916
- return this.path.get([
917
- buildFullPathSegment(
918
- type,
919
- name,
920
- getChildPropertyIndex(refPath.path.node, refPath.childrenIndex),
921
- shiftPath?.tag.value.index,
922
- ),
923
- ]);
2056
+ let node;
2057
+ switch (tag.type) {
2058
+ case Property: {
2059
+ ({ node } = tag.value);
2060
+ break;
2061
+ }
2062
+ case TreeNode: {
2063
+ node = tag;
2064
+ break;
2065
+ }
2066
+ default:
2067
+ throw new Error();
2068
+ }
2069
+ return node && this.path.push(tagsIndex);
924
2070
  }
925
2071
 
926
2072
  equalTo(tagPath) {
927
- return this.node === tagPath.node && this.childrenIndex === tagPath.childrenIndex;
2073
+ return (
2074
+ this.node === tagPath.node &&
2075
+ this.tagsIndex === tagPath.tagsIndex &&
2076
+ arraysEqual(this.propertyIndex, tagPath.propertyIndex)
2077
+ );
928
2078
  }
929
2079
  }
2080
+
2081
+ Object.freeze(TagPath.prototype);