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