boost::spirit研究室

番目のお客様です。

since 2012/03/06

last 2012/03/06




足し算解析

足し算の解析を例に説明します。

コードはこんな感じ

#include <boost/spirit/core.hpp>

using namespace boost;
using namespace boost::spirit;

/**
 * @brief 足し算を行うクラス
 */
struct add_parser : public grammar< add_parser > {

    template<typename S> struct definition {

        rule<S> expr;

        definition(const add_parser& self)
        {
            // 構文定義
            expr = real_p >> '+' >> real_p;
        }

        const rule<S>& start() const { return expr; }
    };

};

/**
 * @brief 足し算を行う
 * @param expr 解析したい文字列
 * @return 解析成功ならtrue
 */
bool    Parse ( const char* expr ){

    add_parser calc;

    // 入力された文字列をcalcで解析
    parse_info<> r = parse(expr, calc);

	//	解析結果を返す
    return r.hit;
}


定型部分

まず、定型部分を簡単に説明をします。

1)boost/spirit/core.hppをinclude

2)grammarクラスを継承して、Parserクラスを定義

struct add_parser : public grammar< add_parser > {

3)Parserクラス内にdefinitionクラスを宣言

template<typename S> struct definition {



構文解析1

構文の書き方を説明します。

足し算は2つの数値の間に + を書きますよね。

例)1+2

spiritではそれをこんな感じで記述します。

expr = real_p >> '+' >> real_p;

exprはrule<S>クラスの実体

real_pはspiritが用意しているセマンティックでdoule型の値を表します。

構文はdefinitionクラスの中に書くので以下のようになります。

template<typename S> struct definition {
    rule<S> expr;
    definition(const add_parser& self)
        expr = real_p >> '+' >> real_p;
    }

    const rule<S>& start() const { return expr; }
}

これで、文字列の構文の解析ができます。

解析するためには以下のように記述します。

add_parser calc;

// 入力された文字列をcalcで解析
parse_info<> r = parse("1+2", calc);

解析結果はr.hitに格納されます。

r.hit == true

なら解析成功です。


フィルタ

上記のソースでは"1+2"はtrueになりますが、"1 + 2"はfalseになります。

スペースがあると解析失敗なわけです。

これに対応するためには、構文にスペースを書くか、

expr = *space_p >> real_p >> *space_p >> '+' >> *space_p >> real_p >> *space_p;

スペースをフィルタしてあげます。

parse_info<> r = parse("1+2", calc, space_p);

space_pはスペースまたはタブを表します。

*space_pは0個以上のspace_pを表します。


構文解析2

続けて、実際に計算をさせて結果を返すように拡張します。

コードはこんな感じ

#include <boost/spirit/core.hpp>
#include <stack>

using namespace boost;
using namespace boost::spirit;
using namespace std;

/**
 * @brief 足し算を行うクラス
 */
struct add_parser : public grammar< add_parser > {
    stack<double>    _val;    //!>計算用スタック

    /**
     * @brief スタックの中身を加算(セマンティックアクション)
     */
    void add ( const char*, const char* ) 
    {

        double r = _val.top();
        _val.pop();

        double l = _val.top();
        _val.pop();

        _val.push( l + r );
    }

    /**
     * @brief 値をスタックに積む(セマンティックアクション)
     * @param v 値
     */
    void push(double v) 
    {
        _val.push(v);
    }

    template<typename S> struct definition {

        rule<S> expr;

        definition(const add_parser& self)
        {
            // 構文定義
            expr = ( real_p[&self.push] >> '+' >> real_p[&self.push] )[&self.add];
        }

        const rule<S>& start() const { return expr; }
    };

};

/**
 * @brief 足し算を行う
 * @param expr 解析したい文字列
 * @param ret 計算結果
 * @return 解析成功ならtrue
 */
bool    Parse ( const char* expr, double &ret ){

    add_parser calc;

    // 入力された文字列をcalcで解析
    parse_info<> r = parse(expr, calc, space_p);

    if(r.hit){    // 入力された文字列の解析成功確認
        ret =  calc._val.top();
    }

    return r.hit;
}

サンプルソース


まず、注目してほしいのはこの行

expr = ( real_p[&self.push] >> '+' >> real_p[&self.push] )[&self.add];

[ ]が増えてますね。

これはセマンティックアクションを指定しています。

意味ですが、「ヒットした時にメソッドを呼べ」という事になります。

real_p[&self.push]

これだと、real_p(double形の数値)にヒットしたらself.pushを呼んでくれます。

selfは「const add_parser&」なので「addparser::push()」が呼び出されます。

仮に1+2を解析した場合、次の順番でメソッドが呼び出されます。(addの引数は今回説明しません)

self.push(1);

self.push(2);

self.add("1","");

後はソースを読めば分かると思いますので割愛します。

他のサイトではセマンティックアクションに関数ファンクタを使用したり、クラス外に宣言したりしていますが、私は次の理由で使用していません。

関数ファンクタ:コンパイルエラーが発生する

クラス外:スタティック変数が必要

戻る