simple-squiggle

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

ConditionalNode.js (9372B)


      1 import { isBigNumber, isComplex, isNode, isUnit, typeOf } from '../../utils/is.js';
      2 import { factory } from '../../utils/factory.js';
      3 import { getPrecedence } from '../operators.js';
      4 var name = 'ConditionalNode';
      5 var dependencies = ['Node'];
      6 export var createConditionalNode = /* #__PURE__ */factory(name, dependencies, _ref => {
      7   var {
      8     Node
      9   } = _ref;
     10 
     11   /**
     12    * A lazy evaluating conditional operator: 'condition ? trueExpr : falseExpr'
     13    *
     14    * @param {Node} condition   Condition, must result in a boolean
     15    * @param {Node} trueExpr    Expression evaluated when condition is true
     16    * @param {Node} falseExpr   Expression evaluated when condition is true
     17    *
     18    * @constructor ConditionalNode
     19    * @extends {Node}
     20    */
     21   function ConditionalNode(condition, trueExpr, falseExpr) {
     22     if (!(this instanceof ConditionalNode)) {
     23       throw new SyntaxError('Constructor must be called with the new operator');
     24     }
     25 
     26     if (!isNode(condition)) throw new TypeError('Parameter condition must be a Node');
     27     if (!isNode(trueExpr)) throw new TypeError('Parameter trueExpr must be a Node');
     28     if (!isNode(falseExpr)) throw new TypeError('Parameter falseExpr must be a Node');
     29     this.condition = condition;
     30     this.trueExpr = trueExpr;
     31     this.falseExpr = falseExpr;
     32   }
     33 
     34   ConditionalNode.prototype = new Node();
     35   ConditionalNode.prototype.type = 'ConditionalNode';
     36   ConditionalNode.prototype.isConditionalNode = true;
     37   /**
     38    * Compile a node into a JavaScript function.
     39    * This basically pre-calculates as much as possible and only leaves open
     40    * calculations which depend on a dynamic scope with variables.
     41    * @param {Object} math     Math.js namespace with functions and constants.
     42    * @param {Object} argNames An object with argument names as key and `true`
     43    *                          as value. Used in the SymbolNode to optimize
     44    *                          for arguments from user assigned functions
     45    *                          (see FunctionAssignmentNode) or special symbols
     46    *                          like `end` (see IndexNode).
     47    * @return {function} Returns a function which can be called like:
     48    *                        evalNode(scope: Object, args: Object, context: *)
     49    */
     50 
     51   ConditionalNode.prototype._compile = function (math, argNames) {
     52     var evalCondition = this.condition._compile(math, argNames);
     53 
     54     var evalTrueExpr = this.trueExpr._compile(math, argNames);
     55 
     56     var evalFalseExpr = this.falseExpr._compile(math, argNames);
     57 
     58     return function evalConditionalNode(scope, args, context) {
     59       return testCondition(evalCondition(scope, args, context)) ? evalTrueExpr(scope, args, context) : evalFalseExpr(scope, args, context);
     60     };
     61   };
     62   /**
     63    * Execute a callback for each of the child nodes of this node
     64    * @param {function(child: Node, path: string, parent: Node)} callback
     65    */
     66 
     67 
     68   ConditionalNode.prototype.forEach = function (callback) {
     69     callback(this.condition, 'condition', this);
     70     callback(this.trueExpr, 'trueExpr', this);
     71     callback(this.falseExpr, 'falseExpr', this);
     72   };
     73   /**
     74    * Create a new ConditionalNode having it's childs be the results of calling
     75    * the provided callback function for each of the childs of the original node.
     76    * @param {function(child: Node, path: string, parent: Node): Node} callback
     77    * @returns {ConditionalNode} Returns a transformed copy of the node
     78    */
     79 
     80 
     81   ConditionalNode.prototype.map = function (callback) {
     82     return new ConditionalNode(this._ifNode(callback(this.condition, 'condition', this)), this._ifNode(callback(this.trueExpr, 'trueExpr', this)), this._ifNode(callback(this.falseExpr, 'falseExpr', this)));
     83   };
     84   /**
     85    * Create a clone of this node, a shallow copy
     86    * @return {ConditionalNode}
     87    */
     88 
     89 
     90   ConditionalNode.prototype.clone = function () {
     91     return new ConditionalNode(this.condition, this.trueExpr, this.falseExpr);
     92   };
     93   /**
     94    * Get string representation
     95    * @param {Object} options
     96    * @return {string} str
     97    */
     98 
     99 
    100   ConditionalNode.prototype._toString = function (options) {
    101     var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
    102     var precedence = getPrecedence(this, parenthesis); // Enclose Arguments in parentheses if they are an OperatorNode
    103     // or have lower or equal precedence
    104     // NOTE: enclosing all OperatorNodes in parentheses is a decision
    105     // purely based on aesthetics and readability
    106 
    107     var condition = this.condition.toString(options);
    108     var conditionPrecedence = getPrecedence(this.condition, parenthesis);
    109 
    110     if (parenthesis === 'all' || this.condition.type === 'OperatorNode' || conditionPrecedence !== null && conditionPrecedence <= precedence) {
    111       condition = '(' + condition + ')';
    112     }
    113 
    114     var trueExpr = this.trueExpr.toString(options);
    115     var truePrecedence = getPrecedence(this.trueExpr, parenthesis);
    116 
    117     if (parenthesis === 'all' || this.trueExpr.type === 'OperatorNode' || truePrecedence !== null && truePrecedence <= precedence) {
    118       trueExpr = '(' + trueExpr + ')';
    119     }
    120 
    121     var falseExpr = this.falseExpr.toString(options);
    122     var falsePrecedence = getPrecedence(this.falseExpr, parenthesis);
    123 
    124     if (parenthesis === 'all' || this.falseExpr.type === 'OperatorNode' || falsePrecedence !== null && falsePrecedence <= precedence) {
    125       falseExpr = '(' + falseExpr + ')';
    126     }
    127 
    128     return condition + ' ? ' + trueExpr + ' : ' + falseExpr;
    129   };
    130   /**
    131    * Get a JSON representation of the node
    132    * @returns {Object}
    133    */
    134 
    135 
    136   ConditionalNode.prototype.toJSON = function () {
    137     return {
    138       mathjs: 'ConditionalNode',
    139       condition: this.condition,
    140       trueExpr: this.trueExpr,
    141       falseExpr: this.falseExpr
    142     };
    143   };
    144   /**
    145    * Instantiate an ConditionalNode from its JSON representation
    146    * @param {Object} json  An object structured like
    147    *                       `{"mathjs": "ConditionalNode", "condition": ..., "trueExpr": ..., "falseExpr": ...}`,
    148    *                       where mathjs is optional
    149    * @returns {ConditionalNode}
    150    */
    151 
    152 
    153   ConditionalNode.fromJSON = function (json) {
    154     return new ConditionalNode(json.condition, json.trueExpr, json.falseExpr);
    155   };
    156   /**
    157    * Get HTML representation
    158    * @param {Object} options
    159    * @return {string} str
    160    */
    161 
    162 
    163   ConditionalNode.prototype.toHTML = function (options) {
    164     var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
    165     var precedence = getPrecedence(this, parenthesis); // Enclose Arguments in parentheses if they are an OperatorNode
    166     // or have lower or equal precedence
    167     // NOTE: enclosing all OperatorNodes in parentheses is a decision
    168     // purely based on aesthetics and readability
    169 
    170     var condition = this.condition.toHTML(options);
    171     var conditionPrecedence = getPrecedence(this.condition, parenthesis);
    172 
    173     if (parenthesis === 'all' || this.condition.type === 'OperatorNode' || conditionPrecedence !== null && conditionPrecedence <= precedence) {
    174       condition = '<span class="math-parenthesis math-round-parenthesis">(</span>' + condition + '<span class="math-parenthesis math-round-parenthesis">)</span>';
    175     }
    176 
    177     var trueExpr = this.trueExpr.toHTML(options);
    178     var truePrecedence = getPrecedence(this.trueExpr, parenthesis);
    179 
    180     if (parenthesis === 'all' || this.trueExpr.type === 'OperatorNode' || truePrecedence !== null && truePrecedence <= precedence) {
    181       trueExpr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + trueExpr + '<span class="math-parenthesis math-round-parenthesis">)</span>';
    182     }
    183 
    184     var falseExpr = this.falseExpr.toHTML(options);
    185     var falsePrecedence = getPrecedence(this.falseExpr, parenthesis);
    186 
    187     if (parenthesis === 'all' || this.falseExpr.type === 'OperatorNode' || falsePrecedence !== null && falsePrecedence <= precedence) {
    188       falseExpr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + falseExpr + '<span class="math-parenthesis math-round-parenthesis">)</span>';
    189     }
    190 
    191     return condition + '<span class="math-operator math-conditional-operator">?</span>' + trueExpr + '<span class="math-operator math-conditional-operator">:</span>' + falseExpr;
    192   };
    193   /**
    194    * Get LaTeX representation
    195    * @param {Object} options
    196    * @return {string} str
    197    */
    198 
    199 
    200   ConditionalNode.prototype._toTex = function (options) {
    201     return '\\begin{cases} {' + this.trueExpr.toTex(options) + '}, &\\quad{\\text{if }\\;' + this.condition.toTex(options) + '}\\\\{' + this.falseExpr.toTex(options) + '}, &\\quad{\\text{otherwise}}\\end{cases}';
    202   };
    203   /**
    204    * Test whether a condition is met
    205    * @param {*} condition
    206    * @returns {boolean} true if condition is true or non-zero, else false
    207    */
    208 
    209 
    210   function testCondition(condition) {
    211     if (typeof condition === 'number' || typeof condition === 'boolean' || typeof condition === 'string') {
    212       return !!condition;
    213     }
    214 
    215     if (condition) {
    216       if (isBigNumber(condition)) {
    217         return !condition.isZero();
    218       }
    219 
    220       if (isComplex(condition)) {
    221         return !!(condition.re || condition.im);
    222       }
    223 
    224       if (isUnit(condition)) {
    225         return !!condition.value;
    226       }
    227     }
    228 
    229     if (condition === null || condition === undefined) {
    230       return false;
    231     }
    232 
    233     throw new TypeError('Unsupported type of condition "' + typeOf(condition) + '"');
    234   }
    235 
    236   return ConditionalNode;
    237 }, {
    238   isClass: true,
    239   isNode: true
    240 });