UNPKG

9.37 kBJavaScriptView Raw
1import { isBigNumber, isComplex, isNode, isUnit, typeOf } from '../../utils/is.js';
2import { factory } from '../../utils/factory.js';
3import { getPrecedence } from '../operators.js';
4var name = 'ConditionalNode';
5var dependencies = ['Node'];
6export 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});
\No newline at end of file