LARA

Handling Scopes in Interpreters

An Example interpreter for language with procedures and blocks

object ScopeInterpreter {
  type Ident = String
 
  case class Program(classes : List[ClassDef], classToRun : Ident)
 
  case class ClassDef(name : Ident, decls : List[ClassDecl])
 
  sealed abstract class Typ
  case object IntType extends Typ
  case object BoolType extends Typ
  case object VoidType extends Typ
 
  case class VarDecl(name : Ident, tp : Typ)
 
  sealed abstract class ClassDecl
  case class FieldDecl(decl : VarDecl) extends ClassDecl
  case class MethodDecl(name : Ident, 
			args : List[VarDecl],
			result : Typ,
		        body : Statement) extends ClassDecl
 
  sealed abstract class Statement
  case class DeclStat(decl : VarDecl) extends Statement
  case class AssignStat(lhs : Ident, rhs : Expression) extends Statement
  case class PrintStat(id : Expression) extends Statement
  case class IfStat(cond : Expression, 
		    trueS : Statement,
		    falseS : Statement) extends Statement
  // lhs = className.methodName(args)
  case class CallStat(lhs : Ident,
		      className : Ident,
		      methodName : Ident,
		      args : List[Expression]) extends Statement
  case class ReturnStat(expr : Expression) extends Statement
  case class BlockStat(sts : List[Statement]) extends Statement
 
  sealed abstract class Expression
  case class Var(name : Ident) extends Expression
  case class IntConst(c : Int) extends Expression
  case class Plus(e1 : Expression, e2 : Expression) extends Expression
  case class BoolConst(c : Boolean) extends Expression
  case class Leq(e1 : Expression, e2 : Expression) extends Expression
  case class And(e1 : Expression, e2 : Expression) extends Expression
 
  sealed abstract class Value
  case class IntValue(iv : Int) extends Value
  case class BoolValue(bv : Boolean) extends Value
  case object UnitValue extends Value
  case object Uninitialized extends Value
 
  sealed abstract class EnvEntry
  case class ClassEntry(cd : ClassDef) extends EnvEntry
  case class VarEntry(decl : VarDecl,
		      var v : Value) extends EnvEntry
  case class MethodEntry(md : MethodDecl) extends EnvEntry
 
  type Env = Map[Ident,EnvEntry]
 
  def varsAsNiceString(e : Env) : String = {
    var res = "{"
    for ((k,v) <- e) {
      v match {
	case VarEntry(vd,v) => {res = res + "" + k + " <- " + v + ",\n"}
	case _ => ()
      }
    }
    res = res + "}"
    res
  }
 
  def error(s : String) = { 
    println(s) 
    println("Fatal error, exiting!")
    exit(-1)
  }
 
  // check that v is initialized and has type t
  def checkType(v : Value, t : Typ) : Unit = {
    (v,t) match {
      case (IntValue(i),IntType) => ()
      case (BoolValue(b),BoolType) => ()
      case (UnitValue,VoidType) => ()
      case (Uninitialized,_) => error("Uninitialized value")
      case (_,_) => error("Run-time type error!")
    }
  }
  def printIntValue(v : Value) : Unit = {
    v match {
      case IntValue(i) => println(i)
      case _ => error("argument of print must be int but was " + v)
    }
  }
  def qualify(cl : String, n : String) = { cl + "$" + n}
  def getVar(e : Env, n : Ident) : VarEntry = {
    e(n) match {
      case (ve : VarEntry) => ve
      case _ => error("Could not find variable n ")
    }
  }
  def getClass(e : Env, n : Ident) : ClassEntry = {
    e(n) match {
      case (ce : ClassEntry) => ce
      case _ => error("Could not find class " + n)
    }
  }
 
  def makeClassEntries(p : Program) : Env = {
    var e : Map[Ident,EnvEntry] = Map()
    for (c <- p.classes) {
      e = e + (c.name -> ClassEntry(c))
      for (d <- c.decls) {
	e = e + (d match {
	  case FieldDecl(d) => 
	    qualify(c.name, d.name) -> VarEntry(d,Uninitialized)
	  case (md : MethodDecl) => 
	    qualify(c.name, md.name) -> MethodEntry(md)
	})
      }
    }
    e
  }
 
  // insert unqualified names for a class
  def withClassScope(e0 : Env, className : Ident) : Env = {
    var e = e0
    val ce : ClassEntry = getClass(e0, className)
    for (d <- ce.cd.decls) {
      d match {
	case FieldDecl(d) => 
	  e = e + (d.name -> e(qualify(className, d.name)))
	case (md : MethodDecl) => 
	  e = e + (md.name -> e(qualify(className, md.name)))
      }
    }
    e
  }
 
  def insertArgs(e : Env,
		 formals : List[VarDecl], 
		 actuals : List[Value]) : Env = {
     (formals,actuals) match {
       case (Nil,Nil) => e
       case (VarDecl(n,t)::vds,v::vs) =>
	 insertArgs(e + (n -> VarEntry(VarDecl(n,t),v)),
		    vds,vs)
       case (Nil,_) => error("More formals than actuals")
       case (_,Nil) => error("More actuals than formals")
     }
   }
 
