'use strict'; var keywords = require('../keywords'); function factory (type, config, load, typed) { /** * Node */ function Node() { if (!(this instanceof Node)) { throw new SyntaxError('Constructor must be called with the new operator'); } } /** * Evaluate the node * @return {*} result */ // TODO: cleanup deprecated code one day. Deprecated since version 0.19.0 Node.prototype.eval = function () { throw new Error('Node.eval is deprecated. ' + 'Use Node.compile(math).eval([scope]) instead.'); }; Node.prototype.type = 'Node'; Node.prototype.isNode = true; /** * Compile the node to javascript code * @param {Object} math math.js instance * @return {{eval: function}} expr Returns an object with a function 'eval', * which can be invoked as expr.eval([scope]), * where scope is an optional object with * variables. */ Node.prototype.compile = function (math) { if (typeof math !== 'object') { throw new TypeError('Object expected for parameter math'); } // definitions globally available inside the closure of the compiled expressions var defs = { math: _transform(math), args: {}, // can be filled with names of FunctionAssignment arguments _validateScope: _validateScope }; var code = this._compile(defs); var defsCode = Object.keys(defs).map(function (name) { return ' var ' + name + ' = defs["' + name + '"];'; }); var factoryCode = defsCode.join(' ') + 'return {' + ' "eval": function (scope) {' + ' if (scope) _validateScope(scope);' + ' scope = scope || {};' + ' return ' + code + ';' + ' }' + '};'; var factory = new Function('defs', factoryCode); return factory(defs); }; /** * Compile the node to javascript code * @param {Object} defs Object which can be used to define functions * and constants globally available inside the closure * of the compiled expression * @return {String} js * @private */ Node.prototype._compile = function (defs) { // must be implemented by each of the Node implementations throw new Error('Cannot compile a Node interface'); }; /** * Execute a callback for each of the child nodes of this node * @param {function(child: Node, path: string, parent: Node)} callback */ Node.prototype.forEach = function (callback) { // must be implemented by each of the Node implementations throw new Error('Cannot run forEach on a Node interface'); }; /** * Create a new Node having it's childs be the results of calling * the provided callback function for each of the childs of the original node. * @param {function(child: Node, path: string, parent: Node): Node} callback * @returns {OperatorNode} Returns a transformed copy of the node */ Node.prototype.map = function (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 */ Node.prototype._ifNode = function (node) { if (!(node && node.isNode)) { 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. */ Node.prototype.traverse = function (callback) { // execute callback for itself callback(this, null, null); // recursively traverse over all childs 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: * * var 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. * Signature: callback(node: Node, index: string, parent: Node) : Node * @return {Node} Returns the original node or its replacement */ Node.prototype.transform = function (callback) { // check itself var replacement = callback(this, null, null); if (replacement !== this) { return replacement; } // traverse over all childs function _transform(node, callback) { return node.map(function (child, path, parent) { var replacement = callback(child, path, parent); return (replacement !== child) ? replacement : _transform(child, callback); }); } return _transform(this, callback); }; /** * Find any node in the node tree matching given filter function. For example, to * find all nodes of type SymbolNode having name 'x': * * var 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 */ Node.prototype.filter = function (callback) { var nodes = []; this.traverse(function (node, path, parent) { if (callback(node, path, parent)) { nodes.push(node); } }); return nodes; }; // TODO: deprecated since version 1.1.0, remove this some day Node.prototype.find = function () { throw new Error('Function Node.find is deprecated. Use Node.filter instead.'); }; // TODO: deprecated since version 1.1.0, remove this some day Node.prototype.match = function () { throw new Error('Function Node.match is deprecated. See functions Node.filter, Node.transform, Node.traverse.'); }; /** * Create a clone of this node, a shallow copy * @return {Node} */ Node.prototype.clone = function () { // must be implemented by each of the Node implementations throw new Error('Cannot clone a Node interface'); }; /** * Get string representation * @return {String} */ Node.prototype.toString = function () { return this._toString(); }; /** * Internal function to generate the string output. * This has to be implemented by every Node * * @param {Object}|function} * @throws {Error} */ Node.prototype._toString = function () { if (this.type === 'Node') { //FIXME remove this in v2??? return ''; } //must be implemented by each of the Node implementations throw new Error('_toString not implemented for ' + this.type); }; /** * Get LaTeX representation. (wrapper function) * This functions get's either an object containing callbacks or * a single callback. It decides whether to call the callback and if * not or if the callback returns nothing, it calls the default * LaTeX implementation of the node (_toTex). * * @param {Object|function} callback(s) * @return {String} */ Node.prototype.toTex = function (callback) { var customTex; if (typeof callback === 'object') { if ((this.type === 'FunctionNode') && callback.hasOwnProperty(this.name)) { //if callback is a map of callback functions and this is a FunctionNode customTex = callback[this.name](this, callback); } } else if (typeof callback === 'function') { //if callback is a function customTex = callback(this, callback); } else if (typeof callback !== 'undefined') { throw new TypeError('Object or function expected as callback'); } if (typeof customTex !== 'undefined') { return customTex; } return this._toTex(callback); }; /** * Internal function to generate the LaTeX output. * This has to be implemented by every Node * * @param {Object}|function} * @throws {Error} */ Node.prototype._toTex = function () { if (this.type === 'Node') { //FIXME remove this in v2??? return ''; } //must be implemented by each of the Node implementations throw new Error('_toTex not implemented for ' + this.type); }; /** * Get identifier. * @return {String} */ Node.prototype.getIdentifier = function () { return this.type; }; /** * Get the content of the current Node. * @return {Node} node **/ Node.prototype.getContent = function () { return this; } /** * Validate the symbol names of a scope. * Throws an error when the scope contains an illegal symbol. * @param {Object} scope */ function _validateScope(scope) { for (var symbol in scope) { if (scope.hasOwnProperty(symbol)) { if (symbol in keywords) { throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword'); } } } } /** * Replace all functions having a transform function attached at property transform * with their transform. * @param {Object} math * @return {Object} transformed * @private */ function _transform(math) { var transformed = Object.create(math); // TODO: bind function or function transform at compile time in FunctionNode instead, use defs there for (var name in math) { if (math.hasOwnProperty(name)) { var value = math[name]; if (value && typeof value.transform === 'function') { transformed[name] = value.transform; } } } return transformed; } return Node; } exports.name = 'Node'; exports.path = 'expression.node'; exports.factory = factory;