"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNode = void 0; var _is = require("../../utils/is.js"); var _keywords = require("../keywords.js"); var _object = require("../../utils/object.js"); var _factory = require("../../utils/factory.js"); var _map = require("../../utils/map.js"); const name = 'Node'; const dependencies = ['mathWithTransform']; const createNode = exports.createNode = /* #__PURE__ */(0, _factory.factory)(name, dependencies, _ref => { let { mathWithTransform } = _ref; /** * Validate the symbol names of a scope. * Throws an error when the scope contains an illegal symbol. * @param {Object} scope */ function _validateScope(scope) { for (const symbol of [..._keywords.keywords]) { if (scope.has(symbol)) { throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword'); } } } class Node { get type() { return 'Node'; } get isNode() { return true; } /** * Evaluate the node * @param {Object} [scope] Scope to read/write variables * @return {*} Returns the result */ evaluate(scope) { return this.compile().evaluate(scope); } /** * Compile the node into an optimized, evauatable JavaScript function * @return {{evaluate: function([Object])}} object * Returns an object with a function 'evaluate', * which can be invoked as expr.evaluate([scope: Object]), * where scope is an optional object with * variables. */ compile() { const expr = this._compile(mathWithTransform, {}); const args = {}; const context = null; function evaluate(scope) { const s = (0, _map.createMap)(scope); _validateScope(s); return expr(s, args, context); } return { evaluate }; } /** * Compile a node into a JavaScript function. * This basically pre-calculates as much as possible and only leaves open * calculations which depend on a dynamic scope with variables. * @param {Object} math Math.js namespace with functions and constants. * @param {Object} argNames An object with argument names as key and `true` * as value. Used in the SymbolNode to optimize * for arguments from user assigned functions * (see FunctionAssignmentNode) or special symbols * like `end` (see IndexNode). * @return {function} Returns a function which can be called like: * evalNode(scope: Object, args: Object, context: *) */ _compile(math, argNames) { throw new Error('Method _compile must be implemented by type ' + this.type); } /** * Execute a callback for each of the child nodes of this node * @param {function(child: Node, path: string, parent: Node)} callback */ forEach(callback) { // must be implemented by each of the Node implementations throw new Error('Cannot run forEach on a Node interface'); } /** * Create a new Node whose children are the results of calling the * provided callback function for each child of the original node. * @param {function(child: Node, path: string, parent: Node): Node} callback * @returns {OperatorNode} Returns a transformed copy of the node */ map(callback) { // must be implemented by each of the Node implementations throw new Error('Cannot run map on a Node interface'); } /** * Validate whether an object is a Node, for use with map * @param {Node} node * @returns {Node} Returns the input if it's a node, else throws an Error * @protected */ _ifNode(node) { if (!(0, _is.isNode)(node)) { throw new TypeError('Callback function must return a Node'); } return node; } /** * Recursively traverse all nodes in a node tree. Executes given callback for * this node and each of its child nodes. * @param {function(node: Node, path: string, parent: Node)} callback * A callback called for every node in the node tree. */ traverse(callback) { // execute callback for itself // eslint-disable-next-line callback(this, null, null); // recursively traverse over all children of a node function _traverse(node, callback) { node.forEach(function (child, path, parent) { callback(child, path, parent); _traverse(child, callback); }); } _traverse(this, callback); } /** * Recursively transform a node tree via a transform function. * * For example, to replace all nodes of type SymbolNode having name 'x' with * a ConstantNode with value 2: * * const res = Node.transform(function (node, path, parent) { * if (node && node.isSymbolNode) && (node.name === 'x')) { * return new ConstantNode(2) * } * else { * return node * } * }) * * @param {function(node: Node, path: string, parent: Node) : Node} callback * A mapping function accepting a node, and returning * a replacement for the node or the original node. The "signature" * of the callback must be: * callback(node: Node, index: string, parent: Node) : Node * @return {Node} Returns the original node or its replacement */ transform(callback) { function _transform(child, path, parent) { const replacement = callback(child, path, parent); if (replacement !== child) { // stop iterating when the node is replaced return replacement; } return child.map(_transform); } return _transform(this, null, null); } /** * Find any node in the node tree matching given filter function. For * example, to find all nodes of type SymbolNode having name 'x': * * const results = Node.filter(function (node) { * return (node && node.isSymbolNode) && (node.name === 'x') * }) * * @param {function(node: Node, path: string, parent: Node) : Node} callback * A test function returning true when a node matches, and false * otherwise. Function signature: * callback(node: Node, index: string, parent: Node) : boolean * @return {Node[]} nodes * An array with nodes matching given filter criteria */ filter(callback) { const nodes = []; this.traverse(function (node, path, parent) { if (callback(node, path, parent)) { nodes.push(node); } }); return nodes; } /** * Create a shallow clone of this node * @return {Node} */ clone() { // must be implemented by each of the Node implementations throw new Error('Cannot clone a Node interface'); } /** * Create a deep clone of this node * @return {Node} */ cloneDeep() { return this.map(function (node) { return node.cloneDeep(); }); } /** * Deep compare this node with another node. * @param {Node} other * @return {boolean} Returns true when both nodes are of the same type and * contain the same values (as do their childs) */ equals(other) { return other ? this.type === other.type && (0, _object.deepStrictEqual)(this, other) : false; } /** * Get string representation. (wrapper function) * * This function can get an object of the following form: * { * handler: //This can be a callback function of the form * // "function callback(node, options)"or * // a map that maps function names (used in FunctionNodes) * // to callbacks * parenthesis: "keep" //the parenthesis option (This is optional) * } * * @param {Object} [options] * @return {string} */ toString(options) { const customString = this._getCustomString(options); if (typeof customString !== 'undefined') { return customString; } return this._toString(options); } /** * Internal function to generate the string output. * This has to be implemented by every Node * * @throws {Error} */ _toString() { // must be implemented by each of the Node implementations throw new Error('_toString not implemented for ' + this.type); } /** * Get a JSON representation of the node * Both .toJSON() and the static .fromJSON(json) should be implemented by all * implementations of Node * @returns {Object} */ toJSON() { throw new Error('Cannot serialize object: toJSON not implemented by ' + this.type); } /** * Get HTML representation. (wrapper function) * * This function can get an object of the following form: * { * handler: //This can be a callback function of the form * // "function callback(node, options)" or * // a map that maps function names (used in FunctionNodes) * // to callbacks * parenthesis: "keep" //the parenthesis option (This is optional) * } * * @param {Object} [options] * @return {string} */ toHTML(options) { const customString = this._getCustomString(options); if (typeof customString !== 'undefined') { return customString; } return this._toHTML(options); } /** * Internal function to generate the HTML output. * This has to be implemented by every Node * * @throws {Error} */ _toHTML() { // must be implemented by each of the Node implementations throw new Error('_toHTML not implemented for ' + this.type); } /** * Get LaTeX representation. (wrapper function) * * This function can get an object of the following form: * { * handler: //This can be a callback function of the form * // "function callback(node, options)"or * // a map that maps function names (used in FunctionNodes) * // to callbacks * parenthesis: "keep" //the parenthesis option (This is optional) * } * * @param {Object} [options] * @return {string} */ toTex(options) { const customString = this._getCustomString(options); if (typeof customString !== 'undefined') { return customString; } return this._toTex(options); } /** * Internal function to generate the LaTeX output. * This has to be implemented by every Node * * @param {Object} [options] * @throws {Error} */ _toTex(options) { // must be implemented by each of the Node implementations throw new Error('_toTex not implemented for ' + this.type); } /** * Helper used by `to...` functions. */ _getCustomString(options) { if (options && typeof options === 'object') { switch (typeof options.handler) { case 'object': case 'undefined': return; case 'function': return options.handler(this, options); default: throw new TypeError('Object or function expected as callback'); } } } /** * Get identifier. * @return {string} */ getIdentifier() { return this.type; } /** * Get the content of the current Node. * @return {Node} node **/ getContent() { return this; } } return Node; }, { isClass: true, isNode: true });