// $Id: Interpreter.scala,v 1.1 2005/10/28 20:46:16 leavens Exp leavens $
package interp;

// Make the domains available
import Domains._;

/** The main definitions of the interpreter.
 * @author Gary T. Leavens from Friedman, Wand, and Haynes
 */
class Interpreter {
  /** Evaluate the program in an initial environment. */
  def evalProgram(pgm: Program): ExpressedValue =
    pgm match {
      case AProgram(body) => evalExp(body, initEnv);
    }

  /** Evaluate the expression in the given environment. */
  def evalExp(exp: Expression, env: Environment): ExpressedValue =
    exp match {
      case LitExp(datum) => datum;
      case VarExp(id) => env(id);
      case PrimAppExp(prim, rands) => {
        val args = evalRands(rands, env);
        try {
          applyPrimitive(prim, args)
        } catch {
           case InterpreterError(s: String) =>
              error(s + ": " + exp);
           case ex => throw ex;
        }
      }
    }
    
  /** Evaluate the list of operands to a list of expressed values. */
  def evalRands(rands: List[Expression], env: Environment): List[ExpressedValue] =
    for (val rand <- rands)
      yield evalRand(rand, env);
    // i.e., rands.map(rand => evalRand(rand, env))
      
  /** Evaluate an operand */    
  def evalRand = evalExp;
    
  /** Apply the given primitive to the arguments. */
  def applyPrimitive(prim: Primitive, args: List[ExpressedValue]): ExpressedValue = 
    Pair(prim, args) match {
      case Pair(AddPrim(), x::y::Nil) => x+y;
      case Pair(SubtractPrim(), x::y::Nil) => x-y;
      case Pair(MultPrim(), x::y::Nil) => x*y;
      case Pair(IncrPrim(), x::Nil) => x+1;
      case Pair(DecrPrim(), x::Nil) => x-1;
      case _ => error("Improper primitive application");   
    }
    
  /** The initial environment. 
   * Note: in the code, we can't write
   * <pre>
   *   emptyEnv + 'i -> 1
   * </pre>
   * because the type of the result is Map, not a ListMap.
   * But using .update requires a type that has .update,
   * which isn't true of all maps.
   */
  def initEnv: Environment = 
     emptyEnv 
       .update('i, 1)
       .update('v, 5)
       .update('x, 10);
}
