UNPKG

9.06 kBJavaScriptView Raw
1// TODO this could be improved by simplifying seperated constants under associative and commutative operators
2import { isFraction, isNode, isOperatorNode } from '../../../utils/is.js';
3import { factory } from '../../../utils/factory.js';
4import { createUtil } from './util.js';
5import { noBignumber, noFraction } from '../../../utils/noop.js';
6var name = 'simplifyConstant';
7var dependencies = ['typed', 'config', 'mathWithTransform', '?fraction', '?bignumber', 'ConstantNode', 'OperatorNode', 'FunctionNode', 'SymbolNode'];
8export var createSimplifyConstant = /* #__PURE__ */factory(name, dependencies, (_ref) => {
9 var {
10 typed,
11 config,
12 mathWithTransform,
13 fraction,
14 bignumber,
15 ConstantNode,
16 OperatorNode,
17 FunctionNode,
18 SymbolNode
19 } = _ref;
20 var {
21 isCommutative,
22 isAssociative,
23 allChildren,
24 createMakeNodeFunction
25 } = createUtil({
26 FunctionNode,
27 OperatorNode,
28 SymbolNode
29 });
30
31 function simplifyConstant(expr, options) {
32 var res = foldFraction(expr, options);
33 return isNode(res) ? res : _toNode(res);
34 }
35
36 function _eval(fnname, args, options) {
37 try {
38 return _toNumber(mathWithTransform[fnname].apply(null, args), options);
39 } catch (ignore) {
40 // sometimes the implicit type conversion causes the evaluation to fail, so we'll try again after removing Fractions
41 args = args.map(function (x) {
42 if (isFraction(x)) {
43 return x.valueOf();
44 }
45
46 return x;
47 });
48 return _toNumber(mathWithTransform[fnname].apply(null, args), options);
49 }
50 }
51
52 var _toNode = typed({
53 Fraction: _fractionToNode,
54 number: function number(n) {
55 if (n < 0) {
56 return unaryMinusNode(new ConstantNode(-n));
57 }
58
59 return new ConstantNode(n);
60 },
61 BigNumber: function BigNumber(n) {
62 if (n < 0) {
63 return unaryMinusNode(new ConstantNode(-n));
64 }
65
66 return new ConstantNode(n); // old parameters: (n.toString(), 'number')
67 },
68 Complex: function Complex(s) {
69 throw new Error('Cannot convert Complex number to Node');
70 }
71 }); // convert a number to a fraction only if it can be expressed exactly,
72 // and when both numerator and denominator are small enough
73
74
75 function _exactFraction(n, options) {
76 var exactFractions = options && options.exactFractions !== false;
77
78 if (exactFractions && isFinite(n) && fraction) {
79 var f = fraction(n);
80 var fractionsLimit = options && typeof options.fractionsLimit === 'number' ? options.fractionsLimit : Infinity; // no limit by default
81
82 if (f.valueOf() === n && f.n < fractionsLimit && f.d < fractionsLimit) {
83 return f;
84 }
85 }
86
87 return n;
88 } // Convert numbers to a preferred number type in preference order: Fraction, number, Complex
89 // BigNumbers are left alone
90
91
92 var _toNumber = typed({
93 'string, Object': function stringObject(s, options) {
94 if (config.number === 'BigNumber') {
95 if (bignumber === undefined) {
96 noBignumber();
97 }
98
99 return bignumber(s);
100 } else if (config.number === 'Fraction') {
101 if (fraction === undefined) {
102 noFraction();
103 }
104
105 return fraction(s);
106 } else {
107 var n = parseFloat(s);
108 return _exactFraction(n, options);
109 }
110 },
111 'Fraction, Object': function FractionObject(s, options) {
112 return s;
113 },
114 // we don't need options here
115 'BigNumber, Object': function BigNumberObject(s, options) {
116 return s;
117 },
118 // we don't need options here
119 'number, Object': function numberObject(s, options) {
120 return _exactFraction(s, options);
121 },
122 'Complex, Object': function ComplexObject(s, options) {
123 if (s.im !== 0) {
124 return s;
125 }
126
127 return _exactFraction(s.re, options);
128 }
129 });
130
131 function unaryMinusNode(n) {
132 return new OperatorNode('-', 'unaryMinus', [n]);
133 }
134
135 function _fractionToNode(f) {
136 var n;
137 var vn = f.s * f.n;
138
139 if (vn < 0) {
140 n = new OperatorNode('-', 'unaryMinus', [new ConstantNode(-vn)]);
141 } else {
142 n = new ConstantNode(vn);
143 }
144
145 if (f.d === 1) {
146 return n;
147 }
148
149 return new OperatorNode('/', 'divide', [n, new ConstantNode(f.d)]);
150 }
151 /*
152 * Create a binary tree from a list of Fractions and Nodes.
153 * Tries to fold Fractions by evaluating them until the first Node in the list is hit, so
154 * `args` should be sorted to have the Fractions at the start (if the operator is commutative).
155 * @param args - list of Fractions and Nodes
156 * @param fn - evaluator for the binary operation evaluator that accepts two Fractions
157 * @param makeNode - creates a binary OperatorNode/FunctionNode from a list of child Nodes
158 * if args.length is 1, returns args[0]
159 * @return - Either a Node representing a binary expression or Fraction
160 */
161
162
163 function foldOp(fn, args, makeNode, options) {
164 return args.reduce(function (a, b) {
165 if (!isNode(a) && !isNode(b)) {
166 try {
167 return _eval(fn, [a, b], options);
168 } catch (ignoreandcontinue) {}
169
170 a = _toNode(a);
171 b = _toNode(b);
172 } else if (!isNode(a)) {
173 a = _toNode(a);
174 } else if (!isNode(b)) {
175 b = _toNode(b);
176 }
177
178 return makeNode([a, b]);
179 });
180 } // destroys the original node and returns a folded one
181
182
183 function foldFraction(node, options) {
184 switch (node.type) {
185 case 'SymbolNode':
186 return node;
187
188 case 'ConstantNode':
189 if (typeof node.value === 'number' || !isNaN(node.value)) {
190 return _toNumber(node.value, options);
191 }
192
193 return node;
194
195 case 'FunctionNode':
196 if (mathWithTransform[node.name] && mathWithTransform[node.name].rawArgs) {
197 return node;
198 }
199
200 {
201 // Process operators as OperatorNode
202 var operatorFunctions = ['add', 'multiply'];
203
204 if (operatorFunctions.indexOf(node.name) === -1) {
205 var args = node.args.map(arg => foldFraction(arg, options)); // If all args are numbers
206
207 if (!args.some(isNode)) {
208 try {
209 return _eval(node.name, args, options);
210 } catch (ignoreandcontine) {}
211 } // Convert all args to nodes and construct a symbolic function call
212
213
214 args = args.map(function (arg) {
215 return isNode(arg) ? arg : _toNode(arg);
216 });
217 return new FunctionNode(node.name, args);
218 } else {// treat as operator
219 }
220 }
221
222 /* falls through */
223
224 case 'OperatorNode':
225 {
226 var fn = node.fn.toString();
227
228 var _args;
229
230 var res;
231 var makeNode = createMakeNodeFunction(node);
232
233 if (isOperatorNode(node) && node.isUnary()) {
234 _args = [foldFraction(node.args[0], options)];
235
236 if (!isNode(_args[0])) {
237 res = _eval(fn, _args, options);
238 } else {
239 res = makeNode(_args);
240 }
241 } else if (isAssociative(node)) {
242 _args = allChildren(node);
243 _args = _args.map(arg => foldFraction(arg, options));
244
245 if (isCommutative(fn)) {
246 // commutative binary operator
247 var consts = [];
248 var vars = [];
249
250 for (var i = 0; i < _args.length; i++) {
251 if (!isNode(_args[i])) {
252 consts.push(_args[i]);
253 } else {
254 vars.push(_args[i]);
255 }
256 }
257
258 if (consts.length > 1) {
259 res = foldOp(fn, consts, makeNode, options);
260 vars.unshift(res);
261 res = foldOp(fn, vars, makeNode, options);
262 } else {
263 // we won't change the children order since it's not neccessary
264 res = foldOp(fn, _args, makeNode, options);
265 }
266 } else {
267 // non-commutative binary operator
268 res = foldOp(fn, _args, makeNode, options);
269 }
270 } else {
271 // non-associative binary operator
272 _args = node.args.map(arg => foldFraction(arg, options));
273 res = foldOp(fn, _args, makeNode, options);
274 }
275
276 return res;
277 }
278
279 case 'ParenthesisNode':
280 // remove the uneccessary parenthesis
281 return foldFraction(node.content, options);
282
283 case 'AccessorNode':
284 /* falls through */
285
286 case 'ArrayNode':
287 /* falls through */
288
289 case 'AssignmentNode':
290 /* falls through */
291
292 case 'BlockNode':
293 /* falls through */
294
295 case 'FunctionAssignmentNode':
296 /* falls through */
297
298 case 'IndexNode':
299 /* falls through */
300
301 case 'ObjectNode':
302 /* falls through */
303
304 case 'RangeNode':
305 /* falls through */
306
307 case 'ConditionalNode':
308 /* falls through */
309
310 default:
311 throw new Error("Unimplemented node type in simplifyConstant: ".concat(node.type));
312 }
313 }
314
315 return simplifyConstant;
316});
\No newline at end of file