diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 2682947..ea57f05 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -12,6 +12,16 @@ enum PublicationTreeNodeStatus { Error, } +export enum TreeTraversalMode { + Leaves, + All, +} + +enum TreeTraversalDirection { + Forward, + Backward, +} + interface PublicationTreeNode { type: PublicationTreeNodeType; status: PublicationTreeNodeStatus; @@ -344,54 +354,80 @@ export class PublicationTree implements AsyncIterable { return this; } - // TODO: Add `previous()` method. - - async next(): Promise> { + /** + * Return the next event in the tree for the given traversal mode. + * + * @param mode The traversal mode. Can be {@link TreeTraversalMode.Leaves} or + * {@link TreeTraversalMode.All}. + * @returns The next event in the tree, or null if the tree is empty. + */ + async next( + mode: TreeTraversalMode = TreeTraversalMode.Leaves + ): Promise> { if (!this.#cursor.target) { if (await this.#cursor.tryMoveTo(this.#bookmark)) { - const event = await this.getEvent(this.#cursor.target!.address); - return { done: false, value: event }; + return this.#yieldEventAtCursor(false); } } - // Based on Raymond Chen's tree traversal algorithm example. - // https://devblogs.microsoft.com/oldnewthing/20200106-00/?p=103300 - do { - if (await this.#cursor.tryMoveToNextSibling()) { - while (await this.#cursor.tryMoveToFirstChild()) { - continue; - } - - if (this.#cursor.target!.status === PublicationTreeNodeStatus.Error) { - return { done: false, value: null }; - } - - const event = await this.getEvent(this.#cursor.target!.address); - return { done: false, value: event }; - } - } while (this.#cursor.tryMoveToParent()); - - if (this.#cursor.target!.status === PublicationTreeNodeStatus.Error) { - return { done: false, value: null }; + if (mode === TreeTraversalMode.Leaves) { + return this.#walkLeaves(TreeTraversalDirection.Forward); } - // If we get to this point, we're at the root node (can't move up any more). - return { done: true, value: null }; + return this.#preorderWalkAll(TreeTraversalDirection.Forward); } - async previous(): Promise> { + /** + * Return the previous event in the tree for the given traversal mode. + * + * @param mode The traversal mode. Can be {@link TreeTraversalMode.Leaves} or + * {@link TreeTraversalMode.All}. + * @returns The previous event in the tree, or null if the tree is empty. + */ + async previous( + mode: TreeTraversalMode = TreeTraversalMode.Leaves + ): Promise> { if (!this.#cursor.target) { if (await this.#cursor.tryMoveTo(this.#bookmark)) { const event = await this.getEvent(this.#cursor.target!.address); return { done: false, value: event }; } } + + if (mode === TreeTraversalMode.Leaves) { + return this.#walkLeaves(TreeTraversalDirection.Backward); + } + + return this.#preorderWalkAll(TreeTraversalDirection.Backward); + } + + async #yieldEventAtCursor(done: boolean): Promise> { + const value = (await this.getEvent(this.#cursor.target!.address)) ?? null; + return { done, value }; + } + + /** + * Walks the tree in the given direction, yielding the event at each leaf. + * + * @param direction The direction to walk the tree. + * @returns The event at the leaf, or null if the tree is empty. + * + * Based on Raymond Chen's tree traversal algorithm example. + * https://devblogs.microsoft.com/oldnewthing/20200106-00/?p=103300 + */ + async #walkLeaves( + direction: TreeTraversalDirection = TreeTraversalDirection.Forward + ): Promise> { + const tryMoveToSibling: () => Promise = direction === TreeTraversalDirection.Forward + ? this.#cursor.tryMoveToNextSibling.bind(this.#cursor) + : this.#cursor.tryMoveToPreviousSibling.bind(this.#cursor); + const tryMoveToChild: () => Promise = direction === TreeTraversalDirection.Forward + ? this.#cursor.tryMoveToFirstChild.bind(this.#cursor) + : this.#cursor.tryMoveToLastChild.bind(this.#cursor); - // Based on Raymond Chen's tree traversal algorithm example. - // https://devblogs.microsoft.com/oldnewthing/20200106-00/?p=103300 do { - if (await this.#cursor.tryMoveToPreviousSibling()) { - while (await this.#cursor.tryMoveToLastChild()) { + if (await tryMoveToSibling()) { + while (await tryMoveToChild()) { continue; } @@ -399,8 +435,7 @@ export class PublicationTree implements AsyncIterable { return { done: false, value: null }; } - const event = await this.getEvent(this.#cursor.target!.address); - return { done: false, value: event }; + return this.#yieldEventAtCursor(false); } } while (this.#cursor.tryMoveToParent()); @@ -408,9 +443,26 @@ export class PublicationTree implements AsyncIterable { return { done: false, value: null }; } + // If we get to this point, we're at the root node (can't move up any more). return { done: true, value: null }; } + /** + * Walks the tree in the given direction, yielding the event at each node. + * + * @param direction The direction to walk the tree. + * @returns The event at the node, or null if the tree is empty. + * + * Based on Raymond Chen's preorder walk algorithm example. + * https://devblogs.microsoft.com/oldnewthing/20200107-00/?p=103304 + */ + async #preorderWalkAll( + direction: TreeTraversalDirection = TreeTraversalDirection.Forward + ): Promise> { + // TODO: Implement this. + return { done: false, value: null }; + } + // #endregion // #region Private Methods