IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

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<> 便利だなあと
(続きは夜)