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