C#のパーサジェネレータ Ironyを使ってみた。その2。 | 妄想プログラマのらくがき帳

2012年10月9日火曜日

C#のパーサジェネレータ Ironyを使ってみた。その2。

前回に引き続き、Ironyについてです。

前回はプロジェクトの参照設定にIrony.dllを追加するところまで書きました。
今回はIronyで文法を定義する方法です。

Ironyで文法を定義するには、Irony.Parsing.Grammarを継承した文法クラスを作成し、
そのクラスのコンストラクタに文法を定義する処理を記述します。
下記のコードは試しに作ってみた簡単な文法クラスのコードです。

[Language("MyGrammar")]
public class MyGrammar : Grammar
{
    public MyGrammar() : base(true)
    {
        //
        //終端記号を定義
        //
        
        //数字
        var number = new NumberLiteral("number");
        number.DefaultIntTypes = new [] { TypeCode.Int32,
                                          TypeCode.Int64,
                                          NumberLiteral.TypeCodeBigInt };
        number.DefaultFloatType = TypeCode.Double;
        //識別子
        var identifier = new IdentifierTerminal("identifier");
        //コメント
        var comment = new CommentTerminal("comment", "//", "\n", "\r");

        //
        //非終端記号を定義
        //

        var Expr = new NonTerminal("Expression");
        var Term = new NonTerminal("Term");
        var BinExpr = new NonTerminal("BinaryExpression");
        var BinOp = new NonTerminal("BinaryOperator");
        var AssignmentStmt = new NonTerminal("AssignmentStatement");
        var AssignmentOp = new NonTerminal("AssignmentOperator");
        var Statement = new NonTerminal("Statement");
        var ProgramLine = new NonTerminal("ProgramLine");
        var Program = new NonTerminal("Program");

        //
        //文法を定義
        //

        Expr.Rule = Term | BinExpr;
        Term.Rule = number | identifier;
        BinExpr.Rule = Expr + BinOp + Expr;
        BinOp.Rule = ToTerm("+") | "-" | "*" | "/";
        AssignmentStmt.Rule = identifier + AssignmentOp + Expr;
        AssignmentOp.Rule = ToTerm("=") | "+=" | "-=" | "*=" | "/=";
        Statement.Rule = AssignmentStmt | Expr | Empty;
        ProgramLine.Rule = Statement + ToTerm(";");
        Program.Rule = MakeStarRule(Program, ProgramLine);
        //文法のルートを設定
        this.Root = Program;

        //
        //演算子の優先順位を定義
        //

        RegisterOperators(1, "+", "-");
        RegisterOperators(2, "*", "/");
        
        this.LanguageFlags = LanguageFlags.NewLineBeforeEOF |
                             LanguageFlags.SupportsBigInt;
    }
}
まず、6~9行目で終端記号を定義しています。
数字や識別子、コメントといった一般的な終端記号は、すでにクラスが用意してあるのでそれを使います。
他にもRegexLiteralやCustomTerminalなどといった終端記号用のクラスがあるので、必要に応じて使い分けます。

次に21~33行目で非終端記号を定義しています。
引数で与えた名前は、パースツリー内の各ノード名として使用されます。

35~49行目では文法を定義しています。
'+'と'-'が演算子オーバーロードされているので、BNFライクに記述することができます。
・定義に文字列リテラルを使う場合は、ToTerm()を使う
・'*'(繰り返し)を記述するには、MakeStarRule()を使う
といったところが、Irony独特の記述方法です。

最後に演算子の優先順位の設定とオプションの設定をしています。

以上で簡単な文法ですが定義完了です。
Irony付属のサンプルとかを見ると、C#の文法とかは定義がもっと複雑になるみたいですが、
上記のような簡単な文法なら簡潔に記述することができてイイ感じです^^
(複雑な文法定義に関しては、そのうち別エントリーで書く予定です)

次回は定義した文法でパーサを作成し、実際にパースしてみます。

0 件のコメント:

コメントを投稿