積んでる技術書が多すぎて床がだんだん見えなくなってきている。どうすんのよ、これ。
少しずつ片付けていくしかないよなぁ。一気にどかっとまとめられるスペースが確保できないものか。
積んでる技術書が多すぎて床がだんだん見えなくなってきている。どうすんのよ、これ。
少しずつ片付けていくしかないよなぁ。一気にどかっとまとめられるスペースが確保できないものか。
連日扇風機を体に当てて寝ているわけですが、どうも朝起きたときにのどが痛くなる。でも、今朝はいつにも増して具合が悪い。鼻水も少し出る。ああ、不快だ。不快だ。不快だ。
とりあえず扇風機の配置を変えよう。
困った。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人と自転車で出かけました。疲れた。だるい。
メロメロなメロディながれているよずっと
ウソみたい!ウソかな? あしたまでおぼえていれるかな?
Firefox3にしてからというもの、Firefox3の起動直後が死ぬほど重くて困っていたのだけど、ライブマークが多いともたつくという情報を見たのでSage Feeds以下のライブマークを通常のブックマークへ変換したら軽くなった。
何か変換する拡張でも無いかと思ったのだけど見つからなかったので、いったんブックマークのバックアップで.jsonファイルを出力し、Emacs上で変換し、再びブックマークの復元で戻す方法をとった。.jsonファイルのSage Feedsの子孫部分だけをNarrowingして、次のような置換を実行。
(defun firefox3-strip-livemark ()
(interactive)
(beginning-of-buffer)
(replace-regexp ""livemark":1," "")
(beginning-of-buffer)
(replace-regexp "{"name":"placesInternal/READ_ONLY"[^}]+}," "")
(beginning-of-buffer)
(replace-regexp "{"name":"livemark/feedURI",[^}]+"value":"\(http://[^"]+\)"},\([^]]+}],\)"type":"text/x-moz-place-container","children":\[\]" "\2"type":"text/x-moz-place","uri":"\1"")
(beginning-of-buffer)
(replace-regexp "{"name":"livemark/[^}]+},?" "")
)
一応手元のは全部置換できたみたい。タイトルに]とか"とか}とかが入っているとうまくいかないかも。
おおう、いつのまにW3CのWorking Draftが。久しぶりにtextlayoutライブラリに手を入れようと思って調べていたら気がついた。JIS X 4051は英語版を作るべき、というような指摘は見たことがあるが、こういう形とは。