SymbolTree.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. 'use strict';
  2. /**
  3. * @module symbol-tree
  4. * @author Joris van der Wel <joris@jorisvanderwel.com>
  5. */
  6. const SymbolTreeNode = require('./SymbolTreeNode');
  7. const TreePosition = require('./TreePosition');
  8. const TreeIterator = require('./TreeIterator');
  9. function returnTrue() {
  10. return true;
  11. }
  12. function reverseArrayIndex(array, reverseIndex) {
  13. return array[array.length - 1 - reverseIndex]; // no need to check `index >= 0`
  14. }
  15. class SymbolTree {
  16. /**
  17. * @constructor
  18. * @alias module:symbol-tree
  19. * @param {string} [description='SymbolTree data'] Description used for the Symbol
  20. */
  21. constructor(description) {
  22. this.symbol = Symbol(description || 'SymbolTree data');
  23. }
  24. /**
  25. * You can use this function to (optionally) initialize an object right after its creation,
  26. * to take advantage of V8's fast properties. Also useful if you would like to
  27. * freeze your object.
  28. *
  29. * `O(1)`
  30. *
  31. * @method
  32. * @alias module:symbol-tree#initialize
  33. * @param {Object} object
  34. * @return {Object} object
  35. */
  36. initialize(object) {
  37. this._node(object);
  38. return object;
  39. }
  40. _node(object) {
  41. if (!object) {
  42. return null;
  43. }
  44. const node = object[this.symbol];
  45. if (node) {
  46. return node;
  47. }
  48. return (object[this.symbol] = new SymbolTreeNode());
  49. }
  50. /**
  51. * Returns `true` if the object has any children. Otherwise it returns `false`.
  52. *
  53. * * `O(1)`
  54. *
  55. * @method hasChildren
  56. * @memberOf module:symbol-tree#
  57. * @param {Object} object
  58. * @return {Boolean}
  59. */
  60. hasChildren(object) {
  61. return this._node(object).hasChildren;
  62. }
  63. /**
  64. * Returns the first child of the given object.
  65. *
  66. * * `O(1)`
  67. *
  68. * @method firstChild
  69. * @memberOf module:symbol-tree#
  70. * @param {Object} object
  71. * @return {Object}
  72. */
  73. firstChild(object) {
  74. return this._node(object).firstChild;
  75. }
  76. /**
  77. * Returns the last child of the given object.
  78. *
  79. * * `O(1)`
  80. *
  81. * @method lastChild
  82. * @memberOf module:symbol-tree#
  83. * @param {Object} object
  84. * @return {Object}
  85. */
  86. lastChild(object) {
  87. return this._node(object).lastChild;
  88. }
  89. /**
  90. * Returns the previous sibling of the given object.
  91. *
  92. * * `O(1)`
  93. *
  94. * @method previousSibling
  95. * @memberOf module:symbol-tree#
  96. * @param {Object} object
  97. * @return {Object}
  98. */
  99. previousSibling(object) {
  100. return this._node(object).previousSibling;
  101. }
  102. /**
  103. * Returns the next sibling of the given object.
  104. *
  105. * * `O(1)`
  106. *
  107. * @method nextSibling
  108. * @memberOf module:symbol-tree#
  109. * @param {Object} object
  110. * @return {Object}
  111. */
  112. nextSibling(object) {
  113. return this._node(object).nextSibling;
  114. }
  115. /**
  116. * Return the parent of the given object.
  117. *
  118. * * `O(1)`
  119. *
  120. * @method parent
  121. * @memberOf module:symbol-tree#
  122. * @param {Object} object
  123. * @return {Object}
  124. */
  125. parent(object) {
  126. return this._node(object).parent;
  127. }
  128. /**
  129. * Find the inclusive descendant that is last in tree order of the given object.
  130. *
  131. * * `O(n)` (worst case) where `n` is the depth of the subtree of `object`
  132. *
  133. * @method lastInclusiveDescendant
  134. * @memberOf module:symbol-tree#
  135. * @param {Object} object
  136. * @return {Object}
  137. */
  138. lastInclusiveDescendant(object) {
  139. let lastChild;
  140. let current = object;
  141. while ((lastChild = this._node(current).lastChild)) {
  142. current = lastChild;
  143. }
  144. return current;
  145. }
  146. /**
  147. * Find the preceding object (A) of the given object (B).
  148. * An object A is preceding an object B if A and B are in the same tree
  149. * and A comes before B in tree order.
  150. *
  151. * * `O(n)` (worst case)
  152. * * `O(1)` (amortized when walking the entire tree)
  153. *
  154. * @method preceding
  155. * @memberOf module:symbol-tree#
  156. * @param {Object} object
  157. * @param {Object} [options]
  158. * @param {Object} [options.root] If set, `root` must be an inclusive ancestor
  159. * of the return value (or else null is returned). This check _assumes_
  160. * that `root` is also an inclusive ancestor of the given `object`
  161. * @return {?Object}
  162. */
  163. preceding(object, options) {
  164. const treeRoot = options && options.root;
  165. if (object === treeRoot) {
  166. return null;
  167. }
  168. const previousSibling = this._node(object).previousSibling;
  169. if (previousSibling) {
  170. return this.lastInclusiveDescendant(previousSibling);
  171. }
  172. // if there is no previous sibling return the parent (might be null)
  173. return this._node(object).parent;
  174. }
  175. /**
  176. * Find the following object (A) of the given object (B).
  177. * An object A is following an object B if A and B are in the same tree
  178. * and A comes after B in tree order.
  179. *
  180. * * `O(n)` (worst case) where `n` is the amount of objects in the entire tree
  181. * * `O(1)` (amortized when walking the entire tree)
  182. *
  183. * @method following
  184. * @memberOf module:symbol-tree#
  185. * @param {!Object} object
  186. * @param {Object} [options]
  187. * @param {Object} [options.root] If set, `root` must be an inclusive ancestor
  188. * of the return value (or else null is returned). This check _assumes_
  189. * that `root` is also an inclusive ancestor of the given `object`
  190. * @param {Boolean} [options.skipChildren=false] If set, ignore the children of `object`
  191. * @return {?Object}
  192. */
  193. following(object, options) {
  194. const treeRoot = options && options.root;
  195. const skipChildren = options && options.skipChildren;
  196. const firstChild = !skipChildren && this._node(object).firstChild;
  197. if (firstChild) {
  198. return firstChild;
  199. }
  200. let current = object;
  201. do {
  202. if (current === treeRoot) {
  203. return null;
  204. }
  205. const nextSibling = this._node(current).nextSibling;
  206. if (nextSibling) {
  207. return nextSibling;
  208. }
  209. current = this._node(current).parent;
  210. } while (current);
  211. return null;
  212. }
  213. /**
  214. * Append all children of the given object to an array.
  215. *
  216. * * `O(n)` where `n` is the amount of children of the given `parent`
  217. *
  218. * @method childrenToArray
  219. * @memberOf module:symbol-tree#
  220. * @param {Object} parent
  221. * @param {Object} [options]
  222. * @param {Object[]} [options.array=[]]
  223. * @param {Function} [options.filter] Function to test each object before it is added to the array.
  224. * Invoked with arguments (object). Should return `true` if an object
  225. * is to be included.
  226. * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.
  227. * @return {Object[]}
  228. */
  229. childrenToArray(parent, options) {
  230. const array = (options && options.array) || [];
  231. const filter = (options && options.filter) || returnTrue;
  232. const thisArg = (options && options.thisArg) || undefined;
  233. const parentNode = this._node(parent);
  234. let object = parentNode.firstChild;
  235. let index = 0;
  236. while (object) {
  237. const node = this._node(object);
  238. node.setCachedIndex(parentNode, index);
  239. if (filter.call(thisArg, object)) {
  240. array.push(object);
  241. }
  242. object = node.nextSibling;
  243. ++index;
  244. }
  245. return array;
  246. }
  247. /**
  248. * Append all inclusive ancestors of the given object to an array.
  249. *
  250. * * `O(n)` where `n` is the amount of ancestors of the given `object`
  251. *
  252. * @method ancestorsToArray
  253. * @memberOf module:symbol-tree#
  254. * @param {Object} object
  255. * @param {Object} [options]
  256. * @param {Object[]} [options.array=[]]
  257. * @param {Function} [options.filter] Function to test each object before it is added to the array.
  258. * Invoked with arguments (object). Should return `true` if an object
  259. * is to be included.
  260. * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.
  261. * @return {Object[]}
  262. */
  263. ancestorsToArray(object, options) {
  264. const array = (options && options.array) || [];
  265. const filter = (options && options.filter) || returnTrue;
  266. const thisArg = (options && options.thisArg) || undefined;
  267. let ancestor = object;
  268. while (ancestor) {
  269. if (filter.call(thisArg, ancestor)) {
  270. array.push(ancestor);
  271. }
  272. ancestor = this._node(ancestor).parent;
  273. }
  274. return array;
  275. }
  276. /**
  277. * Append all descendants of the given object to an array (in tree order).
  278. *
  279. * * `O(n)` where `n` is the amount of objects in the sub-tree of the given `object`
  280. *
  281. * @method treeToArray
  282. * @memberOf module:symbol-tree#
  283. * @param {Object} root
  284. * @param {Object} [options]
  285. * @param {Object[]} [options.array=[]]
  286. * @param {Function} [options.filter] Function to test each object before it is added to the array.
  287. * Invoked with arguments (object). Should return `true` if an object
  288. * is to be included.
  289. * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.
  290. * @return {Object[]}
  291. */
  292. treeToArray(root, options) {
  293. const array = (options && options.array) || [];
  294. const filter = (options && options.filter) || returnTrue;
  295. const thisArg = (options && options.thisArg) || undefined;
  296. let object = root;
  297. while (object) {
  298. if (filter.call(thisArg, object)) {
  299. array.push(object);
  300. }
  301. object = this.following(object, {root: root});
  302. }
  303. return array;
  304. }
  305. /**
  306. * Iterate over all children of the given object
  307. *
  308. * * `O(1)` for a single iteration
  309. *
  310. * @method childrenIterator
  311. * @memberOf module:symbol-tree#
  312. * @param {Object} parent
  313. * @param {Object} [options]
  314. * @param {Boolean} [options.reverse=false]
  315. * @return {Object} An iterable iterator (ES6)
  316. */
  317. childrenIterator(parent, options) {
  318. const reverse = options && options.reverse;
  319. const parentNode = this._node(parent);
  320. return new TreeIterator(
  321. this,
  322. parent,
  323. reverse ? parentNode.lastChild : parentNode.firstChild,
  324. reverse ? TreeIterator.PREV : TreeIterator.NEXT
  325. );
  326. }
  327. /**
  328. * Iterate over all the previous siblings of the given object. (in reverse tree order)
  329. *
  330. * * `O(1)` for a single iteration
  331. *
  332. * @method previousSiblingsIterator
  333. * @memberOf module:symbol-tree#
  334. * @param {Object} object
  335. * @return {Object} An iterable iterator (ES6)
  336. */
  337. previousSiblingsIterator(object) {
  338. return new TreeIterator(
  339. this,
  340. object,
  341. this._node(object).previousSibling,
  342. TreeIterator.PREV
  343. );
  344. }
  345. /**
  346. * Iterate over all the next siblings of the given object. (in tree order)
  347. *
  348. * * `O(1)` for a single iteration
  349. *
  350. * @method nextSiblingsIterator
  351. * @memberOf module:symbol-tree#
  352. * @param {Object} object
  353. * @return {Object} An iterable iterator (ES6)
  354. */
  355. nextSiblingsIterator(object) {
  356. return new TreeIterator(
  357. this,
  358. object,
  359. this._node(object).nextSibling,
  360. TreeIterator.NEXT
  361. );
  362. }
  363. /**
  364. * Iterate over all inclusive ancestors of the given object
  365. *
  366. * * `O(1)` for a single iteration
  367. *
  368. * @method ancestorsIterator
  369. * @memberOf module:symbol-tree#
  370. * @param {Object} object
  371. * @return {Object} An iterable iterator (ES6)
  372. */
  373. ancestorsIterator(object) {
  374. return new TreeIterator(
  375. this,
  376. object,
  377. object,
  378. TreeIterator.PARENT
  379. );
  380. }
  381. /**
  382. * Iterate over all descendants of the given object (in tree order).
  383. *
  384. * Where `n` is the amount of objects in the sub-tree of the given `root`:
  385. *
  386. * * `O(n)` (worst case for a single iteration)
  387. * * `O(n)` (amortized, when completing the iterator)
  388. *
  389. * @method treeIterator
  390. * @memberOf module:symbol-tree#
  391. * @param {Object} root
  392. * @param {Object} options
  393. * @param {Boolean} [options.reverse=false]
  394. * @return {Object} An iterable iterator (ES6)
  395. */
  396. treeIterator(root, options) {
  397. const reverse = options && options.reverse;
  398. return new TreeIterator(
  399. this,
  400. root,
  401. reverse ? this.lastInclusiveDescendant(root) : root,
  402. reverse ? TreeIterator.PRECEDING : TreeIterator.FOLLOWING
  403. );
  404. }
  405. /**
  406. * Find the index of the given object (the number of preceding siblings).
  407. *
  408. * * `O(n)` where `n` is the amount of preceding siblings
  409. * * `O(1)` (amortized, if the tree is not modified)
  410. *
  411. * @method index
  412. * @memberOf module:symbol-tree#
  413. * @param {Object} child
  414. * @return {Number} The number of preceding siblings, or -1 if the object has no parent
  415. */
  416. index(child) {
  417. const childNode = this._node(child);
  418. const parentNode = this._node(childNode.parent);
  419. if (!parentNode) {
  420. // In principal, you could also find out the number of preceding siblings
  421. // for objects that do not have a parent. This method limits itself only to
  422. // objects that have a parent because that lets us optimize more.
  423. return -1;
  424. }
  425. let currentIndex = childNode.getCachedIndex(parentNode);
  426. if (currentIndex >= 0) {
  427. return currentIndex;
  428. }
  429. currentIndex = 0;
  430. let object = parentNode.firstChild;
  431. if (parentNode.childIndexCachedUpTo) {
  432. const cachedUpToNode = this._node(parentNode.childIndexCachedUpTo);
  433. object = cachedUpToNode.nextSibling;
  434. currentIndex = cachedUpToNode.getCachedIndex(parentNode) + 1;
  435. }
  436. while (object) {
  437. const node = this._node(object);
  438. node.setCachedIndex(parentNode, currentIndex);
  439. if (object === child) {
  440. break;
  441. }
  442. ++currentIndex;
  443. object = node.nextSibling;
  444. }
  445. parentNode.childIndexCachedUpTo = child;
  446. return currentIndex;
  447. }
  448. /**
  449. * Calculate the number of children.
  450. *
  451. * * `O(n)` where `n` is the amount of children
  452. * * `O(1)` (amortized, if the tree is not modified)
  453. *
  454. * @method childrenCount
  455. * @memberOf module:symbol-tree#
  456. * @param {Object} parent
  457. * @return {Number}
  458. */
  459. childrenCount(parent) {
  460. const parentNode = this._node(parent);
  461. if (!parentNode.lastChild) {
  462. return 0;
  463. }
  464. return this.index(parentNode.lastChild) + 1;
  465. }
  466. /**
  467. * Compare the position of an object relative to another object. A bit set is returned:
  468. *
  469. * <ul>
  470. * <li>DISCONNECTED : 1</li>
  471. * <li>PRECEDING : 2</li>
  472. * <li>FOLLOWING : 4</li>
  473. * <li>CONTAINS : 8</li>
  474. * <li>CONTAINED_BY : 16</li>
  475. * </ul>
  476. *
  477. * The semantics are the same as compareDocumentPosition in DOM, with the exception that
  478. * DISCONNECTED never occurs with any other bit.
  479. *
  480. * where `n` and `m` are the amount of ancestors of `left` and `right`;
  481. * where `o` is the amount of children of the lowest common ancestor of `left` and `right`:
  482. *
  483. * * `O(n + m + o)` (worst case)
  484. * * `O(n + m)` (amortized, if the tree is not modified)
  485. *
  486. * @method compareTreePosition
  487. * @memberOf module:symbol-tree#
  488. * @param {Object} left
  489. * @param {Object} right
  490. * @return {Number}
  491. */
  492. compareTreePosition(left, right) {
  493. // In DOM terms:
  494. // left = reference / context object
  495. // right = other
  496. if (left === right) {
  497. return 0;
  498. }
  499. /* jshint -W016 */
  500. const leftAncestors = []; { // inclusive
  501. let leftAncestor = left;
  502. while (leftAncestor) {
  503. if (leftAncestor === right) {
  504. return TreePosition.CONTAINS | TreePosition.PRECEDING;
  505. // other is ancestor of reference
  506. }
  507. leftAncestors.push(leftAncestor);
  508. leftAncestor = this.parent(leftAncestor);
  509. }
  510. }
  511. const rightAncestors = []; {
  512. let rightAncestor = right;
  513. while (rightAncestor) {
  514. if (rightAncestor === left) {
  515. return TreePosition.CONTAINED_BY | TreePosition.FOLLOWING;
  516. }
  517. rightAncestors.push(rightAncestor);
  518. rightAncestor = this.parent(rightAncestor);
  519. }
  520. }
  521. const root = reverseArrayIndex(leftAncestors, 0);
  522. if (!root || root !== reverseArrayIndex(rightAncestors, 0)) {
  523. // note: unlike DOM, preceding / following is not set here
  524. return TreePosition.DISCONNECTED;
  525. }
  526. // find the lowest common ancestor
  527. let commonAncestorIndex = 0;
  528. const ancestorsMinLength = Math.min(leftAncestors.length, rightAncestors.length);
  529. for (let i = 0; i < ancestorsMinLength; ++i) {
  530. const leftAncestor = reverseArrayIndex(leftAncestors, i);
  531. const rightAncestor = reverseArrayIndex(rightAncestors, i);
  532. if (leftAncestor !== rightAncestor) {
  533. break;
  534. }
  535. commonAncestorIndex = i;
  536. }
  537. // indexes within the common ancestor
  538. const leftIndex = this.index(reverseArrayIndex(leftAncestors, commonAncestorIndex + 1));
  539. const rightIndex = this.index(reverseArrayIndex(rightAncestors, commonAncestorIndex + 1));
  540. return rightIndex < leftIndex
  541. ? TreePosition.PRECEDING
  542. : TreePosition.FOLLOWING;
  543. }
  544. /**
  545. * Remove the object from this tree.
  546. * Has no effect if already removed.
  547. *
  548. * * `O(1)`
  549. *
  550. * @method remove
  551. * @memberOf module:symbol-tree#
  552. * @param {Object} removeObject
  553. * @return {Object} removeObject
  554. */
  555. remove(removeObject) {
  556. const removeNode = this._node(removeObject);
  557. const parentNode = this._node(removeNode.parent);
  558. const prevNode = this._node(removeNode.previousSibling);
  559. const nextNode = this._node(removeNode.nextSibling);
  560. if (parentNode) {
  561. if (parentNode.firstChild === removeObject) {
  562. parentNode.firstChild = removeNode.nextSibling;
  563. }
  564. if (parentNode.lastChild === removeObject) {
  565. parentNode.lastChild = removeNode.previousSibling;
  566. }
  567. }
  568. if (prevNode) {
  569. prevNode.nextSibling = removeNode.nextSibling;
  570. }
  571. if (nextNode) {
  572. nextNode.previousSibling = removeNode.previousSibling;
  573. }
  574. removeNode.parent = null;
  575. removeNode.previousSibling = null;
  576. removeNode.nextSibling = null;
  577. removeNode.cachedIndex = -1;
  578. removeNode.cachedIndexVersion = NaN;
  579. if (parentNode) {
  580. parentNode.childrenChanged();
  581. }
  582. return removeObject;
  583. }
  584. /**
  585. * Insert the given object before the reference object.
  586. * `newObject` is now the previous sibling of `referenceObject`.
  587. *
  588. * * `O(1)`
  589. *
  590. * @method insertBefore
  591. * @memberOf module:symbol-tree#
  592. * @param {Object} referenceObject
  593. * @param {Object} newObject
  594. * @throws {Error} If the newObject is already present in this SymbolTree
  595. * @return {Object} newObject
  596. */
  597. insertBefore(referenceObject, newObject) {
  598. const referenceNode = this._node(referenceObject);
  599. const prevNode = this._node(referenceNode.previousSibling);
  600. const newNode = this._node(newObject);
  601. const parentNode = this._node(referenceNode.parent);
  602. if (newNode.isAttached) {
  603. throw Error('Given object is already present in this SymbolTree, remove it first');
  604. }
  605. newNode.parent = referenceNode.parent;
  606. newNode.previousSibling = referenceNode.previousSibling;
  607. newNode.nextSibling = referenceObject;
  608. referenceNode.previousSibling = newObject;
  609. if (prevNode) {
  610. prevNode.nextSibling = newObject;
  611. }
  612. if (parentNode && parentNode.firstChild === referenceObject) {
  613. parentNode.firstChild = newObject;
  614. }
  615. if (parentNode) {
  616. parentNode.childrenChanged();
  617. }
  618. return newObject;
  619. }
  620. /**
  621. * Insert the given object after the reference object.
  622. * `newObject` is now the next sibling of `referenceObject`.
  623. *
  624. * * `O(1)`
  625. *
  626. * @method insertAfter
  627. * @memberOf module:symbol-tree#
  628. * @param {Object} referenceObject
  629. * @param {Object} newObject
  630. * @throws {Error} If the newObject is already present in this SymbolTree
  631. * @return {Object} newObject
  632. */
  633. insertAfter(referenceObject, newObject) {
  634. const referenceNode = this._node(referenceObject);
  635. const nextNode = this._node(referenceNode.nextSibling);
  636. const newNode = this._node(newObject);
  637. const parentNode = this._node(referenceNode.parent);
  638. if (newNode.isAttached) {
  639. throw Error('Given object is already present in this SymbolTree, remove it first');
  640. }
  641. newNode.parent = referenceNode.parent;
  642. newNode.previousSibling = referenceObject;
  643. newNode.nextSibling = referenceNode.nextSibling;
  644. referenceNode.nextSibling = newObject;
  645. if (nextNode) {
  646. nextNode.previousSibling = newObject;
  647. }
  648. if (parentNode && parentNode.lastChild === referenceObject) {
  649. parentNode.lastChild = newObject;
  650. }
  651. if (parentNode) {
  652. parentNode.childrenChanged();
  653. }
  654. return newObject;
  655. }
  656. /**
  657. * Insert the given object as the first child of the given reference object.
  658. * `newObject` is now the first child of `referenceObject`.
  659. *
  660. * * `O(1)`
  661. *
  662. * @method prependChild
  663. * @memberOf module:symbol-tree#
  664. * @param {Object} referenceObject
  665. * @param {Object} newObject
  666. * @throws {Error} If the newObject is already present in this SymbolTree
  667. * @return {Object} newObject
  668. */
  669. prependChild(referenceObject, newObject) {
  670. const referenceNode = this._node(referenceObject);
  671. const newNode = this._node(newObject);
  672. if (newNode.isAttached) {
  673. throw Error('Given object is already present in this SymbolTree, remove it first');
  674. }
  675. if (referenceNode.hasChildren) {
  676. this.insertBefore(referenceNode.firstChild, newObject);
  677. }
  678. else {
  679. newNode.parent = referenceObject;
  680. referenceNode.firstChild = newObject;
  681. referenceNode.lastChild = newObject;
  682. referenceNode.childrenChanged();
  683. }
  684. return newObject;
  685. }
  686. /**
  687. * Insert the given object as the last child of the given reference object.
  688. * `newObject` is now the last child of `referenceObject`.
  689. *
  690. * * `O(1)`
  691. *
  692. * @method appendChild
  693. * @memberOf module:symbol-tree#
  694. * @param {Object} referenceObject
  695. * @param {Object} newObject
  696. * @throws {Error} If the newObject is already present in this SymbolTree
  697. * @return {Object} newObject
  698. */
  699. appendChild(referenceObject, newObject) {
  700. const referenceNode = this._node(referenceObject);
  701. const newNode = this._node(newObject);
  702. if (newNode.isAttached) {
  703. throw Error('Given object is already present in this SymbolTree, remove it first');
  704. }
  705. if (referenceNode.hasChildren) {
  706. this.insertAfter(referenceNode.lastChild, newObject);
  707. }
  708. else {
  709. newNode.parent = referenceObject;
  710. referenceNode.firstChild = newObject;
  711. referenceNode.lastChild = newObject;
  712. referenceNode.childrenChanged();
  713. }
  714. return newObject;
  715. }
  716. }
  717. module.exports = SymbolTree;
  718. SymbolTree.TreePosition = TreePosition;