Source: index.js

/**
 * A small library of classes for managing static tree data structures.
 * @module tiny-forest
 */

/**
 * @typedef {function} TreeNodeIndex
 * @param {*} [input] An optional input passed to the function.
 * @returns {{node: TreeNode, path: TreeNode[], result}} Returns an object containing the next node,
 *      the full path up to this node and an optional result value from the node.
 */

/**
 * @typedef {function} TreeFallback
 * @param {Object} input An input passed to the object.
 * @param {Error} input.error The error thrown.
 * @param {TreeNode} input.node The node where the error was thrown.
 * @param {TreeNode[]} input.path The full path leading up to the error.
 * @returns {*}
 */

/** Class representing a node in a tree data strucutre. */
class TreeNode {
    /**
     * Create a TreeNode.
     * @constructor
     * @param {module:tiny-forest~TreeNodeIndex} index A function that represents the node.
     * @param {module:tiny-forest~TreeFallback} fallbackOverride Overrides the tree's default fallback if this node errors.
     */
    constructor(index, fallbackOverride) {
        this.index = index;
        if (fallbackOverride) this.fallbackOverride = fallbackOverride;
    }
}

/**
 * @typedef TreeOptions
 * @type {Object}
 * @property {Object} tree Dictionary of TreeNodes representing the data structure.
 * @property {int} [rootIndex="root"] The index of the root.
 * @property {TreeFallback} [fallback] Fallback function for Trees containing nodes with asynchronous conditionals.
 */

/** Class representing a tree data structure. */
class Tree {
    /**
     * Create a Tree.
     * @constructor
     * @param {module:tiny-forest~TreeOptions} options A dictionary with properties as keys.
     */
    constructor(options) {
        this.tree = options.tree;
        if (options.rootKey) this.rootKey = options.rootKey;
        else this.rootKey = "root";

        if (options.fallback) this.fallback = options.fallback;
        
        this.root = this.tree[this.rootKey];
        
        this.node = this.root;
        this.path = [];

        for (let k in this.tree) { this.tree[k].parentTree = this.tree; }
    }

    nextNode(currentNode, input) {
        try {
            let result = currentNode.index(input);
            // ! This part might be a little redundant but I will make it more elegant later.
            let returnedNode = null;
            let returnedOutput = null;
            if (result) {
                returnedNode = result.next;
                returnedOutput = result.output;
            }
            this.node = returnedNode;
            if (returnedNode) return this.nextNode(returnedNode, returnedOutput);
            else return { lastNode: this.node, path: this.path, output: returnedOutput };
            this.path[this.path.length] = currentNode;
        } catch (e) {
            if (currentNode.fallbackOverride) { return currentNode.fallbackOverride({ error: e, node: this.node, path: this.path }); }
            else if (this.fallback) { return this.fallback({ error: e, node: this.node, path: this.path }); }
            else throw e;
        }
    }

    /**
     * Start climbing the tree starting with root.
     * @function
     * @param {*} [input] Input value provided to the index function of the root node.
     */
    start(input) {
        return this.nextNode(this.root, input);
    }
}

module.exports = { TreeNode, Tree };