[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
早速いってみましょうっ♪
四則演算+変数の使用+変数の中身チェックな対話モードインタプリタを作ってみました。
コードはこちら。
#pragma warning(disable:4100) //引数は関数の本体部で 1 度も参照されません。
#pragma warning(disable:4512) //代入演算子を生成できません。#include <iostream>
#include <map>
#include <string>
#include <stack>#include <boost/spirit/home/qi.hpp>
#include <boost/spirit/home/phoenix.hpp>class VariableManager{
public:
void Set(std::string name, int value){
container[name] = value;
}int Get(std::string name){
return container[name];
}private:
typedef std::map<std::string, int> Container;
Container container;
};template<typename String>
struct Calc : boost::spirit::qi::grammar<typename String::const_iterator, boost::spirit::ascii::space_type>{
typedef typename String::const_iterator Iterator;Calc() : Calc::base_type(line){
using namespace boost::spirit;
using namespace boost::spirit::arg_names;// 構文定義
//数値演算
group = '(' >> expression[_val = _1] >> ')';
factor = lexeme[
int_[_val = _1] //数字
]
| group[_val = _1] //部分式の演算結果
| refVariable[_val = _1]; //変数
term = factor[_val = _1] >> *(('*' >> factor[_val *= _1])
|('/' >> factor[_val /= _1]));
expression = term[_val = _1] >> *(('+' >> term[_val += _1])
| ('-' >> term[_val -= _1]));//変数
variable = lexeme[
ascii::alpha[_val += _1] >> +ascii::alnum[_val += _1]
];//refVariable = variable[_val = vm.Get(_1)];
//refVariable = variable[_val = boost::bind(&Calc::GET_VAL, this, _1)];
refVariable = variable[_val = boost::phoenix::bind(&Calc::GET_VAL, this, _1)];//inVariable = (variable >> expression)[vm.Set(_1, _2)];
//inVariable = (variable >> "=" >> expression)[boost::bind(&Calc::SET_VAL, this, _1, _2)];
inVariable = (variable >> "=" >> expression)[boost::phoenix::bind(&Calc::SET_VAL, this, _1, _2)];//行
//line = expression[boost::phoenix::bind(&Calc::PUSH, this, _1)];
// | inVariable
line = inVariable
| expression[boost::phoenix::bind(&Calc::PUSH, this, _1)];
};boost::spirit::qi::rule<Iterator, int(), boost::spirit::ascii::space_type> expression;
boost::spirit::qi::rule<Iterator, int(), boost::spirit::ascii::space_type> term;
boost::spirit::qi::rule<Iterator, int(), boost::spirit::ascii::space_type> factor;
boost::spirit::qi::rule<Iterator, int(), boost::spirit::ascii::space_type> group;boost::spirit::qi::rule<Iterator, String(), boost::spirit::ascii::space_type> variable;
boost::spirit::qi::rule<Iterator, int(), boost::spirit::ascii::space_type> refVariable;
boost::spirit::qi::rule<Iterator, boost::spirit::ascii::space_type> inVariable;boost::spirit::qi::rule<Iterator, boost::spirit::ascii::space_type> line;
std::stack<int> stack;
VariableManager vm;
bool isResult(){
return !stack.empty();
}int GetResult(){
int i = stack.top();
Reset();
return i;
}void Reset(){
std::stack<int> empty;
std::swap(empty, stack);
}void PUSH(int i){
stack.push(i);
}void SET_VAL(String str, int i){
vm.Set(str, i);
}int GET_VAL(String str){
return vm.Get(str);
}};
int main(void)
{
Calc<std::string> calc;std::string str;
while (getline(std::cin, str))
{
if (str.empty() || str[0] == 'q' || str[0] == 'Q')
break;//boost::algorithm::trim(str);
std::string::const_iterator it = str.begin();
std::string::const_iterator end = str.end();bool result = phrase_parse(it, end, calc, boost::spirit::ascii::space);
if (result && it == end)
{
std::cout << "-------------------------\n";
std::cout << "Parsing succeeded\n";
std::cout << str << " Parses OK: " << std::endl;
if(calc.isResult()) std::cout << calc.GetResult() << std::endl;
}
else if(result)
{
std::cout << "-------------------------\n";
std::cout << "Parsing succeeded\n";
std::cout << std::string(str.begin(), it) << " Parses OK: " << std::endl;
std::cout << std::string(it, end) << " amari" << std::endl;
if(calc.isResult()) std::cout << calc.GetResult() << std::endl;
}
else
{
std::cout << "-------------------------\n";
std::cout << "Parsing failed\n";
std::cout << "-------------------------\n";
calc.Reset();
}
}return 0;
}
ポイントはここです。
#include <boost/spirit/home/phoenix.hpp>
……えーっと、phoenixで何が必要になるかわからないのでもう全部インクルードしちゃえっ!って発想です。
たぶんこれで全部インクルード……のおかげでビルド時間ものびのび。
必要なヘッダだけ後で探した方がよさげなのですorz
で、一番重要なのはここ。
line = inVariable
| expression[boost::phoenix::bind(&Calc::PUSH, this, _1)];
なんと、phoenixがすでにbindを持っていました。
しかもphoenixの_1を普通に使えるので、今までの苦労は何だったのか……って感じに。
使い方は普通のbindと変わらないみたいです。
boost::bindをインクルードしなくなったので、大手を振ってphoenixの_1をそのままかけるようになってコードもすっきり。
あともう一つ。
refVariable = variable[_val = boost::phoenix::bind(&Calc::GET_VAL, this, _1)];
変数マネージャへの変数の参照をphoenixなbindで記述して、そのままphoenixなlambdaで_valに代入なんて無茶が出来ちゃいます。
これ、これを探していたんですっ。感涙物なのです……
ちなみに、前後でコメントアウトしているのは試行錯誤の軌跡です。笑ってやってください。
なにげに重要そうなのがここ。
//line = expression[boost::phoenix::bind(&Calc::PUSH, this, _1)];
// | inVariable
line = inVariable
| expression[boost::phoenix::bind(&Calc::PUSH, this, _1)];
inVariable(変数への代入)の方を先にしないと、たとえば
hoge=10
というスクリプト文がそのままinVariableではなく、hogeの部分だけでexpression→term→factor→refVariable……と解釈されてhogeの中身参照(代入できないので常に0)、=10が余分……と構文解析されます。
最後にここ。
inVariable = (variable >> "=" >> expression)[boost::phoenix::bind(&Calc::SET_VAL, this, _1, _2)];
4の時に見つけた複数の引数をとる方法がそのまま使えるのです。
これで何とか色々出来そうな気分になってきました。
//2009-10-25追記
コードの修正(mutableとconstの勘違い)