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