UNPKG

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