boost::spirit を使ってみる
はじめに
boost::spirit の使い方の勉強。ちょっとずつ追記していく形式で書く
基本的なこと
hpp ファイルと ipp ファイルの中で実装されているので、何もリンクしなくていい。
とりあえず boost/spirit.hpp を読み込めば使えるみたい。
まず、一番シンプルな空文字列のパース
#include <iostream> #include <boost/spirit.hpp> int main(int argc, char** argv) { using namespace boost::spirit; // 空文字列 std::cout << parse("", end_p).full << std::endl; // 1 (成功という意味)と表示 // "a" std::cout << parse("a", end_p).full << std::endl; // 1 (失敗という意味)と表示 return 0; }
コンパイルして実行
./boost/include の部分は boost のヘッダファイルをインストールしたディレクトリ
$ g++ test.cpp -I./boost/include $ ./a.out 1 0
おおお。うまくいきました
整理
gdb で parse 関数周りを見てみる
$ g++ -g -O0 test.cpp -I./boost/include $ gdb a.out
まず rb でブレークポイントを貼る
(gdb) rb boost::spirit::parse\b Breakpoint 1 at 0x402a36: file /home/amachang/boost/include/boost/spirit/home/classic/core/impl/parser.ipp, line 28. boost::spirit::parse_info<const char*> boost::spirit::parse_info<char const*> boost::spirit::parse<char const*, boost::spirit::end_parser>(char const* const&, char const* const&, boost::spirit::parser<boost::spirit::end_parser> const&); Breakpoint 2 at 0x402b06: file /home/amachang/boost/include/boost/spirit/home/classic/core/impl/parser.ipp, line 44. boost::spirit::parse_info<const char*> boost::spirit::parse_info<char const*> boost::spirit::parse<char, boost::spirit::end_parser>(char const*, boost::spirit::parser<boost::spirit::end_parser> const&);
2つに貼られた。 実行する前にこの辺のソースコードをちょっとだけ呼んでおくか。
parser.ipp 44 行目
template <typename CharT, typename DerivedT> inline parse_info<CharT const*> parse(CharT const* str, parser<DerivedT> const& p) { CharT const* last = str; while (*last) last++; return parse(str, last, p); // 22 行目へ! }
なるほど、ほとんど何もやってない関数か。
あと、end_p は parser なのか。試しに
(gdb) p end_p $2 = {<boost::spirit::parser<boost::spirit::end_parser>> = {<No data fields>}, <No data fields>}
なるほどー。 CRTP ってやつですね!
↑値がった、
こんどは、 22 行目あたり
template <typename IteratorT, typename DerivedT> inline parse_info<IteratorT> parse( IteratorT const& first_ , IteratorT const& last , parser<DerivedT> const& p) { IteratorT first = first_; scanner<IteratorT, scanner_policies<> > scan(first, last); match<nil_t> hit = p.derived().parse(scan); return parse_info<IteratorT>( first, hit, hit && (first == last), hit.length()); }
CharT* が IteratorT に暗黙型変換された scanner はここで作られるのね。
↑違ったよくみたら、 IteratorT ってテンプレートパラメータだった。
p.derived().parse(scan) でパースするのか。ここらへんは今はあまり深追いするのは辞める。
parse_info はどんなメンバを持ってるんだろう。
以下のような行を追加して gdb で値を見る
parse_info<const char*> i0 = parse("", end_p); parse_info<const char*> i1 = parse("a", end_p);
こんな感じ
(gdb) p i0 $1 = {stop = 0x461a64 "", hit = true, full = true, length = 0} (gdb) p i1 $2 = {stop = 0x461a65 "a", hit = false, full = false, length = 18446744073709551615}
stop はどこまで、パースしたかを持っている。 hit はパースが成功した。 full はパースが成功して最後までパースした。 length はパースした文字数。 hit が true の場合に有効。って感じらしい。
次に、 ISBN パーサとか
こんな感じ
#include <iostream> #include <boost/spirit.hpp> int main(int argc, char** argv) { if (argc < 2) return 0; using namespace boost::spirit; std::cout << parse(argv[1], str_p("ISBN") >> (str_p("978") | str_p("979")) >> ch_p('-') >> uint_p >> ch_p('-') >> uint_p >> ch_p('-') >> uint_p >> ch_p('-') >> (digit_p | ch_p('X')) >> end_p ).full << std::endl; return 0; }
このように、 >> とか | とか演算子で parser を繋ぐことで様々な意味を持った parser を作りだすことが出来るらしい。
実行
$ ./a.out "ISBN979-12-24234-34234-X" 1 $ ./a.out "ISBN979-12-24234-34234-A" 0
発展
rule っていうのを使うと、組み合わせたパーサーを持てます。
#include <iostream> #include <boost/spirit.hpp> int main(int argc, char** argv) { if (argc < 2) return 0; using namespace boost::spirit; rule<> r = str_p("ISBN") >> (str_p("978") | str_p("979")) >> ch_p('-') >> uint_p >> ch_p('-') >> uint_p >> ch_p('-') >> uint_p >> ch_p('-') >> (digit_p | ch_p('X')) >> end_p; std::cout << parse(argv[1], r).full << std::endl; return 0; }
これはどうなっているんだろう。
rule<> は、 parser を取るコピーコンストラクタ(このコピーコンストラクタは、テンプレートになっていて、内部に型とインスタンスを両方保存する)を持っていて、さらに CRTP で parser を継承している。
なので、このようにできる。
rule<> は様々なテンプレート引数でインスタンス化された parser を持つ役割。
もし、 rule<> を使わなかったら以下のようになる。
#include <iostream> #include <boost/spirit.hpp> int main(int argc, char** argv) { if (argc < 2) return 0; using namespace boost::spirit; boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::sequence< boost::spirit::strlit<const char*>, boost::spirit::alternative< boost::spirit::strlit<const char*>, boost::spirit::strlit<const char*> > >, boost::spirit::chlit<char> >, boost::spirit::uint_parser<unsigned int, 10, 1u, -0x00000000000000001> >, boost::spirit::chlit<char> >, boost::spirit::uint_parser<unsigned int, 10, 1u, -0x00000000000000001> >, boost::spirit::chlit<char> >, boost::spirit::uint_parser<unsigned int, 10, 1u, -0x00000000000000001> >, boost::spirit::chlit<char> >, boost::spirit::alternative< boost::spirit::digit_parser, boost::spirit::chlit<char> > >, boost::spirit::end_parser > r = str_p("ISBN") >> (str_p("978") | str_p("979")) >> ch_p('-') >> uint_p >> ch_p('-') >> uint_p >> ch_p('-') >> uint_p >> ch_p('-') >> (digit_p | ch_p('X')) >> end_p; std::cout << parse(argv[1], r).full << std::endl; return 0; }
rule<> 便利だなあと
(続きは夜)