//Anton Dubrau
//Comp202, Summer 2010, Assignment 4 - Solutions

import java.util.Scanner;


public class Interpreter{
  //variables inside the program
  private static double a,b,c;

  //functions inside the program
  private static String f,g;

  //exit flag
  private static boolean exit = false;


  //main keeps evaluating input and returns result until exit is executed
  public static void main(String[] args){
    //create scanner
    Scanner scanner = new Scanner(System.in);

    //loop, evaluating expressions until exit flag is set
    while(!exit){
      System.out.print(">> ");
      System.out.println("result: "+evaluate(scanner,0));
    }
  }

  //evaluates an expression coming from a scanner recursively
  //and returns the result as a double
  public static double evaluate(Scanner scanner,double x){
    if (scanner.hasNextDouble()){ //base case - just a double
      return scanner.nextDouble();
    } else { //else - we always have an operator
      String cmd = scanner.next();
      
      //binary operations
      if (cmd.equals("+")){
        return evaluate(scanner,x) + evaluate(scanner,x);
      }
      if (cmd.equals("-")){
        return evaluate(scanner,x) - evaluate(scanner,x);
      }
      if (cmd.equals("*")){
        return evaluate(scanner,x) * evaluate(scanner,x);
      }
      if (cmd.equals("/")){
        return evaluate(scanner,x) / evaluate(scanner,x);
      }
      if (cmd.equals("^")){
        return Math.pow(evaluate(scanner,x),evaluate(scanner,x));
      }
      if (cmd.equals("==")){
        if (evaluate(scanner,x) == evaluate(scanner,x)){
          return 1;
        } else {
          return 0;
        }
      }
      if (cmd.equals("<")){
        if (evaluate(scanner,x) < evaluate(scanner,x)){
          return 1;
        } else {
          return 0;
        }
      }

      //transcendental operations
      if (cmd.equals("exp")){
        return Math.exp(evaluate(scanner,x));
      }
      if (cmd.equals("sin")){
        return Math.sin(evaluate(scanner,x));
      }
      if (cmd.equals("cos")){
        return Math.cos(evaluate(scanner,x));
      }
      if (cmd.equals("log")){
        return Math.log(evaluate(scanner,x));
      }
      if (cmd.equals("sqrt")){
        return Math.sqrt(evaluate(scanner,x));
      }

      //reading variables
      if (cmd.equals("a")) return a;
      if (cmd.equals("b")) return b;
      if (cmd.equals("c")) return c;
      if (cmd.equals("x")) return x;
      if (cmd.equals("pi")) return Math.PI;

      //assigning variables
      if (cmd.equals("a=")){
        a = evaluate(scanner,x);
        return a;
      }
      if (cmd.equals("b=")){
        b = evaluate(scanner,x);
        return b;
      }
      if (cmd.equals("c=")){
        c = evaluate(scanner,x);
        return c;
      }

      //reading functions
      if (cmd.equals("f")){
        return evaluate(new Scanner(f),evaluate(scanner,x));
      }
      if (cmd.equals("g")){
        return evaluate(new Scanner(g),evaluate(scanner,x));
      }

      //assigning functions
      if (cmd.equals("f=")){
        f = skip(scanner);
        return 0;
      }
      if (cmd.equals("g=")){
        g = skip(scanner);
        return 0;
      }

      //exit
      if (cmd.equals("exit")){
        exit = true;
        return 0;
      }

      //if statement
      if (cmd.equals("if")){
        if (evaluate(scanner,x) != 0){ //if case
          double result = evaluate(scanner,x);
          skip(scanner);
          return result;
        } else { //else case
          skip(scanner);
          return evaluate(scanner,x);
        }
      }

      //integration - see end of the class
      if (cmd.equals("int")){
        return integrate(skip(scanner),
                         evaluate(scanner,x),
                         evaluate(scanner,x));
      }

      //begin end
      if (cmd.equals("{")){
        double value=0;
        while (!scanner.hasNext("}")){
          value = evaluate(scanner,x);
        }
        scanner.next();
        return value;
      }

      //else
      System.out.println("unrecognized expression: "+cmd);
      return Double.NaN;
    }
  }

  //skips one expression, coming from a given Scanner and returns it as a string
  public static String skip(Scanner scanner){
    //number
    if (scanner.hasNextDouble()) return scanner.next();
    
    String cmd = scanner.next();
    //0 arguments - note: this is not necessary because unrecognized expressions lack arguments
    if (cmd.equals("a") 
        || cmd.equals("b")
        || cmd.equals("c")
        || cmd.equals("x")
        || cmd.equals("pi")
        || cmd.equals("exit")) return cmd;

    //1 argument
    if(cmd.equals("exp")
       || cmd.equals("sin")
       || cmd.equals("cos")
       || cmd.equals("log")
       || cmd.equals("sqrt")
       || cmd.equals("a=")
       || cmd.equals("b=")
       || cmd.equals("c=")
       || cmd.equals("f=")
       || cmd.equals("g=")
       || cmd.equals("f")
       || cmd.equals("g")) return cmd + " " + skip(scanner);

    //2 arguments
    if(cmd.equals("+")
       || cmd.equals("-")
       || cmd.equals("*")
       || cmd.equals("/")
       || cmd.equals("^")
       || cmd.equals("==")
       || cmd.equals("<")){
      return cmd + " " + skip(scanner) + " " + skip(scanner);
    }

    //3 arguments
    if (cmd.equals("if"))
        return cmd + " " 
          + skip(scanner) + " " 
          + skip(scanner) + " " 
          + skip(scanner);
    
    //n arguments
    if(cmd.equals("{")){
      String code = "{ ";
      while(!scanner.hasNext("}")){
        code += skip(scanner) + " ";
      }
      scanner.next(); //skip the }
      return code + " }";
    }

    //else - unrecognized command
    return cmd;
  }


  //the following shows how to do numerical integration
  //this was part of the original assignment, but I dropped it
  //The below function integrates a function, given as a String, from a to b
  //this can be used in the interpreter via
  //int <expression to integrate> <expression denoting a> <expression denoting b>
  static int INTEGRATION_STEPS = 10000;
  public static double integrate(String function,double a,double b){
    double result = 0;

    //integrates f from a to b, using Riemann Sums with INTEGRATION_STEPS steps
    for (int i=0;i<INTEGRATION_STEPS;i++){
      result += evaluate(new Scanner(function),
                         a + (i+.5)*(b-a)/(INTEGRATION_STEPS-1));
    }

    return result/(INTEGRATION_STEPS);
  }
}


