tutorial 5 -- 電卓 γ
次のサンプルは、ASTにboost::variant
を使う例です。関数型言語っぽいプログラムが組めます。(boost::apply_visitor
がocaml等のパターンマッチに似ている)
結構強烈です。コンパイル時間の長さとコンパイルエラーのわかりづらさも強烈ですが……
ASTファイル
#include <boost/variant.hpp> #include <iostream> struct Add; struct Sub; struct Mul; struct Div; template < class OpTag > struct BinOpTerm; template < class OpTag > struct BinOpExpr; typedef boost::variant< int, boost::recursive_wrapper< BinOpTerm<Mul> >, boost::recursive_wrapper< BinOpTerm<Div> > > Term; typedef boost::variant< Term, boost::recursive_wrapper< BinOpExpr<Add> >, boost::recursive_wrapper< BinOpExpr<Sub> > > Expr; template < class OpTag > struct BinOpTerm { Term lhs; Term rhs; BinOpTerm( const Term& x, const Term& y ) : lhs( x ), rhs( y ) {} }; template < class OpTag > struct BinOpExpr { Expr lhs; Expr rhs; BinOpExpr( const Expr& x, const Expr& y ) : lhs( x ), rhs( y ) {} };
(出力用関数は省略)
calc1
と等価のものにしようとすると、大体このようになります。
文法ファイル
%token Number<int> Add Sub Mul Div; %namespace calc; %dont_use_stl; Expr<Expr> : [MakeExpr] Term(0) | [MakeAdd] Expr(0) Add Term(1) | [MakeSub] Expr(0) Sub Term(1) ; Term<Term> : [MakeTerm] Number(0) | [MakeMul] Term(0) Mul Number(1) | [MakeDiv] Term(0) Div Number(1) ;
これもほとんど代わりませんが、Number/Term/Expr
の型は
Number<int> Term<Term> Expr<Expr>
と、どれもポインタ型ではなくなりました。今回のプログラムはメモリリークしません。
操作ファイル
#include "calc2.hpp" #include <iostream> class unexpected_char : public std::exception {}; typedef boost::variant< int, Term, Expr > Value; 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( Value& 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(){} template < class T > void downcast( T& x, Value y ) { x = boost::get<T>( y ); } template < class T > void upcast( Value& x, const T& y ) { x = y; } Expr MakeExpr( const Term& x ) { return Expr( x ); } Expr MakeAdd( const Expr& x, const Term& y ) { std::cerr << "expr " << x << " + " << y << std::endl; return BinOpExpr<Add>( x, y ); } Expr MakeSub( const Expr& x, const Term& y ) { std::cerr << "expr " << x << " - " << y << std::endl; return BinOpExpr<Sub>( x, y ); } Term MakeTerm( int x ) { return Term( x ); } Term MakeMul( const Term& x, int y ) { std::cerr << "expr " << x << " * " << y << std::endl; return BinOpTerm<Mul>( x, y ); } Term MakeDiv( const Term& x, int y ) { std::cerr << "expr " << x << " / " << y << std::endl; return BinOpTerm<Div>( x, y ); } }; struct calculator : public boost::static_visitor<int> { int operator()( int n ) const { return n; } int operator()( const Term& x ) const { return boost::apply_visitor( calculator(), x ); } int operator()( const BinOpTerm<Mul>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) * boost::apply_visitor( calculator(), x.rhs ); } int operator()( const BinOpTerm<Div>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) / boost::apply_visitor( calculator(), x.rhs ); } int operator()( const Expr& x ) const { return boost::apply_visitor( calculator(), x ); } int operator()( const BinOpExpr<Add>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) + boost::apply_visitor( calculator(), x.rhs ); } int operator()( const BinOpExpr<Sub>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) - boost::apply_visitor( calculator(), x.rhs ); } }; 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< Value, SemanticAction > parser( sa ); calc::Token token; for(;;) { Value v; token = scanner.get( v ); if( parser.post( token, v ) ) { break; } } Value v; if( parser.accept( v ) ) { std::cerr << "accpeted\n"; std::cerr << boost::apply_visitor( calculator(), v ) << std::endl; } return 0; }
まず、以下のように、すべての値を格納できる型を定義します。
boost::variant< int, Term, Expr > Value;
これをスキャナの戻り値やパーサのテンプレートパラメータとして用います。今回は文法要素のとりうる値が3種類しかないのでvariant
を使っていますが、より大きな文法ではvariant
の上限にひっかかる可能性があるので、boost::any
を使うことになるかもしれません。
(calc2_ast.hpp
で使っているvariant
とはまったく別の話なので、注意してください。)
次にセマンティックアクションです。おおむね自明だと思いますが、downcast
・upcast
は少し注意が必要です。boost::variant
の仕様にあわせて以下のようにする必要があります。
template < class T > void downcast( T& x, Value y ) { x = boost::get<T>( y ); } template < class T > void upcast( Value& x, const T& y ) { x = y; }
パーサとは無関係ですが、ASTを走査する計算機は以下のように作ります。
struct calculator : public boost::static_visitor<int> { int operator()( int n ) const { return n; } int operator()( const Term& x ) const { return boost::apply_visitor( calculator(), x ); } int operator()( const BinOpTerm<Mul>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) * boost::apply_visitor( calculator(), x.rhs ); } int operator()( const BinOpTerm<Div>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) / boost::apply_visitor( calculator(), x.rhs ); } int operator()( const Expr& x ) const { return boost::apply_visitor( calculator(), x ); } int operator()( const BinOpExpr<Add>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) + boost::apply_visitor( calculator(), x.rhs ); } int operator()( const BinOpExpr<Sub>& x ) const { return boost::apply_visitor( calculator(), x.lhs ) - boost::apply_visitor( calculator(), x.rhs ); } };
ダブルディスパッチもできるようで、ocaml等のmatch
ほどではないですが、なかなかすごいことになってます。
詳しくはboost::variant
のドキュメントをご覧ください。