RangeNode.js (8606B)
1 import { isNode, isSymbolNode } from '../../utils/is.js'; 2 import { factory } from '../../utils/factory.js'; 3 import { getPrecedence } from '../operators.js'; 4 var name = 'RangeNode'; 5 var dependencies = ['Node']; 6 export var createRangeNode = /* #__PURE__ */factory(name, dependencies, _ref => { 7 var { 8 Node 9 } = _ref; 10 11 /** 12 * @constructor RangeNode 13 * @extends {Node} 14 * create a range 15 * @param {Node} start included lower-bound 16 * @param {Node} end included upper-bound 17 * @param {Node} [step] optional step 18 */ 19 function RangeNode(start, end, step) { 20 if (!(this instanceof RangeNode)) { 21 throw new SyntaxError('Constructor must be called with the new operator'); 22 } // validate inputs 23 24 25 if (!isNode(start)) throw new TypeError('Node expected'); 26 if (!isNode(end)) throw new TypeError('Node expected'); 27 if (step && !isNode(step)) throw new TypeError('Node expected'); 28 if (arguments.length > 3) throw new Error('Too many arguments'); 29 this.start = start; // included lower-bound 30 31 this.end = end; // included upper-bound 32 33 this.step = step || null; // optional step 34 } 35 36 RangeNode.prototype = new Node(); 37 RangeNode.prototype.type = 'RangeNode'; 38 RangeNode.prototype.isRangeNode = true; 39 /** 40 * Check whether the RangeNode needs the `end` symbol to be defined. 41 * This end is the size of the Matrix in current dimension. 42 * @return {boolean} 43 */ 44 45 RangeNode.prototype.needsEnd = function () { 46 // find all `end` symbols in this RangeNode 47 var endSymbols = this.filter(function (node) { 48 return isSymbolNode(node) && node.name === 'end'; 49 }); 50 return endSymbols.length > 0; 51 }; 52 /** 53 * Compile a node into a JavaScript function. 54 * This basically pre-calculates as much as possible and only leaves open 55 * calculations which depend on a dynamic scope with variables. 56 * @param {Object} math Math.js namespace with functions and constants. 57 * @param {Object} argNames An object with argument names as key and `true` 58 * as value. Used in the SymbolNode to optimize 59 * for arguments from user assigned functions 60 * (see FunctionAssignmentNode) or special symbols 61 * like `end` (see IndexNode). 62 * @return {function} Returns a function which can be called like: 63 * evalNode(scope: Object, args: Object, context: *) 64 */ 65 66 67 RangeNode.prototype._compile = function (math, argNames) { 68 var range = math.range; 69 70 var evalStart = this.start._compile(math, argNames); 71 72 var evalEnd = this.end._compile(math, argNames); 73 74 if (this.step) { 75 var evalStep = this.step._compile(math, argNames); 76 77 return function evalRangeNode(scope, args, context) { 78 return range(evalStart(scope, args, context), evalEnd(scope, args, context), evalStep(scope, args, context)); 79 }; 80 } else { 81 return function evalRangeNode(scope, args, context) { 82 return range(evalStart(scope, args, context), evalEnd(scope, args, context)); 83 }; 84 } 85 }; 86 /** 87 * Execute a callback for each of the child nodes of this node 88 * @param {function(child: Node, path: string, parent: Node)} callback 89 */ 90 91 92 RangeNode.prototype.forEach = function (callback) { 93 callback(this.start, 'start', this); 94 callback(this.end, 'end', this); 95 96 if (this.step) { 97 callback(this.step, 'step', this); 98 } 99 }; 100 /** 101 * Create a new RangeNode having it's childs be the results of calling 102 * the provided callback function for each of the childs of the original node. 103 * @param {function(child: Node, path: string, parent: Node): Node} callback 104 * @returns {RangeNode} Returns a transformed copy of the node 105 */ 106 107 108 RangeNode.prototype.map = function (callback) { 109 return new RangeNode(this._ifNode(callback(this.start, 'start', this)), this._ifNode(callback(this.end, 'end', this)), this.step && this._ifNode(callback(this.step, 'step', this))); 110 }; 111 /** 112 * Create a clone of this node, a shallow copy 113 * @return {RangeNode} 114 */ 115 116 117 RangeNode.prototype.clone = function () { 118 return new RangeNode(this.start, this.end, this.step && this.step); 119 }; 120 /** 121 * Calculate the necessary parentheses 122 * @param {Node} node 123 * @param {string} parenthesis 124 * @return {Object} parentheses 125 * @private 126 */ 127 128 129 function calculateNecessaryParentheses(node, parenthesis) { 130 var precedence = getPrecedence(node, parenthesis); 131 var parens = {}; 132 var startPrecedence = getPrecedence(node.start, parenthesis); 133 parens.start = startPrecedence !== null && startPrecedence <= precedence || parenthesis === 'all'; 134 135 if (node.step) { 136 var stepPrecedence = getPrecedence(node.step, parenthesis); 137 parens.step = stepPrecedence !== null && stepPrecedence <= precedence || parenthesis === 'all'; 138 } 139 140 var endPrecedence = getPrecedence(node.end, parenthesis); 141 parens.end = endPrecedence !== null && endPrecedence <= precedence || parenthesis === 'all'; 142 return parens; 143 } 144 /** 145 * Get string representation 146 * @param {Object} options 147 * @return {string} str 148 */ 149 150 151 RangeNode.prototype._toString = function (options) { 152 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep'; 153 var parens = calculateNecessaryParentheses(this, parenthesis); // format string as start:step:stop 154 155 var str; 156 var start = this.start.toString(options); 157 158 if (parens.start) { 159 start = '(' + start + ')'; 160 } 161 162 str = start; 163 164 if (this.step) { 165 var step = this.step.toString(options); 166 167 if (parens.step) { 168 step = '(' + step + ')'; 169 } 170 171 str += ':' + step; 172 } 173 174 var end = this.end.toString(options); 175 176 if (parens.end) { 177 end = '(' + end + ')'; 178 } 179 180 str += ':' + end; 181 return str; 182 }; 183 /** 184 * Get a JSON representation of the node 185 * @returns {Object} 186 */ 187 188 189 RangeNode.prototype.toJSON = function () { 190 return { 191 mathjs: 'RangeNode', 192 start: this.start, 193 end: this.end, 194 step: this.step 195 }; 196 }; 197 /** 198 * Instantiate an RangeNode from its JSON representation 199 * @param {Object} json An object structured like 200 * `{"mathjs": "RangeNode", "start": ..., "end": ..., "step": ...}`, 201 * where mathjs is optional 202 * @returns {RangeNode} 203 */ 204 205 206 RangeNode.fromJSON = function (json) { 207 return new RangeNode(json.start, json.end, json.step); 208 }; 209 /** 210 * Get HTML representation 211 * @param {Object} options 212 * @return {string} str 213 */ 214 215 216 RangeNode.prototype.toHTML = function (options) { 217 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep'; 218 var parens = calculateNecessaryParentheses(this, parenthesis); // format string as start:step:stop 219 220 var str; 221 var start = this.start.toHTML(options); 222 223 if (parens.start) { 224 start = '<span class="math-parenthesis math-round-parenthesis">(</span>' + start + '<span class="math-parenthesis math-round-parenthesis">)</span>'; 225 } 226 227 str = start; 228 229 if (this.step) { 230 var step = this.step.toHTML(options); 231 232 if (parens.step) { 233 step = '<span class="math-parenthesis math-round-parenthesis">(</span>' + step + '<span class="math-parenthesis math-round-parenthesis">)</span>'; 234 } 235 236 str += '<span class="math-operator math-range-operator">:</span>' + step; 237 } 238 239 var end = this.end.toHTML(options); 240 241 if (parens.end) { 242 end = '<span class="math-parenthesis math-round-parenthesis">(</span>' + end + '<span class="math-parenthesis math-round-parenthesis">)</span>'; 243 } 244 245 str += '<span class="math-operator math-range-operator">:</span>' + end; 246 return str; 247 }; 248 /** 249 * Get LaTeX representation 250 * @params {Object} options 251 * @return {string} str 252 */ 253 254 255 RangeNode.prototype._toTex = function (options) { 256 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep'; 257 var parens = calculateNecessaryParentheses(this, parenthesis); 258 var str = this.start.toTex(options); 259 260 if (parens.start) { 261 str = "\\left(".concat(str, "\\right)"); 262 } 263 264 if (this.step) { 265 var step = this.step.toTex(options); 266 267 if (parens.step) { 268 step = "\\left(".concat(step, "\\right)"); 269 } 270 271 str += ':' + step; 272 } 273 274 var end = this.end.toTex(options); 275 276 if (parens.end) { 277 end = "\\left(".concat(end, "\\right)"); 278 } 279 280 str += ':' + end; 281 return str; 282 }; 283 284 return RangeNode; 285 }, { 286 isClass: true, 287 isNode: true 288 });