IndexNode.js (9575B)
1 import { isBigNumber, isConstantNode, isNode, isRangeNode, isSymbolNode } from '../../utils/is.js'; 2 import { map } from '../../utils/array.js'; 3 import { escape } from '../../utils/string.js'; 4 import { factory } from '../../utils/factory.js'; 5 import { getSafeProperty } from '../../utils/customs.js'; 6 var name = 'IndexNode'; 7 var dependencies = ['Range', 'Node', 'size']; 8 export var createIndexNode = /* #__PURE__ */factory(name, dependencies, _ref => { 9 var { 10 Range, 11 Node, 12 size 13 } = _ref; 14 15 /** 16 * @constructor IndexNode 17 * @extends Node 18 * 19 * Describes a subset of a matrix or an object property. 20 * Cannot be used on its own, needs to be used within an AccessorNode or 21 * AssignmentNode. 22 * 23 * @param {Node[]} dimensions 24 * @param {boolean} [dotNotation=false] Optional property describing whether 25 * this index was written using dot 26 * notation like `a.b`, or using bracket 27 * notation like `a["b"]` (default). 28 * Used to stringify an IndexNode. 29 */ 30 function IndexNode(dimensions, dotNotation) { 31 if (!(this instanceof IndexNode)) { 32 throw new SyntaxError('Constructor must be called with the new operator'); 33 } 34 35 this.dimensions = dimensions; 36 this.dotNotation = dotNotation || false; // validate input 37 38 if (!Array.isArray(dimensions) || !dimensions.every(isNode)) { 39 throw new TypeError('Array containing Nodes expected for parameter "dimensions"'); 40 } 41 42 if (this.dotNotation && !this.isObjectProperty()) { 43 throw new Error('dotNotation only applicable for object properties'); 44 } 45 } 46 47 IndexNode.prototype = new Node(); 48 IndexNode.prototype.type = 'IndexNode'; 49 IndexNode.prototype.isIndexNode = true; 50 /** 51 * Compile a node into a JavaScript function. 52 * This basically pre-calculates as much as possible and only leaves open 53 * calculations which depend on a dynamic scope with variables. 54 * @param {Object} math Math.js namespace with functions and constants. 55 * @param {Object} argNames An object with argument names as key and `true` 56 * as value. Used in the SymbolNode to optimize 57 * for arguments from user assigned functions 58 * (see FunctionAssignmentNode) or special symbols 59 * like `end` (see IndexNode). 60 * @return {function} Returns a function which can be called like: 61 * evalNode(scope: Object, args: Object, context: *) 62 */ 63 64 IndexNode.prototype._compile = function (math, argNames) { 65 // TODO: implement support for bignumber (currently bignumbers are silently 66 // reduced to numbers when changing the value to zero-based) 67 // TODO: Optimization: when the range values are ConstantNodes, 68 // we can beforehand resolve the zero-based value 69 // optimization for a simple object property 70 var evalDimensions = map(this.dimensions, function (range, i) { 71 if (isRangeNode(range)) { 72 if (range.needsEnd()) { 73 // create a range containing end (like '4:end') 74 var childArgNames = Object.create(argNames); 75 childArgNames.end = true; 76 77 var evalStart = range.start._compile(math, childArgNames); 78 79 var evalEnd = range.end._compile(math, childArgNames); 80 81 var evalStep = range.step ? range.step._compile(math, childArgNames) : function () { 82 return 1; 83 }; 84 return function evalDimension(scope, args, context) { 85 var s = size(context).valueOf(); 86 var childArgs = Object.create(args); 87 childArgs.end = s[i]; 88 return createRange(evalStart(scope, childArgs, context), evalEnd(scope, childArgs, context), evalStep(scope, childArgs, context)); 89 }; 90 } else { 91 // create range 92 var _evalStart = range.start._compile(math, argNames); 93 94 var _evalEnd = range.end._compile(math, argNames); 95 96 var _evalStep = range.step ? range.step._compile(math, argNames) : function () { 97 return 1; 98 }; 99 100 return function evalDimension(scope, args, context) { 101 return createRange(_evalStart(scope, args, context), _evalEnd(scope, args, context), _evalStep(scope, args, context)); 102 }; 103 } 104 } else if (isSymbolNode(range) && range.name === 'end') { 105 // SymbolNode 'end' 106 var _childArgNames = Object.create(argNames); 107 108 _childArgNames.end = true; 109 110 var evalRange = range._compile(math, _childArgNames); 111 112 return function evalDimension(scope, args, context) { 113 var s = size(context).valueOf(); 114 var childArgs = Object.create(args); 115 childArgs.end = s[i]; 116 return evalRange(scope, childArgs, context); 117 }; 118 } else { 119 // ConstantNode 120 var _evalRange = range._compile(math, argNames); 121 122 return function evalDimension(scope, args, context) { 123 return _evalRange(scope, args, context); 124 }; 125 } 126 }); 127 var index = getSafeProperty(math, 'index'); 128 return function evalIndexNode(scope, args, context) { 129 var dimensions = map(evalDimensions, function (evalDimension) { 130 return evalDimension(scope, args, context); 131 }); 132 return index(...dimensions); 133 }; 134 }; 135 /** 136 * Execute a callback for each of the child nodes of this node 137 * @param {function(child: Node, path: string, parent: Node)} callback 138 */ 139 140 141 IndexNode.prototype.forEach = function (callback) { 142 for (var i = 0; i < this.dimensions.length; i++) { 143 callback(this.dimensions[i], 'dimensions[' + i + ']', this); 144 } 145 }; 146 /** 147 * Create a new IndexNode having it's childs be the results of calling 148 * the provided callback function for each of the childs of the original node. 149 * @param {function(child: Node, path: string, parent: Node): Node} callback 150 * @returns {IndexNode} Returns a transformed copy of the node 151 */ 152 153 154 IndexNode.prototype.map = function (callback) { 155 var dimensions = []; 156 157 for (var i = 0; i < this.dimensions.length; i++) { 158 dimensions[i] = this._ifNode(callback(this.dimensions[i], 'dimensions[' + i + ']', this)); 159 } 160 161 return new IndexNode(dimensions, this.dotNotation); 162 }; 163 /** 164 * Create a clone of this node, a shallow copy 165 * @return {IndexNode} 166 */ 167 168 169 IndexNode.prototype.clone = function () { 170 return new IndexNode(this.dimensions.slice(0), this.dotNotation); 171 }; 172 /** 173 * Test whether this IndexNode contains a single property name 174 * @return {boolean} 175 */ 176 177 178 IndexNode.prototype.isObjectProperty = function () { 179 return this.dimensions.length === 1 && isConstantNode(this.dimensions[0]) && typeof this.dimensions[0].value === 'string'; 180 }; 181 /** 182 * Returns the property name if IndexNode contains a property. 183 * If not, returns null. 184 * @return {string | null} 185 */ 186 187 188 IndexNode.prototype.getObjectProperty = function () { 189 return this.isObjectProperty() ? this.dimensions[0].value : null; 190 }; 191 /** 192 * Get string representation 193 * @param {Object} options 194 * @return {string} str 195 */ 196 197 198 IndexNode.prototype._toString = function (options) { 199 // format the parameters like "[1, 0:5]" 200 return this.dotNotation ? '.' + this.getObjectProperty() : '[' + this.dimensions.join(', ') + ']'; 201 }; 202 /** 203 * Get a JSON representation of the node 204 * @returns {Object} 205 */ 206 207 208 IndexNode.prototype.toJSON = function () { 209 return { 210 mathjs: 'IndexNode', 211 dimensions: this.dimensions, 212 dotNotation: this.dotNotation 213 }; 214 }; 215 /** 216 * Instantiate an IndexNode from its JSON representation 217 * @param {Object} json An object structured like 218 * `{"mathjs": "IndexNode", dimensions: [...], dotNotation: false}`, 219 * where mathjs is optional 220 * @returns {IndexNode} 221 */ 222 223 224 IndexNode.fromJSON = function (json) { 225 return new IndexNode(json.dimensions, json.dotNotation); 226 }; 227 /** 228 * Get HTML representation 229 * @param {Object} options 230 * @return {string} str 231 */ 232 233 234 IndexNode.prototype.toHTML = function (options) { 235 // format the parameters like "[1, 0:5]" 236 var dimensions = []; 237 238 for (var i = 0; i < this.dimensions.length; i++) { 239 dimensions[i] = this.dimensions[i].toHTML(); 240 } 241 242 if (this.dotNotation) { 243 return '<span class="math-operator math-accessor-operator">.</span>' + '<span class="math-symbol math-property">' + escape(this.getObjectProperty()) + '</span>'; 244 } else { 245 return '<span class="math-parenthesis math-square-parenthesis">[</span>' + dimensions.join('<span class="math-separator">,</span>') + '<span class="math-parenthesis math-square-parenthesis">]</span>'; 246 } 247 }; 248 /** 249 * Get LaTeX representation 250 * @param {Object} options 251 * @return {string} str 252 */ 253 254 255 IndexNode.prototype._toTex = function (options) { 256 var dimensions = this.dimensions.map(function (range) { 257 return range.toTex(options); 258 }); 259 return this.dotNotation ? '.' + this.getObjectProperty() + '' : '_{' + dimensions.join(',') + '}'; 260 }; // helper function to create a Range from start, step and end 261 262 263 function createRange(start, end, step) { 264 return new Range(isBigNumber(start) ? start.toNumber() : start, isBigNumber(end) ? end.toNumber() : end, isBigNumber(step) ? step.toNumber() : step); 265 } 266 267 return IndexNode; 268 }, { 269 isClass: true, 270 isNode: true 271 });