[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
ノベルゲームエンジンの構文解析にいいライブラリがないかなー……と探して……ごめんなさい、探してないです。
で、boost::spiritを試してみました。
……私には使いこなせそうにない代物でした……
せっかくなので調べた物のメモを。
個人的なメモなので内容にはムラがあります。別サイトなども同時に参照してください。
よければ「こうすればいいよ!」ってアドバイスをください。
またはこれを参考に使いこなしてください。
spiritのバージョンは2.0、現在の最新版です。
動作確認はVC2005+boost1.39.0で行っています。
……spiritは次期boost1.41.0でバージョン2.1になるとかならないとか……
基本的な使い方が変わらないことを祈ります……
まずはダブルクォーテーションで囲まれた文字の切り出しをしてみました。
#pragma warning(disable:4512) //代入演算子を生成できません。
#pragma warning(disable:4100) //引数は関数の本体部で 1 度も参照されません。#include <iostream>
#include <string>
#include <stack>#include <boost/bind.hpp>
#include <boost/spirit/home/qi.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
えっと、まず警告を抑止していますが、これはVCのpragmaです。
警告が大量に出るので、あまりよくないとは思いつつとりあえず抑止しています。
#includeはとりあえず使う物だけ……のはず。
特に重要なのは下の三つです。
template<typename String>
struct StringParser : boost::spirit::qi::grammar<typename String::const_iterator, boost::spirit::ascii::space_type>{
名前空間を完全修飾しているので長いのですが、string型(std::stringとかを期待)をテンプレート引数にとった構造体を作ります。
spiritのgrammarを継承します。
grammarにはいくつかテンプレート引数をとれるのですが、とりあえずString::const_iteratorとspirit内の型を渡しておきます。
このあたりは複雑なことをしなければテンプレ……?
typedef typename String::const_iterator Iterator;
とりあえず便利なのでtypedef。
StringParser() : StringParser::base_type(expression){
using namespace boost::spirit;
namespace sp_arg = boost::spirit::arg_names;
expression = rString[boost::bind(&StringParser::PUSH, this, _1)];
rString = lexeme['\"' >> +(char_ - '\"')[sp_arg::_val += sp_arg::_1] >> '\"'];
}
boost::spirit::qi::rule<Iterator, boost::spirit::ascii::space_type> expression;
boost::spirit::qi::rule<Iterator, String(), boost::spirit::ascii::space_type> rString;
ここが本命です。
自作構造体のコンストラクタで、基底クラスのコンストラクタを呼び出します。
基底クラスのコンストラクタの引数にはspiritのruleのインスタンスを渡します。
構文解析をするときにはここから解析してくれるわけです。
で、後はコンストラクタで構文を指定します。
重要なところから順番に説明していきます。
まずはここ。
boost::spirit::qi::rule<Iterator, String(), boost::spirit::ascii::space_type> rString;
ruleのテンプレート引数の二つ目、String()を渡します。前後はテンプレ……?
これで、rStringが解析した結果がString型で返ってくるようになります。
そして次。
rString = lexeme['\"' >> +(char_ - '\"')[sp_arg::_val += sp_arg::_1] >> '\"'];
lexemeはここからは空白を無視します……という意味の物です。(空白については後述)
で、"、"以外の文字、"の順に文字がありますよ……という記述です。
さらに重要なのはこの行のこの部分。
+(char_ - '\"')[sp_arg::_val += sp_arg::_1]
[]内で囲まれた部分はセマンティックアクションという物です。
基本的には前述のパーサの型にあった関数ポインタか関数オブジェクトを入れるのですが、今回はphoenixというspirit専用?のlambdaを使用しています。
char_はchar型を返してくれるパーサなので、_val(String型)に_1(char型)を+=していく……つまり、文字列を作っているのです。
String型はrStringのruleの第二引数から来ています。
ちなみに、何でわざわざsp_argなんてもので修飾しているのかというと、boost::bindと併用しているからです。
下手にusing namespaceしちゃうと、_1があいまいとか言われちゃいます。
次です。
boost::spirit::qi::rule<Iterator, boost::spirit::ascii::space_type> expression;
rStringの第二引数に相当する物ががありません。
パーサから何か返さない場合はこんな事も出来るみたいです。
というより、第一引数以外はオプション(3つまで)みたいで、順番も関係なさそう……中身はどうなっているのでしょう……
expression = rString[boost::bind(&StringParser::PUSH, this, _1)];
rStringに合致する部分があったら、セマンティックアクションを実行します。
セマンティックアクションには、前述の通り関数ポインタ、関数オブジェクトのほかに、boost::bindでバインドしたメンバ関数とかlambdaなんかも使えます。
この場合、bindで自分のPUSH関数に引数1つ(rStringが返すString型)を与えて呼び出すようにしています。
ここ、bindを使わずに出来そうな気もするのですが……後、thisを使う方針もまずい気がします。
正しい使い方が一番わからない部分……
とりあえずこれで構文解析が出来るようになったので、後はおまけ部分。
mutable std::stack<String> stack;void PUSH(String str) const{
stack.push(str);
}String GetResult(){
std::string s = stack.top();
Reset();
return s;
}void Reset(){
std::stack<String> empty;
std::swap(empty, stack);
}
};
適当なので細かいところは突っ込まないでください。
とりあえず重要?なのはstackがmutableな所と、PUSH関数がconstな所。//2009-10-25追記
どう考えてもおかしいのですが、サンプルと言うことで……
同じ構造体でいろいろ処理するのは想定されていないのでしょう……
えーっと、いりませんでした。
どこかで必要って書いてあったような気がしたのですけど……mutableとconstを試しに外してみたら普通に動きました。
この記事では自戒のためそのまま残しておきます……
後は構文解析を実際に行うコードを。
int main( void)
{
StringParser<std::string> p;
とりあえず実体化。
std::string str;
while (getline(std::cin, str))
{
if (str.empty() || str[0] == 'q' || str[0] == 'Q')
break;
ループ&終了条件。
std::string::const_iterator it = str.begin();
std::string::const_iterator end = str.end();bool result = phrase_parse(it, end, p, boost::spirit::ascii::space);
パースはこうします。phrase_parseはspirit付属の関数。
実体化時の引数のイテレータ2つ、パーサ、そして読み飛ばし文字パーサを渡します。
読み飛ばし文字パーサはspirit組み込みのパーサをそのまま渡しています。
このあたりも詳しくわかっていないのですけど……自作パーサがうまく渡せなかったような。
if (result && it == end)
{
std::cout << "-------------------------\n";
std::cout << "Parsing succeeded\n";
std::cout << str << " Parses OK: " << std::endl;
std::cout << p.GetResult() << std::endl;
p.Reset();
}
else if(result)
{
std::cout << "-------------------------\n";
std::cout << "Parsing succeeded\n";
std::cout << std::string(str.begin(), it) << " Parses OK: " << std::endl;
std::cout << p.GetResult() << std::endl;
std::cout << std::string(it, end) << " amari" << std::endl;
p.Reset();
}
else
{
std::cout << "-------------------------\n";
std::cout << "Parsing failed\n";
std::cout << "-------------------------\n";
p.Reset();
}
}
return 0;
}
後は判定と終了。
判定部分は詳しく書かなくてもわかりますよね……
というわけで大ざっぱですがメモその一でした。