スキャナと構文解析

スキャナの仕事はシーケンシャルな入力データストリームをパーサに供給する事である。 スキャナは、入力が尽きるまで、入力からデータを抽出し、小分けにし、時には修正やフィルタを掛けて、 最終的にはその結果を要求に応じて個々のパーサ要素に委託する。 スキャナは、 STL に適合した二つの前方向イテレータ first と last から合成される。 first は参照で保持され、 last は値で保持される。 first イテレータが参照なのは、再配置できるようにするためである。 次の図は、何が起こっているのかを図解している:

スキャナは、構文解析プロセスの様々な側面をポリシーの集合によって管理する。 スキャナを司るポリシーの集合は三つ存在する:

イテレーションとフィルタリング
認識とマッチ
セマンティックアクションの取り扱い

これらのポリシーはほとんどの場合視界から隠されており、ユーザは一般的にそれらについて知る必要はない。 しかし上級のユーザは自分独自のポリシーを与えて既存のものを無効にし、 自分の要求に沿うように構文解析処理をより一層改良しようとするだろう。 これがどのように為されるか見てみよう。 より詳細な部分については、また後で取り上げる。

scanner は二つのパラメータを要求するテンプレートクラスである。その一つはイテレータの型 IteratorT であり、もう一つはスキャナポリシーの集合 PoliciesT である。 IteratorT のデフォルトは char const* で、一方の PoliciesT のデフォルトは scanner_policies<> である。 後者はそのまますぐに使える(we can use straight out of the box)定義済みのスキャナポリシーの集合である。

    template<
        typename IteratorT  = char const*,
        typename PoliciesT  = scanner_policies<> >
    class scanner;

Spirit は、 C++ 標準テンポラリライブラリ( STL )で形式的に定義されたものと同じイテレータの概念とインタフェースを採用している。 我々は STL コンテナ(例えば listvectorstring など)が提供するイテレータをそのまま使う事ができる。 もしくは、独自のものを書き下ろすこともできる。 イテレータはポインタ(例えば char const* )と同じくらいシンプルになることもある。 対極的に、かなり複雑になることもある;例えば、 LEX のようなレキサをラップするイテレータアダプタなどのように。

非メンバ関数 Parse

フレームワークは構文解析を容易にするいくつかの非メンバ関数を提供する。 これらのパーサ関数は二つの形式を持つ。第一の形式は文字レベルで動作する。 第二は語句レベルで動作し、スキップパーサを要求する。

スキップパーサはただの任意のプリミティブパーサあるいはコンポジットパーサである。 空白と考えられるものをスキップして、スキャナの first イテレータを有効なトークンへ移動させるために用いられる。 例えば C では、タブ '\t' 、改行 '\n' 、復帰 '\r' 、スペース ' ' およびコメントの内側の文字 /*...*/ が空白とみなされる。

文字レベル構文解析

    template <typename IteratorT, typename DerivedT>
    parse_info<IteratorT>
    parse
    (
        IteratorT const&        first,
        IteratorT const&        last,
        parser<DerivedT> const& p
    );
    template <typename CharT, typename DerivedT>
    parse_info<CharT const*>
    parse
    (
        CharT const*            str,
        parser<DerivedT> const& p
    );

二つの異なる形式がある。第一の形式は STL アルゴリズムと同様に firstlast の二つのイテレータの組を受け付ける。 第二の形式はヌル終端文字列を受ける。最後の引数 p は入力の構文解析に用いられるパーサである。

字句レベル構文解析

    template <typename IteratorT, typename ParserT, typename SkipT>
    parse_info<IteratorT>
    parse
    (
        IteratorT const&        first,
        IteratorT const&        last,
        parser<ParserT> const&  p,
        parser<SkipT> const&    skip
    );
    template <typename CharT, typename ParserT, typename SkipT>
    parse_info<CharT const*>
    parse
    (
        CharT const*            str,
        parser<ParserT> const&  p,
        parser<SkipT> const&    skip
    );

上と同様に、二つの異なる形式がある。第一の形式は、STLアルゴリズムと同様に、 firstlast の二つのイテレータの組を受け付ける。 第二の形式はヌル終端文字列を受ける。引数 p は入力の構文解析に用いられるパーサである。最後の引数 skip はスキップパーサである。

parse_info 構造体

上述の関数群は、渡されたイテレータ型でパラメータ化された parse_info 構造体を返す。 parse_info 構造体は以下のメンバを持つ:

