Scalaでパーサーを作ってみる〜8:変数のスコープ

Scalaの勉強をはじめたので、とりあえず簡単なパーサーを作ってみてます。
http://d.hatena.ne.jp/nowokay/20111109#1320815540


前回で変数導入しました。
http://d.hatena.ne.jp/nowokay/20111110#1320899264


けど、

val aa = 23;
println("pre block:" + aa);
{
  val aa = 4;
  println("in block:" + aa);
  val bb = 3;
  println(bb);
};
println("post block:" + aa);
println(bb);

を実行すると

pre block:23
in block:4
3
post block:4
3

のように表示されて、ブロックのなかの変数宣言がブロックの外に影響してます。これは気持ち悪い。


ということで、変数のスコープを導入します。

class Environment(parent:Option[Environment]){
  val variables = Map[String, Any]()
  def get(key:String):Any = {
    if(variables.contains(key)){
      variables(key)
    }else{
      parent match{
        case Some(p) => p.get(key)
        case None => throw new Exception("symbol'%s' not found".format(key))
      }
    }
    
  }
  def set(key:String, value:Any){
    variables(key) = value
  }
}

親環境を導入して、変数参照のときローカル環境に変数がなければ親環境を見るようにしてます。
変数がなければ、例外なげてます。


ブロックであるLinesで、ローカル環境作ってそれを使って各行の処理してます。

        case Lines(exprs) =>{
            val local = new Environment(Some(env))
            exprs.foldLeft(Unit:Any){(result, x) => eval(local, x)}
        }

あと、ループで最後の式を値にするために、みずしまさんに教えてもらったfoldLeft使ってます。


evalが再帰呼び出しになるので、型を解決しておく必要があります。

  def eval(env:Environment, ast:AST):Any = {


で、呼び出します。

    var result = visitor.eval(new Environment(None), ast);


gakuzoさんに教えてもらった書き方でさらにすっきり

  def printLine: Parser[AST] = "println"~"("~>expr<~")"^^PrintLine


これで、スコープが実現できました!

val aa = 23;
println("pre block:" + aa);
{
  val aa = 4;
  println("in block:" + aa);
  val bb = 3;
  println(bb);
};
println("post block:" + aa);
println(bb);

を実行するとこうなります。

pre block:23
in block:4
3
post block:23
例外:symbol'bb' not found


ソースはgistに。
https://gist.github.com/1345875/3fad22314df53a7a534618d58a24a00fb68960f3