2010年1月6日水曜日

ANTLR IDE/分数電卓

金額計算に使えそうな外部 DSL を書きたいが、その前に軽くウォームアップ。

分数電卓を作ってみる。コンソールで式の入力を受付けて計算結果を返す。ただし小数は受け付けない。またゼロ割り算はシンプルに例外送出のみとする。

作業環境・実行環境は Eclipse を使用。コンソールビューの入力と出力がこんな風になるようにしたい。
1+2/5
(1+2)/5
1/2-1/2
0/3-1/4
7/5
3/5
0
-1/4


以下のような仕様で、簡単なJava の有理数クラスも要るので用意しておく。自明なのでコードは省略。
  • 分子と分母を long 値のペアで表現する、Rational という名前の不変クラス。
  • コンストラクタで約分する。
  • add, subtract, multiply, divide メソッドは計算結果を新規インスタンスで返す。
  • toString()は、分母が1なら整数、そうでないなら分数での文字列表現とする。
  • 上記のとおりゼロ割りは例外送出。

この Rational クラスを、以下のような ANTLR コードから使う。AST 生成文法とAST 処理文法を別々にした。
grammar Expr;

options {
output = AST;
ASTLabelType=CommonTree;
}

//prog: (stat {System.out.println($stat.tree.toStringTree());})+;
prog: stat+
;
stat: expr NEWLINE -> expr
| NEWLINE ->
;
expr: multExpr (('+'^ |'-'^) multExpr)*
;
multExpr : atom (('*'^ |'/'^) atom)*
;
atom: INT
| '('! expr ')'!
;

INT : '0'..'9'+ ;
NEWLINE: '\r'? '\n' ;
WS : (' '|'\t'|'\n'|'\r')+ {skip();} ;
tree grammar Eval;

options {
tokenVocab=Expr;
ASTLabelType=CommonTree;
}
@header {
import rational.Rational;
}

prog: stat+
;
stat: expr {System.out.println($expr.value);}
;
expr returns [Rational value]
: ^('+' a=expr b=expr) {$value = a.add(b);}
| ^('-' a=expr b=expr) {$value = a.subtract(b);}
| ^('*' a=expr b=expr) {$value = a.multiply(b);}
| ^('/' a=expr b=expr) {$value = a.devide(b) ;}
| INT {$value = new Rational(Long.parseLong($INT.text), 1);}
;

まあ、普通に動くようだ。

流れ的には、まず文法定義を書いてから、JUnit を用いたテストファーストで Rational クラスを書きながら、ANTLR コードと Java コードの結合を手作業で確認した。実戦だと ANTLR コードの検証も自動化したい。

次にやる事
  • 行列の計算。もともとやりたいのは按分計算込みの事務処理用DSLだけど、その前に簡単な行列の計算がいりそうな感じ。
  • 変数と代入式
  • Java とのインターフェイス。コンソールを使わない形で Java から使えるようにする。

0 件のコメント:

コメントを投稿