You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
compiler_proj3/proj3/src/main/scala/project3/Interpreter.scala

467 lines
14 KiB

package project3
abstract class Interpreter {
type MyVal
def run(ast: Language.Exp): MyVal
}
/**
* This interpreter specifies the semantics of our
* programming language.
*
* The evaluation of each node returns a value.
*/
class ValueInterpreter extends Interpreter with BugReporter {
import Language._
/*
* Values for primitives operators. Already stored in the environment.
*/
val primitives = Map[String, BoxedVal](
"putchar" -> BoxedVal(Primitive("putchar")),
"getchar" -> BoxedVal(Primitive("getchar")),
"toInt" -> BoxedVal(Primitive("toInt")),
"toChar" -> BoxedVal(Primitive("toChar"))
)
/*
* Definition of the values of our language
*
* We can return constant Int, Boolean, Unit or Array.
* We can also return Function. Primitive is a special
* kind of function.
*/
abstract class Val
case class Cst(x: Any) extends Val {
override def toString = if (x != null) x.toString else "null"
}
case class Func(args: List[String], fbody: Exp, var env: ValueEnv) extends Val {
// Add all the value into the existing environment
def withVals(list: List[(String,Val)]) = env = env.withVals(list)
override def toString = s"(${args mkString ","}) => $fbody"
}
case class Primitive(name: String) extends Val
type MyVal = Val
/**
* Env of the interpreter. Keeps track of the value
* of each variable defined.
*/
class Env {
def undef(name: String) =
BUG(s"Undefined identifier $name (should have been found during the semantic analysis)")
def updateVar(name: String, v: Val): Val = undef(name)
def apply(name: String): Val = undef(name)
}
case class BoxedVal(var v: Val)
case class ValueEnv(
vars: Map[String, BoxedVal] = primitives,
outer: Env = new Env) extends Env {
/*
* Return a copy of the current state plus an immutable
* variable 'name' of value 'v'
*/
def withVal(name: String, v: Val): ValueEnv = {
copy(vars = vars + (name -> BoxedVal(v)))
}
/*
* Return a copy of the current state plus all the immutables
* variable in list.
*/
def withVals(list: List[(String,Val)]): ValueEnv = {
copy(vars = vars ++ (list.map {case (n, v) => n -> BoxedVal(v) }))
}
/*
* Update the variable 'name' in this scope or in the
* outer scope.
* Return the new value of the variable
*/
override def updateVar(name: String, v: Val): Val = {
if (vars.contains(name))
vars(name).v = v
else
outer.updateVar(name, v)
v
}
/*
* Return the value of the variable 'name'
*/
override def apply(name: String): Val = {
if (vars.contains(name))
vars(name).v
else
outer(name)
}
}
/*
* Compute and return the result of the unary
* operation 'op' on the value 'v'
*/
def evalUn(op: String)(v: Val) = (op, v) match {
case ("-", Cst(v: Int)) => Cst(-v)
case ("+", Cst(_: Int)) => v
case _ => BUG(s"unary operator $op undefined")
}
/*
* Compute and return the result of the binary
* operation 'op' on the value 'v' and 'w'
* Note: v op w
*/
def evalBin(op: String)(v: Val, w: Val) = (op, v, w) match {
case ("-", Cst(v: Int), Cst(w: Int)) => Cst(v-w)
case ("+", Cst(v: Int), Cst(w: Int)) => Cst(v+w)
case ("*", Cst(v: Int), Cst(w: Int)) => Cst(v*w)
case ("/", Cst(v: Int), Cst(w: Int)) => Cst(v/w)
case ("==", Cst(v: Int), Cst(w: Int)) => Cst(v == w)
case ("!=", Cst(v: Int), Cst(w: Int)) => Cst(v != w)
case ("<=", Cst(v: Int), Cst(w: Int)) => Cst(v <= w)
case (">=", Cst(v: Int), Cst(w: Int)) => Cst(v >= w)
case ("<" , Cst(v: Int), Cst(w: Int)) => Cst(v < w)
case (">" , Cst(v: Int), Cst(w: Int)) => Cst(v > w)
case ("block-get", Cst(arr: Array[Any]), Cst(i: Int)) =>
if (arr(i) == null)
BUG(s"uninitialized memory")
Cst(arr(i))
case _ => BUG(s"binary operator $op undefined")
}
/*
* Compute and return the result of the ternary
* operations 'op' on the value 'v', 'w' and 'z'
*/
def evalTer(op: String)(v: Val, w: Val, z: Val) = (op, v, w, z) match {
case ("block-set", Cst(arr: Array[Any]), Cst(i: Int), Cst(x)) => Cst(arr(i) = x)
case _ => BUG(s"ternary operator $op undefined")
}
def evalPrim(op: String)(eargs: List[Val]) = eargs match {
case List(v, w, z) => evalTer(op)(v, w, z)
case List(v, w) => evalBin(op)(v, w)
case List(v) => evalUn(op)(v)
case _ => BUG(s"no prim with ${eargs.length} arguments")
}
/*
* Evaluate the AST starting with an empty Env
*/
def run(exp: Exp) = eval(exp)(ValueEnv())
/*
* Evaluate the AST within the environment 'env'
*/
def eval(exp: Exp)(env: ValueEnv): Val = exp match {
case Lit(x) => Cst(x)
case Prim(op, args) =>
val eargs = args map { arg => eval(arg)(env) }
evalPrim(op)(eargs)
case Let(x, tp, a, b) =>
eval(b)(env.withVal(x, eval(a)(env)))
case Ref(x) =>
env(x)
case If(cond, tBranch, eBranch) =>
val Cst(v: Boolean) = eval(cond)(env)
if (v)
eval(tBranch)(env)
else
eval(eBranch)(env)
case VarDec(x, tp, rhs, body) =>
eval(body)(env.withVal(x, eval(rhs)(env)))
case VarAssign(x, rhs) =>
env.updateVar(x, eval(rhs)(env))
case While(cond, lBody, body) =>
while (eval(cond)(env) == Cst(true)) {
eval(lBody)(env)
}
eval(body)(env)
case FunDef(_, args, _, fbody) =>
Func(args map { arg => arg.name }, fbody, env)
case LetRec(funs, body) =>
// Evaluate all functions
val funcs = funs map { case fun@FunDef(name, _, _, _) => (name, eval(fun)(env)) }
// Add all functions to the functions environment (recursion)
funcs foreach { case (_, func@Func(_, _, _)) => func.withVals(funcs) }
eval(body)(env.withVals(funcs))
case App(fun, args) =>
// Evaluate the arguments
val eargs = args map { arg => eval(arg)(env) }
// Evaluate the function to be called.
eval(fun)(env) match {
case Func(fargs, fbody, fenv) =>
eval(fbody)(fenv.withVals(fargs zip eargs))
case Primitive("getchar") => Cst(Console.in.read)
case Primitive("putchar") =>
val List(Cst(c: Int)) = eargs
Console.out.write(c)
Console.out.flush
Cst(())
case Primitive("toInt") =>
val List(Cst(c: Char)) = eargs
Cst(c.toInt)
case Primitive("toChar") =>
val List(Cst(c: Int)) = eargs
Cst(c.toChar)
}
case ArrayDec(size, _) =>
val Cst(s: Int) = eval(size)(env)
Cst(new Array[Any](s))
}
}
/*
* Defintion of the value produces by the StackInterpreter
*/
object StackVal extends BugReporter {
import Language._
type Loc = Int
/*
* Location of primitives operators. Already stored in the environment.
*/
val primitives = Map[String, Loc](
"putchar" -> 0,
"getchar" -> 1,
"toInt" -> 2,
"toChar" -> 3
)
/**
* Env of the interpreter. Keep track of the location
* in memory of each variable defined.
*/
class Env {
def apply(name: String): Loc =
BUG(s"Undefined identifier $name (should have been found during the semantic analysis)")
}
case class LocationEnv(
vars: Map[String, Loc] = primitives,
outer: Env = new Env) extends Env {
/*
* Return a copy of the current state plus a
* variable 'name' at the location 'loc'
*/
def withVal(name: String, loc: Loc): LocationEnv = {
copy(vars = vars + (name -> loc))
}
/*
* Return a copy of the current state plus a
* variable 'name' at the location 'loc'
*/
def withVals(list: List[(String,Loc)]): LocationEnv = {
copy(vars = vars ++ list.toMap)
}
/*
* Return the location of the variable 'name'
*/
override def apply(name: String) = vars.get(name) match {
case Some(loc) => loc
case None => outer(name)
}
}
abstract class Val
// Constant values: Int, Boolean, Array
case class Cst(x: Any) extends Val {
override def toString = if (x != null) x.toString else "null"
}
// Function values
case class Func(args: List[String], fbody: Exp, var env: LocationEnv) extends Val {
def withVals(list: List[(String,Int)]) = env = env.withVals(list)
override def toString = s"(${args mkString ","}) => $fbody"
}
// Primitives
case class Primitive(name: String) extends Val
}
/**
* This interpreter is a stack-based interpreter as we have seen
* during the lecture.
*
* Rather than returning the value of a node, it stores it in memory,
* following a well-establish convention.
*
* This interpreter works in a similar manner as a processor.
*/
class StackInterpreter extends Interpreter with BugReporter {
import Language._
import StackVal._
type MyVal = Val
// Memory and flag used by the interpreter
val memory = new Array[Val](1000)
memory(0) = Primitive("putchar")
memory(1) = Primitive("getchar")
memory(2) = Primitive("toInt")
memory(3) = Primitive("toChar")
var flag: Boolean = true
/*
* Compute the result of the operator 'op' on the
* value stored at 'sp' and store it at 'sp'
*/
def evalUn(op: String)(sp: Loc) = (op, memory(sp)) match {
case ("+", _) => ()
case ("-", Cst(x: Int)) => memory(sp) = Cst(-x)
case _ => BUG(s"Unary operator $op undefined")
}
/*
* Compute the result of the operator 'op' on the
* value stored at 'sp' and 'sp1', and store it at 'sp'
*
* TODO: implement the missing case
*/
def evalBin(op: String)(sp: Loc, sp1: Loc) = (op, memory(sp), memory(sp1)) match {
case ("+", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x + y)
case ("-", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x - y)
case ("*", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x * y)
case ("/", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x / y)
case ("==", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x == y)
case ("!=", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x != y)
case ("<=", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x <= y)
case (">=", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x >= y)
case ("<" , Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x < y)
case (">" , Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x > y)
case ("block-get", Cst(arr: Array[Any]), Cst(i: Int)) =>
if (arr(i) == null)
BUG(s"uninitialized memory")
memory(sp) = Cst(arr(i))
case _ => BUG(s"Binary operator $op undefined")
}
def evalTer(op: String)(sp: Loc, sp1: Loc, sp2: Loc) = (op, memory(sp), memory(sp1), memory(sp2)) match {
case ("block-set", Cst(arr: Array[Any]), Cst(i: Int), Cst(x)) => memory(sp) = Cst(arr(i) = x)
case _ => BUG(s"ternary operator $op undefined")
}
def evalPrim(op: String)(idxs: List[Int]) = idxs match {
case List(sp, sp1, sp2) => evalTer(op)(sp, sp1, sp2)
case List(sp, sp1) => evalBin(op)(sp, sp1)
case List(sp) => evalUn(op)(sp)
case _ => BUG(s"no prim with ${idxs.length} arguments")
}
/*
* Evaluate the value of the AST 'exp' within
* an empty environment and return the value.
*/
def run(exp: Exp): Val = {
// Start at 4, putchar, getchar, toChar, toInt are store at 0 and 1, 2, 3!!
eval(exp, 4)(LocationEnv())
memory(4)
}
/*
* Evaluate the value of the AST 'exp' within
* the environment 'env 'and store the result
* at 'sp'.
*
* NOTE: Cond stores its result in the 'flag'
* variable.
*
* TODO: Remove all ???s and implement the
* appropriate code. The result must be the
* same than the evaluator defined above.
*/
def eval(exp: Exp, sp: Loc)(env: LocationEnv): Unit = exp match {
case Lit(x: Unit) => ()
case Lit(x) =>
memory(sp) = Cst(x)
case Prim(op, args) =>
val idxs = List.tabulate(args.length)(i => sp + i)
(args zip idxs) foreach { case (arg, idx) => eval(arg, idx)(env) }
evalPrim(op)(idxs)
case Let(x, tp, a, b) =>
eval(a, sp)(env)
eval(b, sp + 1)(env.withVal(x, sp))
memory(sp) = memory(sp + 1)
case Ref(x) =>
memory(sp) = memory(env(x))
case If(cond, tBranch, eBranch) =>
eval(cond, sp)(env)
val Cst(flag: Boolean) = memory(sp)
if (flag)
eval(tBranch, sp)(env)
else
eval(eBranch, sp)(env)
case VarDec(x, tp, rhs, body) =>
eval(rhs, sp)(env)
eval(body, sp + 1)(env.withVal(x, sp))
memory(sp) = memory(sp + 1)
case VarAssign(x, rhs) =>
eval(rhs, sp)(env)
memory(env(x)) = memory(sp)
case While(cond, lbody, body) =>
eval(cond, sp)(env)
while (memory(sp) == Cst(true)) {
eval(lbody, sp)(env)
eval(cond, sp)(env)
}
eval(body, sp)(env)
case FunDef(_, args, _, fbody) =>
memory(sp) = Func(args map { arg => (arg.name) }, fbody, env)
case LetRec(funs, body) =>
// TODO modify that code
val ids = (sp until sp + funs.length).toList
// Evaluate all functions
val funcs = (funs zip ids) map { case (fun@FunDef(name, _, _, _), idx) => (name, idx) }
// Add all functions to the functions environment (recursion)
(funs zip ids) foreach { case (fun@FunDef(_, _, _, _), idx:Int) => eval(fun, idx)(env.withVals(funcs)) }
eval(body, sp + funs.length)(env.withVals(funcs))
memory(sp) = memory(sp + funs.length)
case App(fun, args) =>
val ids = (sp until sp + args.length).toList
(args zip ids) map { case (arg, idx) => eval(arg, idx)(env) }
eval(fun, sp + args.length)(env)
memory(sp + args.length) match {
case Func(fargs, fbody, fenv) =>
eval(fbody, sp + args.length + 1)(fenv.withVals(fargs zip ids))
memory(sp) = memory(sp + args.length + 1)
case Primitive("getchar") =>
memory(sp) = Cst(Console.in.read)
case Primitive("putchar") =>
val List(Cst(c: Int)) = memory(sp)
Console.out.write(c)
Console.out.flush
Cst(())
case Primitive("toInt") =>
val List(Cst(c: Char)) = memory(sp)
memory(sp) = Cst(c.toInt)
case Primitive("toChar") =>
val List(Cst(c: Int)) = memory(sp)
memory(sp) = Cst(c.toChar)
}
case ArrayDec(size, _) =>
eval(size, sp)(env)
val Cst(idx: Int) = memory(sp)
memory(sp) = Cst(new Array[Any](idx))
}
}