サンプルの動作確認バージョン [GCC4.4/1.41.0] [VC9/1.41.0]
#include <iostream>
#include <string>
#include <vector>
#include <boost/fusion/tuple.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
using namespace std;
using namespace boost;
using namespace boost::spirit;
//
// 例1: 整数リテラル int_ をカンマ ',' で区切ったリスト (% は区切り文字指定のリスト解析)
//
bool parse_csv( const string& str, std::vector<int>& result )
{
string::const_iterator it = str.begin();
return qi::parse(it, str.end(), int_ % ',', result) && it==str.end();
}
//
// 例2: 実数のペア、のリスト(>> は単に文字列の並び、+ は単なる1個以上の繰り返し)
//
bool parse_points( const string& str, std::vector< fusion::tuple<double,double> >& result )
{
string::const_iterator it = str.begin();
return qi::phrase_parse(it, str.end(),
+( "x:" >> double_ >> "y:" >> double_ ),
ascii::space, // 途中に空白を入れても無視する
result
) && it==str.end();
}
//
// 例3: 少しちゃんとした例。数式を計算します
// 文法定義
// expr ::= term ('+' term | '-' term)*
// term ::= fctr ('*' fctr | '/' fctr)*
// fctr ::= int | '(' expr ')'
// 開始記号
// expr
//
template<typename Iterator>
struct calc
: qi::grammar<Iterator, int(), ascii::space_type>
{
qi::rule<Iterator, int(), ascii::space_type> expr, term, fctr;
calc() : calc::base_type(expr)
{
expr = term[_val = _1] >> *( ('+' >> term[_val += _1])
| ('-' >> term[_val -= _1]) );
term = fctr[_val = _1] >> *( ('*' >> fctr[_val *= _1])
| ('/' >> fctr[_val /= _1]) )[&Oops];
fctr = int_ | '(' >> expr >> ')';
}
// 計算ついでに、* と / を見ると意味もなく Oops! と叫ぶコード
static void Oops() { cout << "Oops!" << endl; }
};
// 使用例
int main()
{
{
std::vector<int> result;
if( parse_csv( "1,2,3", result ) )
cout << result.size() << " integers parsed" << endl;
else
cout << "error" << endl;
}
{
std::vector<int> result;
if( parse_csv( "1 2 3", result ) ) // カンマ区切りでない
cout << result.size() << " integers parsed" << endl;
else
cout << "error" << endl;
}
{
std::vector< fusion::tuple<double,double> > result;
if( parse_points( "x: 1 y: 2.0 x: 3 y: -4.5", result ) )
cout << result.size() << " pts parsed" << endl;
else
cout << "error" << endl;
}
for(string str; getline(cin,str) && str.size()>0; )
{
calc<string::iterator> c;
int result = -1;
string::iterator it = str.begin();
if( qi::phrase_parse(it, str.end(), c, ascii::space, result) )
cout << result << endl;
}
}
3 integers parsed error 2 pts parsed 1+2 3 1+2*3 Oops! 7 (1+2)*3 Oops! 9 1-2+3*4/6-5-3 Oops! Oops! -7
Boost 1.41.0 から、Spirit V2 として新しくなりました。 従来の構文解析機能をクリーンアップした Spirit.Qi と、 その逆向き、データ構造を文字列へとシリアライズし直す Spirit.Karma、 そして字句解析に特化した Spirit.Lex の3つの部分に分かれています。
上記のサンプルは Qi の使い方です。 lex+yacc や flex+bison のような、字句解析及び構文解析を行います。 ただし、構文定義には独自の処理系は必要とせず、全て C++ のソースとして記述することが出来ます。例えば上のサンプルで、 構文定義に該当する部分だけを抜き出すと、次の通り。
expr = term >> *( '+'>>term | '-'>>term );
term = fctr >> *( '*'>>fctr | '/'>>fctr );
fctr = int_ | '('>>expr>>')';
式(expr) は 項(term) の後ろに項を0個以上(*) '+' か '-' で続けたもの。 項(term) は 因子(fctr) の後ろに因子を0個以上(*) '*' か '/' で続けたもの。 因子(fctr) は 整数値(int_p) か、式を '(' と ')' でくくったもの。このように BNF記法に近い形で記述すると、あとはオーバーロードされた演算子が フル稼働して、構文解析クラスを作りあげてくれます。
また、構文定義の中には [ ... ] の形で、
その構文規則が使われたときに実行される関数オブジェクトを指定できます。
たとえば、上の例では [&Oops]
という形で、'*' fctr や '/' fctr
を読み込むたびに、Oops 関数を呼び出しています。
もっと役に立つこともできて、これまた上の例では、読み込んだ数式の値を計算しています。
rule
や grammar
を宣言するときに int()
と宣言することで、これらのルールは int の値を返すルールとして扱われます。
例えば [_val = _1]
と書くと、これは Boost.Lambda
とよく似た Boost.Phoenix
という無名関数記述ライブラリの式で、一つ引数 (_1
) を受け取って、
それを現在解釈中のルールの計算値 (_val
) に代入する、という関数になります。
このような計算をくり返していった結果の値は、parse関数の引数に渡した参照から読み取れるので、 上の例ではそれぞれ色々な型の結果を取得しています。 (see also: 組み込みの演算子がどんな型の結果値を返すかの 表 )