@bablr/agast-helpers 0.7.0 → 0.8.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,31 +1,67 @@
1
1
  import { WeakStackFrame } from '@bablr/weak-stack';
2
2
  import * as btree from '@bablr/agast-helpers/btree';
3
- import * as sumtree from '@bablr/agast-helpers/sumtree';
3
+ import * as Children from './children.js';
4
4
  import {
5
5
  ReferenceTag,
6
6
  InitializerTag,
7
- EmbeddedNode,
8
7
  DoctypeTag,
9
8
  OpenNodeTag,
10
9
  CloseNodeTag,
11
10
  GapTag,
12
11
  NullTag,
13
12
  ShiftTag,
13
+ BindingTag,
14
+ Property,
14
15
  } from './symbols.js';
15
- import {
16
- buildInitializerTag,
17
- buildEmbeddedNode,
18
- buildGapTag,
19
- buildReferenceTag,
20
- buildShiftTag,
21
- } from './builders.js';
16
+ import { isString } from './object.js';
17
+
18
+ export const buildPathSegment = (name, index = null, shiftIndex = null) => {
19
+ if (!name) throw new Error();
20
+ return { type: null, name, index, shiftIndex };
21
+ };
22
+
23
+ export const buildTypePathSegment = (type, index = null, shiftIndex = null) => {
24
+ if (!type) throw new Error();
25
+ return { type, name: null, index, shiftIndex };
26
+ };
27
+
28
+ export const buildFullPathSegment = (type, name, index = null, shiftIndex = null) => {
29
+ return { type, name, index, shiftIndex };
30
+ };
31
+
32
+ export const getRoot = (node, index = 0) => {
33
+ if (node == null || !isFragmentNode(node)) {
34
+ return node;
35
+ }
36
+ let idx = getPropertyChildrenIndex(node, '.', null, index);
37
+
38
+ if (idx == null) return null;
39
+
40
+ let tag = Children.getAt(idx + 2, node.children);
41
+
42
+ if (tag.type !== Property) throw new Error();
43
+ return tag.value.node;
44
+ };
45
+
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
+ }
55
+ }
56
+ return arr;
57
+ };
22
58
 
