"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.createSimplify = 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("./simplify/util.js"); var _simplifyConstant = require("./simplify/simplifyConstant.js"); var _object = require("../../utils/object.js"); var _map = require("../../utils/map.js"); var name = 'simplify'; var dependencies = ['config', 'typed', 'parse', 'add', 'subtract', 'multiply', 'divide', 'pow', 'isZero', 'equal', 'resolve', 'simplifyCore', '?fraction', '?bignumber', 'mathWithTransform', 'matrix', 'AccessorNode', 'ArrayNode', 'ConstantNode', 'FunctionNode', 'IndexNode', 'ObjectNode', 'OperatorNode', 'ParenthesisNode', 'SymbolNode']; var createSimplify = /* #__PURE__ */(0, _factory.factory)(name, dependencies, function (_ref) { var config = _ref.config, typed = _ref.typed, parse = _ref.parse, add = _ref.add, subtract = _ref.subtract, multiply = _ref.multiply, divide = _ref.divide, pow = _ref.pow, isZero = _ref.isZero, equal = _ref.equal, resolve = _ref.resolve, simplifyCore = _ref.simplifyCore, fraction = _ref.fraction, bignumber = _ref.bignumber, mathWithTransform = _ref.mathWithTransform, matrix = _ref.matrix, AccessorNode = _ref.AccessorNode, ArrayNode = _ref.ArrayNode, ConstantNode = _ref.ConstantNode, FunctionNode = _ref.FunctionNode, IndexNode = _ref.IndexNode, ObjectNode = _ref.ObjectNode, OperatorNode = _ref.OperatorNode, ParenthesisNode = _ref.ParenthesisNode, SymbolNode = _ref.SymbolNode; var simplifyConstant = (0, _simplifyConstant.createSimplifyConstant)({ typed: typed, config: config, mathWithTransform: mathWithTransform, matrix: matrix, fraction: fraction, bignumber: bignumber, AccessorNode: AccessorNode, ArrayNode: ArrayNode, ConstantNode: ConstantNode, FunctionNode: FunctionNode, IndexNode: IndexNode, ObjectNode: ObjectNode, OperatorNode: OperatorNode, SymbolNode: SymbolNode }); var _createUtil = (0, _util.createUtil)({ FunctionNode: FunctionNode, OperatorNode: OperatorNode, SymbolNode: SymbolNode }), hasProperty = _createUtil.hasProperty, isCommutative = _createUtil.isCommutative, isAssociative = _createUtil.isAssociative, mergeContext = _createUtil.mergeContext, flatten = _createUtil.flatten, unflattenr = _createUtil.unflattenr, unflattenl = _createUtil.unflattenl, createMakeNodeFunction = _createUtil.createMakeNodeFunction, defaultContext = _createUtil.defaultContext, realContext = _createUtil.realContext, positiveContext = _createUtil.positiveContext; /** * Simplify an expression tree. * * A list of rules are applied to an expression, repeating over the list until * no further changes are made. * It's possible to pass a custom set of rules to the function as second * argument. A rule can be specified as an object, string, or function: * * const rules = [ * { l: 'n1*n3 + n2*n3', r: '(n1+n2)*n3' }, * 'n1*n3 + n2*n3 -> (n1+n2)*n3', * function (node) { * // ... return a new node or return the node unchanged * return node * } * ] * * String and object rules consist of a left and right pattern. The left is * used to match against the expression and the right determines what matches * are replaced with. The main difference between a pattern and a normal * expression is that variables starting with the following characters are * interpreted as wildcards: * * - 'n' - matches any Node * - 'c' - matches any ConstantNode * - 'v' - matches any Node that is not a ConstantNode * * The default list of rules is exposed on the function as `simplify.rules` * and can be used as a basis to built a set of custom rules. * * To specify a rule as a string, separate the left and right pattern by '->' * When specifying a rule as an object, the following keys are meaningful: * - l - the left pattern * - r - the right pattern * - s - in lieu of l and r, the string form that is broken at -> to give them * - repeat - whether to repeat this rule until the expression stabilizes * - assuming - gives a context object, as in the 'context' option to * simplify. Every property in the context object must match the current * context in order, or else the rule will not be applied. * - imposeContext - gives a context object, as in the 'context' option to * simplify. Any settings specified will override the incoming context * for all matches of this rule. * * For more details on the theory, see: * * - [Strategies for simplifying math expressions (Stackoverflow)](https://stackoverflow.com/questions/7540227/strategies-for-simplifying-math-expressions) * - [Symbolic computation - Simplification (Wikipedia)](https://en.wikipedia.org/wiki/Symbolic_computation#Simplification) * * An optional `options` argument can be passed as last argument of `simplify`. * Currently available options (defaults in parentheses): * - `consoleDebug` (false): whether to write the expression being simplified * and any changes to it, along with the rule responsible, to console * - `context` (simplify.defaultContext): an object giving properties of * each operator, which determine what simplifications are allowed. The * currently meaningful properties are commutative, associative, * total (whether the operation is defined for all arguments), and * trivial (whether the operation applied to a single argument leaves * that argument unchanged). The default context is very permissive and * allows almost all simplifications. Only properties differing from * the default need to be specified; the default context is used as a * fallback. Additional contexts `simplify.realContext` and * `simplify.positiveContext` are supplied to cause simplify to perform * just simplifications guaranteed to preserve all values of the expression * assuming all variables and subexpressions are real numbers or * positive real numbers, respectively. (Note that these are in some cases * more restrictive than the default context; for example, the default * context will allow `x/x` to simplify to 1, whereas * `simplify.realContext` will not, as `0/0` is not equal to 1.) * - `exactFractions` (true): whether to try to convert all constants to * exact rational numbers. * - `fractionsLimit` (10000): when `exactFractions` is true, constants will * be expressed as fractions only when both numerator and denominator * are smaller than `fractionsLimit`. * * Syntax: * * simplify(expr) * simplify(expr, rules) * simplify(expr, rules) * simplify(expr, rules, scope) * simplify(expr, rules, scope, options) * simplify(expr, scope) * simplify(expr, scope, options) * * Examples: * * math.simplify('2 * 1 * x ^ (2 - 1)') // Node "2 * x" * math.simplify('2 * 3 * x', {x: 4}) // Node "24" * const f = math.parse('2 * 1 * x ^ (2 - 1)') * math.simplify(f) // Node "2 * x" * math.simplify('0.4 * x', {}, {exactFractions: true}) // Node "x * 2 / 5" * math.simplify('0.4 * x', {}, {exactFractions: false}) // Node "0.4 * x" * * See also: * * simplifyCore, derivative, evaluate, parse, rationalize, resolve * * @param {Node | string} expr * The expression to be simplified * @param {Array<{l:string, r: string} | string | function>} [rules] * Optional list with custom rules * @return {Node} Returns the simplified form of `expr` */ var simplify = typed('simplify', { string: function string(expr) { return this(parse(expr), this.rules, (0, _map.createEmptyMap)(), {}); }, 'string, Map | Object': function stringMapObject(expr, scope) { return this(parse(expr), this.rules, scope, {}); }, 'string, Map | Object, Object': function stringMapObjectObject(expr, scope, options) { return this(parse(expr), this.rules, scope, options); }, 'string, Array': function stringArray(expr, rules) { return this(parse(expr), rules, (0, _map.createEmptyMap)(), {}); }, 'string, Array, Map | Object': function stringArrayMapObject(expr, rules, scope) { return this(parse(expr), rules, scope, {}); }, 'string, Array, Map | Object, Object': function stringArrayMapObjectObject(expr, rules, scope, options) { return this(parse(expr), rules, scope, options); }, 'Node, Map | Object': function NodeMapObject(expr, scope) { return this(expr, this.rules, scope, {}); }, 'Node, Map | Object, Object': function NodeMapObjectObject(expr, scope, options) { return this(expr, this.rules, scope, options); }, Node: function Node(expr) { return this(expr, this.rules, (0, _map.createEmptyMap)(), {}); }, 'Node, Array': function NodeArray(expr, rules) { return this(expr, rules, (0, _map.createEmptyMap)(), {}); }, 'Node, Array, Map | Object': function NodeArrayMapObject(expr, rules, scope) { return this(expr, rules, scope, {}); }, 'Node, Array, Object, Object': function NodeArrayObjectObject(expr, rules, scope, options) { return this(expr, rules, (0, _map.createMap)(scope), options); }, 'Node, Array, Map, Object': function NodeArrayMapObject(expr, rules, scope, options) { var debug = options.consoleDebug; rules = _buildRules(rules, options.context); var res = resolve(expr, scope); res = removeParens(res); var visited = {}; var str = res.toString({ parenthesis: 'all' }); while (!visited[str]) { visited[str] = true; _lastsym = 0; // counter for placeholder symbols var laststr = str; if (debug) console.log('Working on: ', str); for (var i = 0; i < rules.length; i++) { var rulestr = ''; if (typeof rules[i] === 'function') { res = rules[i](res, options); if (debug) rulestr = rules[i].name; } else { flatten(res, options.context); res = applyRule(res, rules[i], options.context); if (debug) { rulestr = "".concat(rules[i].l.toString(), " -> ").concat(rules[i].r.toString()); } } if (debug) { var newstr = res.toString({ parenthesis: 'all' }); if (newstr !== laststr) { console.log('Applying', rulestr, 'produced', newstr); laststr = newstr; } } /* Use left-heavy binary tree internally, * since custom rule functions may expect it */ unflattenl(res, options.context); } str = res.toString({ parenthesis: 'all' }); } return res; } }); simplify.defaultContext = defaultContext; simplify.realContext = realContext; simplify.positiveContext = positiveContext; function removeParens(node) { return node.transform(function (node, path, parent) { return (0, _is.isParenthesisNode)(node) ? removeParens(node.content) : node; }); } // All constants that are allowed in rules var SUPPORTED_CONSTANTS = { true: true, false: true, e: true, i: true, Infinity: true, LN2: true, LN10: true, LOG2E: true, LOG10E: true, NaN: true, phi: true, pi: true, SQRT1_2: true, SQRT2: true, tau: true // null: false, // undefined: false, // version: false, }; // Array of strings, used to build the ruleSet. // Each l (left side) and r (right side) are parsed by // the expression parser into a node tree. // Left hand sides are matched to subtrees within the // expression to be parsed and replaced with the right // hand side. // TODO: Add support for constraints on constants (either in the form of a '=' expression or a callback [callback allows things like comparing symbols alphabetically]) // To evaluate lhs constants for rhs constants, use: { l: 'c1+c2', r: 'c3', evaluate: 'c3 = c1 + c2' }. Multiple assignments are separated by ';' in block format. // It is possible to get into an infinite loop with conflicting rules simplify.rules = [simplifyCore, // { l: 'n+0', r: 'n' }, // simplifyCore // { l: 'n^0', r: '1' }, // simplifyCore // { l: '0*n', r: '0' }, // simplifyCore // { l: 'n/n', r: '1'}, // simplifyCore // { l: 'n^1', r: 'n' }, // simplifyCore // { l: '+n1', r:'n1' }, // simplifyCore // { l: 'n--n1', r:'n+n1' }, // simplifyCore { l: 'log(e)', r: '1' }, // temporary rules // Note initially we tend constants to the right because like-term // collection prefers the left, and we would rather collect nonconstants { s: 'n-n1 -> n+-n1', // temporarily replace 'subtract' so we can further flatten the 'add' operator assuming: { subtract: { total: true } } }, { s: 'n-n -> 0', // partial alternative when we can't always subtract assuming: { subtract: { total: false } } }, { s: '-(c*v) -> v * (-c)', // make non-constant terms positive assuming: { multiply: { commutative: true }, subtract: { total: true } } }, { s: '-(c*v) -> (-c) * v', // non-commutative version, part 1 assuming: { multiply: { commutative: false }, subtract: { total: true } } }, { s: '-(v*c) -> v * (-c)', // non-commutative version, part 2 assuming: { multiply: { commutative: false }, subtract: { total: true } } }, { l: '-(n1/n2)', r: '-n1/n2' }, { l: '-v', r: 'v * (-1)' }, // finish making non-constant terms positive { l: '(n1 + n2)*(-1)', r: 'n1*(-1) + n2*(-1)', repeat: true }, // expand negations to achieve as much sign cancellation as possible { l: 'n/n1^n2', r: 'n*n1^-n2' }, // temporarily replace 'divide' so we can further flatten the 'multiply' operator { l: 'n/n1', r: 'n*n1^-1' }, { s: '(n1*n2)^n3 -> n1^n3 * n2^n3', assuming: { multiply: { commutative: true } } }, { s: '(n1*n2)^(-1) -> n2^(-1) * n1^(-1)', assuming: { multiply: { commutative: false } } }, // expand nested exponentiation { s: '(n ^ n1) ^ n2 -> n ^ (n1 * n2)', assuming: { divide: { total: true } } // 1/(1/n) = n needs 1/n to exist }, // collect like factors; into a sum, only do this for nonconstants { l: ' v * ( v * n1 + n2)', r: 'v^2 * n1 + v * n2' }, { s: ' v * (v^n4 * n1 + n2) -> v^(1+n4) * n1 + v * n2', assuming: { divide: { total: true } } // v*1/v = v^(1+-1) needs 1/v }, { s: 'v^n3 * ( v * n1 + n2) -> v^(n3+1) * n1 + v^n3 * n2', assuming: { divide: { total: true } } }, { s: 'v^n3 * (v^n4 * n1 + n2) -> v^(n3+n4) * n1 + v^n3 * n2', assuming: { divide: { total: true } } }, { l: 'n*n', r: 'n^2' }, { s: 'n * n^n1 -> n^(n1+1)', assuming: { divide: { total: true } } // n*1/n = n^(-1+1) needs 1/n }, { s: 'n^n1 * n^n2 -> n^(n1+n2)', assuming: { divide: { total: true } } // ditto for n^2*1/n^2 }, // Unfortunately, to deal with more complicated cancellations, it // becomes necessary to simplify constants twice per pass. It's not // terribly expensive compared to matching rules, so this should not // pose a performance problem. simplifyConstant, // First: before collecting like terms // collect like terms { s: 'n+n -> 2*n', assuming: { add: { total: true } } // 2 = 1 + 1 needs to exist }, { l: 'n+-n', r: '0' }, { l: 'v*n + v', r: 'v*(n+1)' }, // NOTE: leftmost position is special: { l: 'n3*n1 + n3*n2', r: 'n3*(n1+n2)' }, // All sub-monomials tried there. { l: 'n3^(-n4)*n1 + n3 * n2', r: 'n3^(-n4)*(n1 + n3^(n4+1) *n2)' }, { l: 'n3^(-n4)*n1 + n3^n5 * n2', r: 'n3^(-n4)*(n1 + n3^(n4+n5)*n2)' }, { s: 'n*v + v -> (n+1)*v', // noncommutative additional cases assuming: { multiply: { commutative: false } } }, { s: 'n1*n3 + n2*n3 -> (n1+n2)*n3', assuming: { multiply: { commutative: false } } }, { s: 'n1*n3^(-n4) + n2 * n3 -> (n1 + n2*n3^(n4 + 1))*n3^(-n4)', assuming: { multiply: { commutative: false } } }, { s: 'n1*n3^(-n4) + n2 * n3^n5 -> (n1 + n2*n3^(n4 + n5))*n3^(-n4)', assuming: { multiply: { commutative: false } } }, { l: 'n*c + c', r: '(n+1)*c' }, { s: 'c*n + c -> c*(n+1)', assuming: { multiply: { commutative: false } } }, simplifyConstant, // Second: before returning expressions to "standard form" // make factors positive (and undo 'make non-constant terms positive') { s: '(-n)*n1 -> -(n*n1)', assuming: { subtract: { total: true } } }, { s: 'n1*(-n) -> -(n1*n)', // in case * non-commutative assuming: { subtract: { total: true }, multiply: { commutative: false } } }, // final ordering of constants { s: 'c+v -> v+c', assuming: { add: { commutative: true } }, imposeContext: { add: { commutative: false } } }, { s: 'v*c -> c*v', assuming: { multiply: { commutative: true } }, imposeContext: { multiply: { commutative: false } } }, // undo temporary rules // { l: '(-1) * n', r: '-n' }, // #811 added test which proved this is redundant { l: 'n+-n1', r: 'n-n1' }, // undo replace 'subtract' { s: 'n*(n1^-1) -> n/n1', // undo replace 'divide'; for * commutative assuming: { multiply: { commutative: true } } // o.w. / not conventional }, { s: 'n*n1^-n2 -> n/n1^n2', assuming: { multiply: { commutative: true } } // o.w. / not conventional }, { s: 'n^-1 -> 1/n', assuming: { multiply: { commutative: true } } // o.w. / not conventional }, { l: 'n^1', r: 'n' }, // can be produced by power cancellation { s: 'n*(n1/n2) -> (n*n1)/n2', // '*' before '/' assuming: { multiply: { associative: true } } }, { s: 'n-(n1+n2) -> n-n1-n2', // '-' before '+' assuming: { addition: { associative: true, commutative: true } } }, // { l: '(n1/n2)/n3', r: 'n1/(n2*n3)' }, // { l: '(n*n1)/(n*n2)', r: 'n1/n2' }, // simplifyConstant can leave an extra factor of 1, which can always // be eliminated, since the identity always commutes { l: '1*n', r: 'n', imposeContext: { multiply: { commutative: true } } }, { s: 'n1/(n2/n3) -> (n1*n3)/n2', assuming: { multiply: { associative: true } } }, { l: 'n1/(-n2)', r: '-n1/n2' }]; /** * Takes any rule object as allowed by the specification in simplify * and puts it in a standard form used by applyRule */ function _canonicalizeRule(ruleObject, context) { var newRule = {}; if (ruleObject.s) { var lr = ruleObject.s.split('->'); if (lr.length === 2) { newRule.l = lr[0]; newRule.r = lr[1]; } else { throw SyntaxError('Could not parse rule: ' + ruleObject.s); } } else { newRule.l = ruleObject.l; newRule.r = ruleObject.r; } newRule.l = removeParens(parse(newRule.l)); newRule.r = removeParens(parse(newRule.r)); for (var _i = 0, _arr = ['imposeContext', 'repeat', 'assuming']; _i < _arr.length; _i++) { var prop = _arr[_i]; if (prop in ruleObject) { newRule[prop] = ruleObject[prop]; } } if (ruleObject.evaluate) { newRule.evaluate = parse(ruleObject.evaluate); } if (isAssociative(newRule.l, context)) { var makeNode = createMakeNodeFunction(newRule.l); var expandsym = _getExpandPlaceholderSymbol(); newRule.expanded = {}; newRule.expanded.l = makeNode([newRule.l.clone(), expandsym]); // Push the expandsym into the deepest possible branch. // This helps to match the newRule against nodes returned from getSplits() later on. flatten(newRule.expanded.l, context); unflattenr(newRule.expanded.l, context); newRule.expanded.r = makeNode([newRule.r, expandsym]); } return newRule; } /** * Parse the string array of rules into nodes * * Example syntax for rules: * * Position constants to the left in a product: * { l: 'n1 * c1', r: 'c1 * n1' } * n1 is any Node, and c1 is a ConstantNode. * * Apply difference of squares formula: * { l: '(n1 - n2) * (n1 + n2)', r: 'n1^2 - n2^2' } * n1, n2 mean any Node. * * Short hand notation: * 'n1 * c1 -> c1 * n1' */ function _buildRules(rules, context) { // Array of rules to be used to simplify expressions var ruleSet = []; for (var i = 0; i < rules.length; i++) { var rule = rules[i]; var newRule = void 0; var ruleType = (0, _typeof2.default)(rule); switch (ruleType) { case 'string': rule = { s: rule }; /* falls through */ case 'object': newRule = _canonicalizeRule(rule, context); break; case 'function': newRule = rule; break; default: throw TypeError('Unsupported type of rule: ' + ruleType); } // console.log('Adding rule: ' + rules[i]) // console.log(newRule) ruleSet.push(newRule); } return ruleSet; } var _lastsym = 0; function _getExpandPlaceholderSymbol() { return new SymbolNode('_p' + _lastsym++); } function mapRule(nodes, rule, context) { var resNodes = nodes; if (nodes) { for (var i = 0; i < nodes.length; ++i) { var newNode = applyRule(nodes[i], rule, context); if (newNode !== nodes[i]) { if (resNodes === nodes) { resNodes = nodes.slice(); } resNodes[i] = newNode; } } } return resNodes; } /** * Returns a simplfied form of node, or the original node if no simplification was possible. * * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node * @param {Object | Function} rule * @param {Object} context -- information about assumed properties of operators * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The simplified form of `expr`, or the original node if no simplification was possible. */ function applyRule(node, rule, context) { // console.log('Entering applyRule("', rule.l.toString({parenthesis:'all'}), '->', rule.r.toString({parenthesis:'all'}), '",', node.toString({parenthesis:'all'}),')') // check that the assumptions for this rule are satisfied by the current // context: if (rule.assuming) { for (var symbol in rule.assuming) { for (var property in rule.assuming[symbol]) { if (hasProperty(symbol, property, context) !== rule.assuming[symbol][property]) { return node; } } } } var mergedContext = mergeContext(rule.imposeContext, context); // Do not clone node unless we find a match var res = node; // First replace our child nodes with their simplified versions // If a child could not be simplified, applying the rule to it // will have no effect since the node is returned unchanged if (res instanceof OperatorNode || res instanceof FunctionNode) { var newArgs = mapRule(res.args, rule, context); if (newArgs !== res.args) { res = res.clone(); res.args = newArgs; } } else if (res instanceof ParenthesisNode) { if (res.content) { var newContent = applyRule(res.content, rule, context); if (newContent !== res.content) { res = new ParenthesisNode(newContent); } } } else if (res instanceof ArrayNode) { var newItems = mapRule(res.items, rule, context); if (newItems !== res.items) { res = new ArrayNode(newItems); } } else if (res instanceof AccessorNode) { var newObj = res.object; if (res.object) { newObj = applyRule(res.object, rule, context); } var newIndex = res.index; if (res.index) { newIndex = applyRule(res.index, rule, context); } if (newObj !== res.object || newIndex !== res.index) { res = new AccessorNode(newObj, newIndex); } } else if (res instanceof IndexNode) { var newDims = mapRule(res.dimensions, rule, context); if (newDims !== res.dimensions) { res = new IndexNode(newDims); } } else if (res instanceof ObjectNode) { var changed = false; var newProps = {}; for (var prop in res.properties) { newProps[prop] = applyRule(res.properties[prop], rule, context); if (newProps[prop] !== res.properties[prop]) { changed = true; } } if (changed) { res = new ObjectNode(newProps); } } // Try to match a rule against this node var repl = rule.r; var matches = _ruleMatch(rule.l, res, mergedContext)[0]; // If the rule is associative operator, we can try matching it while allowing additional terms. // This allows us to match rules like 'n+n' to the expression '(1+x)+x' or even 'x+1+x' if the operator is commutative. if (!matches && rule.expanded) { repl = rule.expanded.r; matches = _ruleMatch(rule.expanded.l, res, mergedContext)[0]; } if (matches) { // const before = res.toString({parenthesis: 'all'}) // Create a new node by cloning the rhs of the matched rule // we keep any implicit multiplication state if relevant var implicit = res.implicit; res = repl.clone(); if (implicit && 'implicit' in repl) { res.implicit = true; } // Replace placeholders with their respective nodes without traversing deeper into the replaced nodes res = res.transform(function (node) { if (node.isSymbolNode && (0, _object.hasOwnProperty)(matches.placeholders, node.name)) { return matches.placeholders[node.name].clone(); } else { return node; } }); // const after = res.toString({parenthesis: 'all'}) // console.log('Simplified ' + before + ' to ' + after) } if (rule.repeat && res !== node) { res = applyRule(res, rule, context); } return res; } /** * Get (binary) combinations of a flattened binary node * e.g. +(node1, node2, node3) -> [ * +(node1, +(node2, node3)), * +(node2, +(node1, node3)), * +(node3, +(node1, node2))] * */ function getSplits(node, context) { var res = []; var right, rightArgs; var makeNode = createMakeNodeFunction(node); if (isCommutative(node, context)) { for (var i = 0; i < node.args.length; i++) { rightArgs = node.args.slice(0); rightArgs.splice(i, 1); right = rightArgs.length === 1 ? rightArgs[0] : makeNode(rightArgs); res.push(makeNode([node.args[i], right])); } } else { // Keep order, but try all parenthesizations for (var _i2 = 1; _i2 < node.args.length; _i2++) { var left = node.args[0]; if (_i2 > 1) { left = makeNode(node.args.slice(0, _i2)); } rightArgs = node.args.slice(_i2); right = rightArgs.length === 1 ? rightArgs[0] : makeNode(rightArgs); res.push(makeNode([left, right])); } } return res; } /** * Returns the set union of two match-placeholders or null if there is a conflict. */ function mergeMatch(match1, match2) { var res = { placeholders: {} }; // Some matches may not have placeholders; this is OK if (!match1.placeholders && !match2.placeholders) { return res; } else if (!match1.placeholders) { return match2; } else if (!match2.placeholders) { return match1; } // Placeholders with the same key must match exactly for (var key in match1.placeholders) { if ((0, _object.hasOwnProperty)(match1.placeholders, key)) { res.placeholders[key] = match1.placeholders[key]; if ((0, _object.hasOwnProperty)(match2.placeholders, key)) { if (!_exactMatch(match1.placeholders[key], match2.placeholders[key])) { return null; } } } } for (var _key in match2.placeholders) { if ((0, _object.hasOwnProperty)(match2.placeholders, _key)) { res.placeholders[_key] = match2.placeholders[_key]; } } return res; } /** * Combine two lists of matches by applying mergeMatch to the cartesian product of two lists of matches. * Each list represents matches found in one child of a node. */ function combineChildMatches(list1, list2) { var res = []; if (list1.length === 0 || list2.length === 0) { return res; } var merged; for (var i1 = 0; i1 < list1.length; i1++) { for (var i2 = 0; i2 < list2.length; i2++) { merged = mergeMatch(list1[i1], list2[i2]); if (merged) { res.push(merged); } } } return res; } /** * Combine multiple lists of matches by applying mergeMatch to the cartesian product of two lists of matches. * Each list represents matches found in one child of a node. * Returns a list of unique matches. */ function mergeChildMatches(childMatches) { if (childMatches.length === 0) { return childMatches; } var sets = childMatches.reduce(combineChildMatches); var uniqueSets = []; var unique = {}; for (var i = 0; i < sets.length; i++) { var s = JSON.stringify(sets[i]); if (!unique[s]) { unique[s] = true; uniqueSets.push(sets[i]); } } return uniqueSets; } /** * Determines whether node matches rule. * * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} rule * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node * @param {Object} context -- provides assumed properties of operators * @param {Boolean} isSplit -- whether we are in process of splitting an * n-ary operator node into possible binary combinations. * Defaults to false. * @return {Object} Information about the match, if it exists. */ function _ruleMatch(rule, node, context, isSplit) { // console.log('Entering _ruleMatch(' + JSON.stringify(rule) + ', ' + JSON.stringify(node) + ')') // console.log('rule = ' + rule) // console.log('node = ' + node) // console.log('Entering _ruleMatch(', rule.toString({parenthesis:'all'}), ', ', node.toString({parenthesis:'all'}), ', ', context, ')') var res = [{ placeholders: {} }]; if (rule instanceof OperatorNode && node instanceof OperatorNode || rule instanceof FunctionNode && node instanceof FunctionNode) { // If the rule is an OperatorNode or a FunctionNode, then node must match exactly if (rule instanceof OperatorNode) { if (rule.op !== node.op || rule.fn !== node.fn) { return []; } } else if (rule instanceof FunctionNode) { if (rule.name !== node.name) { return []; } } // rule and node match. Search the children of rule and node. if (node.args.length === 1 && rule.args.length === 1 || !isAssociative(node, context) && node.args.length === rule.args.length || isSplit) { // Expect non-associative operators to match exactly, // except in any order if operator is commutative var childMatches = []; for (var i = 0; i < rule.args.length; i++) { var childMatch = _ruleMatch(rule.args[i], node.args[i], context); if (childMatch.length === 0) { // Child did not match, so stop searching immediately break; } // The child matched, so add the information returned from the child to our result childMatches.push(childMatch); } if (childMatches.length !== rule.args.length) { if (!isCommutative(node, context) || // exact match in order needed rule.args.length === 1) { // nothing to commute return []; } if (rule.args.length > 2) { /* Need to generate all permutations and try them. * It's a bit complicated, and unlikely to come up since there * are very few ternary or higher operators. So punt for now. */ throw new Error('permuting >2 commutative non-associative rule arguments not yet implemented'); } /* Exactly two arguments, try them reversed */ var leftMatch = _ruleMatch(rule.args[0], node.args[1], context); if (leftMatch.length === 0) { return []; } var rightMatch = _ruleMatch(rule.args[1], node.args[0], context); if (rightMatch.length === 0) { return []; } childMatches = [leftMatch, rightMatch]; } res = mergeChildMatches(childMatches); } else if (node.args.length >= 2 && rule.args.length === 2) { // node is flattened, rule is not // Associative operators/functions can be split in different ways so we check if the rule matches each // them and return their union. var splits = getSplits(node, context); var splitMatches = []; for (var _i3 = 0; _i3 < splits.length; _i3++) { var matchSet = _ruleMatch(rule, splits[_i3], context, true); // recursing at the same tree depth here splitMatches = splitMatches.concat(matchSet); } return splitMatches; } else if (rule.args.length > 2) { throw Error('Unexpected non-binary associative function: ' + rule.toString()); } else { // Incorrect number of arguments in rule and node, so no match return []; } } else if (rule instanceof SymbolNode) { // If the rule is a SymbolNode, then it carries a special meaning // according to the first character of the symbol node name. // c.* matches a ConstantNode // n.* matches any node if (rule.name.length === 0) { throw new Error('Symbol in rule has 0 length...!?'); } if (SUPPORTED_CONSTANTS[rule.name]) { // built-in constant must match exactly if (rule.name !== node.name) { return []; } } else if (rule.name[0] === 'n' || rule.name.substring(0, 2) === '_p') { // rule matches _anything_, so assign this node to the rule.name placeholder // Assign node to the rule.name placeholder. // Our parent will check for matches among placeholders. res[0].placeholders[rule.name] = node; } else if (rule.name[0] === 'v') { // rule matches any variable thing (not a ConstantNode) if (!(0, _is.isConstantNode)(node)) { res[0].placeholders[rule.name] = node; } else { // Mis-match: rule was expecting something other than a ConstantNode return []; } } else if (rule.name[0] === 'c') { // rule matches any ConstantNode if (node instanceof ConstantNode) { res[0].placeholders[rule.name] = node; } else { // Mis-match: rule was expecting a ConstantNode return []; } } else { throw new Error('Invalid symbol in rule: ' + rule.name); } } else if (rule instanceof ConstantNode) { // Literal constant must match exactly if (!equal(rule.value, node.value)) { return []; } } else { // Some other node was encountered which we aren't prepared for, so no match return []; } // It's a match! // console.log('_ruleMatch(' + rule.toString() + ', ' + node.toString() + ') found a match') return res; } /** * Determines whether p and q (and all their children nodes) are identical. * * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} p * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} q * @return {Object} Information about the match, if it exists. */ function _exactMatch(p, q) { if (p instanceof ConstantNode && q instanceof ConstantNode) { if (!equal(p.value, q.value)) { return false; } } else if (p instanceof SymbolNode && q instanceof SymbolNode) { if (p.name !== q.name) { return false; } } else if (p instanceof OperatorNode && q instanceof OperatorNode || p instanceof FunctionNode && q instanceof FunctionNode) { if (p instanceof OperatorNode) { if (p.op !== q.op || p.fn !== q.fn) { return false; } } else if (p instanceof FunctionNode) { if (p.name !== q.name) { return false; } } if (p.args.length !== q.args.length) { return false; } for (var i = 0; i < p.args.length; i++) { if (!_exactMatch(p.args[i], q.args[i])) { return false; } } } else { return false; } return true; } return simplify; }); exports.createSimplify = createSimplify;