immutable-tree.1.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Map as ImmutableMap, OrderedSet } from 'immutable';
  7. import { Transform } from './transform';
  8. export { ImmutableTree, TransientTree }
  9. /**
  10. * An immutable tree where each node requires a unique reference.
  11. * Represented as an immutable map.
  12. */
  13. interface ImmutableTree {
  14. readonly version: number,
  15. readonly root: Transform,
  16. readonly nodes: ImmutableTree.Nodes,
  17. readonly children: ImmutableTree.Children,
  18. asTransient(): TransientTree
  19. }
  20. namespace ImmutableTree {
  21. type Ref = Transform.Ref
  22. export interface ChildSet {
  23. readonly size: number,
  24. readonly values: OrderedSet<Ref>['values'],
  25. has(ref: Ref): boolean,
  26. readonly forEach: OrderedSet<Ref>['forEach']
  27. }
  28. export type Node = Transform
  29. export type Nodes = ImmutableMap<Ref, Transform>
  30. export type Children = ImmutableMap<Ref, ChildSet>
  31. class Impl implements ImmutableTree {
  32. get root() { return this.nodes.get(Transform.RootRef)! }
  33. asTransient(): TransientTree {
  34. return new TransientTree(this);
  35. }
  36. constructor(public nodes: ImmutableTree.Nodes, public children: Children, public version: number) {
  37. }
  38. }
  39. /**
  40. * Create an instance of an immutable tree.
  41. */
  42. export function create<T>(root: Transform): ImmutableTree {
  43. return new Impl(ImmutableMap([[root.ref, root]]), ImmutableMap([[root.ref, OrderedSet()]]), 0);
  44. }
  45. export function construct<T>(nodes: Nodes, children: Children, version: number): ImmutableTree {
  46. return new Impl(nodes, children, version);
  47. }
  48. type VisitorCtx = { tree: ImmutableTree, state: any, f: (node: Node, tree: ImmutableTree, state: any) => boolean | undefined | void };
  49. function _postOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPostOrder(this, this.tree.nodes.get(c!)); }
  50. function _doPostOrder(ctx: VisitorCtx, root: Node) {
  51. const children = ctx.tree.children.get(root.ref);
  52. if (checkSetRef && children.size) {
  53. children.forEach(_postOrderFunc, ctx);
  54. }
  55. ctx.f(root, ctx.tree, ctx.state);
  56. }
  57. /**
  58. * Visit all nodes in a subtree in "post order", meaning leafs get visited first.
  59. */
  60. export function doPostOrder<S>(tree: ImmutableTree, root: Node, state: S, f: (node: Node, tree: ImmutableTree, state: S) => boolean | undefined | void): S {
  61. const ctx: VisitorCtx = { tree, state, f };
  62. _doPostOrder(ctx, root);
  63. return ctx.state;
  64. }
  65. function _preOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPreOrder(this, this.tree.nodes.get(c!)); }
  66. function _doPreOrder(ctx: VisitorCtx, root: Node) {
  67. const ret = ctx.f(root, ctx.tree, ctx.state);
  68. if (typeof ret === 'boolean' && !ret) return;
  69. const children = ctx.tree.children.get(root.ref);
  70. if (checkSetRef && children.size) {
  71. children.forEach(_preOrderFunc, ctx);
  72. }
  73. }
  74. /**
  75. * Visit all nodes in a subtree in "pre order", meaning leafs get visited last.
  76. * If the visitor function returns false, the visiting for that branch is interrupted.
  77. */
  78. export function doPreOrder<S>(tree: ImmutableTree, root: Node, state: S, f: (node: Node, tree: ImmutableTree, state: S) => boolean | undefined | void): S {
  79. const ctx: VisitorCtx = { tree, state, f };
  80. _doPreOrder(ctx, root);
  81. return ctx.state;
  82. }
  83. function _subtree(n: Node, _: any, subtree: Node[]) { subtree.push(n); }
  84. /**
  85. * Get all nodes in a subtree, leafs come first.
  86. */
  87. export function subtreePostOrder<T>(tree: ImmutableTree, root: Node) {
  88. return doPostOrder<Node[]>(tree, root, [], _subtree);
  89. }
  90. function _visitChildToJson(this: Ref[], ref: Ref) { this.push(ref); }
  91. interface ToJsonCtx { tree: ImmutableTree, nodes: [Transform.Serialized, any[]][] }
  92. function _visitNodeToJson(this: ToJsonCtx, node: Node) {
  93. const children: Ref[] = [];
  94. this.tree.children.get(node.ref).forEach(_visitChildToJson as any, children);
  95. this.nodes.push([Transform.toJSON(node), children]);
  96. }
  97. export interface Serialized {
  98. nodes: [any /** value */, number[] /** children indices */][]
  99. }
  100. export function toJSON<T>(tree: ImmutableTree): Serialized {
  101. const ctx: ToJsonCtx = { tree, nodes: [] };
  102. tree.nodes.forEach(_visitNodeToJson as any, ctx);
  103. const map = new Map<string, number>();
  104. let i = 0;
  105. for (const n of ctx.nodes) map.set(n[0].ref, i++);
  106. for (const n of ctx.nodes) {
  107. const children = n[1];
  108. for (i = 0; i < children.length; i++) {
  109. children[i] = map.get(children[i]);
  110. }
  111. }
  112. return {
  113. nodes: ctx.nodes
  114. };
  115. }
  116. export function fromJSON<T>(data: Serialized): ImmutableTree {
  117. const nodes = ImmutableMap<Ref, Node>().asMutable();
  118. const children = ImmutableMap<Ref, OrderedSet<Ref>>().asMutable();
  119. const values = data.nodes.map(n => Transform.fromJSON(n[0]));
  120. let i = 0;
  121. for (const value of values) {
  122. const node = data.nodes[i++];
  123. const ref = value.ref;
  124. nodes.set(ref, value);
  125. children.set(ref, OrderedSet(node[1].map(c => values[c].ref)));
  126. }
  127. return new Impl(nodes.asImmutable(), children.asImmutable(), 0);
  128. }
  129. }
  130. class TransientTree implements ImmutableTree {
  131. nodes = this.tree.nodes.asMutable();
  132. children = this.tree.children.asMutable();
  133. version: number = this.tree.version + 1;
  134. private mutations: Map<Transform.Ref, OrderedSet<Transform.Ref>> = new Map();
  135. get root() { return this.nodes.get(Transform.RootRef)! }
  136. asTransient() {
  137. return this.asImmutable().asTransient();
  138. }
  139. private addChild(parent: Transform.Ref, child: Transform.Ref) {
  140. if (this.mutations.has(parent)) {
  141. this.mutations.get(parent)!.add(child);
  142. } else {
  143. const set = (this.children.get(parent) as OrderedSet<Transform.Ref>).asMutable();
  144. set.add(child);
  145. this.children.set(parent, set);
  146. this.mutations.set(parent, set);
  147. }
  148. }
  149. private removeChild(parent: Transform.Ref, child: Transform.Ref) {
  150. if (this.mutations.has(parent)) {
  151. this.mutations.get(parent)!.remove(child);
  152. } else {
  153. const set = (this.children.get(parent) as OrderedSet<Transform.Ref>).asMutable();
  154. set.remove(child);
  155. this.children.set(parent, set);
  156. this.mutations.set(parent, set);
  157. }
  158. }
  159. private clearRoot() {
  160. const parent = Transform.RootRef;
  161. if (this.children.get(parent).size === 0) return;
  162. const set = OrderedSet<Transform.Ref>();
  163. this.children.set(parent, set);
  164. this.mutations.set(parent, set);
  165. }
  166. add(transform: Transform) {
  167. const ref = transform.ref;
  168. const children = this.children.get(transform.parent);
  169. if (!children) parentNotPresent(transform.parent);
  170. if (!children.has(transform.ref)) {
  171. this.addChild(transform.parent, transform.ref);
  172. }
  173. if (!this.children.has(transform.ref)) {
  174. this.children.set(transform.ref, OrderedSet());
  175. }
  176. this.nodes.set(ref, transform);
  177. return transform;
  178. }
  179. set(transform: Transform) {
  180. ensurePresent(this.nodes, transform.ref);
  181. this.nodes.set(transform.ref, transform);
  182. }
  183. remove(ref: Transform.Ref): Transform[] {
  184. const { nodes, mutations, children } = this;
  185. const node = nodes.get(ref);
  186. if (!node) return [];
  187. const st = ImmutableTree.subtreePostOrder(this, node);
  188. if (ref === Transform.RootRef) {
  189. st.pop();
  190. this.clearRoot();
  191. } else {
  192. this.removeChild(node.parent, node.ref);
  193. }
  194. for (const n of st) {
  195. nodes.delete(n.ref);
  196. children.delete(n.ref);
  197. mutations.delete(n.ref);
  198. }
  199. return st;
  200. }
  201. // removeChildren(ref: ImmutableTree.Ref): TransientTree.Node[] {
  202. // const { nodes, mutations } = this;
  203. // let node = nodes.get(ref);
  204. // if (!node || !node.children.size) return [];
  205. // node = this.mutate(ref);
  206. // const st = ImmutableTree.subtreePostOrder(this, node);
  207. // // remove the last node which is the parent
  208. // st.pop();
  209. // node.children.clear();
  210. // for (const n of st) {
  211. // nodes.delete(n.value.ref);
  212. // mutations.delete(n.value.ref);
  213. // }
  214. // return st;
  215. // }
  216. asImmutable() {
  217. if (this.mutations.size === 0) return this.tree;
  218. this.mutations.forEach(fixChildMutations, this.children);
  219. return ImmutableTree.construct(this.nodes.asImmutable(), this.children.asImmutable(), this.version);
  220. }
  221. constructor(private tree: ImmutableTree) {
  222. }
  223. }
  224. function fixChildMutations(this: ImmutableTree.Children, m: OrderedSet<Transform.Ref>, k: Transform.Ref) { this.set(k, m.asImmutable()); }
  225. function checkSetRef(oldRef: Transform.Ref, newRef: Transform.Ref) {
  226. if (oldRef !== newRef) {
  227. throw new Error(`Cannot setValue of node '${oldRef}' because the new value has a different ref '${newRef}'.`);
  228. }
  229. }
  230. function parentNotPresent(ref: Transform.Ref) {
  231. throw new Error(`Parent '${ref}' must be present in the tree.`);
  232. }
  233. function ensurePresent(nodes: ImmutableTree.Nodes, ref: Transform.Ref) {
  234. if (!nodes.has(ref)) {
  235. throw new Error(`Node '${ref}' is not present in the tree.`);
  236. }
  237. }