Node.js (11583B)
1 import { isNode } from '../../utils/is.js'; 2 import { keywords } from '../keywords.js'; 3 import { deepStrictEqual } from '../../utils/object.js'; 4 import { factory } from '../../utils/factory.js'; 5 import { createMap } from '../../utils/map.js'; 6 var name = 'Node'; 7 var dependencies = ['mathWithTransform']; 8 export var createNode = /* #__PURE__ */factory(name, dependencies, _ref => { 9 var { 10 mathWithTransform 11 } = _ref; 12 13 /** 14 * Node 15 */ 16 function Node() { 17 if (!(this instanceof Node)) { 18 throw new SyntaxError('Constructor must be called with the new operator'); 19 } 20 } 21 /** 22 * Evaluate the node 23 * @param {Object} [scope] Scope to read/write variables 24 * @return {*} Returns the result 25 */ 26 27 28 Node.prototype.evaluate = function (scope) { 29 return this.compile().evaluate(scope); 30 }; 31 32 Node.prototype.type = 'Node'; 33 Node.prototype.isNode = true; 34 Node.prototype.comment = ''; 35 /** 36 * Compile the node into an optimized, evauatable JavaScript function 37 * @return {{evaluate: function([Object])}} object 38 * Returns an object with a function 'evaluate', 39 * which can be invoked as expr.evaluate([scope: Object]), 40 * where scope is an optional object with 41 * variables. 42 */ 43 44 Node.prototype.compile = function () { 45 var expr = this._compile(mathWithTransform, {}); 46 47 var args = {}; 48 var context = null; 49 50 function evaluate(scope) { 51 var s = createMap(scope); 52 53 _validateScope(s); 54 55 return expr(s, args, context); 56 } 57 58 return { 59 evaluate 60 }; 61 }; 62 /** 63 * Compile a node into a JavaScript function. 64 * This basically pre-calculates as much as possible and only leaves open 65 * calculations which depend on a dynamic scope with variables. 66 * @param {Object} math Math.js namespace with functions and constants. 67 * @param {Object} argNames An object with argument names as key and `true` 68 * as value. Used in the SymbolNode to optimize 69 * for arguments from user assigned functions 70 * (see FunctionAssignmentNode) or special symbols 71 * like `end` (see IndexNode). 72 * @return {function} Returns a function which can be called like: 73 * evalNode(scope: Object, args: Object, context: *) 74 */ 75 76 77 Node.prototype._compile = function (math, argNames) { 78 throw new Error('Method _compile should be implemented by type ' + this.type); 79 }; 80 /** 81 * Execute a callback for each of the child nodes of this node 82 * @param {function(child: Node, path: string, parent: Node)} callback 83 */ 84 85 86 Node.prototype.forEach = function (callback) { 87 // must be implemented by each of the Node implementations 88 throw new Error('Cannot run forEach on a Node interface'); 89 }; 90 /** 91 * Create a new Node having it's childs be the results of calling 92 * the provided callback function for each of the childs of the original node. 93 * @param {function(child: Node, path: string, parent: Node): Node} callback 94 * @returns {OperatorNode} Returns a transformed copy of the node 95 */ 96 97 98 Node.prototype.map = function (callback) { 99 // must be implemented by each of the Node implementations 100 throw new Error('Cannot run map on a Node interface'); 101 }; 102 /** 103 * Validate whether an object is a Node, for use with map 104 * @param {Node} node 105 * @returns {Node} Returns the input if it's a node, else throws an Error 106 * @protected 107 */ 108 109 110 Node.prototype._ifNode = function (node) { 111 if (!isNode(node)) { 112 throw new TypeError('Callback function must return a Node'); 113 } 114 115 return node; 116 }; 117 /** 118 * Recursively traverse all nodes in a node tree. Executes given callback for 119 * this node and each of its child nodes. 120 * @param {function(node: Node, path: string, parent: Node)} callback 121 * A callback called for every node in the node tree. 122 */ 123 124 125 Node.prototype.traverse = function (callback) { 126 // execute callback for itself 127 // eslint-disable-next-line 128 callback(this, null, null); // recursively traverse over all childs of a node 129 130 function _traverse(node, callback) { 131 node.forEach(function (child, path, parent) { 132 callback(child, path, parent); 133 134 _traverse(child, callback); 135 }); 136 } 137 138 _traverse(this, callback); 139 }; 140 /** 141 * Recursively transform a node tree via a transform function. 142 * 143 * For example, to replace all nodes of type SymbolNode having name 'x' with a 144 * ConstantNode with value 2: 145 * 146 * const res = Node.transform(function (node, path, parent) { 147 * if (node && node.isSymbolNode) && (node.name === 'x')) { 148 * return new ConstantNode(2) 149 * } 150 * else { 151 * return node 152 * } 153 * }) 154 * 155 * @param {function(node: Node, path: string, parent: Node) : Node} callback 156 * A mapping function accepting a node, and returning 157 * a replacement for the node or the original node. 158 * Signature: callback(node: Node, index: string, parent: Node) : Node 159 * @return {Node} Returns the original node or its replacement 160 */ 161 162 163 Node.prototype.transform = function (callback) { 164 function _transform(child, path, parent) { 165 var replacement = callback(child, path, parent); 166 167 if (replacement !== child) { 168 // stop iterating when the node is replaced 169 return replacement; 170 } 171 172 return child.map(_transform); 173 } 174 175 return _transform(this, null, null); 176 }; 177 /** 178 * Find any node in the node tree matching given filter function. For example, to 179 * find all nodes of type SymbolNode having name 'x': 180 * 181 * const results = Node.filter(function (node) { 182 * return (node && node.isSymbolNode) && (node.name === 'x') 183 * }) 184 * 185 * @param {function(node: Node, path: string, parent: Node) : Node} callback 186 * A test function returning true when a node matches, and false 187 * otherwise. Function signature: 188 * callback(node: Node, index: string, parent: Node) : boolean 189 * @return {Node[]} nodes An array with nodes matching given filter criteria 190 */ 191 192 193 Node.prototype.filter = function (callback) { 194 var nodes = []; 195 this.traverse(function (node, path, parent) { 196 if (callback(node, path, parent)) { 197 nodes.push(node); 198 } 199 }); 200 return nodes; 201 }; 202 /** 203 * Create a shallow clone of this node 204 * @return {Node} 205 */ 206 207 208 Node.prototype.clone = function () { 209 // must be implemented by each of the Node implementations 210 throw new Error('Cannot clone a Node interface'); 211 }; 212 /** 213 * Create a deep clone of this node 214 * @return {Node} 215 */ 216 217 218 Node.prototype.cloneDeep = function () { 219 return this.map(function (node) { 220 return node.cloneDeep(); 221 }); 222 }; 223 /** 224 * Deep compare this node with another node. 225 * @param {Node} other 226 * @return {boolean} Returns true when both nodes are of the same type and 227 * contain the same values (as do their childs) 228 */ 229 230 231 Node.prototype.equals = function (other) { 232 return other ? deepStrictEqual(this, other) : false; 233 }; 234 /** 235 * Get string representation. (wrapper function) 236 * 237 * This function can get an object of the following form: 238 * { 239 * handler: //This can be a callback function of the form 240 * // "function callback(node, options)"or 241 * // a map that maps function names (used in FunctionNodes) 242 * // to callbacks 243 * parenthesis: "keep" //the parenthesis option (This is optional) 244 * } 245 * 246 * @param {Object} [options] 247 * @return {string} 248 */ 249 250 251 Node.prototype.toString = function (options) { 252 var customString = this._getCustomString(options); 253 254 if (typeof customString !== 'undefined') { 255 return customString; 256 } 257 258 return this._toString(options); 259 }; 260 /** 261 * Get a JSON representation of the node 262 * Both .toJSON() and the static .fromJSON(json) should be implemented by all 263 * implementations of Node 264 * @returns {Object} 265 */ 266 267 268 Node.prototype.toJSON = function () { 269 throw new Error('Cannot serialize object: toJSON not implemented by ' + this.type); 270 }; 271 /** 272 * Get HTML representation. (wrapper function) 273 * 274 * This function can get an object of the following form: 275 * { 276 * handler: //This can be a callback function of the form 277 * // "function callback(node, options)" or 278 * // a map that maps function names (used in FunctionNodes) 279 * // to callbacks 280 * parenthesis: "keep" //the parenthesis option (This is optional) 281 * } 282 * 283 * @param {Object} [options] 284 * @return {string} 285 */ 286 287 288 Node.prototype.toHTML = function (options) { 289 var customString = this._getCustomString(options); 290 291 if (typeof customString !== 'undefined') { 292 return customString; 293 } 294 295 return this.toHTML(options); 296 }; 297 /** 298 * Internal function to generate the string output. 299 * This has to be implemented by every Node 300 * 301 * @throws {Error} 302 */ 303 304 305 Node.prototype._toString = function () { 306 // must be implemented by each of the Node implementations 307 throw new Error('_toString not implemented for ' + this.type); 308 }; 309 /** 310 * Get LaTeX representation. (wrapper function) 311 * 312 * This function can get an object of the following form: 313 * { 314 * handler: //This can be a callback function of the form 315 * // "function callback(node, options)"or 316 * // a map that maps function names (used in FunctionNodes) 317 * // to callbacks 318 * parenthesis: "keep" //the parenthesis option (This is optional) 319 * } 320 * 321 * @param {Object} [options] 322 * @return {string} 323 */ 324 325 326 Node.prototype.toTex = function (options) { 327 var customString = this._getCustomString(options); 328 329 if (typeof customString !== 'undefined') { 330 return customString; 331 } 332 333 return this._toTex(options); 334 }; 335 /** 336 * Internal function to generate the LaTeX output. 337 * This has to be implemented by every Node 338 * 339 * @param {Object} [options] 340 * @throws {Error} 341 */ 342 343 344 Node.prototype._toTex = function (options) { 345 // must be implemented by each of the Node implementations 346 throw new Error('_toTex not implemented for ' + this.type); 347 }; 348 /** 349 * Helper used by `to...` functions. 350 */ 351 352 353 Node.prototype._getCustomString = function (options) { 354 if (options && typeof options === 'object') { 355 switch (typeof options.handler) { 356 case 'object': 357 case 'undefined': 358 return; 359 360 case 'function': 361 return options.handler(this, options); 362 363 default: 364 throw new TypeError('Object or function expected as callback'); 365 } 366 } 367 }; 368 /** 369 * Get identifier. 370 * @return {string} 371 */ 372 373 374 Node.prototype.getIdentifier = function () { 375 return this.type; 376 }; 377 /** 378 * Get the content of the current Node. 379 * @return {Node} node 380 **/ 381 382 383 Node.prototype.getContent = function () { 384 return this; 385 }; 386 /** 387 * Validate the symbol names of a scope. 388 * Throws an error when the scope contains an illegal symbol. 389 * @param {Object} scope 390 */ 391 392 393 function _validateScope(scope) { 394 for (var symbol of [...keywords]) { 395 if (scope.has(symbol)) { 396 throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword'); 397 } 398 } 399 } 400 401 return Node; 402 }, { 403 isClass: true, 404 isNode: true 405 });