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;
            }
          }
        }
      }
    }
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)
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 )