コンパイラ作成
最後にこの計算機言語のメインプログラムについて解説します。
まず、fslexにて作成した字句解析器とfsyaccで作成した構文解析器の動作確認用にtestProjのProgram.fsに以下の2か所の部分を追記しました(※3と※9)。
open System open Microsoft.FSharp.Text.Lexing open Ast open Lexer open Parser // factorの評価 let rec evalFactor factor = match factor with | Float x -> x | Integer x -> float x | ParenEx x -> evalExpr x // termの評価 and evalTerm term = match term with | Times (term, fact) -> (evalTerm term) * (evalFactor fact) | Divide (term, fact) -> (evalTerm term) / (evalFactor fact) | Factor fact -> evalFactor fact // expressionの評価 and evalExpr expr = match expr with | Plus (expr, term) -> (evalExpr expr) + (evalTerm term) | Minus (expr, term) -> (evalExpr expr) - (evalTerm term) | Term term -> evalTerm term // equationの評価 ※1 and evalEquation eq = match eq with | Equation expr -> evalExpr expr printfn "Calculator" let rec readAndProcess() = //※2 printf ":" match Console.ReadLine() with | "quit" -> () | expr -> try printfn "Lexing [%s]" expr //ここから追記 ※3 字句解析器の動作確認用の追記です。 let lexbuf = LexBuffer<char>.FromString(expr) //字句解析準備 let parse txt = let mutable keepParse = true let mutable tokenList = [] while keepParse = true do let parsedToken = Lexer.tokenize txt //※4 字句解析関数呼び出し tokenList <- tokenList @ [parsedToken] //※5 トークンをリストに if parsedToken = Parser.EOF then keepParse <- false //※6 終了フラグ tokenList let tokens = parse lexbuf printfn "Tokens : %A" tokens //トークン表示 //ここまで追記 let lexbuff = LexBuffer<char>.FromString(expr) //※7 printfn "Parsing..." let equation = Parser.start Lexer.tokenize lexbuff //※8 //以下の1行を追記 ※9 構文解析器の動作確認用追記です。 printfn "equation is %A" equation printfn "Evaluating Equation..." let result = evalEquation equation //※10 printfn "Result: %s" (result.ToString()) with ex -> printfn "Unhandled Exception: %s" ex.Message readAndProcess() readAndProcess()
実際に操作しながら、解説してみます。ソリューションをビルドして、開始すると、コマンドラインアプリケーションが起動します。
例として、2 + 3 * 5と入力してみてください。readAndProcess()(※2)は再帰関数です。標準入力からの文字列がquitでない限り、繰り返されます。まず、最初にfslexで作成した字句解析器の動作確認をします。文字列(2 + 3 * 5)を受け取り、文字を読むために字句解析用に用意したバッファlexbufにバインドします。
その後、parseという関数を作成し、字句解析関数Lexer.tokenizeを呼び(※4)、lexbufを引数として渡すと、トークン列を返すようにします。その戻り値をリストに入れていきます(※5)。トークンが最後EOFになったらフラグをfalseにしてリストの中身を表示しています(※6)。
INT32 2; PLUS; INT32 3; ASTER; INT32 5; EOF
その後、※7で再度バッファを作成し、構文解析関数Parser.startを呼んでいます(※8)。Parser.startは字句解析結果(Lexer.tokeninze lexbuff)を必要とし、結果をequationにバインドします。構文解析器の動作確認として、Parser.startが戻した結果を確認するために※9の1行を追加すると、次のように、equationが表示され、構文解析器が動作していることが分かります。
Equation (Plus (Term (Factor (Integer 2)),Times (Factor (Integer 3)
最後にequationを引数として、関数evalEquationを呼び出し、方程式(equation)、式(expr)、項(term)、因子(factor)の評価を行います。以下は式の評価と結果を導き出すまでのその書き換え作業です。
evalEquation (Equation (Plus (Term (Factor (Integer 2)),Times (Factor (Integer 3), Integer 5)))) ↓ evalExpr (Plus (Term (Factor (Integer 2)),Times (Factor (Integer 3), Integer 5))) ↓ (evalExpr (Term (Factor (Integer 2)) ) + (evalTerm (Times (Factor (Integer 3), Integer 5))) ↓ (evalTerm(Factor(Integer 2))) + (((evalTerm(Factor(Integer 3))) * (evalFactor(Integer5))) ↓ (evalFactor(Integer2)) + (evalFactor(Integer 3) * (float 5) ↓ (float 2) + ((float 3) * (float 5)) ↓ (float 2) + (float 15) ↓ 17
上記例の出力結果は以下のようになります。
Calculator :2 + 3 * 5 Lexing [2 + 3 * 5] Tokens : [INT32 2; PLUS; INT32 3; ASTER; INT32 5; EOF] Parsing... equation is Equation (Plus (Term (Factor (Integer 2)),Times (Factor (Integer 3), Integer 5))) Evaluating Equation... Result: 17
トークンとして定義されていない文字の入力に対しては以下のようなエラーが表示されます。
:ai + 1 Lexing [ai + 1] Unhandled Exception: unrecognized input
まとめ
コマンドラインアプリケーションのfslexとfsyaccですが、lex/yaccと仕様は大変似ています。しかし、ツールの特徴でもあるMSBuildタスクで使える、というメリットを生かして使いこなせるようになるとツールの使い勝手がさらに良くなると思います。次回はF#LOPのコンピュテーション式表現について解説します。