parse_info
stop 構文解析した最後の位置を指す(すなわち、パーサは入力をこの位置まで認識し、処理した)
hit 構文解析が成功したならば真。これは完全一致(パーサは入力を全て消費した)か 部分一致(パーサは入力の一部のみを消費した)のいずれかである。
full 完全一致した場合に真(すなわち、パーサは入力を全て消費した)
length パーサによって消費された文字数。これは(部分または完全を問わず)一致した場合のみ有効である。

イテレータを用いた直接的な構文解析

時々、低レベルな部分に足を踏み込んで、パーサの parse メンバ関数を直接呼び出したいと思うことがある。 parse 非メンバ関数があるので、実のところ、こうしたスキャナの込み入った部分について知る必要はない。 しかし、将来のいつか、頭巾の下に潜り込まなければならなくなった時には、それに遭遇することになるだろう。 だから、その必要が来た時に何を扱うのかを知っておくのは良いことである。

文字レベルで作業するなら、その手順は非常に単純である:

    scanner<IteratorT> scan(first, last);

    if (p.parse(scan))
    {
        //  構文解析成功。もし first == last なら完全構文解析。
        // すなわち、入力をすべて認識した。
    }
    else
    {
        //  構文解析失敗。パーサは入力の認識に失敗した
    }

ここで p は使用したいパーサ、 firstlast は入力を参照するイテレータの組である。 我々は与えられたイテレータからスキャナを作成するだけである。ここで用いるスキャナの型は、デフォルトでは scanner_policies<> である。

字句レベルで動作させたい場合は、状況は多少複雑になる:

    typedef skip_parser_iteration_policy<SkipT> iter_policy_t;
    typedef scanner_policies<iter_policy_t> scanner_policies_t;
    typedef scanner<IteratorT, scanner_policies_t> scanner_t;

    iter_policy_t iter_policy(skip);
    scanner_policies_t policies(iter_policy);
    scanner_t scan(first, last, policies);

    if (p.parse(scan))
    {
        //  構文解析成功。もし first == last なら完全構文解析。
        // すなわち、入力をすべて認識した。
    }
    else
    {
        //  構文解析失敗。パーサは入力の認識に失敗した
    }

ここで SkipT はスキップパーサ skip の型である。 この場合でも p は使用したいパーサ、 firstlast は入力を参照するイテレータの組である。 skip_parser_iteration_policy は与えられたスキップパーサ型 SkipT からスキャナのイテレータポリシーを作成する。このポリシーはスキップパーサによって認識される部分をスキップする。 その後、そのイテレータポリシーはスキャナを作成するのに使われる。 scanner_policies クラスはイテレーションポリシーを含む、スキャナに関連するすべてのポリシーをラップする。

lexeme_scanner

字句レベル構文解析から文字レベル構文解析に切り替えるという lexeme_ddirectives.htmlを参照)の 魔法は、空白のスキップを無効にすることで行われている。 これは スキャナ に手を入れることで為される。 一方この時、その lexeme_d の内側のすべてのパーサは変換されたスキャナ型を与えられている。これはほとんどの場合問題にならない。 しかし、 lexeme_d の内側でルールが呼ばれた時に適切なスキャナ型を持っていなければ、コンパイラは窒息する。 もしあるルールが lexeme_d の内側で用いられなければならないのであれば、 そのルールの型は次のようになっていなければならない:

    rule<lexeme_scanner<ScannerT>::type> r;

ここで ScannerT は用いられるスキャナの実際の型である。
テンプレートクラスまたはテンプレート関数の内側でこれが用いられる時は、 lexeme_scanner の前に "typename" を必ず加えるように注意すること。

as_lower_scanner

同様に、 as_lower_d はスキャナから受け取ったすべての文字をフィルタリングし、小文字に変換することで動作する。 これもまた scanner に手を入れることで為されている。 そして先ほどの場合と同じく、 as_lower_d の内側のすべてのパーサは変換されたスキャナ型を与えられる。 もしあるルールが lexeme_d[訳注: as_lower_d の誤りと思われる] の内側で用いられなければならないのであれば、 そのルールの型は次のようになっていなければならない:

    rule<as_lower_scanner<ScannerT>::type> r;

ここで ScannerT は用いられるスキャナの実際の型である。
テンプレートクラスまたはテンプレート関数の内側でこれが用いられる時は、 lexeme_scanner[訳注:as_lower_scannerの誤りと思われる] の前に "typename" を必ず加えるように注意すること。



このドキュメントの対象: Boost Version 1.30.0
最新版ドキュメント(英語)