simple-squiggle

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

ConditionalNode.js (9704B)


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