1 | 'use strict'
|
2 |
|
3 | const keywords = require('../keywords')
|
4 | const escape = require('../../utils/string').escape
|
5 | const forEach = require('../../utils/array').forEach
|
6 | const join = require('../../utils/array').join
|
7 | const latex = require('../../utils/latex')
|
8 | const operators = require('../operators')
|
9 | const setSafeProperty = require('../../utils/customs').setSafeProperty
|
10 |
|
11 | function factory (type, config, load, typed) {
|
12 | const Node = load(require('./Node'))
|
13 |
|
14 | |
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
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 |
|
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 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
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 |
|
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 |
|
101 |
|
102 |
|
103 | FunctionAssignmentNode.prototype.forEach = function (callback) {
|
104 | callback(this.expr, 'expr', this)
|
105 | }
|
106 |
|
107 | |
108 |
|
109 |
|
110 |
|
111 |
|
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 |
|
121 |
|
122 |
|
123 | FunctionAssignmentNode.prototype.clone = function () {
|
124 | return new FunctionAssignmentNode(this.name, this.params.slice(0), this.expr)
|
125 | }
|
126 |
|
127 | |
128 |
|
129 |
|
130 |
|
131 |
|
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 |
|
143 |
|
144 |
|
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 |
|
157 |
|
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 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | FunctionAssignmentNode.fromJSON = function (json) {
|
183 | return new FunctionAssignmentNode(json.name, json.params, json.expr)
|
184 | }
|
185 |
|
186 | |
187 |
|
188 |
|
189 |
|
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 |
|
206 |
|
207 |
|
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 | }
|
222 | exports.name = 'FunctionAssignmentNode'
|
223 | exports.path = 'expression.node'
|
224 | exports.factory = factory
|