simple-squiggle

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

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