Scala Interpreter Blocks

时间:2018-06-08 19:47:54

标签: scala interpreter

目前我正在努力解释Scala中的Blocks。我的代码看起来像这样:

AST:

case class Braces(value: Node) extends Node
case class Block(value: List[Node]) extends Node
case class VariableDeclaration(name: String, value: Node) extends Node
case class VariableAssignment(name: String, value: Node) extends Node
case class CallFunction(name: String, value: List[Node]) extends Node
case class Variable(name: String) extends Node
case class Integer(int: Int) extends Node

解释器: (我不想更改签名或解释函数的参数。我的首选解决方案包括全局环境堆栈)

    sealed trait iValue
    type VarEnv = Map[String, iValue]

    def interpret(env: VarEnv, body: Node): iValue = body match
    {
        case Block(value: List[Node]) => {
          ???
        }
        case VariableDeclaration(....)
        case VariableAssignment(....)
        case CallFunction(....)
    }

示例测试用例应如下所示:

  {
    $a = 5;
    $b = 4;
    {
      $a = 2;
      b = add(a, b);
    };
    add(b, { a; });
  }
  iValue(11))

如何实现这一点,包括隐藏变量?

1 个答案:

答案 0 :(得分:2)

我刚刚完成了一个功能很强的翻译代码,这里有一个简短的演练。

  1. 您想重新分配变量,因此它看起来像是一种命令式语言。因此,我们将分别处理语句和表达。以下是与示例相关的AST元素:

    sealed trait Stmt
    case class DeclAsgn(varName: String, rhs: Expr) extends Stmt
    case class Asgn(varName: String, rhs: Expr) extends Stmt
    case class Print(expr: Expr) extends Stmt
    case class Block(stmts: List[Stmt]) extends Stmt
    
    sealed trait Expr
    case class Num(n: Int) extends Expr
    case class Sub(a: Expr, b: Expr) extends Expr
    case class Var(name: String) extends Expr
    
  2. 我们希望评估表达式。因此,我们必须定义是什么。目前,只有整数值(iValue?)和默认None - 值(如Python中的None,而不是Scala - None):

    sealed trait Value
    case object NoneValue extends Value
    case class IntValue(i: Int) extends Value
    
  3. 现在有趣的部分:环境。请注意区分"环境"和"阻止环境"是不够的:块可以嵌套到任意深度,所以你需要一个完整的堆栈变量绑定:

    case class Env(stack: List[Map[String, Value]]) extends (String => Value) {
      def apply(varName: String) = stack match {
        case Nil => throw new Error("Undefined var: " + varName)
        case h :: t => h.getOrElse(varName, Env(t)(varName))
      }
      def enterBlock: Env = Env(Map.empty[String, Value] :: stack)
      def exitBlock: Env = Env(stack.tail)
      def withDeclaredVar(name: String): Env = {
        val h :: t = stack
        if (h contains name) throw new Error(s"Variable $name already declared")
        else Env(h.updated(name, NoneValue) :: t)
      }
      def updated(name: String, value: Value): Env = stack match {
        case Nil => throw new Error("Could not set variable " + name)
        case h :: t => 
          if (h contains name) Env(h.updated(name, value) :: t)
          else Env(h :: Env(t).updated(name, value).stack)
      }
      def withDeclaredVar(name: String, value: Value): Env = {
        this.withDeclaredVar(name).updated(name, value)
      }
    }
    

    现在,每次输入一个块,你只需在这个堆栈的顶部放一个新的空Map[String, Value],这样就可以将所有新声明的变量插入到这个地图中(遮蔽以前的级别),并且你完成了这个块,你可以简单地丢弃最顶层的地图(取消之前阴影层的阴影)。

    这里还有一个小辅助方法,用于实例化整个程序运行的初始空环境:

    object Env {
      def empty = Env(List(Map.empty[String, Value]))
    }
    
  4. 一旦正确设置了所有数据结构,表达式的评估和副作用语句的解释就很简单:

    def eval(env: Env, expr: Expr): Value = expr match {
      case Var(v) => env(v)
      case Sub(a, b) => (eval(env, a), eval(env, b)) match {
        case (IntValue(va), IntValue(vb)) => IntValue(va - vb)
        case sthElse => throw new Error("`Sub` not applicable to " + sthElse)
      }
      case Num(n) => IntValue(n)
    }
    
    def interpret(env: Env, stmt: Stmt): Env = stmt match {
      case Block(stmts) =>
        stmts.foldLeft(env.enterBlock)(interpret).exitBlock
      case Print(e) => {
        println(eval(env, e))
        env
      }
      case DeclAsgn(v, r) => {
        val rhsVal = eval(env, r)
        env.withDeclaredVar(v, rhsVal)
      }
      case Asgn(v, r) => {
        var rhsVal = eval(env, r)
        env.updated(v, rhsVal)
      }
    }
    
  5. 为了看到一些东西,让我们快速定义漂亮的打印功能:

    def prettyPrint(prog: Stmt): String = prog match {
      case DeclAsgn(v, r) => s"$$${v} = ${prettyPrint(r)}"
      case Asgn(v, r) => s"${v} = ${prettyPrint(r)}"
      case Print(e) => s"print(${prettyPrint(e)})"
      case Block(xs) => xs
        .map(prettyPrint)
        .mkString(";\n")
        .split("\n")
        .map("  " + _)
        .mkString("{\n", "\n", "\n}")
    }
    
    def prettyPrint(expr: Expr): String = expr match {
      case Num(n) => n.toString
      case Sub(a, b) => s"sub(${prettyPrint(a)},${prettyPrint(b)})"
      case Var(v) => v
    }
    
  6. 现在举个例子。我已经插入了一些print - 语句,因此我们可以观察到中间结果。这是AST代码:

        /* example */ {
          import scala.language.implicitConversions
          val c = Var("c")
          val d = Var("d")
          implicit def intToNum(i: Int): Expr = Num(i)
          val ast = Block(List(
            DeclAsgn("c", 5),
            DeclAsgn("d", 4),
            Block(List(
              Asgn("c", 3),
              DeclAsgn("d", 45),
              Print(c),
              Print(d),
              Print(Sub(d, c))
            )),
            Print(c),
            Print(d),
            Print(Sub(c, d))
          ))
          println(prettyPrint(ast))
          interpret(Env.empty, ast)
        }   
    

    这是漂亮的印刷版本:

    {
      $c = 5;
      $d = 4;
      {
        c = 3;
        $d = 45;
        print(c);
        print(d);
        print(sub(d,c))
      };
      print(c);
      print(d);
      print(sub(c,d))
    }
    

    这是输出:

    IntValue(3)
    IntValue(45)
    IntValue(42)
    IntValue(3)
    IntValue(4)
    IntValue(-1)
    

    正如您所看到的,c的值被遮蔽,然后没有被遮蔽,内部块中设置的d的值被简单地丢弃。