// 簡単な計算機プログラム
#include <iostream>
#include <string>
#include <stack>
#include <boost/spirit.hpp>
using namespace std;
using namespace boost::spirit;
// アクションの定義
// (簡単のため、グローバル関数/変数で。。。)
namespace calc_action
{
stack<int> stk;
void clear() { stk = stack<int>(); }
int answer() { return stk.top(); }
int toppop() { int a = stk.top(); stk.pop(); return a; }
void PUSH(int n)
{
stk.push( n );
}
void ADD(const char*, const char*)
{
int b = toppop(), a = toppop();
stk.push(a+b);
}
void SUB(const char*, const char*)
{
int b = toppop(), a = toppop();
stk.push(a-b);
}
void MUL(const char*, const char*)
{
int b = toppop(), a = toppop();
stk.push(a*b);
}
void DIV(const char*, const char*)
{
int b = toppop(), a = toppop();
stk.push(a/b);
}
}
// 文法を定義した definition テンプレートをメンバに持つ
struct calc : public grammar<calc>
{
template<typename S> struct definition
{
// 文法定義
// expr ::= term ('+' term | '-' term)*
// term ::= fctr ('*' fctr | '/' fctr)*
// fctr ::= int | '(' expr ')'
//
// 開始記号
// expr
rule<S> expr, term, fctr;
definition(const calc& self)
{
using namespace calc_action;
expr = term >> *( ('+' >> term)[&ADD]
| ('-' >> term)[&SUB] );
term = fctr >> *( ('*' >> fctr)[&MUL]
| ('/' >> fctr)[&DIV] );
fctr = int_p[&PUSH]
| '(' >> expr >> ')';
}
const rule<S>& start() const { return expr; }
};
};
// 使用例
int main()
{
for(string str; getline(cin,str) && str.size()>0; )
{
calc calc;
// 入力一行を上の文法に従い、空白は無視しつつ解析
parse_info<> r = parse(str.c_str(), calc, space_p);
if( r.full )
cout << calc_action::answer() << endl;
else
cout << "error" << endl;
calc_action::clear();
}
return 0;
}
1+2 3 1+2*3 7 (1+2)*3 9 1-2+3*4/6-5-3 -7 hello error
lex+yacc や flex+bison のような、字句解析及び構文解析を行います。 ただし、構文定義には独自の処理系は必要とせず、全て C++ のソースとして記述することが出来ます。例えば上のサンプルで、 構文定義に該当する部分だけを抜き出すと、次の通り。
expr = term >> *( '+'>>term | '-'>>term );
term = fctr >> *( '*'>>fctr | '/'>>fctr );
fctr = int_p | '('>>expr>>')';
式(expr) は 項(term) の後ろに項を0個以上(*) '+' か '-' で続けたもの。 項(term) は 因子(fctr) の後ろに因子を0個以上(*) '*' か '/' で続けたもの。 因子(fctr) は 整数値(int_p) か、式を '(' と ')' でくくったもの。このように BNF記法に近い形で記述すると、あとはオーバーロードされた演算子が フル稼働して、構文解析クラスを作りあげてくれます。
また、構文定義の中には [ ... ] の形で、 その構文規則が使われたときに実行される関数オブジェクトを指定出来ます。 上の例ではこの呼び出し機能を用いて、スタック計算機を実装しています。勿論、 この呼び出し機構(Semantic Action)を使わずに一旦構文木を作ったりするのも可。
spiritのバックエンドとして、phoenix というライブラリが利用されています。要するに lambda と似たような事をするライブラリで現在lambdaと統合作業中らしいですが、 二階の多相型高階関数や adaptable closure などlambdaに無い機能も備えていて、なかなか面白いヤツです。
<boost/spirit/phoenix.hpp> から触れるので、
いじってみると楽しいかもしれません。