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.acceptparser.posttrueを返して後でだけ有効です。errorの場合はvは不定になり、falseが帰ります。正常終了の場合はvにルート文法の左辺に関連付けられた値が代入され、trueを返します。

yacc等を使った経験があれば、特に難しいことはないと思います。

実行

% ./calc0
8+3*7
^D
expr 3 * 7
expr 8 + 21
accepted
29