1 | import { isNode } from '../../utils/is'
|
2 |
|
3 | import { keywords } from '../keywords'
|
4 | import { escape } from '../../utils/string'
|
5 | import { forEach, join } from '../../utils/array'
|
6 | import { toSymbol } from '../../utils/latex'
|
7 | import { getPrecedence } from '../operators'
|
8 | import { setSafeProperty } from '../../utils/customs'
|
9 | import { factory } from '../../utils/factory'
|
10 |
|
11 | const name = 'FunctionAssignmentNode'
|
12 | const dependencies = [
|
13 | 'typed',
|
14 | 'Node'
|
15 | ]
|
16 |
|
17 | export const createFunctionAssignmentNode = factory(name, dependencies, ({ typed, Node }) => {
|
18 | |
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
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 |
|
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 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
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 |
|
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 |
|
105 |
|
106 |
|
107 | FunctionAssignmentNode.prototype.forEach = function (callback) {
|
108 | callback(this.expr, 'expr', this)
|
109 | }
|
110 |
|
111 | |
112 |
|
113 |
|
114 |
|
115 |
|
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 |
|
125 |
|
126 |
|
127 | FunctionAssignmentNode.prototype.clone = function () {
|
128 | return new FunctionAssignmentNode(this.name, this.params.slice(0), this.expr)
|
129 | }
|
130 |
|
131 | |
132 |
|
133 |
|
134 |
|
135 |
|
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 |
|
147 |
|
148 |
|
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 |
|
161 |
|
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 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | FunctionAssignmentNode.fromJSON = function (json) {
|
187 | return new FunctionAssignmentNode(json.name, json.params, json.expr)
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 |
|
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 |
|
210 |
|
211 |
|
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 })
|