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