/** Compiles ParameterExpressions */
  @Override
  public void visit(ParameterExpression param) {
    if (param == null) {
      throw new ArgumentException(PARAMETER + E_IS_NULL);
    }
    if (parameters.containsKey(param.getName())) {
      /** If the parameter itself is and expression */
      if (parameters.get(param.getName()) instanceof ExpressionCompiler) {
        // The parameter is itself another Expression
        ExpressionCompiler expr = (ExpressionCompiler) parameters.get(param.getName());
        expr.setParameters(parameters);
        expr.setMethodResolver(getMethodResolver());
        expr.setParameterizedResolver(getParameterizedResolver());
        // TODO: Need a way to pass in the compile type here, maybe from
        // global scope of API call for compile...
        result.setResult(
            (LogicalExpression) ((ExpressionCompiler) parameters.get(param.getName())).compile());
      } else {
        result.setResult((LogicalExpression) parameters.get(param.getName()));
      }
    } else {

      ParameterizedArgs args = new ParameterizedArgs(result, param.getName());

      resolveParameterizedReference(args);

      if (args.getResult() == null)
        throw new CompilationException(
            String.format(E_PARAMETER_S_WAS_NOT_DEFINED, param.getName()));

      result.setResult((LogicalExpression) args.getResult());
    }
  }
  /** Compiles MethodExpressions */
  @Override
  public void visit(MethodExpression method) {
    if (method == null) {
      throw new ArgumentException(METHOD + E_IS_NULL);
    }

    LogicalExpression[] parameters = new LogicalExpression[method.getParameters().length];
    for (int i = 0; i < parameters.length; i++) {
      parameters[i] = evaluate(method.getParameters()[i]);
    }

    MethodArgs args = new MethodArgs(method.getName());
    args.setParameters(parameters);

    resolveMethod(args);

    // If an external implementation was found get the result back
    if (args.getResult() != null) {
      result.setResult((LogicalExpression) args.getResult());
      return;
    }

    defaultMethodResolver.resolve(args);
    result.setResult((LogicalExpression) args.getResult());
  }
  /** Compiles UnaryExpression */
  @Override
  public void visit(UnaryExpression expression) {
    if (expression == null) {
      throw new ArgumentException(EXPRESSION + E_IS_NULL);
    }

    expression.getExpression().accept(this);

    switch (expression.getType()) {
      case NOT:
        result.setResult(new Not(result.getResult()));
        break;

      case NEGATE:
        result.setResult(new Negate(result.getResult()));
        break;
    }
  }
  /** Compiles ValueExpressions */
  @Override
  public void visit(ValueExpression expression) {
    if (expression == null) {
      throw new ArgumentException(EXPRESSION + E_IS_NULL);
    }

    // FIXME: Treat all numerics as BigDecimal or better yet, MutableDecimal
    // Also - what other types must we handle?
    // BYTE? BYTEARRAY? CHARACTER?
    switch (expression.getType()) {
      case BOOLEAN:
        result.setResult(
            new Literal<Boolean>(ValueType.BOOLEAN, Boolean.parseBoolean(expression.getText())));
        break;

      case DATETIME:
        result.setResult(
            new Literal<DateTime>(
                ValueType.DATETIME,
                // DateTime.parse(expression.getText().substring(1,
                // expression.getText().length() - 2))));
                DateTime.parse(expression.getText())));
        break;
      case INTEGER:
      case DECIMAL:
        result.setResult(
            new Literal<Double>(ValueType.DECIMAL, Convert.parseNumeric(expression.getText())));
        break;

      case TEXT:
        result.setResult(new Literal<String>(ValueType.TEXT, expression.getText()));
        break;

      case VARIABLE:
        Variable var = new Variable(expression.getText());
        // Record for easy update before compute
        result.addOrUpdateVariable(var);
        result.setResult(var);
        break;
    }
  }
  @Override
  public void visit(AssignmentExpression assignment) {
    if (assignment == null) {
      throw new ArgumentException(ASSIGNMENT + E_IS_NULL);
    }
    /** processes the left hand expression and saves the value to result */
    assignment.getLeft().accept(this);
    LogicalExpression left = result.getResult();
    // LHS must always be a variable.
    if (!(left instanceof Variable)) {
      throw new ArgumentException(E_ASSIGNMENT_LHS_NOT_VARIABLE);
    }
    assignment.setLeft(left);

    /** processes the right hand expression and saves the value to result */
    assignment.getRight().accept(this);
    assignment.setRight(result.getResult());

    /** No magic here, just set the original instance */
    result.setResult(assignment);
  }
  /** Compiles BinaryExpressions */
  @Override
  public void visit(BinaryExpression expression) {
    if (expression == null) {
      throw new ArgumentException(EXPRESSION + E_IS_NULL);
    }
    /** processes the left hand expression and saves the value to result */
    expression.getLeft().accept(this);
    LogicalExpression left = result.getResult();

    /** processes the right hand expression and saves the value to result */
    expression.getRight().accept(this);
    LogicalExpression right = result.getResult();

    switch (expression.getType()) {
      case AND:
        result.setResult(new And(left, right));
        break;

      case OR:
        result.setResult(new Or(left, right));
        break;

      case DIV:
        result.setResult(new Divide(left, right));
        break;

      case EQ:
        result.setResult(new Equal(left, right));
        break;

      case GT:
        result.setResult(new Greater(left, right));
        break;

      case GTE:
        result.setResult(new GreaterEqual(left, right));
        break;

      case LT:
        result.setResult(new Lesser(left, right));
        break;

      case LTE:
        result.setResult(new LesserEqual(left, right));
        break;

      case MINUS:
        result.setResult(new Minus(left, right));
        break;

      case MOD:
        result.setResult(new Modulo(left, right));
        break;

      case NE:
        result.setResult(new NotEqual(left, right));
        break;

      case PLUS:
        result.setResult(new Plus(left, right));
        break;

      case MULT:
        result.setResult(new Multiply(left, right));
        break;

      case POW:
        result.setResult(new Power(left, right));
        break;
    }
  }