23
59
  export const getOpenTag = (node) => {
24
60
  if (!node.children.length) return null;
25
- let tag = sumtree.getAt(0, node.children);
61
+ let tag = Children.getAt(0, node.children);
26
62
  if (tag.type === NullTag || tag.type === GapTag) return null;
27
63
  if (tag.type === DoctypeTag) {
28
- tag = sumtree.getAt(1, node.children);
64
+ tag = Children.getAt(1, node.children);
29
65
  }
30
66
  if (tag && tag.type !== OpenNodeTag) throw new Error();
31
67
  return tag;
@@ -33,13 +69,13 @@ export const getOpenTag = (node) => {
33
69
 
34
70
  export const getCloseTag = (node) => {
35
71
  const { children } = node;
36
- const tag = sumtree.getAt(-1, children);
72
+ const tag = Children.getAt(-1, children);
37
73
  if (tag.type !== CloseNodeTag) return null;
38
74
  return tag;
39
75
  };
40
76
 
41
77
  export const isNullNode = (node) => {
42
- return node && node.type === null && sumtree.getAt(0, node.children).type === NullTag;
78
+ return node && node.type === null && Children.getAt(0, node.children).type === NullTag;
43
79
  };
44
80
 
45
81
  export const isFragmentNode = (node) => {
@@ -47,13 +83,23 @@ export const isFragmentNode = (node) => {
47
83
  };
48
84
 
49
85
  export const isGapNode = (node) => {
50
- return node && node.type === null && sumtree.getAt(0, node.children).type === GapTag;
86
+ return node && node.type === null && Children.getAt(0, node.children).type === GapTag;
87
+ };
88
+
89
+ export const isStubNode = (node) => {
90
+ return (
91
+ node && node.type === null && [GapTag, NullTag].includes(Children.getAt(0, node.children).type)
92
+ );
51
93
  };
52
94
 
53
95
  export const getChildPropertyIndex = (agAstNode, childrenIndex) => {
54
- let stack = sumtree.findPath(childrenIndex, agAstNode.children);
96
+ let child = Children.getAt(childrenIndex, agAstNode.children);
97
+
98
+ let refIndex = child.type === ShiftTag ? childrenIndex - child.value.index * 3 : childrenIndex;
99
+
100
+ let stack = Children.findPath(refIndex, agAstNode.children);
55
101
  let { node, index: leafIdx } = stack.value;
56
- let leaf = sumtree.getAt(leafIdx, node);
102
+ let leaf = Children.getAt(leafIdx, node);
57
103
 
58
104
  if (leaf.type !== ReferenceTag) return null;
59
105
 
@@ -63,7 +109,7 @@ export const getChildPropertyIndex = (agAstNode, childrenIndex) => {
63
109
  if (!isArray) return null;
64
110
 
65
111
  for (let i = leafIdx; i >= 0; i--) {
66
- let value = sumtree.getAt(i, node);
112
+ let value = Children.getAt(i, node);
67
113
 
68
114
  if (value.type === ReferenceTag && value.value.name === name) {
69
115
  count++;
@@ -77,8 +123,8 @@ export const getChildPropertyIndex = (agAstNode, childrenIndex) => {
77
123
 
78
124
  do {
79
125
  for (let i = leafIdx - 1; i >= 0; i--) {
80
- let childNode = sumtree.getValues(node)[i];
81
- let { references } = sumtree.getSums(childNode);
126
+ let childNode = Children.getValues(node)[i];
127
+ let { references } = Children.getSums(childNode);
82
128
 
83
129
  if (hasOwn(references, name)) {
84
130
  count += references[name];
@@ -95,61 +141,69 @@ export const getChildPropertyIndex = (agAstNode, childrenIndex) => {
95
141
  return count - 1;
96
142
  };
97
143
 
98
- export const getPropertyChildrenIndex = (agAstNode, reference) => {
99
- let { index, name } = reference.value;
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;
148
+
100
149
  if (isArray) {
101
150
  if (index == null) {
102
- index = btree.getSize(agAstNode.properties[name]) - 1;
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
+ }
103
156
  }
104
157
  }
105
158
 
106
- let firstRefIndex = __getPropertyChildrenIndex(agAstNode, name, 0);
107
- let nextTag = sumtree.getAt(firstRefIndex + 1, agAstNode.children);
108
-
109
- return __getPropertyChildrenIndex(
110
- agAstNode,
111
- name,
112
- index + (nextTag.type === InitializerTag ? 1 : 0),
113
- );
159
+ return __getPropertyChildrenIndex(agAstNode, type, name, index + initializerOffset);
114
160
  };
115
161
 
116
162
  export const getInitializerChildrenIndex = (agAstNode, reference) => {
117
- return __getPropertyChildrenIndex(agAstNode, reference.value.name, 0);
163
+ let { type, name } = reference.value;
164
+ return __getPropertyChildrenIndex(agAstNode, type, name, 0);
118
165
  };
119
166
 
120
- const __getPropertyChildrenIndex = (agAstNode, name, index) => {
167
+ const __getPropertyChildrenIndex = (agAstNode, type, name, index) => {
121
168
  let nameCount = 0;
122
169
  let node = agAstNode.children;
123
170
  let idx = -1;
124
171
 
125
172
  // drill into subtrees, passing over subtrees with too few references of the desired name
126
173
  outer: while (node) {
127
- let isLeaf = sumtree.isLeafNode(node);
128
- let sums = sumtree.getSums(node);
129
- let valueNameCount = sums.references[name];
174
+ let isLeaf = Children.isLeafNode(node);
175
+ let sums = Children.getSums(node);
176
+ let valueNameCount = type == null ? sums.references[name] : sums.specialTypes[type];
130
177
 
131
178
  if (nameCount + valueNameCount < index) {
132
179
  return null;
133
180
  }
134
181
 
135
- for (const value of sumtree.getValues(node)) {
182
+ for (const value of Children.getValues(node)) {
136
183
  if (isLeaf) {
137
184
  idx++;
138
185
  let tag = value;
139
- if (tag.type === ReferenceTag && tag.value.name === name) {
186
+ if (
187
+ tag.type === ReferenceTag &&
188
+ ((name != null && tag.value.name === name) || (type != null && tag.value.type === type))
189
+ ) {
140
190
  nameCount += 1;
141
191
  if (nameCount > index) {
142
192
  return idx;
143
193
  }
144
194
  }
145
195
  } else {
146
- let valueSums = sumtree.getSums(value);
147
- if (nameCount + valueSums.references[name] > index) {
196
+ let valueSums = Children.getSums(value);
197
+ if (
198
+ nameCount + (type == null ? valueSums.references[name] : valueSums.specialTypes[type]) >
199
+ index
200
+ ) {
148
201
  node = value;
149
202
  continue outer;
150
203
  } else {
151
- nameCount += valueSums.references[name] ?? 0;
152
- idx += sumtree.getSize(value);
204
+ nameCount +=
205
+ (type == null ? valueSums.references[name] : valueSums.specialTypes[type]) ?? 0;
206
+ idx += Children.getSize(value);
153
207
  }
154
208
  }
155
209
  }
@@ -160,9 +214,49 @@ const __getPropertyChildrenIndex = (agAstNode, name, index) => {
160
214
  return null;
161
215
  };
162
216
 
163
- const { hasOwn } = Object;
217
+ const { hasOwn, freeze } = Object;
164
218
  const { isArray } = Array;
165
219
 
220
+ export const getOriginalFirstNode = (node, height = 1) => {
221
+ for (let child of Children.traverse(node.children)) {
222
+ if (child.type === Property) {
223
+ return getProperties(buildPathSegment(child.value.reference.name, 0, height), node.properties)
224
+ .node;
225
+ }
226
+ }
227
+ return null;
228
+ };
229
+
230
+ export const getFirstNode = (node) => {
231
+ return getFirstNodeProperty(node)?.node;
232
+ };
233
+
234
+ export const getFirstNodeProperty = (node) => {
235
+ for (let child of Children.traverse(node.children)) {
236
+ if (child.type === Property) {
237
+ return child.value;
238
+ }
239
+ }
240
+ return null;
241
+ };
242
+
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;
258
+ };
259
+
166
260
  export const referencesAreEqual = (a, b) => {
167
261
  return (
168
262
  a === b ||
@@ -173,163 +267,85 @@ export const referencesAreEqual = (a, b) => {
173
267
  );
174
268
  };
175
269
 
176
- export const getProperties = (ref, properties) => {
177
- const { name, index, isArray } = ref.value;
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_;
178
274
 
179
- if (name === '.') {
180
- if (!hasOwn(properties, name)) {
181
- return null;
182
- }
183
- }
275
+ if (hasOwn(properties, name)) {
276
+ let prop = properties[name];
184
277
 
185
- if (isArray || Array.isArray(properties[name])) {
186
- return btree.getAt(index ?? -1, properties[name]);
187
- } else {
188
- return properties[name];
189
- }
190
- };
278
+ if (prop.reference.isArray) {
279
+ prop = btree.getAt(index ?? -1, prop.node);
280
+ }
191
281
 
192
- export const getPropertiesSimple = (ref, properties) => {
193
- const { name, index, isArray } = ref.value;
282
+ if (!prop) return null;
194
283
 
195
- if (!hasOwn(properties, name)) {
196
- return null;
197
- }
284
+ if (prop.reference.flags.expression) {
285
+ prop = btree.getAt(shiftIndex ?? -1, prop.node);
286
+ }
198
287
 
199
- if (isArray) {
200
- return properties[name][index == null ? properties[name].length - 1 : index];
201
- } else {
202
- return properties[name];
288
+ return prop;
203
289
  }
290
+ return null;
204
291
  };
205
292
 
206
293
  export const isDefined = (obj, key) => hasOwn(obj, key) && obj[key] !== undefined;
207
294
  export const isUndefined = (obj, key) => !hasOwn(obj, key) || obj[key] === undefined;
208
295
 
209
- export const get = (ref, node) => {
210
- const result = getProperties(ref, node.properties);
211
- const { flags } = result.reference.value;
212
-
213
- return flags.expression ? btree.getAt(-1, result.node) : result?.node;
214
- };
215
-
216
- export const getShifted = (shiftIndex, ref, node) => {
217
- const { flags } = ref.value;
296
+ export const get = (path, node) => {
297
+ if (!node) throw new Error();
218
298
 
219
- const result = getProperties(ref, node.properties);
220
- return flags.expression ? btree.getAt(shiftIndex ?? -1, result.node) : result?.node;
221
- };
299
+ if (path == null) throw new Error('Bad path');
222
300
 
223
- export const add = (node, reference, value, shift = null) => {
224
- if (node == null || reference == null) throw new Error('invalid arguments to add');
225
- if (value === node) {
226
- throw new Error('cannot add a node to itself');
301
+ if (!isArray(path)) {
302
+ path = [path];
227
303
  }
228
304
 
229
- const { properties } = node;
230
- const { name, isArray, flags } = reference.value;
305
+ let node_ = node;
306
+ for (let i = 0; i < path.length; i++) {
307
+ let nameOrSeg = path[i];
308
+ let isName = typeof nameOrSeg === 'string';
309
+ let property;
231
310
 
232
- if (node.type && name === '.') {
233
- throw new Error('Only fragments can have . properties');
234
- }
311
+ if (!isName && (!nameOrSeg || !hasOwn(nameOrSeg, 'shiftIndex'))) throw new Error('Bad path');
235
312
 
236
- if (Array.isArray(value) && !isArray) throw new Error();
237
- if (Array.isArray(value) && value.length) throw new Error();
313
+ let name = isName ? nameOrSeg : nameOrSeg.name;
238
314
 
239
- if (name == null) throw new Error();
240
-
241
- const lastChild = sumtree.getAt(-1, node.children);
242
-
243
- if (lastChild.type === ReferenceTag) {
244
- if (!referencesAreEqual(lastChild, reference)) throw new Error();
245
- } else if (lastChild.type !== ShiftTag) {
246
- if ([ShiftTag, ReferenceTag].includes(sumtree.getAt(-1, node.children).type)) throw new Error();
247
- node.children = sumtree.push(node.children, shift > 0 ? buildShiftTag(shift) : reference);
248
- }
315
+ if (name) {
316
+ let { index } = nameOrSeg;
249
317
 
250
- if (name === '#' || name === '@') {
251
- node.children = sumtree.push(node.children, buildEmbeddedNode(value));
252
- } else {
253
- if (isArray) {
254
- let isInitializer = Array.isArray(value);
255
- let exists = !isInitializer && isDefined(properties, name);
256
-
257
- let existed = exists;
258
- if (!existed) {
259
- if (isInitializer && value.length)
260
- throw new Error('Array value only allowed for initialization');
318
+ property = getProperties(
319
+ isName ? buildPathSegment(name, index) : nameOrSeg,
320
+ node_.properties,
321
+ );
261
322
 
262
- properties[name] = [];
263
- node.children = sumtree.push(node.children, buildInitializerTag(true));
264
- exists = !isInitializer;
265
- }
323
+ ({ node: node_ } = property || {});
324
+ } else if (!isName) {
325
+ let { type } = nameOrSeg;
326
+ if (type !== '.') throw new Error('not implemented');
266
327
 
267
- if (exists) {
268
- if (!existed) {
269
- if (sumtree.getAt(-1, node.children).type === ReferenceTag) throw new Error();
270
- node.children = sumtree.push(node.children, reference);
271
- }
328
+ let refIndex = getPropertyChildrenIndex(node_, '.');
272
329
 
273
- let newBinding;
274
- if (flags.expression) {
275
- let shiftedNodes = shift > 0 ? btree.getAt(-1, properties[name])?.node : [];
330
+ if (refIndex == null) return null;
276
331
 
277
- newBinding = {
278
- reference,
279
- node: btree.push(shiftedNodes, value),
280
- };
281
- } else {
282
- newBinding = { reference, node: value };
283
- }
284
-
285
- properties[name] =
286
- shift > 0
287
- ? btree.replaceAt(-1, properties[name], newBinding)
288
- : btree.push(properties[name], newBinding);
289
-
290
- node.children = sumtree.push(node.children, buildGapTag(value));
291
- }
332
+ node_ = Children.getAt(refIndex + 2, node_.children).value.node;
292
333
  } else {
293
- if (value === undefined) {
294
- if (isUndefined(properties, name)) {
295
- let newBinding;
296
- if (flags.expression) {
297
- let shiftedNodes = shift > 0 ? btree.getAt(-1, properties[name])?.node : [];
298
-
299
- newBinding = {
300
- reference,
301
- node: btree.push(shiftedNodes, undefined),
302
- };
303
- } else {
304
- newBinding = { reference, node: undefined };
305
- }
306
- properties[name] = newBinding;
307
- node.children = sumtree.push(node.children, buildInitializerTag());
308
- }
309
- } else {
310
- if (flags.expression) {
311
- if (shift == null) {
312
- throw new Error();
313
- }
314
-
315
- let shiftedNodes = shift ? properties[name]?.node : [];
316
- properties[name] = { reference, node: btree.push(shiftedNodes, value) };
317
- } else {
318
- if (shift != null) throw new Error();
319
- if (hasOwn(properties, name) && properties[name].node !== undefined) {
320
- throw new Error();
321
- }
322
- properties[name] = { reference, node: value };
323
- }
324
- node.children = sumtree.push(node.children, buildGapTag(value));
325
- }
334
+ throw new Error();
326
335
  }
336
+
337
+ if (!node_) return null;
327
338
  }
339
+
340
+ return node_;
328
341
  };
329
342
 
330
343
  export function* allTagPathsFor(range, options = {}) {
331
344
  if (range == null) return;
332
345
 
346
+ if (range[0] && !(range[0] instanceof TagPath)) throw new Error();
347
+ if (range[1] && !(range[1] instanceof TagPath)) throw new Error();
348
+
333
349
  const { unshift = false } = options;
334
350
  let startPath = range[0];
335
351
  let endPath = range[1];
@@ -337,7 +353,7 @@ export function* allTagPathsFor(range, options = {}) {
337
353
 
338
354
  while (path) {
339
355
  if (path.inner && path.previousSibling.tag.type === ReferenceTag) {
340
- path = new TagPath(path.innerPath, 0);
356
+ path = new TagPath(path.inner, 0);
341
357
  }
342
358
 
343
359
  if (path.path.depth < startPath.path.depth) {
@@ -370,25 +386,25 @@ export function* allTagPathsFor(range, options = {}) {
370
386
  }
371
387
 
372
388
  export function* allTagsFor(range, options = {}) {
373
- for (const path of allTagPathsFor(range, options)) {
389
+ for (let path of allTagPathsFor(range, options)) {
374
390
  yield path.tag;
375
391
  }
376
392
  }
377
393
 
378
394
  export const buildFullRange = (node) => {
379
- const sum = sumtree.getSize(node.children);
395
+ let sum = Children.getSize(node.children);
380
396
  return sum ? [0, sum - 1] : null;
381
397
  };
382
398
 
383
399
  export function* ownTagPathsFor(range) {
384
400
  if (!isArray(range)) throw new Error();
385
401
 
386
- const startPath = range[0];
387
- const endPath = range[1];
402
+ let startPath = range[0];
403
+ let endPath = range[1];
388
404
 
389
- if (startPath.outer !== endPath.outer) throw new Error();
405
+ if (startPath.parent.node !== endPath.parent.node) throw new Error();
390
406
 
391
- const { children } = startPath.outer;
407
+ let { children } = startPath.parent.node;
392
408
 
393
409
  for (let i = startPath.childrenIndex; i < endPath.childrenIndex; i++) {
394
410
  yield children[i];
@@ -397,7 +413,7 @@ export function* ownTagPathsFor(range) {
397
413
 
398
414
  const findRight = (arr, predicate) => {
399
415
  for (let i = arr.length - 1; i >= 0; i--) {
400
- const value = arr[i];
416
+ let value = arr[i];
401
417
  if (predicate(value)) return value;
402
418
  }
403
419
  return null;
@@ -420,7 +436,7 @@ const buildSkips = (frame) => {
420
436
  skipsByFrame.set(frame, skips);
421
437
  }
422
438
 
423
- skips[skipIdx] = frame.at(frame.depth - skipAmount);
439
+ skips[skipIdx] = frame.atDepth(frame.depth - skipAmount);
424
440
 
425
441
  skipIdx++;
426
442
  skipAmount = skipAmounts[skipIdx];
@@ -441,15 +457,33 @@ const skipToDepth = (depth, frame) => {
441
457
  return parent;
442
458
  };
443
459
 
460
+ export let pathForTagPath = (tagPath) => {
461
+ let refTagPath = tagPath;
462
+ let isShift = tagPath.tag.type === ShiftTag;
463
+
464
+ if (isShift) {
465
+ refTagPath = tagPath.siblingAt(tagPath.childrenIndex - tagPath.tag.value.index * 3);
466
+ }
467
+
468
+ let { type, name } = refTagPath.tag.value;
469
+
470
+ if (refTagPath.tag.type !== ReferenceTag) throw new Error();
471
+
472
+ let index = getChildPropertyIndex(tagPath.node, refTagPath.childrenIndex);
473
+ let shiftIndex = isShift ? (tagPath.tag.value.index ?? 0) + 1 : null;
474
+
475
+ return [{ type, name, index, shiftIndex }];
476
+ };
477
+
444
478
  export const Path = class AgastPath extends WeakStackFrame {
445
479
  static from(node) {
446
- return this.create(node);
480
+ return node && this.create(node);
447
481
  }
448
482
 
449
483
  constructor(parent, node, referenceIndex = null) {
450
484
  super(parent);
451
485
 
452
- if (!(hasOwn(node, 'type') && hasOwn(node, 'language'))) throw new Error();
486
+ if (!(hasOwn(node, 'type') && hasOwn(node, 'properties'))) throw new Error();
453
487
 
454
488
  if (parent && referenceIndex == null) throw new Error();
455
489
  if (!node) throw new Error();
@@ -458,10 +492,10 @@ export const Path = class AgastPath extends WeakStackFrame {
458
492
  this.node = node;
459
493
  this.referenceIndex = referenceIndex; // in the parent
460
494
 
461
- if (
462
- referenceIndex != null &&
463
- ![ReferenceTag, ShiftTag].includes(sumtree.getAt(referenceIndex, parent.node.children).type)
464
- ) {
495
+ let referenceTag =
496
+ referenceIndex != null && Children.getAt(referenceIndex, parent.node.children);
497
+
498
+ if (referenceTag && ![ReferenceTag, ShiftTag].includes(referenceTag.type)) {
465
499
  throw new Error();
466
500
  }
467
501
 
@@ -469,13 +503,28 @@ export const Path = class AgastPath extends WeakStackFrame {
469
503
  throw new Error();
470
504
  }
471
505
 
506
+ // if (
507
+ // parent &&
508
+ // (['#', '@'].includes(this.reference.value.type)
509
+ // ? parent.tagPathAt(referenceIndex + 1).tag.value
510
+ // : get(pathForTagPath(parent.tagPathAt(referenceIndex)), parent.node)) !== node
511
+ // ) {
512
+ // throw new Error('Path not reachable');
513
+ // }
514
+
472
515
  if (!Number.isFinite(this.depth)) throw new Error();
473
516
 
474
517
  buildSkips(this);
518
+
519
+ freeze(this);
475
520
  }
476
521
 
477
522
  get openTagPath() {
478
- return TagPath.from(this, 0);
523
+ let tagPath = TagPath.from(this, 0);
524
+ if (tagPath.tag.type === DoctypeTag) {
525
+ tagPath = tagPath.nextSibling;
526
+ }
527
+ return tagPath;
479
528
  }
480
529
 
481
530
  get openTag() {
@@ -483,42 +532,71 @@ export const Path = class AgastPath extends WeakStackFrame {
483
532
  }
484
533
 
485
534
  get closeTagPath() {
486
- return TagPath.from(this, -1);
535
+ let tagPath = TagPath.from(this, -1);
536
+ if (tagPath.tag.type !== CloseNodeTag) return null;
537
+ return tagPath;
487
538
  }
488
539
 
489
540
  get closeTag() {
490
541
  return this.closeTagPath.tag;
491
542
  }
492
543
 
493
- get reference() {
494
- return this.outer && sumtree.getAt(this.referenceIndex, this.outer.children);
544
+ tagPathAt(idx) {
545
+ return TagPath.from(this, idx);
495
546
  }
496
547
 
497
- get referencePath() {
498
- return this.outer && new TagPath(this.parent, this.referenceIndex);
548
+ get reference() {
549
+ return (
550
+ (this.parent?.node ?? null) && Children.getAt(this.referenceIndex, this.parent.node.children)
551
+ );
499
552
  }
500
553
 
501
- get gap() {
502
- return this.outer && sumtree.getAt(this.referenceIndex + 1, this.outer.children);
554
+ get referenceTagPath() {
555
+ return (this.parent?.node ?? null) && new TagPath(this.parent, this.referenceIndex);
503
556
  }
504
557
 
505
- get gapPath() {
506
- return this.outer && new TagPath(this.parent, this.referenceIndex + 1);
507
- }
558
+ get(path) {
559
+ let path_ = path;
560
+ if (typeof path_ === 'string') {
561
+ path_ = [path_];
562
+ }
563
+ if (!isArray(path_)) throw new Error();
508
564
 
509
- get outer() {
510
- return this.parent?.node;
511
- }
565
+ let pathInst = this;
566
+ for (let i = 0; i < path_.length; i++) {
567
+ let nameOrSeg = path_[i];
512
568
 
513
- get(reference, shiftIndex) {
514
- let node = getShifted(shiftIndex, reference, this.node);
569
+ if (isString(nameOrSeg)) {
570
+ nameOrSeg = { type: null, name: nameOrSeg, index: null, shiftIndex: null };
571
+ }
572
+
573
+ let property = getProperties(nameOrSeg, pathInst.node.properties);
574
+
575
+ if (!property) return null;
576
+
577
+ let { node, reference: ref } = property;
578
+
579
+ let shiftIndex_ = nameOrSeg.shiftIndex;
580
+
581
+ if (shiftIndex_ && shiftIndex_ < 0) {
582
+ shiftIndex_ = btree.getSize(node) + shiftIndex_;
583
+ } else if (shiftIndex_ == null && ref.flags.expression) {
584
+ shiftIndex_ = 0;
585
+ }
515
586
 
516
- let shiftOffset = (shiftIndex ?? 0) * 2;
587
+ let shiftOffset = shiftIndex_ ? (shiftIndex_ - 1) * 3 : 0;
517
588
 
518
- return node && this.push(node, getPropertyChildrenIndex(this.node, reference) + shiftOffset);
589
+ pathInst = pathInst.push(
590
+ node,
591
+ getPropertyChildrenIndex(pathInst.node, nameOrSeg.type, nameOrSeg.name, nameOrSeg.index) +
592
+ shiftOffset,
593
+ );
594
+ }
595
+
596
+ return pathInst;
519
597
  }
520
598
 
521
- at(depth) {
599
+ atDepth(depth) {
522
600
  return skipToDepth(depth, this);
523
601
  }
524
602
  };
@@ -532,14 +610,20 @@ export class TagPath {
532
610
  constructor(path, childrenIndex) {
533
611
  if (path == null || childrenIndex == null) throw new Error();
534
612
 
613
+ let tag = Children.getAt(childrenIndex, path.node.children);
614
+
615
+ if (tag == null) throw new Error();
616
+
535
617
  this.path = path;
618
+ this.node = path.node;
619
+ this.tag = tag;
536
620
  this.childrenIndex = childrenIndex;
537
621
 
538
- if (this.tag == null) throw new Error();
622
+ freeze(this);
539
623
  }
540
624
 
541
625
  static from(path, childrenIndex) {
542
- let size = sumtree.getSize(path.node.children);
626
+ let size = Children.getSize(path.node.children);
543
627
  let index = childrenIndex < 0 ? size + childrenIndex : childrenIndex;
544
628
 
545
629
  return index >= 0 && index < size ? new TagPath(path, index) : null;
@@ -549,33 +633,21 @@ export class TagPath {
549
633
  return TagPath.from(Path.from(node), childrenIndex);
550
634
  }
551
635
 
552
- get tag() {
553
- return this.child;
554
- }
555
-
556
- get node() {
557
- return this.path.node;
558
- }
559
-
560
636
  get child() {
561
- return sumtree.getAt(this.childrenIndex, this.path.node.children);
562
- }
563
-
564
- siblingPathAt(index) {
565
- return TagPath.from(this.path, index);
637
+ return this.tag;
566
638
  }
567
639
 
568
640
  siblingAt(index) {
569
- return this.siblingPathAt(index)?.tag;
641
+ return TagPath.from(this.path, index);
570
642
  }
571
643
 
572
644
  get nextSibling() {
573
645
  const { path, childrenIndex } = this;
574
646
 
575
647
  const child =
576
- childrenIndex + 1 >= sumtree.getSize(path.node.children)
648
+ childrenIndex + 1 >= Children.getSize(path.node.children)
577
649
  ? null
578
- : sumtree.getAt(childrenIndex + 1, path.node.children);
650
+ : Children.getAt(childrenIndex + 1, path.node.children);
579
651
 
580
652
  return child && new TagPath(path, childrenIndex + 1);
581
653
  }
@@ -586,8 +658,8 @@ export class TagPath {
586
658
  let leaving = false;
587
659
 
588
660
  for (;;) {
589
- let prevTag = sumtree.getAt(childrenIndex - 1, path.node.children);
590
- let tag = sumtree.getAt(childrenIndex, path.node.children);
661
+ let prevTag = Children.getAt(childrenIndex - 1, path.node.children);
662
+ let tag = Children.getAt(childrenIndex, path.node.children);
591
663
  let isInitialTag = path.node === this.path.node && childrenIndex === this.childrenIndex;
592
664
  let wasLeaving = leaving;
593
665
  leaving = false;
@@ -595,130 +667,58 @@ export class TagPath {
595
667
  if (!tag) return null;
596
668
 
597
669
  // done
598
- if (
599
- !isInitialTag &&
600
- tag.type !== EmbeddedNode &&
601
- (tag.type !== GapTag || isGapNode(path.node) || prevTag.type === ShiftTag)
602
- ) {
670
+ if (!isInitialTag && tag.type !== Property) {
603
671
  return new TagPath(path, childrenIndex);
604
672
  }
605
673
 
606
674
  // in
607
- if (tag.type === EmbeddedNode && !wasLeaving) {
608
- path = path.push(tag.value, childrenIndex - 1);
609
- childrenIndex = 0;
610
- continue;
611
- }
612
-
613
- // in
614
- if (tag.type === GapTag && !wasLeaving && !isGapNode(path.node)) {
615
- let refIndex = childrenIndex - 1;
616
- let refTag;
617
- let prevTag = sumtree.getAt(childrenIndex - 1, path.node.children);
618
- let nextTag = sumtree.getAt(childrenIndex + 1, path.node.children);
619
-
675
+ if (tag.type === Property && !wasLeaving) {
676
+ // jump to gap tag?
620
677
  if (
621
678
  path.parent &&
622
- sumtree.getAt(path.referenceIndex, path.outer.children)?.type === ShiftTag
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
623
684
  ) {
624
- let third = sumtree.getAt(2, path.node.children);
625
- if (childrenIndex === 2 || (childrenIndex === 4 && third.type === InitializerTag)) {
626
- childrenIndex = path.referenceIndex + 1;
627
- path = path.parent;
628
- leaving = true;
629
- continue;
630
- }
631
- }
632
-
633
- if (prevTag.type === ReferenceTag) {
634
- refTag = prevTag;
635
-
636
- if (nextTag && nextTag.type === ShiftTag) {
637
- const shifts = getProperties(refTag, path.node.properties).node;
685
+ let { reference: ref } = tag.value;
686
+ let shiftStack = path.node.properties[ref.name].node;
638
687
 
639
- if (!Array.isArray(shifts)) throw new Error();
640
-
641
- const { name, isArray, flags } = refTag.value;
642
- let resolvedReference = refTag;
643
- if (isArray) {
644
- let index = getChildPropertyIndex(path.node, refIndex);
645
- resolvedReference =
646
- index === -1 ? null : buildReferenceTag(name, index != null, flags, index);
647
- }
648
-
649
- path = path.get(resolvedReference, 0);
650
- childrenIndex = 0;
651
-
652
- if (!path) {
653
- return null;
654
- }
655
- continue;
656
- } else {
657
- if (
658
- !['#', '@'].includes(refTag.value.name) &&
659
- (!refTag.value.isArray || getChildPropertyIndex(path.node, refIndex) != null)
660
- ) {
661
- const { name, isArray, flags } = refTag.value;
662
- let resolvedReference = refTag;
663
- if (isArray) {
664
- let index = getChildPropertyIndex(path.node, refIndex);
665
- resolvedReference =
666
- index === -1 ? null : buildReferenceTag(name, index != null, flags, index);
667
- }
668
-
669
- if (resolvedReference) {
670
- path = path.get(resolvedReference);
671
- childrenIndex = 0;
672
-
673
- if (!path) {
674
- return null;
675
- }
676
- continue;
677
- }
678
- }
679
- }
680
- } else if (prevTag.type === ShiftTag) {
681
- let refIndex = childrenIndex - prevTag.value.index * 2 - 1;
682
- let refTag = sumtree.getAt(refIndex, path.node.children);
683
-
684
- const { name, isArray, flags } = refTag.value;
685
- let resolvedReference = refTag;
686
- if (isArray) {
687
- let index = getChildPropertyIndex(path.node, refIndex);
688
- resolvedReference =
689
- index === -1 ? null : buildReferenceTag(name, index != null, flags, index);
688
+ if (ref.isArray) {
689
+ shiftStack = btree.getAt(-1, shiftStack).node;
690
690
  }
691
+ let gapNode = btree.getAt(0, shiftStack).node;
691
692
 
692
- if (resolvedReference) {
693
- path = path.get(resolvedReference, prevTag.value.index);
694
- // this was introducing errors
695
- // caused us to return to a point before we left
696
- path.referenceIndex = childrenIndex;
697
- childrenIndex = sumtree.getAt(2, path.node.children).type === InitializerTag ? 5 : 3;
698
- continue;
699
- }
700
- } else {
701
- throw new Error();
693
+ path = new Path(path, gapNode, childrenIndex - 2);
694
+ childrenIndex = 0;
695
+ continue;
702
696
  }
697
+
698
+ path = path.push(tag.value.node, childrenIndex - 2);
699
+ childrenIndex = 0;
700
+ continue;
703
701
  }
704
702
 
705
703
  // shift
706
- if (tag.type === ShiftTag) {
707
- let refIndex = childrenIndex - tag.value.index * 2;
708
- let refTag = sumtree.getAt(refIndex, path.node.children);
709
-
710
- const { name, isArray, flags } = refTag.value;
711
- let resolvedReference = null;
704
+ if (tag.type === BindingTag && prevTag.type === ShiftTag) {
705
+ let refIndex = childrenIndex - 1 - prevTag.value.index * 3;
706
+ 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
712
  if (isArray) {
713
- let index = getChildPropertyIndex(path.node, refIndex);
714
- resolvedReference =
715
- index === -1 ? null : buildReferenceTag(name, index != null, flags, index);
716
- } else {
717
- resolvedReference = refTag;
713
+ index = getChildPropertyIndex(path.node, refIndex);
714
+ pathDesc = buildPathSegment(name, index);
718
715
  }
719
716
 
720
- if (resolvedReference) {
721
- path = path.get(resolvedReference, tag.value.index);
717
+ // HMM
718
+ pathDesc.shiftIndex = prevTag.value.height;
719
+
720
+ if (index !== -1) {
721
+ path = path.get([pathDesc]);
722
722
 
723
723
  if (!path) return null;
724
724
 
@@ -728,25 +728,30 @@ export class TagPath {
728
728
  }
729
729
 
730
730
  // over
731
- if (path.node && childrenIndex + 1 < sumtree.getSize(path.node.children)) {
731
+ if (path.node && childrenIndex + 1 < Children.getSize(path.node.children)) {
732
732
  childrenIndex++;
733
733
  continue;
734
734
  }
735
735
 
736
736
  // out
737
- if (path.referenceIndex != null) {
737
+ if (path.referenceIndex != null && path.parent) {
738
738
  do {
739
- if (sumtree.getAt(path.referenceIndex + 2, path.outer.children)?.type === ShiftTag) {
739
+ if (
740
+ path.parent &&
741
+ Children.getAt(path.referenceIndex, path.parent.node.children)?.type === ShiftTag
742
+ ) {
740
743
  childrenIndex =
741
- sumtree.getSize(path.outer.children) > path.referenceIndex + 2
744
+ Children.getSize(path.parent.node.children) > path.referenceIndex + 2
742
745
  ? path.referenceIndex + 2
743
746
  : null;
744
747
  } else {
745
- childrenIndex = path.referenceIndex + 1;
748
+ childrenIndex = path.referenceIndex + 2;
746
749
  }
747
750
 
748
751
  path = path.parent;
749
752
  leaving = true;
753
+
754
+ if (!path) return null;
750
755
  } while (childrenIndex == null);
751
756
 
752
757
  leaving = true;
@@ -763,70 +768,47 @@ export class TagPath {
763
768
  let leaving = false;
764
769
 
765
770
  for (;;) {
766
- let tag = sumtree.getAt(childrenIndex, path.node.children);
771
+ let tag = Children.getAt(childrenIndex, path.node.children);
767
772
  let isInitialTag = path.node === this.path.node && childrenIndex === this.childrenIndex;
768
773
  let wasLeaving = leaving;
769
774
  leaving = false;
770
775
 
771
776
  if (!tag) return null;
772
777
 
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;
790
+ }
791
+ }
792
+
773
793
  // done
774
- if (
775
- !isInitialTag &&
776
- tag.type !== EmbeddedNode &&
777
- tag.type !== ShiftTag &&
778
- (tag.type !== GapTag || isGapNode(path.node))
779
- ) {
794
+ if (!isInitialTag && tag.type !== Property) {
780
795
  return new TagPath(path, childrenIndex);
781
796
  }
782
797
 
783
798
  // in
784
- if (tag.type === EmbeddedNode && !wasLeaving) {
785
- path = path.push(tag.value, childrenIndex - 1);
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);
786
806
  childrenIndex = 0;
787
807
  continue;
788
808
  }
789
809
 
790
- // in
791
- if (tag.type === GapTag && !wasLeaving && !isGapNode(path.node)) {
792
- let refIndex = childrenIndex - 1;
793
- let refTag;
794
- let prevTag = sumtree.getAt(childrenIndex - 1, path.node.children);
795
-
796
- if (prevTag.type === ShiftTag) {
797
- // continue
798
- } else if (prevTag.type === ReferenceTag) {
799
- refTag = prevTag;
800
-
801
- if (
802
- !['#', '@'].includes(refTag.value.name) &&
803
- (!refTag.value.isArray || getChildPropertyIndex(path.node, refIndex) != null)
804
- ) {
805
- const { name, isArray, flags } = refTag.value;
806
- let resolvedReference = refTag;
807
- if (isArray) {
808
- let index = getChildPropertyIndex(path.node, refIndex);
809
- resolvedReference =
810
- index === -1 ? null : buildReferenceTag(name, index != null, flags, index);
811
- }
812
-
813
- if (resolvedReference) {
814
- path = path.get(resolvedReference);
815
- childrenIndex = 0;
816
-
817
- if (!path) {
818
- return null;
819
- }
820
- continue;
821
- }
822
- }
823
- } else {
824
- throw new Error();
825
- }
826
- }
827
-
828
810
  // over
829
- if (path.node && childrenIndex + 1 < sumtree.getSize(path.node.children)) {
811
+ if (path.node && childrenIndex + 1 < Children.getSize(path.node.children)) {
830
812
  childrenIndex++;
831
813
  continue;
832
814
  }
@@ -834,7 +816,7 @@ export class TagPath {
834
816
  // out
835
817
  if (path.referenceIndex != null) {
836
818
  do {
837
- childrenIndex = path.referenceIndex + 1;
819
+ childrenIndex = path.referenceIndex + 2;
838
820
 
839
821
  path = path.parent;
840
822
  leaving = true;
@@ -866,7 +848,7 @@ export class TagPath {
866
848
  let leaving = false;
867
849
 
868
850
  for (;;) {
869
- let tag = sumtree.getAt(childrenIndex, path.node.children);
851
+ let tag = Children.getAt(childrenIndex, path.node.children);
870
852
  let isInitialTag = path.node === this.path.node && childrenIndex === this.childrenIndex;
871
853
  let wasLeaving = leaving;
872
854
  leaving = false;
@@ -874,62 +856,19 @@ export class TagPath {
874
856
  if (!tag) return null;
875
857
 
876
858
  // done
877
- if (
878
- !isInitialTag &&
879
- tag.type !== EmbeddedNode &&
880
- tag.type !== ShiftTag &&
881
- (tag.type !== GapTag || isGapNode(path.node))
882
- ) {
859
+ if (!isInitialTag && tag.type !== ShiftTag && tag.type !== Property) {
883
860
  return new TagPath(path, childrenIndex);
884
861
  }
885
862
 
886
863
  // in
887
- if (tag.type === EmbeddedNode && !wasLeaving) {
888
- path = path.push(tag.value, childrenIndex - 1);
889
- childrenIndex = sumtree.getSize(tag.value.children) - 1;
864
+ if (tag.type === Property && !wasLeaving) {
865
+ path = path.push(tag.value.node, childrenIndex - 1);
866
+ childrenIndex = Children.getSize(tag.value.children) - 1;
890
867
  continue;
891
868
  }
892
869
 
893
- // in
894
- if (tag.type === GapTag && !wasLeaving && !isGapNode(path.node)) {
895
- let refIndex = childrenIndex - 1;
896
- let refTag;
897
- let prevTag = sumtree.getAt(childrenIndex - 1, path.node.children);
898
-
899
- if (prevTag.type === ShiftTag) {
900
- // continue
901
- } else if (prevTag.type === ReferenceTag) {
902
- refTag = prevTag;
903
-
904
- if (
905
- !['#', '@'].includes(refTag.value.name) &&
906
- (!refTag.value.isArray || getChildPropertyIndex(path.node, refIndex) != null)
907
- ) {
908
- const { name, isArray, flags } = refTag.value;
909
- let resolvedReference = refTag;
910
- if (isArray) {
911
- let index = getChildPropertyIndex(path.node, refIndex);
912
- resolvedReference =
913
- index === -1 ? null : buildReferenceTag(name, index != null, flags, index);
914
- }
915
-
916
- if (resolvedReference) {
917
- path = path.get(resolvedReference);
918
- childrenIndex = 0;
919
-
920
- if (!path) {
921
- return null;
922
- }
923
- continue;
924
- }
925
- }
926
- } else {
927
- throw new Error();
928
- }
929
- }
930
-
931
870
  // over
932
- if (path.node && childrenIndex + 1 < sumtree.getSize(path.node.children)) {
871
+ if (path.node && childrenIndex + 1 < Children.getSize(path.node.children)) {
933
872
  childrenIndex--;
934
873
  continue;
935
874
  }
@@ -951,38 +890,37 @@ export class TagPath {
951
890
  }
952
891
  }
953
892
 
954
- get inner() {
955
- return this.innerPath?.node;
893
+ get innerNode() {
894
+ return this.inner?.node;
956
895
  }
957
896
 
958
- get innerPath() {
959
- let { tag, previousSibling: refPath } = this;
897
+ get inner() {
898
+ let { tag, previousSibling } = this;
899
+
900
+ let refPath = previousSibling?.previousSibling;
960
901
 
961
- if (tag.type !== GapTag || isGapNode(this.node)) {
902
+ if (tag.type !== Property) {
962
903
  return null;
963
904
  }
964
905
 
965
906
  let shiftPath = null;
966
907
  if (refPath.tag.type === ShiftTag) {
967
908
  shiftPath = refPath;
968
- refPath = TagPath.from(this.path, this.childrenIndex - refPath.tag.value.index * 2 - 1);
909
+ refPath = TagPath.from(this.path, this.childrenIndex - refPath.tag.value.index * 3 - 2);
969
910
  }
970
911
 
971
912
  if (refPath.tag.type !== ReferenceTag) throw new Error();
972
913
 
973
- let resolvedRef = refPath.tag;
914
+ const { type, name } = refPath.tag.value;
974
915
 
975
- if (refPath.tag.value.isArray) {
976
- const { name, flags, isArray } = refPath.tag.value;
977
- resolvedRef = buildReferenceTag(
916
+ return this.path.get([
917
+ buildFullPathSegment(
918
+ type,
978
919
  name,
979
- isArray,
980
- flags,
981
920
  getChildPropertyIndex(refPath.path.node, refPath.childrenIndex),
982
- );
983
- }
984
-
985
- return this.path.get(resolvedRef, shiftPath?.tag.value.index);
921
+ shiftPath?.tag.value.index,
922
+ ),
923
+ ]);
986
924
  }
987
925
 
988
926
  equalTo(tagPath) {