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