  def interpExpr(e : Env)(expr : Expression) : Value = {
    expr match {
      case Var(n) => getVar(e, n).v
      case IntConst(i) => IntValue(i)
      case Plus(expr1,expr2) => {
	 (interpExpr(e)(expr1),interpExpr(e)(expr2)) match {
	    case (IntValue(i1),IntValue(i2)) => IntValue(i1 + i2)
	   case (_,_) => error("Run-time type error: arguments of '+'"+
			       "are not both integers, tried '" + 
			       expr1 + " + " + expr2 +"'" + " in env" +
			       varsAsNiceString(e))
	 }
       }
       case BoolConst(b) => BoolValue(b)
       case Leq(expr1, expr2) => {
	 (interpExpr(e)(expr1),interpExpr(e)(expr2)) match {
	   case (IntValue(i1),IntValue(i2)) => BoolValue(i1 <= i2)
	   case (_,_) => error("Run-time type error: arguments of '<='"+
			       "are not both integers!")
	 }
       }
       case And(expr1, expr2) => {
	 (interpExpr(e)(expr1),interpExpr(e)(expr2)) match {
	   case (BoolValue(b1),BoolValue(b2)) => BoolValue(b1 && b2)
	   case (_,_) => error("Run-time type error: arguments of '&&'"+
			       "are not both booleans!")
	 }
       }
     }
  }
 
  def interpCall(e : Env, className : Ident, methodName : Ident, 
		 actualArgs : List[Value]) : Value = {
    val md = e(qualify(className,methodName)) match {
      case MethodEntry(md) => md
      case _ => error("Unknown method " + qualify(className,methodName))
    }
    val e1 =  withClassScope(e, className)
    val e2 = insertArgs(e1, md.args, actualArgs)
    val res = interpStats(e2, UnitValue, List(md.body))
    checkType(res, md.result)
    res
  }
 
  def interpStats(e : Env, v : Value, 
		  stmts : List[Statement]) : Value = {
   stmts match {
	case Nil => v
	case stmt::stmts1 => {
          stmt match {
	    case DeclStat(VarDecl(n,t)) =>
	      interpStats(e + (n -> VarEntry(VarDecl(n,t),Uninitialized)),
			   v, stmts1)
	    case AssignStat(id,rhs) => {
	      val ve = getVar(e,id)
	      val res = interpExpr(e)(rhs)
	      checkType(res, ve.decl.tp)
	      ve.v = res
	      interpStats(e, UnitValue, stmts1)
	    }
	    case PrintStat(expr) => { 
	      printIntValue(interpExpr(e)(expr))
	      interpStats(e, UnitValue, stmts1)
	    }
	    case IfStat(cond,trueS,falseS) => {
	      interpExpr(e)(cond) match {
		case BoolValue(b) => {
		  if (b) {
		    interpStats(e, UnitValue, trueS :: stmts1)
		  } else {
		    interpStats(e, UnitValue, falseS :: stmts1)
		  }
		}
		case _ => error("Expected boolean as condition")
	      }
	    }
	    case CallStat(lhs,className,methodName,args) => {
	      val ve = getVar(e, lhs)
	      val res = interpCall(e, className, methodName, 
				   args.map(interpExpr(e)))
	      checkType(res, ve.decl.tp)
	      ve.v = res
	      interpStats(e, UnitValue, stmts1)
	    }
	    case ReturnStat(expr) => interpExpr(e)(expr)
	    case BlockStat(sts) => 
	      interpStats(e, UnitValue, sts ::: stmts1)
	  }
	}
      }
    }
 
  def interpret(p : Program) = {
    var e0 : Env = makeClassEntries(p)
    val v = interpCall(e0, p.classToRun, "main", List())
    checkType(v, VoidType)
  }
 
  def main(args : Array[String]) = {
    val prog1 = Program(
      List(ClassDef("Example", List(
	FieldDecl(VarDecl("x", BoolType)),
	FieldDecl(VarDecl("y",IntType)),
	FieldDecl(VarDecl("z",IntType)),
	MethodDecl("compute", 
		   List(VarDecl("x", IntType),
			VarDecl("y", IntType)),
		   IntType,
		   BlockStat(List(
		     DeclStat(VarDecl("z", IntType)),
		     AssignStat("z", IntConst(3)),
		     ReturnStat(Plus(Plus(Var("x"),Var("y")),Var("z")))
		   ))
		 ),
	MethodDecl("main", List(), VoidType, BlockStat(List(
	  DeclStat(VarDecl("res", IntType)),
	  AssignStat("x", BoolConst(true)),
	  AssignStat("y", IntConst(10)),
	  AssignStat("z", IntConst(17)),
	  CallStat("res", "Example", "compute", 
		   List(Var("z"), Plus(Var("z"),IntConst(1)))),
	  PrintStat(Var("res"))
	)))
      ))),
      "Example"
    )
    interpret(prog1)
  }
}