aoinh;gw;oihasgr;oihnsardgrlojknh;
aoinh;gw;oihasgr;oihnsardgrlojknh;
ほんの少し吉野家に行っていなかったら、生姜焼定食が変わっているじゃないですか。メニュー写真の見た目では、無造作に炒めた肉とタマネギが乗っていて今ひとつ不味そうに見えたのですが、いざ食べてみると……とんでもない改悪でした。
何これ、豚丼の具材を生姜ダレで焼いただけじゃん。肉薄いし少ないし(タマネギで多く見せかけているだけ)。このご時世、値上げは仕方ないと思うよ。でもさ、これはちょっと無いんじゃないの? 俺の豚生姜焼定食を返せ。いや、その前に俺の牛鉄鍋膳(当然とじ卵)を返せ。
所詮吉野家は牛丼の味しか維持する気が無いんだろうな。
これで吉野家で定食を食べるという選択肢は消えた。
そろそろ鬱陶しくなってきたなぁ。最後に最後に切ったのいつだっけ。
積んでる技術書が多すぎて床がだんだん見えなくなってきている。どうすんのよ、これ。
少しずつ片付けていくしかないよなぁ。一気にどかっとまとめられるスペースが確保できないものか。
連日扇風機を体に当てて寝ているわけですが、どうも朝起きたときにのどが痛くなる。でも、今朝はいつにも増して具合が悪い。鼻水も少し出る。ああ、不快だ。不快だ。不快だ。
とりあえず扇風機の配置を変えよう。
困った。pt_parseを使おうが、ast_parseを使おうが、マッチ文字長が0のノードが消えてしまう。
文法: nums = *int_p alps = *alpha_p nums_alps = nums >> alps 入力: "ABC" 出力(pt_parseの戻り値のtrees。idはルール名に置き換え): nums_alps alps alps A alps B alps C 入力: "0ABC" 出力: nums_alps nums nums 0 alps alps A alps B alps C 入力: "" 出力: (ID=0な空のノード一つ)
構文木のnumsに対応する部分を処理するとき、numsが消えているかどうか、もっと言えばnums_alps自体が消えているかどうかをいちいちチェックしないといけないわけですか? 構文解析後なのに? 意味が分かりません。
トレースしてみたら、boost_1_35_0/boost/spirit/tree/common.hppのconcat_match()内でlength()==0故にマッチ情報が捨てられてしまうみたい。えー、ちゃんと連結してくれよー。何か意図があるのかな。
独自のポリシークラスで処理すれば何とかなるんだろうけど、メンテナンスのことを考えるとそこまではしたくないので、この方向はあきらめることにする。
なので、次はクロージャでなんとかする方向で考えてみる。構文定義部分が汚くなっちゃうけどしかたない。
たまにサガミへ行くのですが、そば湯が出てきたり出てこなかったりします。催促すれば良いんでしょうが、気が引けてしまう私は小心者なのでしょうか。
Boost.Spiritでオプションファイル解析。
アプリケーションに渡すちょっとしたオプション指定ファイルを作ろうと思ったので、boost::spiritで構文解析器を書いてみることにしました。
まずは文法を決めなければなりません。
OptionA 123 OptionB "ABCDE"
みたいな単純なkeyとvalueの組み合わせでも良いんですが、もう少し複雑な構造を表現しようとすると、少し面倒なことになってしまいます。
OptionC_Item0 "AAAA" OptionC_Item1 "BBBB" OptionC_Item2 "CCCC" OptionD_Item0_Name "AAAA" OptionD_Item0_Type 0 OptionD_Item1_Name "BBBB" OptionD_Item1_Type 0 OptionD_Item2_Name "CCCC" OptionD_Item2_Type 1 OptionD_Item2_Value 123
なので、値の部分にタプルとマップを指定できるようにしようと思います。
タプルは値の有限の列、マップは識別子から値への対応関係とします。
タプルとマップでOptionCとOptionDを書き直すと次のようになります。
OptionC_Items ["AAAA" "BBBB" "CCCC"] OptionD_Items [ {Name "AAAA" Type 0} {Name "BBBB" Type 0} {Name "CCCC" Type 1 Value 123} ]
よく見ると、このファイル全体も、{ }で囲まれては居ませんが一種のマップのようなものです。このあたりはまとめられそうです。
文法をまとめます。
option-file ::= map-body | オプションファイルはマップの{ }内の中身と同じです。 |
map ::= '{' map-body '}' | マップは{ }とその中身です。 |
map-body ::= map-element* | マップの中身は0個以上のマップ要素です。 |
map-element ::= map-key map-value | マップ要素はマップキーとマップ値の対です。 |
map-key ::= ident | マップキーは識別子(Identifier)です。 |
map-value ::= value | マップ値は任意の値です。 |
tuple ::= '[' tuple-body ']' | タプルは[ ]とその中身です。 |
tuple-body ::= tuple-element* | タプルの中身は0個以上のタプル要素です。 |
tuple-element ::= value | タプル要素は任意の値です。 |
value ::= integer | string | map | tuple | 値は整数、文字列、マップ、タプルのいずれかです。 |
integer ::= [1-9][0-9]* | 整数は1から9で始まる数字です。 |
string ::= '"' [^"n]* '"' | 文字列は"で囲まれた文字の列です。 |
ident ::= [a-zA-Z_][a-zA-Z0-9_]* | 識別子はアルファベットやアンダーバーで始まる文字列です。 |
以上で文法がきまりましたので、これをboost::spiritで解析しようと思います。
struct OptionFileGrammer : public boost::spirit::grammar<OptionFileGrammer> { template<typename ScannerT> struct definition { typedef boost::spirit::rule<ScannerT> rule_t; rule_t opt_file; rule_t map, map_body, map_element, map_value, map_key; rule_t tuple, tuple_body, tuple_element; rule_t value; rule_t integer_l; rule_t string_l; rule_t ident; definition(const OptionFileGrammer &self) { using namespace boost::spirit; opt_file = map_body; map = ch_p('{') >> map_body >> ch_p('}'); map_body = *map_element; map_element = map_key >> map_value; map_value = value; map_key = ident; tuple = ch_p('[') >> tuple_body >> ch_p(']'); tuple_body = *tuple_element; tuple_element = value; value = integer_l | string_l | map | tuple; integer_l = int_p; string_l = lexeme_d[ confix_p('"', *c_escape_ch_p, '"') ]; ident = lexeme_d[ (alpha_p | ch_p('_')) >> *(alnum_p | ch_p('_')); } const rule_t &start() const { return opt_file;} }; }; int main(int argc, char **argv) { std::string s; // sへ読み込み。 OptionFileGrammer optGr; parse_info<std::string::const_iterator> info = parse(s.begin(), s.end(), optGr >> end_p, space_p | comment_p(';')); if(!info.full){ std::cout << "Parse Error." << std::endl; return -1; } }
definitionの中はわりと文法定義そのものです。
整数値はint_pそのままを、文字列はconfix_pとc_escape_ch_pというおあつらえ向きなものがありました。元の文法では抜けていましたが、符号やエスケープシーケンスにも対応しましょう。
スキップパーサーのおかげで、空白スキップ、コメントスキップも簡潔に書けます。
ここではparseの引数にspace_p | comment_p(';')を指定することで実現しています。
しかし、スキップパーサーのおかげで、string_lやidentの定義でハマりました。例えばA128は有効な識別子ですが、スキップパーサーのおかげでA12(空白)8もA128と同等に扱われてしまいます。これを回避するには、lexeme_d[ ]で囲って、スキップパーサーを一時的に無効にすればいいようです。
終端前の空白文字が残ってしまいフルマッチしない現象にも遭遇しました。これはここのNovember 28, 2007に書いてありますが、Boost1.34以降で変わった挙動らしく、end_pを最後に置けばいいようです。
以上でとりあえず構文解析ができるようになりました。
ただ、これだけでは入力テキストが文法にマッチするか否かが分かるだけなので、本来の目的は達成できていません。
このオプションの指定値は何?といったアプリケーション側からの問い合わせに答えられるよう、何らかのデータ構造を構築する必要があります。
そのあたりはまだよく調べていないので次の機会に。セマンティックアクションでゴリゴリやればいいのか、それとも直接的に木を構築する方法が用意されているのか。
職場の人3人と自転車で出かけました。疲れた。だるい。