FunctionAssignmentNode.js (8290B)
1 import { isNode } from '../../utils/is.js'; 2 import { keywords } from '../keywords.js'; 3 import { escape } from '../../utils/string.js'; 4 import { forEach, join } from '../../utils/array.js'; 5 import { toSymbol } from '../../utils/latex.js'; 6 import { getPrecedence } from '../operators.js'; 7 import { factory } from '../../utils/factory.js'; 8 var name = 'FunctionAssignmentNode'; 9 var dependencies = ['typed', 'Node']; 10 export var createFunctionAssignmentNode = /* #__PURE__ */factory(name, dependencies, _ref => { 11 var { 12 typed, 13 Node 14 } = _ref; 15 16 /** 17 * @constructor FunctionAssignmentNode 18 * @extends {Node} 19 * Function assignment 20 * 21 * @param {string} name Function name 22 * @param {string[] | Array.<{name: string, type: string}>} params 23 * Array with function parameter names, or an 24 * array with objects containing the name 25 * and type of the parameter 26 * @param {Node} expr The function expression 27 */ 28 function FunctionAssignmentNode(name, params, expr) { 29 if (!(this instanceof FunctionAssignmentNode)) { 30 throw new SyntaxError('Constructor must be called with the new operator'); 31 } // validate input 32 33 34 if (typeof name !== 'string') throw new TypeError('String expected for parameter "name"'); 35 if (!Array.isArray(params)) throw new TypeError('Array containing strings or objects expected for parameter "params"'); 36 if (!isNode(expr)) throw new TypeError('Node expected for parameter "expr"'); 37 if (keywords.has(name)) throw new Error('Illegal function name, "' + name + '" is a reserved keyword'); 38 this.name = name; 39 this.params = params.map(function (param) { 40 return param && param.name || param; 41 }); 42 this.types = params.map(function (param) { 43 return param && param.type || 'any'; 44 }); 45 this.expr = expr; 46 } 47 48 FunctionAssignmentNode.prototype = new Node(); 49 FunctionAssignmentNode.prototype.type = 'FunctionAssignmentNode'; 50 FunctionAssignmentNode.prototype.isFunctionAssignmentNode = true; 51 /** 52 * Compile a node into a JavaScript function. 53 * This basically pre-calculates as much as possible and only leaves open 54 * calculations which depend on a dynamic scope with variables. 55 * @param {Object} math Math.js namespace with functions and constants. 56 * @param {Object} argNames An object with argument names as key and `true` 57 * as value. Used in the SymbolNode to optimize 58 * for arguments from user assigned functions 59 * (see FunctionAssignmentNode) or special symbols 60 * like `end` (see IndexNode). 61 * @return {function} Returns a function which can be called like: 62 * evalNode(scope: Object, args: Object, context: *) 63 */ 64 65 FunctionAssignmentNode.prototype._compile = function (math, argNames) { 66 var childArgNames = Object.create(argNames); 67 forEach(this.params, function (param) { 68 childArgNames[param] = true; 69 }); // compile the function expression with the child args 70 71 var evalExpr = this.expr._compile(math, childArgNames); 72 73 var name = this.name; 74 var params = this.params; 75 var signature = join(this.types, ','); 76 var syntax = name + '(' + join(this.params, ', ') + ')'; 77 return function evalFunctionAssignmentNode(scope, args, context) { 78 var signatures = {}; 79 80 signatures[signature] = function () { 81 var childArgs = Object.create(args); 82 83 for (var i = 0; i < params.length; i++) { 84 childArgs[params[i]] = arguments[i]; 85 } 86 87 return evalExpr(scope, childArgs, context); 88 }; 89 90 var fn = typed(name, signatures); 91 fn.syntax = syntax; 92 scope.set(name, fn); 93 return fn; 94 }; 95 }; 96 /** 97 * Execute a callback for each of the child nodes of this node 98 * @param {function(child: Node, path: string, parent: Node)} callback 99 */ 100 101 102 FunctionAssignmentNode.prototype.forEach = function (callback) { 103 callback(this.expr, 'expr', this); 104 }; 105 /** 106 * Create a new FunctionAssignmentNode having it's childs be the results of calling 107 * the provided callback function for each of the childs of the original node. 108 * @param {function(child: Node, path: string, parent: Node): Node} callback 109 * @returns {FunctionAssignmentNode} Returns a transformed copy of the node 110 */ 111 112 113 FunctionAssignmentNode.prototype.map = function (callback) { 114 var expr = this._ifNode(callback(this.expr, 'expr', this)); 115 116 return new FunctionAssignmentNode(this.name, this.params.slice(0), expr); 117 }; 118 /** 119 * Create a clone of this node, a shallow copy 120 * @return {FunctionAssignmentNode} 121 */ 122 123 124 FunctionAssignmentNode.prototype.clone = function () { 125 return new FunctionAssignmentNode(this.name, this.params.slice(0), this.expr); 126 }; 127 /** 128 * Is parenthesis needed? 129 * @param {Node} node 130 * @param {Object} parenthesis 131 * @private 132 */ 133 134 135 function needParenthesis(node, parenthesis) { 136 var precedence = getPrecedence(node, parenthesis); 137 var exprPrecedence = getPrecedence(node.expr, parenthesis); 138 return parenthesis === 'all' || exprPrecedence !== null && exprPrecedence <= precedence; 139 } 140 /** 141 * get string representation 142 * @param {Object} options 143 * @return {string} str 144 */ 145 146 147 FunctionAssignmentNode.prototype._toString = function (options) { 148 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep'; 149 var expr = this.expr.toString(options); 150 151 if (needParenthesis(this, parenthesis)) { 152 expr = '(' + expr + ')'; 153 } 154 155 return this.name + '(' + this.params.join(', ') + ') = ' + expr; 156 }; 157 /** 158 * Get a JSON representation of the node 159 * @returns {Object} 160 */ 161 162 163 FunctionAssignmentNode.prototype.toJSON = function () { 164 var types = this.types; 165 return { 166 mathjs: 'FunctionAssignmentNode', 167 name: this.name, 168 params: this.params.map(function (param, index) { 169 return { 170 name: param, 171 type: types[index] 172 }; 173 }), 174 expr: this.expr 175 }; 176 }; 177 /** 178 * Instantiate an FunctionAssignmentNode from its JSON representation 179 * @param {Object} json An object structured like 180 * `{"mathjs": "FunctionAssignmentNode", name: ..., params: ..., expr: ...}`, 181 * where mathjs is optional 182 * @returns {FunctionAssignmentNode} 183 */ 184 185 186 FunctionAssignmentNode.fromJSON = function (json) { 187 return new FunctionAssignmentNode(json.name, json.params, json.expr); 188 }; 189 /** 190 * get HTML representation 191 * @param {Object} options 192 * @return {string} str 193 */ 194 195 196 FunctionAssignmentNode.prototype.toHTML = function (options) { 197 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep'; 198 var params = []; 199 200 for (var i = 0; i < this.params.length; i++) { 201 params.push('<span class="math-symbol math-parameter">' + escape(this.params[i]) + '</span>'); 202 } 203 204 var expr = this.expr.toHTML(options); 205 206 if (needParenthesis(this, parenthesis)) { 207 expr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + expr + '<span class="math-parenthesis math-round-parenthesis">)</span>'; 208 } 209 210 return '<span class="math-function">' + escape(this.name) + '</span>' + '<span class="math-parenthesis math-round-parenthesis">(</span>' + params.join('<span class="math-separator">,</span>') + '<span class="math-parenthesis math-round-parenthesis">)</span><span class="math-operator math-assignment-operator math-variable-assignment-operator math-binary-operator">=</span>' + expr; 211 }; 212 /** 213 * get LaTeX representation 214 * @param {Object} options 215 * @return {string} str 216 */ 217 218 219 FunctionAssignmentNode.prototype._toTex = function (options) { 220 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep'; 221 var expr = this.expr.toTex(options); 222 223 if (needParenthesis(this, parenthesis)) { 224 expr = "\\left(".concat(expr, "\\right)"); 225 } 226 227 return '\\mathrm{' + this.name + '}\\left(' + this.params.map(toSymbol).join(',') + '\\right):=' + expr; 228 }; 229 230 return FunctionAssignmentNode; 231 }, { 232 isClass: true, 233 isNode: true 234 });