tutorial 3 -- 電卓 α
次は演算を含むサンプルです。
文法ファイル
%token Number<int> Add Sub Mul Div; %namespace calc; %dont_use_stl; Expr<int> : [Identity] Term(0) | [MakeAdd] Expr(0) Add Term(1) | [MakeSub] Expr(0) Sub Term(1) ; Term<int> : [Identity] Number(0) | [MakeMul] Term(0) Mul Number(1) | [MakeDiv] Term(0) Div Number(1) ;
BNFを使った経験があれば、見た目でおおよそわかると思います。前述のように、規則の中で(数値)が後ろにつかない要素は、トークン種別だけが重要で、セマンティックアクションでは用いられないことを意味しています。
操作ファイル
#include "calc0.hpp" #include <iostream> class unexpected_char : public std::exception {}; template < class It > class scanner { public: typedef int char_type; public: scanner( It b, It e ) : b_(b), e_(e), c_(b), unget_(EOF) { } calc::Token get( int& v ) { int c; do { c = getc(); } while( isspace( c ) ); // 記号類 switch( c ) { case '+': return calc::token_Add; case '-': return calc::token_Sub; case '*': return calc::token_Mul; case '/': return calc::token_Div; case EOF: return calc::token_eof; } // 整数 if( isdigit( c ) ) { int n = 0; while( c != EOF && isdigit( c ) ) { n *= 10; n += c - '0'; c = getc(); } ungetc( c ); v = n; return calc::token_Number; } std::cerr << char(c) << std::endl; throw unexpected_char(); } private: char_type getc() { int c; if( unget_ != EOF ) { c = unget_; unget_ = EOF; } else if( c_ == e_ ) { c = EOF; } else { c = *c_++; } return c; } void ungetc( char_type c ) { if( c != EOF ) { unget_ = c; } } private: It b_; It e_; It c_; char_type unget_; }; struct SemanticAction { void syntax_error(){} void stack_overflow(){} void downcast( int& x, int y ) { x = y; } void upcast( int& x, int y ) { x = y; } int Identity( int n ) { return n; } int MakeAdd( int x, int y ) { std::cerr << "expr " << x << " + " << y << std::endl; return x + y ; } int MakeSub( int x, int y ) { std::cerr << "expr " << x << " - " << y << std::endl; return x - y ; } int MakeMul( int x, int y ) { std::cerr << "expr " << x << " * " << y << std::endl; return x * y ; } int MakeDiv( int x, int y ) { std::cerr << "expr " << x << " / " << y << std::endl; return x / y ; } }; int main( int, char** ) { // スキャナ typedef std::istreambuf_iterator<char> is_iterator; is_iterator b( std::cin ); is_iterator e; scanner< is_iterator > s( b, e ); SemanticAction sa; calc::Parser< int, SemanticAction > parser( sa ); calc::Token token; for(;;) { int v; token = s.get( v ); if( parser.post( token, v ) ) { break; } } int v; if( parser.accept( v ) ) { std::cerr << "accpeted\n"; std::cerr << v << std::endl; } return 0; }
今回はスキャナが必要なので、手書きのスキャナを用意しました。難しいことはしていないので、内容に関してはソースを読んでください。手書きが面倒ならば、いまどきはboost::regex
を使うなど色々な方法があると思います。
セマンティックアクションでは、新しい要素は登場していません。
main
関数では、以下の新しい要素が導入されています。
parser.post
の戻り値
全体を受理したとき、もしくはエラーを検出したときにはtrue
を、まだ受理が終わってない場合にはfalse
を返します。
parser.accept
の戻り値
parser.accept
はparser.post
がtrue
を返して後でだけ有効です。errorの場合はv
は不定になり、false
が帰ります。正常終了の場合はv
にルート文法の左辺に関連付けられた値が代入され、true
を返します。
yacc等を使った経験があれば、特に難しいことはないと思います。
実行
% ./calc0 8+3*7 ^D expr 3 * 7 expr 8 + 21 accepted 29