UNPKG

8.21 kBPlain TextView Raw
1// *****************************************************************************
2// Copyright (C) 2017 TypeFox and others.
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License v. 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0.
7//
8// This Source Code may also be made available under the following Secondary
9// Licenses when the conditions for such availability set forth in the Eclipse
10// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11// with the GNU Classpath Exception which is available at
12// https://www.gnu.org/software/classpath/license.html.
13//
14// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15// *****************************************************************************
16
17import { TreeNode, CompositeTreeNode } from './tree';
18import { ExpandableTreeNode } from './tree-expansion';
19
20export interface TreeIterator extends Iterator<TreeNode> {
21}
22
23export namespace TreeIterator {
24
25 export interface Options {
26 readonly pruneCollapsed: boolean
27 readonly pruneSiblings: boolean
28 }
29
30 export const DEFAULT_OPTIONS: Options = {
31 pruneCollapsed: false,
32 pruneSiblings: false
33 };
34
35}
36
37export abstract class AbstractTreeIterator implements TreeIterator, Iterable<TreeNode> {
38
39 protected readonly delegate: IterableIterator<TreeNode>;
40 protected readonly options: TreeIterator.Options;
41
42 constructor(protected readonly root: TreeNode, options?: Partial<TreeIterator.Options>) {
43 this.options = {
44 ...TreeIterator.DEFAULT_OPTIONS,
45 ...options
46 };
47 this.delegate = this.iterator(this.root);
48 }
49
50 // tslint:disable-next-line:typedef
51 [Symbol.iterator]() {
52 return this.delegate;
53 }
54
55 next(): IteratorResult<TreeNode> {
56 return this.delegate.next();
57 }
58
59 protected abstract iterator(node: TreeNode): IterableIterator<TreeNode>;
60
61 protected children(node: TreeNode): TreeNode[] | undefined {
62 if (!CompositeTreeNode.is(node)) {
63 return undefined;
64 }
65 if (this.options.pruneCollapsed && this.isCollapsed(node)) {
66 return undefined;
67 }
68 return node.children.slice();
69 }
70
71 protected isCollapsed(node: TreeNode): boolean {
72 return ExpandableTreeNode.isCollapsed(node);
73 }
74
75 protected isEmpty(nodes: TreeNode[] | undefined): boolean {
76 return nodes === undefined || nodes.length === 0;
77 }
78
79}
80
81export class DepthFirstTreeIterator extends AbstractTreeIterator {
82
83 protected iterator(root: TreeNode): IterableIterator<TreeNode> {
84 return Iterators.depthFirst(root, this.children.bind(this));
85 }
86
87}
88
89export class BreadthFirstTreeIterator extends AbstractTreeIterator {
90
91 protected iterator(root: TreeNode): IterableIterator<TreeNode> {
92 return Iterators.breadthFirst(root, this.children.bind(this));
93 }
94
95}
96
97/**
98 * This tree iterator visits all nodes from top to bottom considering the following rules.
99 *
100 * Let assume the following tree:
101 * ```
102 * R
103 * |
104 * +---1
105 * | |
106 * | +---1.1
107 * | |
108 * | +---1.2
109 * | |
110 * | +---1.3
111 * | | |
112 * | | +---1.3.1
113 * | | |
114 * | | +---1.3.2
115 * | |
116 * | +---1.4
117 * |
118 * +---2
119 * |
120 * +---2.1
121 * ```
122 * When selecting `1.2` as the root, the normal `DepthFirstTreeIterator` would stop on `1.2` as it does not have children,
123 * but this iterator will visit the next sibling (`1.3` and `1.4` but **not** `1.1`) nodes. So the expected traversal order will be
124 * `1.2`, `1.3`, `1.3.1`, `1.3.2`, and `1.4` then jumps to `2` and continues with `2.1`.
125 */
126export class TopDownTreeIterator extends AbstractTreeIterator {
127
128 protected iterator(root: TreeNode): IterableIterator<TreeNode> {
129 const doNext = this.doNext.bind(this);
130 return (function* (): IterableIterator<TreeNode> {
131 let next = root;
132 while (next) {
133 yield next;
134 next = doNext(next);
135 }
136 }).bind(this)();
137 }
138
139 protected doNext(node: TreeNode): TreeNode | undefined {
140 return this.findFirstChild(node) || this.findNextSibling(node);
141 }
142
143 protected findFirstChild(node: TreeNode): TreeNode | undefined {
144 return (this.children(node) || [])[0];
145 }
146
147 protected findNextSibling(node: TreeNode | undefined): TreeNode | undefined {
148 if (!node) {
149 return undefined;
150 }
151 if (this.options.pruneSiblings && node === this.root) {
152 return undefined;
153 }
154 if (node.nextSibling) {
155 return node.nextSibling;
156 }
157 return this.findNextSibling(node.parent);
158 }
159
160}
161
162/**
163 * Unlike other tree iterators, this does not visit all the nodes, it stops once it reaches the root node
164 * while traversing up the tree hierarchy in an inverse pre-order fashion. This is the counterpart of the `TopDownTreeIterator`.
165 */
166export class BottomUpTreeIterator extends AbstractTreeIterator {
167
168 protected iterator(root: TreeNode): IterableIterator<TreeNode> {
169 const doNext = this.doNext.bind(this);
170 return (function* (): IterableIterator<TreeNode> {
171 let next = root;
172 while (next) {
173 yield next;
174 next = doNext(next);
175 }
176 }).bind(this)();
177 }
178
179 protected doNext(node: TreeNode): TreeNode | undefined {
180 const previousSibling = node.previousSibling;
181 const lastChild = this.lastChild(previousSibling);
182 return lastChild || node.parent;
183 }
184
185 protected lastChild(node: TreeNode | undefined): TreeNode | undefined {
186 const children = node ? this.children(node) : [];
187 if (this.isEmpty(children)) {
188 return node;
189 }
190 if (CompositeTreeNode.is(node)) {
191 const lastChild = CompositeTreeNode.getLastChild(node);
192 return this.lastChild(lastChild);
193 }
194 return undefined;
195 }
196
197}
198
199export namespace Iterators {
200
201 /**
202 * Generator for depth first, pre-order tree traversal iteration.
203 */
204 export function* depthFirst<T>(root: T, children: (node: T) => T[] | undefined, include: (node: T) => boolean = () => true): IterableIterator<T> {
205 let stack: T[] = [];
206 stack.push(root);
207 while (stack.length > 0) {
208 const top = stack.pop()!;
209 yield top;
210 stack = stack.concat((children(top) || []).filter(include).reverse());
211 }
212 }
213
214 /**
215 * Generator for breadth first tree traversal iteration.
216 */
217 export function* breadthFirst<T>(root: T, children: (node: T) => T[] | undefined, include: (node: T) => boolean = () => true): IterableIterator<T> {
218 let queue: T[] = [];
219 queue.push(root);
220 while (queue.length > 0) {
221 const head = queue.shift()!;
222 yield head;
223 queue = queue.concat((children(head) || []).filter(include));
224 }
225 }
226
227 /**
228 * Returns with the iterator of the argument.
229 */
230 export function asIterator<T>(elements: ReadonlyArray<T>): IterableIterator<T> {
231 return elements.slice()[Symbol.iterator]();
232 }
233
234 /**
235 * Returns an iterator that cycles indefinitely over the elements of iterable.
236 * - If `start` is given it starts the iteration from that element. Otherwise, it starts with the first element of the array.
237 * - If `start` is given, it must contain by the `elements` array. Otherwise, an error will be thrown.
238 *
239 * **Warning**: Typical uses of the resulting iterator may produce an infinite loop. You should use an explicit break.
240 */
241 export function* cycle<T>(elements: ReadonlyArray<T>, start?: T): IterableIterator<T> {
242 const copy = elements.slice();
243 let index = !!start ? copy.indexOf(start) : 0;
244 if (index === -1) {
245 throw new Error(`${start} is not contained in ${copy}.`);
246 }
247 while (true) {
248 yield copy[index];
249 index++;
250 if (index === copy.length) {
251 index = 0;
252 }
253 }
254 }
255
256}