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