/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { AstNode, AstNodeKind, ListAstNode } from './ast'; /** * Concatenates a list of (2,3) AstNode's into a single (2,3) AstNode. * This mutates the items of the input array! * If all items have the same height, this method has runtime O(items.length). * Otherwise, it has runtime O(items.length * max(log(items.length), items.max(i => i.height))). */ export function concat23Trees(items: AstNode[]): AstNode | null { if (items.length === 0) { return null; } if (items.length === 1) { return items[0]; } let i = 0; /** * Reads nodes of same height and concatenates them to a single node. */ function readNode(): AstNode | null { if (i >= items.length) { return null; } const start = i; const height = items[start].listHeight; i++; while (i < items.length && items[i].listHeight === height) { i++; } if (i - start >= 2) { return concat23TreesOfSameHeight(start === 0 && i === items.length ? items : items.slice(start, i), false); } else { return items[start]; } } // The items might not have the same height. // We merge all items by using a binary concat operator. let first = readNode()!; // There must be a first item let second = readNode(); if (!second) { return first; } for (let item = readNode(); item; item = readNode()) { // Prefer concatenating smaller trees, as the runtime of concat depends on the tree height. if (heightDiff(first, second) <= heightDiff(second, item)) { first = concat(first, second); second = item; } else { second = concat(second, item); } } const result = concat(first, second); return result; } export function concat23TreesOfSameHeight(items: AstNode[], createImmutableLists: boolean = false): AstNode | null { if (items.length === 0) { return null; } if (items.length === 1) { return items[0]; } let length = items.length; // All trees have same height, just create parent nodes. while (length > 3) { const newLength = length >> 1; for (let i = 0; i < newLength; i++) { const j = i << 1; items[i] = ListAstNode.create23(items[j], items[j + 1], j + 3 === length ? items[j + 2] : null, createImmutableLists); } length = newLength; } return ListAstNode.create23(items[0], items[1], length >= 3 ? items[2] : null, createImmutableLists); } function heightDiff(node1: AstNode, node2: AstNode): number { return Math.abs(node1.listHeight - node2.listHeight); } function concat(node1: AstNode, node2: AstNode): AstNode { if (node1.listHeight === node2.listHeight) { return ListAstNode.create23(node1, node2, null, false); } else if (node1.listHeight > node2.listHeight) { // node1 is the tree we want to insert into return append(node1 as ListAstNode, node2); } else { return prepend(node2 as ListAstNode, node1); } } /** * Appends the given node to the end of this (2,3) tree. * Returns the new root. */ function append(list: ListAstNode, nodeToAppend: AstNode): AstNode { list = list.toMutable() as ListAstNode; let curNode: AstNode = list; const parents = new Array(); let nodeToAppendOfCorrectHeight: AstNode | undefined; while (true) { // assert nodeToInsert.listHeight <= curNode.listHeight if (nodeToAppend.listHeight === curNode.listHeight) { nodeToAppendOfCorrectHeight = nodeToAppend; break; } // assert 0 <= nodeToInsert.listHeight < curNode.listHeight if (curNode.kind !== AstNodeKind.List) { throw new Error('unexpected'); } parents.push(curNode); // assert 2 <= curNode.childrenLength <= 3 curNode = curNode.makeLastElementMutable()!; } // assert nodeToAppendOfCorrectHeight!.listHeight === curNode.listHeight for (let i = parents.length - 1; i >= 0; i--) { const parent = parents[i]; if (nodeToAppendOfCorrectHeight) { // Can we take the element? if (parent.childrenLength >= 3) { // assert parent.childrenLength === 3 && parent.listHeight === nodeToAppendOfCorrectHeight.listHeight + 1 // we need to split to maintain (2,3)-tree property. // Send the third element + the new element to the parent. nodeToAppendOfCorrectHeight = ListAstNode.create23(parent.unappendChild()!, nodeToAppendOfCorrectHeight, null, false); } else { parent.appendChildOfSameHeight(nodeToAppendOfCorrectHeight); nodeToAppendOfCorrectHeight = undefined; } } else { parent.handleChildrenChanged(); } } if (nodeToAppendOfCorrectHeight) { return ListAstNode.create23(list, nodeToAppendOfCorrectHeight, null, false); } else { return list; } } /** * Prepends the given node to the end of this (2,3) tree. * Returns the new root. */ function prepend(list: ListAstNode, nodeToAppend: AstNode): AstNode { list = list.toMutable() as ListAstNode; let curNode: AstNode = list; const parents = new Array(); // assert nodeToInsert.listHeight <= curNode.listHeight while (nodeToAppend.listHeight !== curNode.listHeight) { // assert 0 <= nodeToInsert.listHeight < curNode.listHeight if (curNode.kind !== AstNodeKind.List) { throw new Error('unexpected'); } parents.push(curNode); // assert 2 <= curNode.childrenFast.length <= 3 curNode = curNode.makeFirstElementMutable()!; } let nodeToPrependOfCorrectHeight: AstNode | undefined = nodeToAppend; // assert nodeToAppendOfCorrectHeight!.listHeight === curNode.listHeight for (let i = parents.length - 1; i >= 0; i--) { const parent = parents[i]; if (nodeToPrependOfCorrectHeight) { // Can we take the element? if (parent.childrenLength >= 3) { // assert parent.childrenLength === 3 && parent.listHeight === nodeToAppendOfCorrectHeight.listHeight + 1 // we need to split to maintain (2,3)-tree property. // Send the third element + the new element to the parent. nodeToPrependOfCorrectHeight = ListAstNode.create23(nodeToPrependOfCorrectHeight, parent.unprependChild()!, null, false); } else { parent.prependChildOfSameHeight(nodeToPrependOfCorrectHeight); nodeToPrependOfCorrectHeight = undefined; } } else { parent.handleChildrenChanged(); } } if (nodeToPrependOfCorrectHeight) { return ListAstNode.create23(nodeToPrependOfCorrectHeight, list, null, false); } else { return list; } }