526 lines
17 KiB
JavaScript
526 lines
17 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||
|
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.createSimplifyConstant = void 0;
|
||
|
|
||
|
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
||
|
|
||
|
var _is = require("../../../utils/is.js");
|
||
|
|
||
|
var _factory = require("../../../utils/factory.js");
|
||
|
|
||
|
var _util = require("./util.js");
|
||
|
|
||
|
var _noop = require("../../../utils/noop.js");
|
||
|
|
||
|
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
||
|
|
||
|
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
||
|
|
||
|
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
||
|
|
||
|
var name = 'simplifyConstant';
|
||
|
var dependencies = ['typed', 'config', 'mathWithTransform', 'matrix', '?fraction', '?bignumber', 'AccessorNode', 'ArrayNode', 'ConstantNode', 'FunctionNode', 'IndexNode', 'ObjectNode', 'OperatorNode', 'SymbolNode'];
|
||
|
var createSimplifyConstant = /* #__PURE__ */(0, _factory.factory)(name, dependencies, function (_ref) {
|
||
|
var typed = _ref.typed,
|
||
|
config = _ref.config,
|
||
|
mathWithTransform = _ref.mathWithTransform,
|
||
|
matrix = _ref.matrix,
|
||
|
fraction = _ref.fraction,
|
||
|
bignumber = _ref.bignumber,
|
||
|
AccessorNode = _ref.AccessorNode,
|
||
|
ArrayNode = _ref.ArrayNode,
|
||
|
ConstantNode = _ref.ConstantNode,
|
||
|
FunctionNode = _ref.FunctionNode,
|
||
|
IndexNode = _ref.IndexNode,
|
||
|
ObjectNode = _ref.ObjectNode,
|
||
|
OperatorNode = _ref.OperatorNode,
|
||
|
SymbolNode = _ref.SymbolNode;
|
||
|
|
||
|
var _createUtil = (0, _util.createUtil)({
|
||
|
FunctionNode: FunctionNode,
|
||
|
OperatorNode: OperatorNode,
|
||
|
SymbolNode: SymbolNode
|
||
|
}),
|
||
|
isCommutative = _createUtil.isCommutative,
|
||
|
isAssociative = _createUtil.isAssociative,
|
||
|
allChildren = _createUtil.allChildren,
|
||
|
createMakeNodeFunction = _createUtil.createMakeNodeFunction;
|
||
|
|
||
|
function simplifyConstant(expr, options) {
|
||
|
return _ensureNode(foldFraction(expr, options));
|
||
|
}
|
||
|
|
||
|
function _removeFractions(thing) {
|
||
|
if ((0, _is.isFraction)(thing)) {
|
||
|
return thing.valueOf();
|
||
|
}
|
||
|
|
||
|
if (thing instanceof Array) {
|
||
|
return thing.map(_removeFractions);
|
||
|
}
|
||
|
|
||
|
if ((0, _is.isMatrix)(thing)) {
|
||
|
return matrix(_removeFractions(thing.valueOf()));
|
||
|
}
|
||
|
|
||
|
return thing;
|
||
|
}
|
||
|
|
||
|
function _eval(fnname, args, options) {
|
||
|
try {
|
||
|
return mathWithTransform[fnname].apply(null, args);
|
||
|
} catch (ignore) {
|
||
|
// sometimes the implicit type conversion causes the evaluation to fail, so we'll try again after removing Fractions
|
||
|
args = args.map(_removeFractions);
|
||
|
return _toNumber(mathWithTransform[fnname].apply(null, args), options);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var _toNode = typed({
|
||
|
Fraction: _fractionToNode,
|
||
|
number: function number(n) {
|
||
|
if (n < 0) {
|
||
|
return unaryMinusNode(new ConstantNode(-n));
|
||
|
}
|
||
|
|
||
|
return new ConstantNode(n);
|
||
|
},
|
||
|
BigNumber: function BigNumber(n) {
|
||
|
if (n < 0) {
|
||
|
return unaryMinusNode(new ConstantNode(-n));
|
||
|
}
|
||
|
|
||
|
return new ConstantNode(n); // old parameters: (n.toString(), 'number')
|
||
|
},
|
||
|
Complex: function Complex(s) {
|
||
|
throw new Error('Cannot convert Complex number to Node');
|
||
|
},
|
||
|
string: function string(s) {
|
||
|
return new ConstantNode(s);
|
||
|
},
|
||
|
Matrix: function Matrix(m) {
|
||
|
return new ArrayNode(m.valueOf().map(function (e) {
|
||
|
return _toNode(e);
|
||
|
}));
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function _ensureNode(thing) {
|
||
|
if ((0, _is.isNode)(thing)) {
|
||
|
return thing;
|
||
|
}
|
||
|
|
||
|
return _toNode(thing);
|
||
|
} // convert a number to a fraction only if it can be expressed exactly,
|
||
|
// and when both numerator and denominator are small enough
|
||
|
|
||
|
|
||
|
function _exactFraction(n, options) {
|
||
|
var exactFractions = options && options.exactFractions !== false;
|
||
|
|
||
|
if (exactFractions && isFinite(n) && fraction) {
|
||
|
var f = fraction(n);
|
||
|
var fractionsLimit = options && typeof options.fractionsLimit === 'number' ? options.fractionsLimit : Infinity; // no limit by default
|
||
|
|
||
|
if (f.valueOf() === n && f.n < fractionsLimit && f.d < fractionsLimit) {
|
||
|
return f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return n;
|
||
|
} // Convert numbers to a preferred number type in preference order: Fraction, number, Complex
|
||
|
// BigNumbers are left alone
|
||
|
|
||
|
|
||
|
var _toNumber = typed({
|
||
|
'string, Object': function stringObject(s, options) {
|
||
|
if (config.number === 'BigNumber') {
|
||
|
if (bignumber === undefined) {
|
||
|
(0, _noop.noBignumber)();
|
||
|
}
|
||
|
|
||
|
return bignumber(s);
|
||
|
} else if (config.number === 'Fraction') {
|
||
|
if (fraction === undefined) {
|
||
|
(0, _noop.noFraction)();
|
||
|
}
|
||
|
|
||
|
return fraction(s);
|
||
|
} else {
|
||
|
var n = parseFloat(s);
|
||
|
return _exactFraction(n, options);
|
||
|
}
|
||
|
},
|
||
|
'Fraction, Object': function FractionObject(s, options) {
|
||
|
return s;
|
||
|
},
|
||
|
// we don't need options here
|
||
|
'BigNumber, Object': function BigNumberObject(s, options) {
|
||
|
return s;
|
||
|
},
|
||
|
// we don't need options here
|
||
|
'number, Object': function numberObject(s, options) {
|
||
|
return _exactFraction(s, options);
|
||
|
},
|
||
|
'Complex, Object': function ComplexObject(s, options) {
|
||
|
if (s.im !== 0) {
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
return _exactFraction(s.re, options);
|
||
|
},
|
||
|
'Matrix, Object': function MatrixObject(s, options) {
|
||
|
return matrix(_exactFraction(s.valueOf()));
|
||
|
},
|
||
|
'Array, Object': function ArrayObject(s, options) {
|
||
|
return s.map(_exactFraction);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function unaryMinusNode(n) {
|
||
|
return new OperatorNode('-', 'unaryMinus', [n]);
|
||
|
}
|
||
|
|
||
|
function _fractionToNode(f) {
|
||
|
var n;
|
||
|
var vn = f.s * f.n;
|
||
|
|
||
|
if (vn < 0) {
|
||
|
n = new OperatorNode('-', 'unaryMinus', [new ConstantNode(-vn)]);
|
||
|
} else {
|
||
|
n = new ConstantNode(vn);
|
||
|
}
|
||
|
|
||
|
if (f.d === 1) {
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
return new OperatorNode('/', 'divide', [n, new ConstantNode(f.d)]);
|
||
|
}
|
||
|
/* Handles constant indexing of ArrayNodes, matrices, and ObjectNodes */
|
||
|
|
||
|
|
||
|
function _foldAccessor(obj, index, options) {
|
||
|
if (!(0, _is.isIndexNode)(index)) {
|
||
|
// don't know what to do with that...
|
||
|
return new AccessorNode(_ensureNode(obj), _ensureNode(index));
|
||
|
}
|
||
|
|
||
|
if ((0, _is.isArrayNode)(obj) || (0, _is.isMatrix)(obj)) {
|
||
|
var remainingDims = Array.from(index.dimensions);
|
||
|
/* We will resolve constant indices one at a time, looking
|
||
|
* just in the first or second dimensions because (a) arrays
|
||
|
* of more than two dimensions are likely rare, and (b) pulling
|
||
|
* out the third or higher dimension would be pretty intricate.
|
||
|
* The price is that we miss simplifying [..3d array][x,y,1]
|
||
|
*/
|
||
|
|
||
|
while (remainingDims.length > 0) {
|
||
|
if ((0, _is.isConstantNode)(remainingDims[0]) && typeof remainingDims[0].value !== 'string') {
|
||
|
var first = _toNumber(remainingDims.shift().value, options);
|
||
|
|
||
|
if ((0, _is.isArrayNode)(obj)) {
|
||
|
obj = obj.items[first - 1];
|
||
|
} else {
|
||
|
// matrix
|
||
|
obj = obj.valueOf()[first - 1];
|
||
|
|
||
|
if (obj instanceof Array) {
|
||
|
obj = matrix(obj);
|
||
|
}
|
||
|
}
|
||
|
} else if (remainingDims.length > 1 && (0, _is.isConstantNode)(remainingDims[1]) && typeof remainingDims[1].value !== 'string') {
|
||
|
var second = _toNumber(remainingDims[1].value, options);
|
||
|
|
||
|
var tryItems = [];
|
||
|
var fromItems = (0, _is.isArrayNode)(obj) ? obj.items : obj.valueOf();
|
||
|
|
||
|
var _iterator = _createForOfIteratorHelper(fromItems),
|
||
|
_step;
|
||
|
|
||
|
try {
|
||
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
||
|
var item = _step.value;
|
||
|
|
||
|
if ((0, _is.isArrayNode)(item)) {
|
||
|
tryItems.push(item.items[second - 1]);
|
||
|
} else if ((0, _is.isMatrix)(obj)) {
|
||
|
tryItems.push(item[second - 1]);
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} catch (err) {
|
||
|
_iterator.e(err);
|
||
|
} finally {
|
||
|
_iterator.f();
|
||
|
}
|
||
|
|
||
|
if (tryItems.length === fromItems.length) {
|
||
|
if ((0, _is.isArrayNode)(obj)) {
|
||
|
obj = new ArrayNode(tryItems);
|
||
|
} else {
|
||
|
// matrix
|
||
|
obj = matrix(tryItems);
|
||
|
}
|
||
|
|
||
|
remainingDims.splice(1, 1);
|
||
|
} else {
|
||
|
// extracting slice along 2nd dimension failed, give up
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
// neither 1st or 2nd dimension is constant, give up
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (remainingDims.length === index.dimensions.length) {
|
||
|
/* No successful constant indexing */
|
||
|
return new AccessorNode(_ensureNode(obj), index);
|
||
|
}
|
||
|
|
||
|
if (remainingDims.length > 0) {
|
||
|
/* Indexed some but not all dimensions */
|
||
|
index = new IndexNode(remainingDims);
|
||
|
return new AccessorNode(_ensureNode(obj), index);
|
||
|
}
|
||
|
/* All dimensions were constant, access completely resolved */
|
||
|
|
||
|
|
||
|
return obj;
|
||
|
}
|
||
|
|
||
|
if ((0, _is.isObjectNode)(obj) && index.dimensions.length === 1 && (0, _is.isConstantNode)(index.dimensions[0])) {
|
||
|
var key = index.dimensions[0].value;
|
||
|
|
||
|
if (key in obj.properties) {
|
||
|
return obj.properties[key];
|
||
|
}
|
||
|
|
||
|
return new ConstantNode(); // undefined
|
||
|
}
|
||
|
/* Don't know how to index this sort of obj, at least not with this index */
|
||
|
|
||
|
|
||
|
return new AccessorNode(_ensureNode(obj), index);
|
||
|
}
|
||
|
/*
|
||
|
* Create a binary tree from a list of Fractions and Nodes.
|
||
|
* Tries to fold Fractions by evaluating them until the first Node in the list is hit, so
|
||
|
* `args` should be sorted to have the Fractions at the start (if the operator is commutative).
|
||
|
* @param args - list of Fractions and Nodes
|
||
|
* @param fn - evaluator for the binary operation evaluator that accepts two Fractions
|
||
|
* @param makeNode - creates a binary OperatorNode/FunctionNode from a list of child Nodes
|
||
|
* if args.length is 1, returns args[0]
|
||
|
* @return - Either a Node representing a binary expression or Fraction
|
||
|
*/
|
||
|
|
||
|
|
||
|
function foldOp(fn, args, makeNode, options) {
|
||
|
return args.reduce(function (a, b) {
|
||
|
if (!(0, _is.isNode)(a) && !(0, _is.isNode)(b)) {
|
||
|
try {
|
||
|
return _eval(fn, [a, b], options);
|
||
|
} catch (ignoreandcontinue) {}
|
||
|
|
||
|
a = _toNode(a);
|
||
|
b = _toNode(b);
|
||
|
} else if (!(0, _is.isNode)(a)) {
|
||
|
a = _toNode(a);
|
||
|
} else if (!(0, _is.isNode)(b)) {
|
||
|
b = _toNode(b);
|
||
|
}
|
||
|
|
||
|
return makeNode([a, b]);
|
||
|
});
|
||
|
} // destroys the original node and returns a folded one
|
||
|
|
||
|
|
||
|
function foldFraction(node, options) {
|
||
|
switch (node.type) {
|
||
|
case 'SymbolNode':
|
||
|
return node;
|
||
|
|
||
|
case 'ConstantNode':
|
||
|
switch ((0, _typeof2.default)(node.value)) {
|
||
|
case 'number':
|
||
|
return _toNumber(node.value, options);
|
||
|
|
||
|
case 'string':
|
||
|
return node.value;
|
||
|
|
||
|
default:
|
||
|
if (!isNaN(node.value)) return _toNumber(node.value, options);
|
||
|
}
|
||
|
|
||
|
return node;
|
||
|
|
||
|
case 'FunctionNode':
|
||
|
if (mathWithTransform[node.name] && mathWithTransform[node.name].rawArgs) {
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
{
|
||
|
// Process operators as OperatorNode
|
||
|
var operatorFunctions = ['add', 'multiply'];
|
||
|
|
||
|
if (operatorFunctions.indexOf(node.name) === -1) {
|
||
|
var args = node.args.map(function (arg) {
|
||
|
return foldFraction(arg, options);
|
||
|
}); // If all args are numbers
|
||
|
|
||
|
if (!args.some(_is.isNode)) {
|
||
|
try {
|
||
|
return _eval(node.name, args, options);
|
||
|
} catch (ignoreandcontinue) {}
|
||
|
} // Size of a matrix does not depend on entries
|
||
|
|
||
|
|
||
|
if (node.name === 'size' && args.length === 1 && (0, _is.isArrayNode)(args[0])) {
|
||
|
var sz = [];
|
||
|
var section = args[0];
|
||
|
|
||
|
while ((0, _is.isArrayNode)(section)) {
|
||
|
sz.push(section.items.length);
|
||
|
section = section.items[0];
|
||
|
}
|
||
|
|
||
|
return matrix(sz);
|
||
|
} // Convert all args to nodes and construct a symbolic function call
|
||
|
|
||
|
|
||
|
return new FunctionNode(node.name, args.map(_ensureNode));
|
||
|
} else {// treat as operator
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* falls through */
|
||
|
|
||
|
case 'OperatorNode':
|
||
|
{
|
||
|
var fn = node.fn.toString();
|
||
|
|
||
|
var _args;
|
||
|
|
||
|
var res;
|
||
|
var makeNode = createMakeNodeFunction(node);
|
||
|
|
||
|
if ((0, _is.isOperatorNode)(node) && node.isUnary()) {
|
||
|
_args = [foldFraction(node.args[0], options)];
|
||
|
|
||
|
if (!(0, _is.isNode)(_args[0])) {
|
||
|
res = _eval(fn, _args, options);
|
||
|
} else {
|
||
|
res = makeNode(_args);
|
||
|
}
|
||
|
} else if (isAssociative(node, options.context)) {
|
||
|
_args = allChildren(node, options.context);
|
||
|
_args = _args.map(function (arg) {
|
||
|
return foldFraction(arg, options);
|
||
|
});
|
||
|
|
||
|
if (isCommutative(fn, options.context)) {
|
||
|
// commutative binary operator
|
||
|
var consts = [];
|
||
|
var vars = [];
|
||
|
|
||
|
for (var i = 0; i < _args.length; i++) {
|
||
|
if (!(0, _is.isNode)(_args[i])) {
|
||
|
consts.push(_args[i]);
|
||
|
} else {
|
||
|
vars.push(_args[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (consts.length > 1) {
|
||
|
res = foldOp(fn, consts, makeNode, options);
|
||
|
vars.unshift(res);
|
||
|
res = foldOp(fn, vars, makeNode, options);
|
||
|
} else {
|
||
|
// we won't change the children order since it's not neccessary
|
||
|
res = foldOp(fn, _args, makeNode, options);
|
||
|
}
|
||
|
} else {
|
||
|
// non-commutative binary operator
|
||
|
res = foldOp(fn, _args, makeNode, options);
|
||
|
}
|
||
|
} else {
|
||
|
// non-associative binary operator
|
||
|
_args = node.args.map(function (arg) {
|
||
|
return foldFraction(arg, options);
|
||
|
});
|
||
|
res = foldOp(fn, _args, makeNode, options);
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
case 'ParenthesisNode':
|
||
|
// remove the uneccessary parenthesis
|
||
|
return foldFraction(node.content, options);
|
||
|
|
||
|
case 'AccessorNode':
|
||
|
return _foldAccessor(foldFraction(node.object, options), foldFraction(node.index, options), options);
|
||
|
|
||
|
case 'ArrayNode':
|
||
|
{
|
||
|
var foldItems = node.items.map(function (item) {
|
||
|
return foldFraction(item, options);
|
||
|
});
|
||
|
|
||
|
if (foldItems.some(_is.isNode)) {
|
||
|
return new ArrayNode(foldItems.map(_ensureNode));
|
||
|
}
|
||
|
/* All literals -- return a Matrix so we can operate on it */
|
||
|
|
||
|
|
||
|
return matrix(foldItems);
|
||
|
}
|
||
|
|
||
|
case 'IndexNode':
|
||
|
{
|
||
|
return new IndexNode(node.dimensions.map(function (n) {
|
||
|
return simplifyConstant(n, options);
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
case 'ObjectNode':
|
||
|
{
|
||
|
var foldProps = {};
|
||
|
|
||
|
for (var prop in node.properties) {
|
||
|
foldProps[prop] = simplifyConstant(node.properties[prop], options);
|
||
|
}
|
||
|
|
||
|
return new ObjectNode(foldProps);
|
||
|
}
|
||
|
|
||
|
case 'AssignmentNode':
|
||
|
/* falls through */
|
||
|
|
||
|
case 'BlockNode':
|
||
|
/* falls through */
|
||
|
|
||
|
case 'FunctionAssignmentNode':
|
||
|
/* falls through */
|
||
|
|
||
|
case 'RangeNode':
|
||
|
/* falls through */
|
||
|
|
||
|
case 'ConditionalNode':
|
||
|
/* falls through */
|
||
|
|
||
|
default:
|
||
|
throw new Error("Unimplemented node type in simplifyConstant: ".concat(node.type));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return simplifyConstant;
|
||
|
});
|
||
|
exports.createSimplifyConstant = createSimplifyConstant;
|