immutable.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. import { TransientTree } from './transient';
  9. import { StateTreeBuilder } from './builder';
  10. export { StateTree }
  11. /**
  12. * An immutable tree where each node requires a unique reference.
  13. * Represented as an immutable map.
  14. */
  15. interface StateTree {
  16. readonly root: Transform,
  17. readonly nodes: StateTree.Nodes,
  18. readonly children: StateTree.Children,
  19. asTransient(): TransientTree,
  20. build(): StateTreeBuilder.Root
  21. }
  22. namespace StateTree {
  23. type Ref = Transform.Ref
  24. export interface ChildSet {
  25. readonly size: number,
  26. readonly values: OrderedSet<Ref>['values'],
  27. has(ref: Ref): boolean,
  28. readonly forEach: OrderedSet<Ref>['forEach'],
  29. readonly map: OrderedSet<Ref>['map']
  30. }
  31. export type Node = Transform
  32. export type Nodes = ImmutableMap<Ref, Transform>
  33. export type Children = ImmutableMap<Ref, ChildSet>
  34. class Impl implements StateTree {
  35. get root() { return this.nodes.get(Transform.RootRef)! }
  36. asTransient(): TransientTree {
  37. return new TransientTree(this);
  38. }
  39. build(): StateTreeBuilder.Root {
  40. return new StateTreeBuilder.Root(this);
  41. }
  42. constructor(public nodes: StateTree.Nodes, public children: Children) {
  43. }
  44. }
  45. /**
  46. * Create an instance of an immutable tree.
  47. */
  48. export function createEmpty(): StateTree {
  49. const root = Transform.createRoot();
  50. return create(ImmutableMap([[root.ref, root]]), ImmutableMap([[root.ref, OrderedSet()]]));
  51. }
  52. export function create(nodes: Nodes, children: Children): StateTree {
  53. return new Impl(nodes, children);
  54. }
  55. type VisitorCtx = { tree: StateTree, state: any, f: (node: Node, tree: StateTree, state: any) => boolean | undefined | void };
  56. function _postOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPostOrder(this, this.tree.nodes.get(c!)); }
  57. function _doPostOrder(ctx: VisitorCtx, root: Node) {
  58. const children = ctx.tree.children.get(root.ref);
  59. if (children && children.size) {
  60. children.forEach(_postOrderFunc, ctx);
  61. }
  62. ctx.f(root, ctx.tree, ctx.state);
  63. }
  64. /**
  65. * Visit all nodes in a subtree in "post order", meaning leafs get visited first.
  66. */
  67. export function doPostOrder<S>(tree: StateTree, root: Node, state: S, f: (node: Node, tree: StateTree, state: S) => boolean | undefined | void): S {
  68. const ctx: VisitorCtx = { tree, state, f };
  69. _doPostOrder(ctx, root);
  70. return ctx.state;
  71. }
  72. function _preOrderFunc(this: VisitorCtx, c: Ref | undefined) { _doPreOrder(this, this.tree.nodes.get(c!)); }
  73. function _doPreOrder(ctx: VisitorCtx, root: Node) {
  74. const ret = ctx.f(root, ctx.tree, ctx.state);
  75. if (typeof ret === 'boolean' && !ret) return;
  76. const children = ctx.tree.children.get(root.ref);
  77. if (children && children.size) {
  78. children.forEach(_preOrderFunc, ctx);
  79. }
  80. }
  81. /**
  82. * Visit all nodes in a subtree in "pre order", meaning leafs get visited last.
  83. * If the visitor function returns false, the visiting for that branch is interrupted.
  84. */
  85. export function doPreOrder<S>(tree: StateTree, root: Node, state: S, f: (node: Node, tree: StateTree, state: S) => boolean | undefined | void): S {
  86. const ctx: VisitorCtx = { tree, state, f };
  87. _doPreOrder(ctx, root);
  88. return ctx.state;
  89. }
  90. function _subtree(n: Node, _: any, subtree: Node[]) { subtree.push(n); }
  91. /**
  92. * Get all nodes in a subtree, leafs come first.
  93. */
  94. export function subtreePostOrder<T>(tree: StateTree, root: Node) {
  95. return doPostOrder<Node[]>(tree, root, [], _subtree);
  96. }
  97. // function _visitChildToJson(this: Ref[], ref: Ref) { this.push(ref); }
  98. // interface ToJsonCtx { nodes: Transform.Serialized[] }
  99. function _visitNodeToJson(node: Node, tree: StateTree, ctx: Transform.Serialized[]) {
  100. // const children: Ref[] = [];
  101. // tree.children.get(node.ref).forEach(_visitChildToJson as any, children);
  102. ctx.push(Transform.toJSON(node));
  103. }
  104. export interface Serialized {
  105. /** Tree nodes serialized in pre-order */
  106. nodes: Transform.Serialized[]
  107. }
  108. export function toJSON<T>(tree: StateTree): Serialized {
  109. const nodes: Transform.Serialized[] = [];
  110. doPreOrder(tree, tree.root, nodes, _visitNodeToJson);
  111. return { nodes };
  112. }
  113. export function fromJSON<T>(data: Serialized): StateTree {
  114. const nodes = ImmutableMap<Ref, Node>().asMutable();
  115. const children = ImmutableMap<Ref, OrderedSet<Ref>>().asMutable();
  116. for (const s of data.nodes) {
  117. const node = Transform.fromJSON(s);
  118. nodes.set(node.ref, node);
  119. if (!children.has(node.ref)) {
  120. children.set(node.ref, OrderedSet<Ref>().asMutable());
  121. }
  122. if (node.ref !== node.parent) children.get(node.parent).add(node.ref);
  123. }
  124. for (const s of data.nodes) {
  125. children.set(s.ref, children.get(s.ref).asImmutable());
  126. }
  127. return create(nodes.asImmutable(), children.asImmutable());
  128. }
  129. }