1 | import { isBigNumber, isComplex, isNode, isUnit, typeOf } from '../../utils/is'
|
2 | import { factory } from '../../utils/factory'
|
3 | import { getPrecedence } from '../operators'
|
4 |
|
5 | const name = 'ConditionalNode'
|
6 | const dependencies = [
|
7 | 'Node'
|
8 | ]
|
9 |
|
10 | export const createConditionalNode = factory(name, dependencies, ({ Node }) => {
|
11 | |
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
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 | if (!isNode(condition)) throw new TypeError('Parameter condition must be a Node')
|
26 | if (!isNode(trueExpr)) throw new TypeError('Parameter trueExpr must be a Node')
|
27 | if (!isNode(falseExpr)) throw new TypeError('Parameter falseExpr must be a Node')
|
28 |
|
29 | this.condition = condition
|
30 | this.trueExpr = trueExpr
|
31 | this.falseExpr = falseExpr
|
32 | }
|
33 |
|
34 | ConditionalNode.prototype = new Node()
|
35 |
|
36 | ConditionalNode.prototype.type = 'ConditionalNode'
|
37 |
|
38 | ConditionalNode.prototype.isConditionalNode = true
|
39 |
|
40 | |
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | ConditionalNode.prototype._compile = function (math, argNames) {
|
54 | const evalCondition = this.condition._compile(math, argNames)
|
55 | const evalTrueExpr = this.trueExpr._compile(math, argNames)
|
56 | const evalFalseExpr = this.falseExpr._compile(math, argNames)
|
57 |
|
58 | return function evalConditionalNode (scope, args, context) {
|
59 | return testCondition(evalCondition(scope, args, context))
|
60 | ? evalTrueExpr(scope, args, context)
|
61 | : evalFalseExpr(scope, args, context)
|
62 | }
|
63 | }
|
64 |
|
65 | |
66 |
|
67 |
|
68 |
|
69 | ConditionalNode.prototype.forEach = function (callback) {
|
70 | callback(this.condition, 'condition', this)
|
71 | callback(this.trueExpr, 'trueExpr', this)
|
72 | callback(this.falseExpr, 'falseExpr', this)
|
73 | }
|
74 |
|
75 | |
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | ConditionalNode.prototype.map = function (callback) {
|
82 | return new ConditionalNode(
|
83 | this._ifNode(callback(this.condition, 'condition', this)),
|
84 | this._ifNode(callback(this.trueExpr, 'trueExpr', this)),
|
85 | this._ifNode(callback(this.falseExpr, 'falseExpr', this))
|
86 | )
|
87 | }
|
88 |
|
89 | |
90 |
|
91 |
|
92 |
|
93 | ConditionalNode.prototype.clone = function () {
|
94 | return new ConditionalNode(this.condition, this.trueExpr, this.falseExpr)
|
95 | }
|
96 |
|
97 | |
98 |
|
99 |
|
100 |
|
101 |
|
102 | ConditionalNode.prototype._toString = function (options) {
|
103 | const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep'
|
104 | const precedence = getPrecedence(this, parenthesis)
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | let condition = this.condition.toString(options)
|
111 | const conditionPrecedence = getPrecedence(this.condition, parenthesis)
|
112 | if ((parenthesis === 'all') ||
|
113 | (this.condition.type === 'OperatorNode') ||
|
114 | ((conditionPrecedence !== null) && (conditionPrecedence <= precedence))) {
|
115 | condition = '(' + condition + ')'
|
116 | }
|
117 |
|
118 | let trueExpr = this.trueExpr.toString(options)
|
119 | const truePrecedence = getPrecedence(this.trueExpr, parenthesis)
|
120 | if ((parenthesis === 'all') ||
|
121 | (this.trueExpr.type === 'OperatorNode') ||
|
122 | ((truePrecedence !== null) && (truePrecedence <= precedence))) {
|
123 | trueExpr = '(' + trueExpr + ')'
|
124 | }
|
125 |
|
126 | let falseExpr = this.falseExpr.toString(options)
|
127 | const falsePrecedence = getPrecedence(this.falseExpr, parenthesis)
|
128 | if ((parenthesis === 'all') ||
|
129 | (this.falseExpr.type === 'OperatorNode') ||
|
130 | ((falsePrecedence !== null) && (falsePrecedence <= precedence))) {
|
131 | falseExpr = '(' + falseExpr + ')'
|
132 | }
|
133 | return condition + ' ? ' + trueExpr + ' : ' + falseExpr
|
134 | }
|
135 |
|
136 | |
137 |
|
138 |
|
139 |
|
140 | ConditionalNode.prototype.toJSON = function () {
|
141 | return {
|
142 | mathjs: 'ConditionalNode',
|
143 | condition: this.condition,
|
144 | trueExpr: this.trueExpr,
|
145 | falseExpr: this.falseExpr
|
146 | }
|
147 | }
|
148 |
|
149 | |
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | ConditionalNode.fromJSON = function (json) {
|
157 | return new ConditionalNode(json.condition, json.trueExpr, json.falseExpr)
|
158 | }
|
159 |
|
160 | |
161 |
|
162 |
|
163 |
|
164 |
|
165 | ConditionalNode.prototype.toHTML = function (options) {
|
166 | const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep'
|
167 | const precedence = getPrecedence(this, parenthesis)
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 | let condition = this.condition.toHTML(options)
|
174 | const conditionPrecedence = getPrecedence(this.condition, parenthesis)
|
175 | if ((parenthesis === 'all') ||
|
176 | (this.condition.type === 'OperatorNode') ||
|
177 | ((conditionPrecedence !== null) && (conditionPrecedence <= precedence))) {
|
178 | condition = '<span class="math-parenthesis math-round-parenthesis">(</span>' + condition + '<span class="math-parenthesis math-round-parenthesis">)</span>'
|
179 | }
|
180 |
|
181 | let trueExpr = this.trueExpr.toHTML(options)
|
182 | const truePrecedence = getPrecedence(this.trueExpr, parenthesis)
|
183 | if ((parenthesis === 'all') ||
|
184 | (this.trueExpr.type === 'OperatorNode') ||
|
185 | ((truePrecedence !== null) && (truePrecedence <= precedence))) {
|
186 | trueExpr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + trueExpr + '<span class="math-parenthesis math-round-parenthesis">)</span>'
|
187 | }
|
188 |
|
189 | let falseExpr = this.falseExpr.toHTML(options)
|
190 | const falsePrecedence = getPrecedence(this.falseExpr, parenthesis)
|
191 | if ((parenthesis === 'all') ||
|
192 | (this.falseExpr.type === 'OperatorNode') ||
|
193 | ((falsePrecedence !== null) && (falsePrecedence <= precedence))) {
|
194 | falseExpr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + falseExpr + '<span class="math-parenthesis math-round-parenthesis">)</span>'
|
195 | }
|
196 | return condition + '<span class="math-operator math-conditional-operator">?</span>' + trueExpr + '<span class="math-operator math-conditional-operator">:</span>' + falseExpr
|
197 | }
|
198 |
|
199 | |
200 |
|
201 |
|
202 |
|
203 |
|
204 | ConditionalNode.prototype._toTex = function (options) {
|
205 | return '\\begin{cases} {' +
|
206 | this.trueExpr.toTex(options) + '}, &\\quad{\\text{if }\\;' +
|
207 | this.condition.toTex(options) +
|
208 | '}\\\\{' + this.falseExpr.toTex(options) +
|
209 | '}, &\\quad{\\text{otherwise}}\\end{cases}'
|
210 | }
|
211 |
|
212 | |
213 |
|
214 |
|
215 |
|
216 |
|
217 | function testCondition (condition) {
|
218 | if (typeof condition === 'number' ||
|
219 | typeof condition === 'boolean' ||
|
220 | typeof condition === 'string') {
|
221 | return !!condition
|
222 | }
|
223 |
|
224 | if (condition) {
|
225 | if (isBigNumber(condition)) {
|
226 | return !condition.isZero()
|
227 | }
|
228 |
|
229 | if (isComplex(condition)) {
|
230 | return !!((condition.re || condition.im))
|
231 | }
|
232 |
|
233 | if (isUnit(condition)) {
|
234 | return !!condition.value
|
235 | }
|
236 | }
|
237 |
|
238 | if (condition === null || condition === undefined) {
|
239 | return false
|
240 | }
|
241 |
|
242 | throw new TypeError('Unsupported type of condition "' + typeOf(condition) + '"')
|
243 | }
|
244 |
|
245 | return ConditionalNode
|
246 | }, { isClass: true, isNode: true })
|