simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 });