lib/integral.js
function(expr, context, subIntegral) {
      if(expr.type === "OperatorNode" && expr.op === '*') {
        var argsAsPower = expr.args.map(getExprInPowerForm);

        // Collect common bases (this is O(n^2) worst case)
        var reducedArgs = argsAsPower.reduce(function(acc, exprPower) {
          for(var i = 0; i < acc.length; i++) {
            if(acc[i].base.equals(exprPower.base)) {
              acc[i].power = new OperatorNode('+', 'add', [
                acc[i].power,
                exprPower.power
              ]);
              return acc;
            }
          }

          // Did not share a common base with any other factor so far
          acc.push(exprPower);
          return acc;
        }, []);

        if(reducedArgs.length < expr.args.length) {
          // We combined some things
          var reducedExpr = powerFactorsToNode(reducedArgs);

          return subIntegral(reducedExpr, context, "combining powers");
        }
      }

      function getExprInPowerForm(expr) {
        if(expr.type === "OperatorNode" && expr.op === '^') {
          return {
            base: expr.args[0],
            power: expr.args[1]
          };
        } else {
          return {
            base: expr,
            power: new ConstantNode(1)
          };
        }
      }

      function powerFactorsToNode(factors) {
        if(factors.length === 1) {
          return powerToNode(factors[0]);
        } else {
          return new OperatorNode('*', 'multiply', factors.map(powerToNode));
        }

        function powerToNode(powerExpr) {
          return new OperatorNode('^', 'pow', [powerExpr.base, powerExpr.power]);
        }
      }
    }
function(expr, context, subIntegral) {
      if(expr.type === "OperatorNode" && expr.op === '*') {
        var argsAsPower = expr.args.map(getExprInPowerForm);

        // Collect common bases (this is O(n^2) worst case)
        var reducedArgs = argsAsPower.reduce(function(acc, exprPower) {
          for(var i = 0; i < acc.length; i++) {
            if(acc[i].base.equals(exprPower.base)) {
              acc[i].power = new OperatorNode('+', 'add', [
                acc[i].power,
                exprPower.power
              ]);
              return acc;
            }
          }

          // Did not share a common base with any other factor so far
          acc.push(exprPower);
          return acc;
        }, []);

        if(reducedArgs.length < expr.args.length) {
          // We combined some things
          var reducedExpr = powerFactorsToNode(reducedArgs);

          return subIntegral(reducedExpr, context, "combining powers");
        }
      }

      function getExprInPowerForm(expr) {
        if(expr.type === "OperatorNode" && expr.op === '^') {
          return {
            base: expr.args[0],
            power: expr.args[1]
          };
        } else {
          return {
            base: expr,
            power: new ConstantNode(1)
          };
        }
      }

      function powerFactorsToNode(factors) {
        if(factors.length === 1) {
          return powerToNode(factors[0]);
        } else {
          return new OperatorNode('*', 'multiply', factors.map(powerToNode));
        }

        function powerToNode(powerExpr) {
          return new OperatorNode('^', 'pow', [powerExpr.base, powerExpr.power]);
        }
      }
    }
integral(f(x)^g(x) * f(x)^h(x), x) = integral(f(x)^(g(x)+h(x)), x)