UNPKG

mahler

Version:

A automated task composer and HTN based planner for building autonomous system agents

195 lines • 6.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Node = void 0; exports.isValue = isValue; exports.isFork = isFork; exports.reduce = reduce; exports.mapReduce = mapReduce; exports.reverse = reverse; exports.find = find; exports.toString = toString; exports.createFork = createFork; exports.createJoin = createJoin; exports.createValue = createValue; const assert_1 = require("./assert"); function isValue(n) { return n._tag === 'value'; } function isFork(n) { return n._tag === 'fork'; } // Utility function to visit every node in the DAG in a // depth first fashion, applying a reducer function to every // node function traverse(root, initial, reducer, exit = () => false, coords = { fork: 0, branch: 0, index: 0, depth: 0 }) { if (root == null) { // We have reached the end of the graph without // triggering the exit condition return { done: true, exited: false, acc: initial }; } // if (exit(initial)) { // The exit condition was met, terminate the search return { done: true, exited: true, acc: initial }; } if (isValue(root)) { return traverse(root.next, reducer(initial, root, coords), reducer, exit, { ...coords, index: coords.index + 1, depth: coords.depth + 1, }); } // Process the node, we need to increase the fork index before // processing if (isFork(root)) { initial = reducer(initial, root, coords); let res = null; for (const [index, bNode] of root.next.entries()) { const r = traverse(bNode, initial, reducer, exit, { ...coords, index: 0, branch: index, fork: coords.fork + 1, depth: coords.depth + 1, }); if (r.done && r.exited) { return r; } else if (!r.done) { res = r; } initial = r.acc; } // Call the reducer once on the join node let next = null; if (res != null) { (0, assert_1.assert)(!res.done); initial = reducer(initial, res.node, coords); next = res.node.next; } return traverse(next, initial, reducer, exit, { ...coords, depth: coords.depth + 2, }); } // if the first node of the graph is a JOIN then // we just ignore it if (coords.depth === 0) { return traverse(root.next, initial, reducer, exit, coords); } return { done: false, node: root, acc: initial }; } /** * Visit every node once, applying reducer function for every node and * exiting early if an exit predicate is met. * * Iteration of the dag is done Depth-first. When a fork node is * reached, branches are traversed in order until a join node (or a null) is found * before continuing with the following branch */ function reduceWhile(node, initial, reducer = (acc) => acc, exit = () => false) { const res = traverse(node, initial, reducer, exit); return res.acc; } function reduce(node, reducer, initial) { return reduceWhile(node, initial, (acc, n) => isValue(n) ? reducer(acc, n) : acc); } // Traverse the DAG following branches of a forking node in parallel // and combining results after function traverseCombine(root, initial, reducer, combiner, depth = 0) { if (root == null) { return { done: true, acc: initial }; } if (isValue(root)) { return traverseCombine(root.next, reducer(initial, root), reducer, combiner, depth + 1); } if (isFork(root)) { // Call the reducer with the fork node first initial = reducer(initial, root); // Then call each branch independently const ends = root.next.map((n) => traverseCombine(n, initial, reducer, combiner, depth + 1)); (0, assert_1.assert)(ends.length > 0, 'Malformed DAG found, empty Fork node'); const [res] = ends; (0, assert_1.assert)(res != null); // Typescript is not smart enough to figure out that res cannot be undefined (0, assert_1.assert)(!res.done, 'Malformed DAG found, disconnected fork branch'); // Combine the results from the branches passing the // join node const acc = combiner(ends.map((r) => r.acc), res.node); return traverseCombine(res.node.next, acc, reducer, combiner, depth + 2); } // If the first node of the graph is a join, we ignore it if (depth === 0) { return traverseCombine(root.next, initial, reducer, combiner, depth); } return { done: false, node: root, acc: initial }; } function reduceCombine(root, initial, reducer, combiner) { // The any below is because typescript is being weird in interpreting the types const res = traverseCombine(root, initial, reducer, combiner); return res.acc; } function mapReduce(root, initial, mapper, reducer) { return reduceCombine(root, initial, (acc, n) => (isValue(n) ? mapper(n, acc) : acc), reducer); } function reverse(root) { return reduceCombine(root, null, (prev, n) => { if (isValue(n)) { n.next = prev; return n; } return exports.Node.join(prev); }, (nodes) => exports.Node.fork(nodes.filter((n) => n != null))); } function find(root, condition) { return reduceWhile(root, null, // Select the node if it matches the condition (_, n) => (isValue(n) && condition(n) ? n : null), // Exit as soon as the node is found (v) => v != null); } function toString(root, toStr) { function indentIf(r, cond = true) { if (!cond) { return ''; } return ' '.repeat(r); } const res = reduceWhile(root, { str: '', indent: -2 }, (acc, node, { fork, branch, index }) => { let str = acc.str; if (index === 0 && fork > 0) { // add a `~` to mark the first element of the branch str += indentIf(acc.indent + 1, branch > 0) + '~ '; } if (isFork(node)) { acc.str = str + indentIf(acc.indent + 2, index > 0) + '+ '; // A new fork increases the base indent by 2 acc.indent += 2; return acc; } if (isValue(node)) { str += indentIf(acc.indent + 2, index > 0) + '- '; str += toStr(node) + '\n'; acc.str = str; return acc; } // An empty node resets the fork depth acc.indent -= 2; return acc; }); return res.str.trim(); } function createFork(next = []) { return { _tag: 'fork', next }; } function createJoin(next = null) { return { _tag: 'join', next }; } function createValue(data) { return { _tag: 'value', next: null, ...data }; } exports.Node = { value: createValue, fork: createFork, join: createJoin, }; //# sourceMappingURL=dag.js.map