Scalaでパーサーを作ってみる〜9:関数の定義と呼び出し

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


前回、変数のスコープを導入しました。
http://d.hatena.ne.jp/nowokay/20111111#1320987235


そうすると、やっぱり関数定義したいですよね。
ということで

def hoge(x) ={
  x * 2
}

みたいな形で関数を定義してhoge(3)とやったら6が返るようにしてみます。

構文要素

まず構文要素を定義します。

case class Func(params:List[String], proc:AST) extends AST
case class FuncDef(name: String, func: Func) extends AST
case class FuncCall(func:AST, params:List[AST]) extends AST

関数をあらわすFuncと、関数定義のFuncDef、関数呼び出しのFuncCallを定義します。

構文定義

そしたら構文の定義です。
まずは関数定義の構文。今回戻り値なしの関数は考えません。

def funcDef:Parser[FuncDef] = "def"~>ident~opt("("~>repsep(ident, ",")<~")")~"="~expr^^{
  case v~params~_~proc => {
    val p = params match{
      case Some(pr) => pr
      case None => Nil
    }
    FuncDef(v.name, Func(for(pm <- p) yield{pm.name}, proc))
  }
}

ここで、Funcオブジェクトを作って関数名とともにFuncDefにつっこみます。


そしたら関数呼び出し。

def funcCall: Parser[AST] = factor~opt("("~>repsep(expr, ",")<~")")^^{
  case fac~param =>{
    param match{
      case Some(p) => FuncCall(fac, p)
      case None => fac
    }
  }
}

ここでは、「(x, y)」のようなものが続いたら関数呼び出しということにして、そうじゃなければそのままfactorとして扱います。


これをいままでの構文に組み込みます。
関数定義のfuncDefは、lineということにします。

def line: Parser[AST] = expr | assignment | funcDef

関数定義も式じゃない。


呼び出しは、掛け算をあらわすtermのところに入れます。

def term : Parser[AST] = chainl1(funcCall, 
  "*"^^{op => (left:AST, right:AST) => MulOp(left, right)})

掛け算、mulにしとけばよかったなー。まあいっか。

処理

で、構文の処理はできたので、実際の動作を記述します。
FuncDefは、構文処理でFuncオブジェクト作ってるので、環境につっこむだけ。

case FuncDef(name, func) =>{
  env.set(name, func)
}


呼び出しはこんな感じで。

case FuncCall(func, params) =>{
  visit(func) match{
    case f:Func => {
      val local = new Environment(Some(env))
      var arg = params
      for(pn <- f.params){
        val a::suc = arg
        local.set(pn, visit(a))
        arg = suc
      }
      eval(local, f.proc)
    }
  }
}

関数呼び出すときにローカルな環境をつくって、引数の値を設定してから、その環境とともに関数本体の処理を呼び出します。

実行

こんな感じになりました!

def twice(x) = x * 2;
println("twice 3:" + twice(3));

実行するとこう。

twice 3:6

関数の定義・呼び出しできてます。


再帰でループもできるぜ!

def rec(x) = {
  if(0 < x){
    println(x);
    rec(x - 1)
  }else{
    0
  }
};
rec(3);

実行すると

3
2
1

となります。
ちなみに、関数定義の最後にもセミコロンが必要です。いけてない!


あと、変数と区別なく環境につっこんでるので、実際には値として関数を保持した変数ということになっています。
なので別の変数に入れれちゃう。

val tw = twice;
println("tw 5:" + tw(5)); 

こうなる。

tw 5:10

なんかおもろい!


これで、逐次実行できて条件分岐もあってループできるということでチューリング完全な感じになるので、いっぱしのプログラミング言語になりましたよ!


ソースはこれ
https://gist.github.com/1345875/a791b87ef0b54f3ced3d68bd51b113221892f3b2