lib/integral.js
function(expr, context, subIntegral) {
      var createIntegralWrapper = null;

      var uniqueParent = getParentOfUniqueVariable(expr);
      if(uniqueParent !== null && uniqueParent.type === "OperatorNode") {
        if(uniqueParent.op === '+' || uniqueParent.op === '-') {
          if(uniqueParent.args.length === 1) {
            // unary + or -
            createIntegralWrapper = function(int) {
              return new OperatorNode(uniqueParent.op, uniqueParent.fn, [int]);
            }
          } else {
            createIntegralWrapper = function(int) {
              return int;
            }
          }
        } else if(uniqueParent.op === '*') {
          createIntegralWrapper = function(int) {
            return new OperatorNode('/', 'divide', [int,
              // "remove" the variable of integration
              replaceNodeInTree(uniqueParent, context.variable, new ConstantNode(1))
            ]);
          };
        }

        if(createIntegralWrapper !== null) {
          var preIntegral = replaceNodeInTree(expr, uniqueParent, context.variable.clone());
          var int = subIntegral(preIntegral, context, "linear substitution");
          if(int) {
            var backSubstituted = replaceNodeInTree(int, context.variable, uniqueParent);
            return createIntegralWrapper(backSubstituted);
          }
        }
      }

      function replaceNodeInTree(expr, node, replacement) {
        return replaceHelper(expr);

        function replaceHelper(curNode) {
          return node.equals(curNode) ? replacement : curNode.map(replaceHelper);
        }
      }

      function getParentOfUniqueVariable(expr) {
        return helper(expr, null);

        function helper(expr, parent) {
          if(context.isConstant(expr)) {
            return null;
          } else if(expr.type === "SymbolNode" && expr.name === context.variable.name) {
            return parent;
          } else {
            var nonConstantChildren = [];
            expr.forEach(function(child) {
              if(!context.isConstant(child)) {
                nonConstantChildren.push(child);
              }
            });

            if(nonConstantChildren.length === 1) {
              return helper(nonConstantChildren[0], expr);
            } else {
              return null;
            }
          }
        }
      }
    }
function(expr, context, subIntegral) {
      var createIntegralWrapper = null;

      var uniqueParent = getParentOfUniqueVariable(expr);
      if(uniqueParent !== null && uniqueParent.type === "OperatorNode") {
        if(uniqueParent.op === '+' || uniqueParent.op === '-') {
          if(uniqueParent.args.length === 1) {
            // unary + or -
            createIntegralWrapper = function(int) {
              return new OperatorNode(uniqueParent.op, uniqueParent.fn, [int]);
            }
          } else {
            createIntegralWrapper = function(int) {
              return int;
            }
          }
        } else if(uniqueParent.op === '*') {
          createIntegralWrapper = function(int) {
            return new OperatorNode('/', 'divide', [int,
              // "remove" the variable of integration
              replaceNodeInTree(uniqueParent, context.variable, new ConstantNode(1))
            ]);
          };
        }

        if(createIntegralWrapper !== null) {
          var preIntegral = replaceNodeInTree(expr, uniqueParent, context.variable.clone());
          var int = subIntegral(preIntegral, context, "linear substitution");
          if(int) {
            var backSubstituted = replaceNodeInTree(int, context.variable, uniqueParent);
            return createIntegralWrapper(backSubstituted);
          }
        }
      }

      function replaceNodeInTree(expr, node, replacement) {
        return replaceHelper(expr);

        function replaceHelper(curNode) {
          return node.equals(curNode) ? replacement : curNode.map(replaceHelper);
        }
      }

      function getParentOfUniqueVariable(expr) {
        return helper(expr, null);

        function helper(expr, parent) {
          if(context.isConstant(expr)) {
            return null;
          } else if(expr.type === "SymbolNode" && expr.name === context.variable.name) {
            return parent;
          } else {
            var nonConstantChildren = [];
            expr.forEach(function(child) {
              if(!context.isConstant(child)) {
                nonConstantChildren.push(child);
              }
            });

            if(nonConstantChildren.length === 1) {
              return helper(nonConstantChildren[0], expr);
            } else {
              return null;
            }
          }
        }
      }
    }
integral(f(a*x + b), x) = 1/a * F(a*x + b) where F(u) = integral(f(u), u) We also only handle the case where u shows up only once in f(u)