UNPKG

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