simplify.js (38673B)
1 "use strict"; 2 3 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 5 Object.defineProperty(exports, "__esModule", { 6 value: true 7 }); 8 exports.createSimplify = void 0; 9 10 var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); 11 12 var _is = require("../../utils/is.js"); 13 14 var _factory = require("../../utils/factory.js"); 15 16 var _util = require("./simplify/util.js"); 17 18 var _simplifyConstant = require("./simplify/simplifyConstant.js"); 19 20 var _object = require("../../utils/object.js"); 21 22 var _map = require("../../utils/map.js"); 23 24 var name = 'simplify'; 25 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']; 26 var createSimplify = /* #__PURE__ */(0, _factory.factory)(name, dependencies, function (_ref) { 27 var config = _ref.config, 28 typed = _ref.typed, 29 parse = _ref.parse, 30 add = _ref.add, 31 subtract = _ref.subtract, 32 multiply = _ref.multiply, 33 divide = _ref.divide, 34 pow = _ref.pow, 35 isZero = _ref.isZero, 36 equal = _ref.equal, 37 resolve = _ref.resolve, 38 simplifyCore = _ref.simplifyCore, 39 fraction = _ref.fraction, 40 bignumber = _ref.bignumber, 41 mathWithTransform = _ref.mathWithTransform, 42 matrix = _ref.matrix, 43 AccessorNode = _ref.AccessorNode, 44 ArrayNode = _ref.ArrayNode, 45 ConstantNode = _ref.ConstantNode, 46 FunctionNode = _ref.FunctionNode, 47 IndexNode = _ref.IndexNode, 48 ObjectNode = _ref.ObjectNode, 49 OperatorNode = _ref.OperatorNode, 50 ParenthesisNode = _ref.ParenthesisNode, 51 SymbolNode = _ref.SymbolNode; 52 var simplifyConstant = (0, _simplifyConstant.createSimplifyConstant)({ 53 typed: typed, 54 config: config, 55 mathWithTransform: mathWithTransform, 56 matrix: matrix, 57 fraction: fraction, 58 bignumber: bignumber, 59 AccessorNode: AccessorNode, 60 ArrayNode: ArrayNode, 61 ConstantNode: ConstantNode, 62 FunctionNode: FunctionNode, 63 IndexNode: IndexNode, 64 ObjectNode: ObjectNode, 65 OperatorNode: OperatorNode, 66 SymbolNode: SymbolNode 67 }); 68 69 var _createUtil = (0, _util.createUtil)({ 70 FunctionNode: FunctionNode, 71 OperatorNode: OperatorNode, 72 SymbolNode: SymbolNode 73 }), 74 hasProperty = _createUtil.hasProperty, 75 isCommutative = _createUtil.isCommutative, 76 isAssociative = _createUtil.isAssociative, 77 mergeContext = _createUtil.mergeContext, 78 flatten = _createUtil.flatten, 79 unflattenr = _createUtil.unflattenr, 80 unflattenl = _createUtil.unflattenl, 81 createMakeNodeFunction = _createUtil.createMakeNodeFunction, 82 defaultContext = _createUtil.defaultContext, 83 realContext = _createUtil.realContext, 84 positiveContext = _createUtil.positiveContext; 85 /** 86 * Simplify an expression tree. 87 * 88 * A list of rules are applied to an expression, repeating over the list until 89 * no further changes are made. 90 * It's possible to pass a custom set of rules to the function as second 91 * argument. A rule can be specified as an object, string, or function: 92 * 93 * const rules = [ 94 * { l: 'n1*n3 + n2*n3', r: '(n1+n2)*n3' }, 95 * 'n1*n3 + n2*n3 -> (n1+n2)*n3', 96 * function (node) { 97 * // ... return a new node or return the node unchanged 98 * return node 99 * } 100 * ] 101 * 102 * String and object rules consist of a left and right pattern. The left is 103 * used to match against the expression and the right determines what matches 104 * are replaced with. The main difference between a pattern and a normal 105 * expression is that variables starting with the following characters are 106 * interpreted as wildcards: 107 * 108 * - 'n' - matches any Node 109 * - 'c' - matches any ConstantNode 110 * - 'v' - matches any Node that is not a ConstantNode 111 * 112 * The default list of rules is exposed on the function as `simplify.rules` 113 * and can be used as a basis to built a set of custom rules. 114 * 115 * To specify a rule as a string, separate the left and right pattern by '->' 116 * When specifying a rule as an object, the following keys are meaningful: 117 * - l - the left pattern 118 * - r - the right pattern 119 * - s - in lieu of l and r, the string form that is broken at -> to give them 120 * - repeat - whether to repeat this rule until the expression stabilizes 121 * - assuming - gives a context object, as in the 'context' option to 122 * simplify. Every property in the context object must match the current 123 * context in order, or else the rule will not be applied. 124 * - imposeContext - gives a context object, as in the 'context' option to 125 * simplify. Any settings specified will override the incoming context 126 * for all matches of this rule. 127 * 128 * For more details on the theory, see: 129 * 130 * - [Strategies for simplifying math expressions (Stackoverflow)](https://stackoverflow.com/questions/7540227/strategies-for-simplifying-math-expressions) 131 * - [Symbolic computation - Simplification (Wikipedia)](https://en.wikipedia.org/wiki/Symbolic_computation#Simplification) 132 * 133 * An optional `options` argument can be passed as last argument of `simplify`. 134 * Currently available options (defaults in parentheses): 135 * - `consoleDebug` (false): whether to write the expression being simplified 136 * and any changes to it, along with the rule responsible, to console 137 * - `context` (simplify.defaultContext): an object giving properties of 138 * each operator, which determine what simplifications are allowed. The 139 * currently meaningful properties are commutative, associative, 140 * total (whether the operation is defined for all arguments), and 141 * trivial (whether the operation applied to a single argument leaves 142 * that argument unchanged). The default context is very permissive and 143 * allows almost all simplifications. Only properties differing from 144 * the default need to be specified; the default context is used as a 145 * fallback. Additional contexts `simplify.realContext` and 146 * `simplify.positiveContext` are supplied to cause simplify to perform 147 * just simplifications guaranteed to preserve all values of the expression 148 * assuming all variables and subexpressions are real numbers or 149 * positive real numbers, respectively. (Note that these are in some cases 150 * more restrictive than the default context; for example, the default 151 * context will allow `x/x` to simplify to 1, whereas 152 * `simplify.realContext` will not, as `0/0` is not equal to 1.) 153 * - `exactFractions` (true): whether to try to convert all constants to 154 * exact rational numbers. 155 * - `fractionsLimit` (10000): when `exactFractions` is true, constants will 156 * be expressed as fractions only when both numerator and denominator 157 * are smaller than `fractionsLimit`. 158 * 159 * Syntax: 160 * 161 * simplify(expr) 162 * simplify(expr, rules) 163 * simplify(expr, rules) 164 * simplify(expr, rules, scope) 165 * simplify(expr, rules, scope, options) 166 * simplify(expr, scope) 167 * simplify(expr, scope, options) 168 * 169 * Examples: 170 * 171 * math.simplify('2 * 1 * x ^ (2 - 1)') // Node "2 * x" 172 * math.simplify('2 * 3 * x', {x: 4}) // Node "24" 173 * const f = math.parse('2 * 1 * x ^ (2 - 1)') 174 * math.simplify(f) // Node "2 * x" 175 * math.simplify('0.4 * x', {}, {exactFractions: true}) // Node "x * 2 / 5" 176 * math.simplify('0.4 * x', {}, {exactFractions: false}) // Node "0.4 * x" 177 * 178 * See also: 179 * 180 * simplifyCore, derivative, evaluate, parse, rationalize, resolve 181 * 182 * @param {Node | string} expr 183 * The expression to be simplified 184 * @param {Array<{l:string, r: string} | string | function>} [rules] 185 * Optional list with custom rules 186 * @return {Node} Returns the simplified form of `expr` 187 */ 188 189 190 var simplify = typed('simplify', { 191 string: function string(expr) { 192 return this(parse(expr), this.rules, (0, _map.createEmptyMap)(), {}); 193 }, 194 'string, Map | Object': function stringMapObject(expr, scope) { 195 return this(parse(expr), this.rules, scope, {}); 196 }, 197 'string, Map | Object, Object': function stringMapObjectObject(expr, scope, options) { 198 return this(parse(expr), this.rules, scope, options); 199 }, 200 'string, Array': function stringArray(expr, rules) { 201 return this(parse(expr), rules, (0, _map.createEmptyMap)(), {}); 202 }, 203 'string, Array, Map | Object': function stringArrayMapObject(expr, rules, scope) { 204 return this(parse(expr), rules, scope, {}); 205 }, 206 'string, Array, Map | Object, Object': function stringArrayMapObjectObject(expr, rules, scope, options) { 207 return this(parse(expr), rules, scope, options); 208 }, 209 'Node, Map | Object': function NodeMapObject(expr, scope) { 210 return this(expr, this.rules, scope, {}); 211 }, 212 'Node, Map | Object, Object': function NodeMapObjectObject(expr, scope, options) { 213 return this(expr, this.rules, scope, options); 214 }, 215 Node: function Node(expr) { 216 return this(expr, this.rules, (0, _map.createEmptyMap)(), {}); 217 }, 218 'Node, Array': function NodeArray(expr, rules) { 219 return this(expr, rules, (0, _map.createEmptyMap)(), {}); 220 }, 221 'Node, Array, Map | Object': function NodeArrayMapObject(expr, rules, scope) { 222 return this(expr, rules, scope, {}); 223 }, 224 'Node, Array, Object, Object': function NodeArrayObjectObject(expr, rules, scope, options) { 225 return this(expr, rules, (0, _map.createMap)(scope), options); 226 }, 227 'Node, Array, Map, Object': function NodeArrayMapObject(expr, rules, scope, options) { 228 var debug = options.consoleDebug; 229 rules = _buildRules(rules, options.context); 230 var res = resolve(expr, scope); 231 res = removeParens(res); 232 var visited = {}; 233 var str = res.toString({ 234 parenthesis: 'all' 235 }); 236 237 while (!visited[str]) { 238 visited[str] = true; 239 _lastsym = 0; // counter for placeholder symbols 240 241 var laststr = str; 242 if (debug) console.log('Working on: ', str); 243 244 for (var i = 0; i < rules.length; i++) { 245 var rulestr = ''; 246 247 if (typeof rules[i] === 'function') { 248 res = rules[i](res, options); 249 if (debug) rulestr = rules[i].name; 250 } else { 251 flatten(res, options.context); 252 res = applyRule(res, rules[i], options.context); 253 254 if (debug) { 255 rulestr = "".concat(rules[i].l.toString(), " -> ").concat(rules[i].r.toString()); 256 } 257 } 258 259 if (debug) { 260 var newstr = res.toString({ 261 parenthesis: 'all' 262 }); 263 264 if (newstr !== laststr) { 265 console.log('Applying', rulestr, 'produced', newstr); 266 laststr = newstr; 267 } 268 } 269 /* Use left-heavy binary tree internally, 270 * since custom rule functions may expect it 271 */ 272 273 274 unflattenl(res, options.context); 275 } 276 277 str = res.toString({ 278 parenthesis: 'all' 279 }); 280 } 281 282 return res; 283 } 284 }); 285 simplify.defaultContext = defaultContext; 286 simplify.realContext = realContext; 287 simplify.positiveContext = positiveContext; 288 289 function removeParens(node) { 290 return node.transform(function (node, path, parent) { 291 return (0, _is.isParenthesisNode)(node) ? removeParens(node.content) : node; 292 }); 293 } // All constants that are allowed in rules 294 295 296 var SUPPORTED_CONSTANTS = { 297 true: true, 298 false: true, 299 e: true, 300 i: true, 301 Infinity: true, 302 LN2: true, 303 LN10: true, 304 LOG2E: true, 305 LOG10E: true, 306 NaN: true, 307 phi: true, 308 pi: true, 309 SQRT1_2: true, 310 SQRT2: true, 311 tau: true // null: false, 312 // undefined: false, 313 // version: false, 314 315 }; // Array of strings, used to build the ruleSet. 316 // Each l (left side) and r (right side) are parsed by 317 // the expression parser into a node tree. 318 // Left hand sides are matched to subtrees within the 319 // expression to be parsed and replaced with the right 320 // hand side. 321 // TODO: Add support for constraints on constants (either in the form of a '=' expression or a callback [callback allows things like comparing symbols alphabetically]) 322 // 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. 323 // It is possible to get into an infinite loop with conflicting rules 324 325 simplify.rules = [simplifyCore, // { l: 'n+0', r: 'n' }, // simplifyCore 326 // { l: 'n^0', r: '1' }, // simplifyCore 327 // { l: '0*n', r: '0' }, // simplifyCore 328 // { l: 'n/n', r: '1'}, // simplifyCore 329 // { l: 'n^1', r: 'n' }, // simplifyCore 330 // { l: '+n1', r:'n1' }, // simplifyCore 331 // { l: 'n--n1', r:'n+n1' }, // simplifyCore 332 { 333 l: 'log(e)', 334 r: '1' 335 }, // temporary rules 336 // Note initially we tend constants to the right because like-term 337 // collection prefers the left, and we would rather collect nonconstants 338 { 339 s: 'n-n1 -> n+-n1', 340 // temporarily replace 'subtract' so we can further flatten the 'add' operator 341 assuming: { 342 subtract: { 343 total: true 344 } 345 } 346 }, { 347 s: 'n-n -> 0', 348 // partial alternative when we can't always subtract 349 assuming: { 350 subtract: { 351 total: false 352 } 353 } 354 }, { 355 s: '-(c*v) -> v * (-c)', 356 // make non-constant terms positive 357 assuming: { 358 multiply: { 359 commutative: true 360 }, 361 subtract: { 362 total: true 363 } 364 } 365 }, { 366 s: '-(c*v) -> (-c) * v', 367 // non-commutative version, part 1 368 assuming: { 369 multiply: { 370 commutative: false 371 }, 372 subtract: { 373 total: true 374 } 375 } 376 }, { 377 s: '-(v*c) -> v * (-c)', 378 // non-commutative version, part 2 379 assuming: { 380 multiply: { 381 commutative: false 382 }, 383 subtract: { 384 total: true 385 } 386 } 387 }, { 388 l: '-(n1/n2)', 389 r: '-n1/n2' 390 }, { 391 l: '-v', 392 r: 'v * (-1)' 393 }, // finish making non-constant terms positive 394 { 395 l: '(n1 + n2)*(-1)', 396 r: 'n1*(-1) + n2*(-1)', 397 repeat: true 398 }, // expand negations to achieve as much sign cancellation as possible 399 { 400 l: 'n/n1^n2', 401 r: 'n*n1^-n2' 402 }, // temporarily replace 'divide' so we can further flatten the 'multiply' operator 403 { 404 l: 'n/n1', 405 r: 'n*n1^-1' 406 }, { 407 s: '(n1*n2)^n3 -> n1^n3 * n2^n3', 408 assuming: { 409 multiply: { 410 commutative: true 411 } 412 } 413 }, { 414 s: '(n1*n2)^(-1) -> n2^(-1) * n1^(-1)', 415 assuming: { 416 multiply: { 417 commutative: false 418 } 419 } 420 }, // expand nested exponentiation 421 { 422 s: '(n ^ n1) ^ n2 -> n ^ (n1 * n2)', 423 assuming: { 424 divide: { 425 total: true 426 } 427 } // 1/(1/n) = n needs 1/n to exist 428 429 }, // collect like factors; into a sum, only do this for nonconstants 430 { 431 l: ' v * ( v * n1 + n2)', 432 r: 'v^2 * n1 + v * n2' 433 }, { 434 s: ' v * (v^n4 * n1 + n2) -> v^(1+n4) * n1 + v * n2', 435 assuming: { 436 divide: { 437 total: true 438 } 439 } // v*1/v = v^(1+-1) needs 1/v 440 441 }, { 442 s: 'v^n3 * ( v * n1 + n2) -> v^(n3+1) * n1 + v^n3 * n2', 443 assuming: { 444 divide: { 445 total: true 446 } 447 } 448 }, { 449 s: 'v^n3 * (v^n4 * n1 + n2) -> v^(n3+n4) * n1 + v^n3 * n2', 450 assuming: { 451 divide: { 452 total: true 453 } 454 } 455 }, { 456 l: 'n*n', 457 r: 'n^2' 458 }, { 459 s: 'n * n^n1 -> n^(n1+1)', 460 assuming: { 461 divide: { 462 total: true 463 } 464 } // n*1/n = n^(-1+1) needs 1/n 465 466 }, { 467 s: 'n^n1 * n^n2 -> n^(n1+n2)', 468 assuming: { 469 divide: { 470 total: true 471 } 472 } // ditto for n^2*1/n^2 473 474 }, // Unfortunately, to deal with more complicated cancellations, it 475 // becomes necessary to simplify constants twice per pass. It's not 476 // terribly expensive compared to matching rules, so this should not 477 // pose a performance problem. 478 simplifyConstant, // First: before collecting like terms 479 // collect like terms 480 { 481 s: 'n+n -> 2*n', 482 assuming: { 483 add: { 484 total: true 485 } 486 } // 2 = 1 + 1 needs to exist 487 488 }, { 489 l: 'n+-n', 490 r: '0' 491 }, { 492 l: 'v*n + v', 493 r: 'v*(n+1)' 494 }, // NOTE: leftmost position is special: 495 { 496 l: 'n3*n1 + n3*n2', 497 r: 'n3*(n1+n2)' 498 }, // All sub-monomials tried there. 499 { 500 l: 'n3^(-n4)*n1 + n3 * n2', 501 r: 'n3^(-n4)*(n1 + n3^(n4+1) *n2)' 502 }, { 503 l: 'n3^(-n4)*n1 + n3^n5 * n2', 504 r: 'n3^(-n4)*(n1 + n3^(n4+n5)*n2)' 505 }, { 506 s: 'n*v + v -> (n+1)*v', 507 // noncommutative additional cases 508 assuming: { 509 multiply: { 510 commutative: false 511 } 512 } 513 }, { 514 s: 'n1*n3 + n2*n3 -> (n1+n2)*n3', 515 assuming: { 516 multiply: { 517 commutative: false 518 } 519 } 520 }, { 521 s: 'n1*n3^(-n4) + n2 * n3 -> (n1 + n2*n3^(n4 + 1))*n3^(-n4)', 522 assuming: { 523 multiply: { 524 commutative: false 525 } 526 } 527 }, { 528 s: 'n1*n3^(-n4) + n2 * n3^n5 -> (n1 + n2*n3^(n4 + n5))*n3^(-n4)', 529 assuming: { 530 multiply: { 531 commutative: false 532 } 533 } 534 }, { 535 l: 'n*c + c', 536 r: '(n+1)*c' 537 }, { 538 s: 'c*n + c -> c*(n+1)', 539 assuming: { 540 multiply: { 541 commutative: false 542 } 543 } 544 }, simplifyConstant, // Second: before returning expressions to "standard form" 545 // make factors positive (and undo 'make non-constant terms positive') 546 { 547 s: '(-n)*n1 -> -(n*n1)', 548 assuming: { 549 subtract: { 550 total: true 551 } 552 } 553 }, { 554 s: 'n1*(-n) -> -(n1*n)', 555 // in case * non-commutative 556 assuming: { 557 subtract: { 558 total: true 559 }, 560 multiply: { 561 commutative: false 562 } 563 } 564 }, // final ordering of constants 565 { 566 s: 'c+v -> v+c', 567 assuming: { 568 add: { 569 commutative: true 570 } 571 }, 572 imposeContext: { 573 add: { 574 commutative: false 575 } 576 } 577 }, { 578 s: 'v*c -> c*v', 579 assuming: { 580 multiply: { 581 commutative: true 582 } 583 }, 584 imposeContext: { 585 multiply: { 586 commutative: false 587 } 588 } 589 }, // undo temporary rules 590 // { l: '(-1) * n', r: '-n' }, // #811 added test which proved this is redundant 591 { 592 l: 'n+-n1', 593 r: 'n-n1' 594 }, // undo replace 'subtract' 595 { 596 s: 'n*(n1^-1) -> n/n1', 597 // undo replace 'divide'; for * commutative 598 assuming: { 599 multiply: { 600 commutative: true 601 } 602 } // o.w. / not conventional 603 604 }, { 605 s: 'n*n1^-n2 -> n/n1^n2', 606 assuming: { 607 multiply: { 608 commutative: true 609 } 610 } // o.w. / not conventional 611 612 }, { 613 s: 'n^-1 -> 1/n', 614 assuming: { 615 multiply: { 616 commutative: true 617 } 618 } // o.w. / not conventional 619 620 }, { 621 l: 'n^1', 622 r: 'n' 623 }, // can be produced by power cancellation 624 { 625 s: 'n*(n1/n2) -> (n*n1)/n2', 626 // '*' before '/' 627 assuming: { 628 multiply: { 629 associative: true 630 } 631 } 632 }, { 633 s: 'n-(n1+n2) -> n-n1-n2', 634 // '-' before '+' 635 assuming: { 636 addition: { 637 associative: true, 638 commutative: true 639 } 640 } 641 }, // { l: '(n1/n2)/n3', r: 'n1/(n2*n3)' }, 642 // { l: '(n*n1)/(n*n2)', r: 'n1/n2' }, 643 // simplifyConstant can leave an extra factor of 1, which can always 644 // be eliminated, since the identity always commutes 645 { 646 l: '1*n', 647 r: 'n', 648 imposeContext: { 649 multiply: { 650 commutative: true 651 } 652 } 653 }, { 654 s: 'n1/(n2/n3) -> (n1*n3)/n2', 655 assuming: { 656 multiply: { 657 associative: true 658 } 659 } 660 }, { 661 l: 'n1/(-n2)', 662 r: '-n1/n2' 663 }]; 664 /** 665 * Takes any rule object as allowed by the specification in simplify 666 * and puts it in a standard form used by applyRule 667 */ 668 669 function _canonicalizeRule(ruleObject, context) { 670 var newRule = {}; 671 672 if (ruleObject.s) { 673 var lr = ruleObject.s.split('->'); 674 675 if (lr.length === 2) { 676 newRule.l = lr[0]; 677 newRule.r = lr[1]; 678 } else { 679 throw SyntaxError('Could not parse rule: ' + ruleObject.s); 680 } 681 } else { 682 newRule.l = ruleObject.l; 683 newRule.r = ruleObject.r; 684 } 685 686 newRule.l = removeParens(parse(newRule.l)); 687 newRule.r = removeParens(parse(newRule.r)); 688 689 for (var _i = 0, _arr = ['imposeContext', 'repeat', 'assuming']; _i < _arr.length; _i++) { 690 var prop = _arr[_i]; 691 692 if (prop in ruleObject) { 693 newRule[prop] = ruleObject[prop]; 694 } 695 } 696 697 if (ruleObject.evaluate) { 698 newRule.evaluate = parse(ruleObject.evaluate); 699 } 700 701 if (isAssociative(newRule.l, context)) { 702 var makeNode = createMakeNodeFunction(newRule.l); 703 704 var expandsym = _getExpandPlaceholderSymbol(); 705 706 newRule.expanded = {}; 707 newRule.expanded.l = makeNode([newRule.l.clone(), expandsym]); // Push the expandsym into the deepest possible branch. 708 // This helps to match the newRule against nodes returned from getSplits() later on. 709 710 flatten(newRule.expanded.l, context); 711 unflattenr(newRule.expanded.l, context); 712 newRule.expanded.r = makeNode([newRule.r, expandsym]); 713 } 714 715 return newRule; 716 } 717 /** 718 * Parse the string array of rules into nodes 719 * 720 * Example syntax for rules: 721 * 722 * Position constants to the left in a product: 723 * { l: 'n1 * c1', r: 'c1 * n1' } 724 * n1 is any Node, and c1 is a ConstantNode. 725 * 726 * Apply difference of squares formula: 727 * { l: '(n1 - n2) * (n1 + n2)', r: 'n1^2 - n2^2' } 728 * n1, n2 mean any Node. 729 * 730 * Short hand notation: 731 * 'n1 * c1 -> c1 * n1' 732 */ 733 734 735 function _buildRules(rules, context) { 736 // Array of rules to be used to simplify expressions 737 var ruleSet = []; 738 739 for (var i = 0; i < rules.length; i++) { 740 var rule = rules[i]; 741 var newRule = void 0; 742 var ruleType = (0, _typeof2.default)(rule); 743 744 switch (ruleType) { 745 case 'string': 746 rule = { 747 s: rule 748 }; 749 750 /* falls through */ 751 752 case 'object': 753 newRule = _canonicalizeRule(rule, context); 754 break; 755 756 case 'function': 757 newRule = rule; 758 break; 759 760 default: 761 throw TypeError('Unsupported type of rule: ' + ruleType); 762 } // console.log('Adding rule: ' + rules[i]) 763 // console.log(newRule) 764 765 766 ruleSet.push(newRule); 767 } 768 769 return ruleSet; 770 } 771 772 var _lastsym = 0; 773 774 function _getExpandPlaceholderSymbol() { 775 return new SymbolNode('_p' + _lastsym++); 776 } 777 778 function mapRule(nodes, rule, context) { 779 var resNodes = nodes; 780 781 if (nodes) { 782 for (var i = 0; i < nodes.length; ++i) { 783 var newNode = applyRule(nodes[i], rule, context); 784 785 if (newNode !== nodes[i]) { 786 if (resNodes === nodes) { 787 resNodes = nodes.slice(); 788 } 789 790 resNodes[i] = newNode; 791 } 792 } 793 } 794 795 return resNodes; 796 } 797 /** 798 * Returns a simplfied form of node, or the original node if no simplification was possible. 799 * 800 * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node 801 * @param {Object | Function} rule 802 * @param {Object} context -- information about assumed properties of operators 803 * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The simplified form of `expr`, or the original node if no simplification was possible. 804 */ 805 806 807 function applyRule(node, rule, context) { 808 // console.log('Entering applyRule("', rule.l.toString({parenthesis:'all'}), '->', rule.r.toString({parenthesis:'all'}), '",', node.toString({parenthesis:'all'}),')') 809 // check that the assumptions for this rule are satisfied by the current 810 // context: 811 if (rule.assuming) { 812 for (var symbol in rule.assuming) { 813 for (var property in rule.assuming[symbol]) { 814 if (hasProperty(symbol, property, context) !== rule.assuming[symbol][property]) { 815 return node; 816 } 817 } 818 } 819 } 820 821 var mergedContext = mergeContext(rule.imposeContext, context); // Do not clone node unless we find a match 822 823 var res = node; // First replace our child nodes with their simplified versions 824 // If a child could not be simplified, applying the rule to it 825 // will have no effect since the node is returned unchanged 826 827 if (res instanceof OperatorNode || res instanceof FunctionNode) { 828 var newArgs = mapRule(res.args, rule, context); 829 830 if (newArgs !== res.args) { 831 res = res.clone(); 832 res.args = newArgs; 833 } 834 } else if (res instanceof ParenthesisNode) { 835 if (res.content) { 836 var newContent = applyRule(res.content, rule, context); 837 838 if (newContent !== res.content) { 839 res = new ParenthesisNode(newContent); 840 } 841 } 842 } else if (res instanceof ArrayNode) { 843 var newItems = mapRule(res.items, rule, context); 844 845 if (newItems !== res.items) { 846 res = new ArrayNode(newItems); 847 } 848 } else if (res instanceof AccessorNode) { 849 var newObj = res.object; 850 851 if (res.object) { 852 newObj = applyRule(res.object, rule, context); 853 } 854 855 var newIndex = res.index; 856 857 if (res.index) { 858 newIndex = applyRule(res.index, rule, context); 859 } 860 861 if (newObj !== res.object || newIndex !== res.index) { 862 res = new AccessorNode(newObj, newIndex); 863 } 864 } else if (res instanceof IndexNode) { 865 var newDims = mapRule(res.dimensions, rule, context); 866 867 if (newDims !== res.dimensions) { 868 res = new IndexNode(newDims); 869 } 870 } else if (res instanceof ObjectNode) { 871 var changed = false; 872 var newProps = {}; 873 874 for (var prop in res.properties) { 875 newProps[prop] = applyRule(res.properties[prop], rule, context); 876 877 if (newProps[prop] !== res.properties[prop]) { 878 changed = true; 879 } 880 } 881 882 if (changed) { 883 res = new ObjectNode(newProps); 884 } 885 } // Try to match a rule against this node 886 887 888 var repl = rule.r; 889 890 var matches = _ruleMatch(rule.l, res, mergedContext)[0]; // If the rule is associative operator, we can try matching it while allowing additional terms. 891 // 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. 892 893 894 if (!matches && rule.expanded) { 895 repl = rule.expanded.r; 896 matches = _ruleMatch(rule.expanded.l, res, mergedContext)[0]; 897 } 898 899 if (matches) { 900 // const before = res.toString({parenthesis: 'all'}) 901 // Create a new node by cloning the rhs of the matched rule 902 // we keep any implicit multiplication state if relevant 903 var implicit = res.implicit; 904 res = repl.clone(); 905 906 if (implicit && 'implicit' in repl) { 907 res.implicit = true; 908 } // Replace placeholders with their respective nodes without traversing deeper into the replaced nodes 909 910 911 res = res.transform(function (node) { 912 if (node.isSymbolNode && (0, _object.hasOwnProperty)(matches.placeholders, node.name)) { 913 return matches.placeholders[node.name].clone(); 914 } else { 915 return node; 916 } 917 }); // const after = res.toString({parenthesis: 'all'}) 918 // console.log('Simplified ' + before + ' to ' + after) 919 } 920 921 if (rule.repeat && res !== node) { 922 res = applyRule(res, rule, context); 923 } 924 925 return res; 926 } 927 /** 928 * Get (binary) combinations of a flattened binary node 929 * e.g. +(node1, node2, node3) -> [ 930 * +(node1, +(node2, node3)), 931 * +(node2, +(node1, node3)), 932 * +(node3, +(node1, node2))] 933 * 934 */ 935 936 937 function getSplits(node, context) { 938 var res = []; 939 var right, rightArgs; 940 var makeNode = createMakeNodeFunction(node); 941 942 if (isCommutative(node, context)) { 943 for (var i = 0; i < node.args.length; i++) { 944 rightArgs = node.args.slice(0); 945 rightArgs.splice(i, 1); 946 right = rightArgs.length === 1 ? rightArgs[0] : makeNode(rightArgs); 947 res.push(makeNode([node.args[i], right])); 948 } 949 } else { 950 // Keep order, but try all parenthesizations 951 for (var _i2 = 1; _i2 < node.args.length; _i2++) { 952 var left = node.args[0]; 953 954 if (_i2 > 1) { 955 left = makeNode(node.args.slice(0, _i2)); 956 } 957 958 rightArgs = node.args.slice(_i2); 959 right = rightArgs.length === 1 ? rightArgs[0] : makeNode(rightArgs); 960 res.push(makeNode([left, right])); 961 } 962 } 963 964 return res; 965 } 966 /** 967 * Returns the set union of two match-placeholders or null if there is a conflict. 968 */ 969 970 971 function mergeMatch(match1, match2) { 972 var res = { 973 placeholders: {} 974 }; // Some matches may not have placeholders; this is OK 975 976 if (!match1.placeholders && !match2.placeholders) { 977 return res; 978 } else if (!match1.placeholders) { 979 return match2; 980 } else if (!match2.placeholders) { 981 return match1; 982 } // Placeholders with the same key must match exactly 983 984 985 for (var key in match1.placeholders) { 986 if ((0, _object.hasOwnProperty)(match1.placeholders, key)) { 987 res.placeholders[key] = match1.placeholders[key]; 988 989 if ((0, _object.hasOwnProperty)(match2.placeholders, key)) { 990 if (!_exactMatch(match1.placeholders[key], match2.placeholders[key])) { 991 return null; 992 } 993 } 994 } 995 } 996 997 for (var _key in match2.placeholders) { 998 if ((0, _object.hasOwnProperty)(match2.placeholders, _key)) { 999 res.placeholders[_key] = match2.placeholders[_key]; 1000 } 1001 } 1002 1003 return res; 1004 } 1005 /** 1006 * Combine two lists of matches by applying mergeMatch to the cartesian product of two lists of matches. 1007 * Each list represents matches found in one child of a node. 1008 */ 1009 1010 1011 function combineChildMatches(list1, list2) { 1012 var res = []; 1013 1014 if (list1.length === 0 || list2.length === 0) { 1015 return res; 1016 } 1017 1018 var merged; 1019 1020 for (var i1 = 0; i1 < list1.length; i1++) { 1021 for (var i2 = 0; i2 < list2.length; i2++) { 1022 merged = mergeMatch(list1[i1], list2[i2]); 1023 1024 if (merged) { 1025 res.push(merged); 1026 } 1027 } 1028 } 1029 1030 return res; 1031 } 1032 /** 1033 * Combine multiple lists of matches by applying mergeMatch to the cartesian product of two lists of matches. 1034 * Each list represents matches found in one child of a node. 1035 * Returns a list of unique matches. 1036 */ 1037 1038 1039 function mergeChildMatches(childMatches) { 1040 if (childMatches.length === 0) { 1041 return childMatches; 1042 } 1043 1044 var sets = childMatches.reduce(combineChildMatches); 1045 var uniqueSets = []; 1046 var unique = {}; 1047 1048 for (var i = 0; i < sets.length; i++) { 1049 var s = JSON.stringify(sets[i]); 1050 1051 if (!unique[s]) { 1052 unique[s] = true; 1053 uniqueSets.push(sets[i]); 1054 } 1055 } 1056 1057 return uniqueSets; 1058 } 1059 /** 1060 * Determines whether node matches rule. 1061 * 1062 * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} rule 1063 * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node 1064 * @param {Object} context -- provides assumed properties of operators 1065 * @param {Boolean} isSplit -- whether we are in process of splitting an 1066 * n-ary operator node into possible binary combinations. 1067 * Defaults to false. 1068 * @return {Object} Information about the match, if it exists. 1069 */ 1070 1071 1072 function _ruleMatch(rule, node, context, isSplit) { 1073 // console.log('Entering _ruleMatch(' + JSON.stringify(rule) + ', ' + JSON.stringify(node) + ')') 1074 // console.log('rule = ' + rule) 1075 // console.log('node = ' + node) 1076 // console.log('Entering _ruleMatch(', rule.toString({parenthesis:'all'}), ', ', node.toString({parenthesis:'all'}), ', ', context, ')') 1077 var res = [{ 1078 placeholders: {} 1079 }]; 1080 1081 if (rule instanceof OperatorNode && node instanceof OperatorNode || rule instanceof FunctionNode && node instanceof FunctionNode) { 1082 // If the rule is an OperatorNode or a FunctionNode, then node must match exactly 1083 if (rule instanceof OperatorNode) { 1084 if (rule.op !== node.op || rule.fn !== node.fn) { 1085 return []; 1086 } 1087 } else if (rule instanceof FunctionNode) { 1088 if (rule.name !== node.name) { 1089 return []; 1090 } 1091 } // rule and node match. Search the children of rule and node. 1092 1093 1094 if (node.args.length === 1 && rule.args.length === 1 || !isAssociative(node, context) && node.args.length === rule.args.length || isSplit) { 1095 // Expect non-associative operators to match exactly, 1096 // except in any order if operator is commutative 1097 var childMatches = []; 1098 1099 for (var i = 0; i < rule.args.length; i++) { 1100 var childMatch = _ruleMatch(rule.args[i], node.args[i], context); 1101 1102 if (childMatch.length === 0) { 1103 // Child did not match, so stop searching immediately 1104 break; 1105 } // The child matched, so add the information returned from the child to our result 1106 1107 1108 childMatches.push(childMatch); 1109 } 1110 1111 if (childMatches.length !== rule.args.length) { 1112 if (!isCommutative(node, context) || // exact match in order needed 1113 rule.args.length === 1) { 1114 // nothing to commute 1115 return []; 1116 } 1117 1118 if (rule.args.length > 2) { 1119 /* Need to generate all permutations and try them. 1120 * It's a bit complicated, and unlikely to come up since there 1121 * are very few ternary or higher operators. So punt for now. 1122 */ 1123 throw new Error('permuting >2 commutative non-associative rule arguments not yet implemented'); 1124 } 1125 /* Exactly two arguments, try them reversed */ 1126 1127 1128 var leftMatch = _ruleMatch(rule.args[0], node.args[1], context); 1129 1130 if (leftMatch.length === 0) { 1131 return []; 1132 } 1133 1134 var rightMatch = _ruleMatch(rule.args[1], node.args[0], context); 1135 1136 if (rightMatch.length === 0) { 1137 return []; 1138 } 1139 1140 childMatches = [leftMatch, rightMatch]; 1141 } 1142 1143 res = mergeChildMatches(childMatches); 1144 } else if (node.args.length >= 2 && rule.args.length === 2) { 1145 // node is flattened, rule is not 1146 // Associative operators/functions can be split in different ways so we check if the rule matches each 1147 // them and return their union. 1148 var splits = getSplits(node, context); 1149 var splitMatches = []; 1150 1151 for (var _i3 = 0; _i3 < splits.length; _i3++) { 1152 var matchSet = _ruleMatch(rule, splits[_i3], context, true); // recursing at the same tree depth here 1153 1154 1155 splitMatches = splitMatches.concat(matchSet); 1156 } 1157 1158 return splitMatches; 1159 } else if (rule.args.length > 2) { 1160 throw Error('Unexpected non-binary associative function: ' + rule.toString()); 1161 } else { 1162 // Incorrect number of arguments in rule and node, so no match 1163 return []; 1164 } 1165 } else if (rule instanceof SymbolNode) { 1166 // If the rule is a SymbolNode, then it carries a special meaning 1167 // according to the first character of the symbol node name. 1168 // c.* matches a ConstantNode 1169 // n.* matches any node 1170 if (rule.name.length === 0) { 1171 throw new Error('Symbol in rule has 0 length...!?'); 1172 } 1173 1174 if (SUPPORTED_CONSTANTS[rule.name]) { 1175 // built-in constant must match exactly 1176 if (rule.name !== node.name) { 1177 return []; 1178 } 1179 } else if (rule.name[0] === 'n' || rule.name.substring(0, 2) === '_p') { 1180 // rule matches _anything_, so assign this node to the rule.name placeholder 1181 // Assign node to the rule.name placeholder. 1182 // Our parent will check for matches among placeholders. 1183 res[0].placeholders[rule.name] = node; 1184 } else if (rule.name[0] === 'v') { 1185 // rule matches any variable thing (not a ConstantNode) 1186 if (!(0, _is.isConstantNode)(node)) { 1187 res[0].placeholders[rule.name] = node; 1188 } else { 1189 // Mis-match: rule was expecting something other than a ConstantNode 1190 return []; 1191 } 1192 } else if (rule.name[0] === 'c') { 1193 // rule matches any ConstantNode 1194 if (node instanceof ConstantNode) { 1195 res[0].placeholders[rule.name] = node; 1196 } else { 1197 // Mis-match: rule was expecting a ConstantNode 1198 return []; 1199 } 1200 } else { 1201 throw new Error('Invalid symbol in rule: ' + rule.name); 1202 } 1203 } else if (rule instanceof ConstantNode) { 1204 // Literal constant must match exactly 1205 if (!equal(rule.value, node.value)) { 1206 return []; 1207 } 1208 } else { 1209 // Some other node was encountered which we aren't prepared for, so no match 1210 return []; 1211 } // It's a match! 1212 // console.log('_ruleMatch(' + rule.toString() + ', ' + node.toString() + ') found a match') 1213 1214 1215 return res; 1216 } 1217 /** 1218 * Determines whether p and q (and all their children nodes) are identical. 1219 * 1220 * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} p 1221 * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} q 1222 * @return {Object} Information about the match, if it exists. 1223 */ 1224 1225 1226 function _exactMatch(p, q) { 1227 if (p instanceof ConstantNode && q instanceof ConstantNode) { 1228 if (!equal(p.value, q.value)) { 1229 return false; 1230 } 1231 } else if (p instanceof SymbolNode && q instanceof SymbolNode) { 1232 if (p.name !== q.name) { 1233 return false; 1234 } 1235 } else if (p instanceof OperatorNode && q instanceof OperatorNode || p instanceof FunctionNode && q instanceof FunctionNode) { 1236 if (p instanceof OperatorNode) { 1237 if (p.op !== q.op || p.fn !== q.fn) { 1238 return false; 1239 } 1240 } else if (p instanceof FunctionNode) { 1241 if (p.name !== q.name) { 1242 return false; 1243 } 1244 } 1245 1246 if (p.args.length !== q.args.length) { 1247 return false; 1248 } 1249 1250 for (var i = 0; i < p.args.length; i++) { 1251 if (!_exactMatch(p.args[i], q.args[i])) { 1252 return false; 1253 } 1254 } 1255 } else { 1256 return false; 1257 } 1258 1259 return true; 1260 } 1261 1262 return simplify; 1263 }); 1264 exports.createSimplify = createSimplify;