UNPKG

11.5 kBJavaScriptView Raw
1import { isAccessorNode, isIndexNode, isNode, isSymbolNode } from '../../utils/is';
2import { getSafeProperty, setSafeProperty } from '../../utils/customs';
3import { factory } from '../../utils/factory';
4import { accessFactory } from './utils/access';
5import { assignFactory } from './utils/assign';
6import { getPrecedence } from '../operators';
7var name = 'AssignmentNode';
8var dependencies = ['subset', '?matrix', // FIXME: should not be needed at all, should be handled by subset
9'Node'];
10export var createAssignmentNode = /* #__PURE__ */factory(name, dependencies, function (_ref) {
11 var subset = _ref.subset,
12 matrix = _ref.matrix,
13 Node = _ref.Node;
14 var access = accessFactory({
15 subset: subset
16 });
17 var assign = assignFactory({
18 subset: subset,
19 matrix: matrix
20 });
21 /**
22 * @constructor AssignmentNode
23 * @extends {Node}
24 *
25 * Define a symbol, like `a=3.2`, update a property like `a.b=3.2`, or
26 * replace a subset of a matrix like `A[2,2]=42`.
27 *
28 * Syntax:
29 *
30 * new AssignmentNode(symbol, value)
31 * new AssignmentNode(object, index, value)
32 *
33 * Usage:
34 *
35 * new AssignmentNode(new SymbolNode('a'), new ConstantNode(2)) // a=2
36 * new AssignmentNode(new SymbolNode('a'), new IndexNode('b'), new ConstantNode(2)) // a.b=2
37 * new AssignmentNode(new SymbolNode('a'), new IndexNode(1, 2), new ConstantNode(3)) // a[1,2]=3
38 *
39 * @param {SymbolNode | AccessorNode} object Object on which to assign a value
40 * @param {IndexNode} [index=null] Index, property name or matrix
41 * index. Optional. If not provided
42 * and `object` is a SymbolNode,
43 * the property is assigned to the
44 * global scope.
45 * @param {Node} value The value to be assigned
46 */
47
48 function AssignmentNode(object, index, value) {
49 if (!(this instanceof AssignmentNode)) {
50 throw new SyntaxError('Constructor must be called with the new operator');
51 }
52
53 this.object = object;
54 this.index = value ? index : null;
55 this.value = value || index; // validate input
56
57 if (!isSymbolNode(object) && !isAccessorNode(object)) {
58 throw new TypeError('SymbolNode or AccessorNode expected as "object"');
59 }
60
61 if (isSymbolNode(object) && object.name === 'end') {
62 throw new Error('Cannot assign to symbol "end"');
63 }
64
65 if (this.index && !isIndexNode(this.index)) {
66 // index is optional
67 throw new TypeError('IndexNode expected as "index"');
68 }
69
70 if (!isNode(this.value)) {
71 throw new TypeError('Node expected as "value"');
72 } // readonly property name
73
74
75 Object.defineProperty(this, 'name', {
76 get: function () {
77 if (this.index) {
78 return this.index.isObjectProperty() ? this.index.getObjectProperty() : '';
79 } else {
80 return this.object.name || '';
81 }
82 }.bind(this),
83 set: function set() {
84 throw new Error('Cannot assign a new name, name is read-only');
85 }
86 });
87 }
88
89 AssignmentNode.prototype = new Node();
90 AssignmentNode.prototype.type = 'AssignmentNode';
91 AssignmentNode.prototype.isAssignmentNode = true;
92 /**
93 * Compile a node into a JavaScript function.
94 * This basically pre-calculates as much as possible and only leaves open
95 * calculations which depend on a dynamic scope with variables.
96 * @param {Object} math Math.js namespace with functions and constants.
97 * @param {Object} argNames An object with argument names as key and `true`
98 * as value. Used in the SymbolNode to optimize
99 * for arguments from user assigned functions
100 * (see FunctionAssignmentNode) or special symbols
101 * like `end` (see IndexNode).
102 * @return {function} Returns a function which can be called like:
103 * evalNode(scope: Object, args: Object, context: *)
104 */
105
106 AssignmentNode.prototype._compile = function (math, argNames) {
107 var evalObject = this.object._compile(math, argNames);
108
109 var evalIndex = this.index ? this.index._compile(math, argNames) : null;
110
111 var evalValue = this.value._compile(math, argNames);
112
113 var name = this.object.name;
114
115 if (!this.index) {
116 // apply a variable to the scope, for example `a=2`
117 if (!isSymbolNode(this.object)) {
118 throw new TypeError('SymbolNode expected as object');
119 }
120
121 return function evalAssignmentNode(scope, args, context) {
122 return setSafeProperty(scope, name, evalValue(scope, args, context));
123 };
124 } else if (this.index.isObjectProperty()) {
125 // apply an object property for example `a.b=2`
126 var prop = this.index.getObjectProperty();
127 return function evalAssignmentNode(scope, args, context) {
128 var object = evalObject(scope, args, context);
129 var value = evalValue(scope, args, context);
130 return setSafeProperty(object, prop, value);
131 };
132 } else if (isSymbolNode(this.object)) {
133 // update a matrix subset, for example `a[2]=3`
134 return function evalAssignmentNode(scope, args, context) {
135 var childObject = evalObject(scope, args, context);
136 var value = evalValue(scope, args, context);
137 var index = evalIndex(scope, args, childObject); // Important: we pass childObject instead of context
138
139 setSafeProperty(scope, name, assign(childObject, index, value));
140 return value;
141 };
142 } else {
143 // isAccessorNode(node.object) === true
144 // update a matrix subset, for example `a.b[2]=3`
145 // we will not use the compile function of the AccessorNode, but compile it
146 // ourselves here as we need the parent object of the AccessorNode:
147 // wee need to apply the updated object to parent object
148 var evalParentObject = this.object.object._compile(math, argNames);
149
150 if (this.object.index.isObjectProperty()) {
151 var parentProp = this.object.index.getObjectProperty();
152 return function evalAssignmentNode(scope, args, context) {
153 var parent = evalParentObject(scope, args, context);
154 var childObject = getSafeProperty(parent, parentProp);
155 var index = evalIndex(scope, args, childObject); // Important: we pass childObject instead of context
156
157 var value = evalValue(scope, args, context);
158 setSafeProperty(parent, parentProp, assign(childObject, index, value));
159 return value;
160 };
161 } else {
162 // if some parameters use the 'end' parameter, we need to calculate the size
163 var evalParentIndex = this.object.index._compile(math, argNames);
164
165 return function evalAssignmentNode(scope, args, context) {
166 var parent = evalParentObject(scope, args, context);
167 var parentIndex = evalParentIndex(scope, args, parent); // Important: we pass parent instead of context
168
169 var childObject = access(parent, parentIndex);
170 var index = evalIndex(scope, args, childObject); // Important: we pass childObject instead of context
171
172 var value = evalValue(scope, args, context);
173 assign(parent, parentIndex, assign(childObject, index, value));
174 return value;
175 };
176 }
177 }
178 };
179 /**
180 * Execute a callback for each of the child nodes of this node
181 * @param {function(child: Node, path: string, parent: Node)} callback
182 */
183
184
185 AssignmentNode.prototype.forEach = function (callback) {
186 callback(this.object, 'object', this);
187
188 if (this.index) {
189 callback(this.index, 'index', this);
190 }
191
192 callback(this.value, 'value', this);
193 };
194 /**
195 * Create a new AssignmentNode having it's childs be the results of calling
196 * the provided callback function for each of the childs of the original node.
197 * @param {function(child: Node, path: string, parent: Node): Node} callback
198 * @returns {AssignmentNode} Returns a transformed copy of the node
199 */
200
201
202 AssignmentNode.prototype.map = function (callback) {
203 var object = this._ifNode(callback(this.object, 'object', this));
204
205 var index = this.index ? this._ifNode(callback(this.index, 'index', this)) : null;
206
207 var value = this._ifNode(callback(this.value, 'value', this));
208
209 return new AssignmentNode(object, index, value);
210 };
211 /**
212 * Create a clone of this node, a shallow copy
213 * @return {AssignmentNode}
214 */
215
216
217 AssignmentNode.prototype.clone = function () {
218 return new AssignmentNode(this.object, this.index, this.value);
219 };
220 /*
221 * Is parenthesis needed?
222 * @param {node} node
223 * @param {string} [parenthesis='keep']
224 * @private
225 */
226
227
228 function needParenthesis(node, parenthesis) {
229 if (!parenthesis) {
230 parenthesis = 'keep';
231 }
232
233 var precedence = getPrecedence(node, parenthesis);
234 var exprPrecedence = getPrecedence(node.value, parenthesis);
235 return parenthesis === 'all' || exprPrecedence !== null && exprPrecedence <= precedence;
236 }
237 /**
238 * Get string representation
239 * @param {Object} options
240 * @return {string}
241 */
242
243
244 AssignmentNode.prototype._toString = function (options) {
245 var object = this.object.toString(options);
246 var index = this.index ? this.index.toString(options) : '';
247 var value = this.value.toString(options);
248
249 if (needParenthesis(this, options && options.parenthesis)) {
250 value = '(' + value + ')';
251 }
252
253 return object + index + ' = ' + value;
254 };
255 /**
256 * Get a JSON representation of the node
257 * @returns {Object}
258 */
259
260
261 AssignmentNode.prototype.toJSON = function () {
262 return {
263 mathjs: 'AssignmentNode',
264 object: this.object,
265 index: this.index,
266 value: this.value
267 };
268 };
269 /**
270 * Instantiate an AssignmentNode from its JSON representation
271 * @param {Object} json An object structured like
272 * `{"mathjs": "AssignmentNode", object: ..., index: ..., value: ...}`,
273 * where mathjs is optional
274 * @returns {AssignmentNode}
275 */
276
277
278 AssignmentNode.fromJSON = function (json) {
279 return new AssignmentNode(json.object, json.index, json.value);
280 };
281 /**
282 * Get HTML representation
283 * @param {Object} options
284 * @return {string}
285 */
286
287
288 AssignmentNode.prototype.toHTML = function (options) {
289 var object = this.object.toHTML(options);
290 var index = this.index ? this.index.toHTML(options) : '';
291 var value = this.value.toHTML(options);
292
293 if (needParenthesis(this, options && options.parenthesis)) {
294 value = '<span class="math-paranthesis math-round-parenthesis">(</span>' + value + '<span class="math-paranthesis math-round-parenthesis">)</span>';
295 }
296
297 return object + index + '<span class="math-operator math-assignment-operator math-variable-assignment-operator math-binary-operator">=</span>' + value;
298 };
299 /**
300 * Get LaTeX representation
301 * @param {Object} options
302 * @return {string}
303 */
304
305
306 AssignmentNode.prototype._toTex = function (options) {
307 var object = this.object.toTex(options);
308 var index = this.index ? this.index.toTex(options) : '';
309 var value = this.value.toTex(options);
310
311 if (needParenthesis(this, options && options.parenthesis)) {
312 value = "\\left(".concat(value, "\\right)");
313 }
314
315 return object + index + ':=' + value;
316 };
317
318 return AssignmentNode;
319}, {
320 isClass: true,
321 isNode: true
322});
\No newline at end of file