CodeZine(コードジン)

特集ページ一覧

F#のfslexとfsyaccを用いたコンパイラ作成

C#プログラマのためのF#入門(9)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/08/17 14:00

目次

コンパイラ作成

 最後にこの計算機言語のメインプログラムについて解説します。

 まず、fslexにて作成した字句解析器とfsyaccで作成した構文解析器の動作確認用にtestProjのProgram.fsに以下の2か所の部分を追記しました(※3※9)。

[リスト10]計算機で字句解析器と構文解析期の動作を確認
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)。

[リスト11]字句解析結果
INT32 2; PLUS; INT32 3; ASTER; INT32 5; EOF

 その後、※7で再度バッファを作成し、構文解析関数Parser.startを呼んでいます(※8)。Parser.startは字句解析結果(Lexer.tokeninze lexbuff)を必要とし、結果をequationにバインドします。構文解析器の動作確認として、Parser.startが戻した結果を確認するために※9の1行を追加すると、次のように、equationが表示され、構文解析器が動作していることが分かります。

[リスト12]構文解析結果
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

 上記例の出力結果は以下のようになります。

[リスト13]testProj実行例
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

 トークンとして定義されていない文字の入力に対しては以下のようなエラーが表示されます。

[リスト14]エラーの例
:ai + 1
Lexing [ai + 1]
Unhandled Exception: unrecognized input

まとめ

 コマンドラインアプリケーションのfslexとfsyaccですが、lex/yaccと仕様は大変似ています。しかし、ツールの特徴でもあるMSBuildタスクで使える、というメリットを生かして使いこなせるようになるとツールの使い勝手がさらに良くなると思います。次回はF#LOPのコンピュテーション式表現について解説します。



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:C#プログラマのためのF#入門

もっと読む

著者プロフィール

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

  • WINGSプロジェクト 星山 仁美(ホシヤマ ヒトミ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5