パスワードを忘れた? アカウント作成
295572 journal

Yak!の日記: Boost Spirit Qi の rule と Skipper の有無について

日記 by Yak!

Boost Spirit Qi の rule と Skipper についてちょっと戦った結果のメモ。

Boost Spirit Qi の Parser コンセプトに従う p は

p.parse(f, l, context, skip, attr)

の形の式が有効でなければならない。基本的に parse は以下のように定義されているので任意の型の Skipper を受け取れるはずである。

template <typename Context, typename Skipper, typename Attribute>
bool parse(Iterator& first, Iterator const& last
  , Context& context, Skipper const& skipper
  , Attribute& attr) const;

が、rule (grammer も)は宣言時に Skipper の型を指定しなければならない。これは右辺の式を boost::function で保存しているからだ。

template <//snip...
>
struct rule
      : // snip...
{
// snip...
    typedef function<
        bool(Iterator& first, Iterator const& last
          , context_type& context
          , skipper_type const& skipper
        )>
    function_type;
// snip...
    template <typename Context, typename Skipper
      , typename Attribute, typename Params>
    bool parse(Iterator& first, Iterator const& last
      , Context& caller_context, Skipper const& skipper
      , Attribute& attr, Params const& params) const
    {
        if (f)
        {
// snip...
            if (f(first, last, context, skipper))
            {
// snip...
    }
    function_type f;
// snip...

正確には parse() の呼び出し自体ではなくその内部の f の呼び出しの時点で型に関するエラー(変換不能だったりマッチする関数がない)が発生する。この辺りの挙動について書かれたのが Boost Spirit の Tutorial にある以下の記述のはずなのだが、

rule<Iterator> r;

(snip) This rule cannot be used with phrase_parse. It can only be used with the parse function

rule<std::string::iterator, space_type> r;

This type of rule can be used for both phrase_parse and parse.

実のところこれは誤りである(むしろ逆だ)。どちらかというと rule は指定された Skipper しか受け取れないと考えた方が良い。実際には次のような挙動になる(以下、OK はコンパイル可能で BAD はコンパイル不可能とする)。

#include <boost/spirit/include/qi.hpp>
#include <string>

int main()
{
    namespace qi = boost::spirit::qi;

    std::string str;
    typedef std::string::iterator Iterator;
    Iterator first = str.begin(), last = str.end();

    qi::rule<Iterator, qi::space_type> rule_with_skip, rule_with_skip2;
    qi::rule<Iterator> rule_without_skip, rule_without_skip2;

    rule_with_skip    = qi::int_;
    rule_without_skip = qi::int_;

    phrase_parse(first, last, rule_with_skip,    qi::space);
    phrase_parse(first, last, rule_without_skip, qi::space); // OK
//  phrase_parse(first, last, rule_with_skip,    qi::cntrl); // BAD
    phrase_parse(first, last, rule_without_skip, qi::cntrl); // OK
//  parse       (first, last, rule_with_skip);               // BAD
    parse       (first, last, rule_without_skip);

//  snip..

    return 0;
}

あれ? Skipper なしの rule に対して適当な Skipper を指定した phrase_parse() が呼べてるじゃんと思うかもしれないがこちらの方が例外だと考えた方が良いだろう。Skipper なしは実際には boost::spirit::unused_type を Skipper に指定したことと同じになる。そして boost::spirit::unused_type は boost::fusion::unused_type と同じであり、以下の変換コンストラクタが定義されているため任意の型から型変換可能である。

namespace boost { namespace fusion
{
    struct unused_type
    { // snip..

        template <typename T>
        unused_type(T const&)
        {
        }
// snip..
}}

ということで適当な Skipper を渡しても勝手に boost::spirit::unused_type に変換されて受け取れてしまうのだ。ただし pre-skip が行われるだけで rule の右辺には Skipper は引き継がれない。なお Reference 側にはこの挙動自体は書かれている。

さて、長かったがまだもう少し気をつける部分がある。Boost Spirit では skip, no_skip, lexme ディレクティブにより Skipper の有無を途中で切り替えることができる(今回の話上は lexme は no_skip と同じと思えばよい)。基本的な考え方は rule の左辺の Skipper 指定が右辺に引き渡されることに注意して「rule は指定された Skipper しか受けとれない」に従えばいいので次のような結果になる。

    rule_with_skip2    = rule_with_skip >> rule_without_skip; // OK
//  rule_without_skip2 = rule_with_skip >> rule_without_skip; // BAD
    rule_with_skip2    = rule_with_skip >> qi::no_skip[rule_without_skip]; // OK
    rule_without_skip2 = qi::skip(qi::space)[rule_with_skip] >> rule_without_skip; // OK

つまり必要な時には skip で Skipper を指定するよう調節してやらなければならないということだ。no_skip は無くても通る。

最後に引数無しの skip についても少しだけ注意があるので触れておこう。

    rule_with_skip2    = qi::no_skip[qi::skip[rule_with_skip]]; // OK
    rule_with_skip2    = qi::no_skip[rule_without_skip2];
//  rule_without_skip2 = qi::skip[rule_with_skip];              // BAD
    rule_without_skip2 = qi::skip(qi::space)[rule_with_skip];   // OK

引数無しの skip は no_skip で一度無指定になった Skipper を復元する。従って一番上の例では qi::space が復元されるのでコンパイルできる。しかしこの効果は Skipper 無指定の rule を経由すると消えてしまい skip を付けても Skipper 無指定と同じ状態になってしまう。これは戻すべき Skipper の型を静的に判断しているためだ。一度 unused_type に変換(この場合は変換コンストラクタによるものではなく upcast)されてしまうと元に戻せなくなり unused_type つまり無指定と同じになってしまう。この場合には明示的に Skipper を指定してやる必要がある。

以上を把握すれば Skipper の有無によるエラーは回避できるだろう。実に誰得な記事であったと言えるが、これも PDF の仕様が複雑なのがいけないのだ。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

あと、僕は馬鹿なことをするのは嫌いですよ (わざとやるとき以外は)。-- Larry Wall

読み込み中...