プリミティブ

フレームワークはいくつかの基本的なパーサをあらかじめ定義している。 これらはユーザがより複雑なパーサを組み立てる際の最も基本的な組み立て部品である。 これらプリミティブパーサはテンプレートクラスであり、非常に柔軟にできている。

これらすべてのプリミティブパーサは、直接またはテンプレート補助関数によって実体化されるクラスである。 通常、補助関数は打鍵数が少ない分、扱いがずっと単純である。

ジェネレータ関数 ch_p による文字リテラルパーサは既に見たが、これは実はパーサではなく、正確にはパーサジェネレータである。 この文字リテラルパーサの背後にある実際のテンプレートクラスは chlit<CharT> クラスである。 chlit オブジェクトをインスタンス化するには、文字の型を決定するテンプレートパラメータとして、文字型 CharT を明示的に与えなければならない。 この型は典型的には入力型に対応し、普通は char ないし wchar_t である。 次の式は一つの文字 'X' を認識するテンプレートパーサオブジェクトを生成する。

    chlit<char>('X');

chlit のジェネレータ関数である ch_p を用いることで、 chlit<> の使い方が簡単になる。 (これは Spirit のパーサクラスのほとんどに当てはまることで、それを言うなら、ほとんどが対応するジェネレータ関数を持っている)。 関数を呼び出しの方が、引数を推定する際にテンプレート型を推定してくれるので、ずっと便利である。 上述の例は ch_p 補助関数を使うことで、より冗長にならない表現ができる。

    ch_p('X')  // chlit<char>('X') オブジェクトと等価
パーサジェネレータ

パーサジェネレータ関数の呼び出しは、パーサそのものと等価である。 そのため、技術的に言えば文字パーサを生成する関数であるにも関わらず、我々はよく ch_p を文字パーサと呼ぶことがある。

次の断片的な文法はこれらの方法の実例を示している:

    // rule はパーサオブジェクトを”格納”できる。これらについては
    // 後に述べるが、とりあえず rule はある種の型だと考えれば良い
    rule<> r1, r2, r3;

    chlit<char> x('X');     // x という名前のパーサを宣言

    r1 = chlit<char>('X');  //  明示的な宣言
    r2 = x;                 //  x を用いたもの
    r3 = ch_p('X')          //  ジェネレータを用いたもの

chlit と ch_p

一つの文字リテラルに一致する。 chlit は一つのテンプレート型パラメータをもち、そのデフォルトは char である (つまり、 chlit<>chlit<char> と等価である)。 この型パラメータは chlit が構文解析する際に扱う文字型である。 前述のように、関数ジェネレータ版は実際の関数引数からテンプレート型引数を推測する。 chlit クラスのコンストラクタは一つのパラメータ、入力に対してマッチする文字、を受け取る。 例えば:

    r1 = chlit<>('X');
    r2 = chlit<wchar_t>(L'X');
    r3 = ch_p('X');

元の例に戻ると:

    group = '(' >> expr >> ')';
    expr1 = integer | group;
    expr2 = expr1 >> *(('*' >> expr1) | ('/' >> expr1));
    expr  = expr2 >> *(('+' >> expr2) | ('-' >> expr2));

この文法宣言中の文字リテラル '('')''+''-''*' および '/' は舞台裏でこっそりと作られた chlit オブジェクトである。

文字オペランド

これが動作するのは、 operator>> の二つの特殊なテンプレート化されたオーバーロードのおかげである。これらは (char, ParserT) または (parserT, char) を取る。 これらの関数は文字を chlit オブジェクトに変換する。

明示的に宣言したければ:

    chlit<> plus('+');
    chlit<> minus('-');
    chlit<> times('*');
    chlit<> divide('/');
    chlit<> oppar('(');
    chlit<> clpar(')');

range と range_p

範囲内の文字を示す range は上下の文字の組から作られる。 このパーサは両端を含む range の範囲にある文字にマッチする。 chlit と同様、 range はデフォルトが char である一つのテンプレート型パラメータを持つ。 range クラスのコンストラクタは二つのパラメータ、入力に対してマッチする文字範囲(〜から〜まで)を受け取る。 関数ジェネレータ版は range_p である。 例えば:

    range<>('A','Z')    // matches 'A'..'Z'
    range_p('a','z')    // matches 'a'..'z'

最初の文字は二つ目よりもその環境の文字エンコーディング的に"前"でなければならない。 range は chlit と同様、一文字用のパーサである。

文字のマッピング

文字のマッピングは本質的にプラットフォーム依存である。 標準は、例えば 'A' < 'Z' であることを保証しない。 たとえ多くの場合、 ASCII 、 IOS-8859-1 、あるいは Uniocde のような我々が用いている文字コードにおいてそうであると承知しているにも関わらず、である。 他のプラットフォームに移植する際には注意が必要である。

strlit と str_p

このパーサは文字列リテラルにマッチする。 strlit は一つのテンプレート型パラメータ、イテレータ型を持つ。 内部的には、 strlit は文字列ないし文字コンテナを指す始端/終端のイテレータを保持している。 strlit は現在の入力ストリームとこの文字列をマッチしようとする。 このテンプレート型パラメータのデフォルトは char const* である。 strlit は二つのコンストラクタを持つ。 一つは、ヌル終端を持つ文字列へのポインタを受け取る。 もう一つのコンストラクタは始端/終端イテレータの組を受け取る。 関数ジェネレータ版は str_p である。 例えば:

    strlit<>("Hello World")
    str_p("Hello World")

    std::string msg("Hello World");
    strlit<>(msg.begin(), msg.end());
文字レベル構文解析と語句レベル構文解析

典型的な構文解析器には、文字(単語や語彙の形をなすシンボル)を考慮するものと、語句(文の形をなす単語) を考慮するものという、異なるドメインのものがある。 ある文法の終端記号となる予約語、演算子、リテラル文字列、数値定数等々のエンティティは、 普通、分けられた字句解析ステージで抽出される。

これまでの例で明らかなように、 Spirit フレームワークは語句レベルと同じように文字レベルの構文解析タスクを処理する。 この点で標準的な構文解析の技法とまったく異なることに注目して欲しい。 Spirit フレームワークでは字句解析器はシームレスに統合されていると考えても良い。

Spirit パーサライブラリは別の字句解析器を必要としないが、字句解析器を持ってはいけない理由はない。 誰でも、いつでも、必要なだけ多くのパーサレイヤを持つことができる。 原理的には、プリプロセッサ、字句解析器、そして構文解析器のすべてを、同じフレームワークを用いてきちんと作ることができる。

chseq と chseq_p

文字シーケンスにマッチする。 chseq は strlit と同じテンプレート型パラメータとコンストラクタパラメータを持つ。 関数ジェネレータ版は chseq_p である。 例:

    chseq<>("ABCDEFG")
    chseq_p("ABCDEFG")

strlit では単語の区切りは絶対である。すなわち、文字レベルでのみ動作する。 一方で strlit の双子である chseq は、文字レベルと語句レベルのどちらでも動作することができる。 これは単に文字列の間の空白を無視できるという意味である。例えば:

    chseq<>("ABCDEFG")

これは以下を構文解析できる:

    ABCDEFG
    A B C D E F G
    AB CD EFG

他の文字パーサ

フレームワークは、一文字を処理するパーサの完全なレパートリーを定義している。 ch_p やこれまでに紹介した他のジェネレータ関数とは異なり、 これらのパーサは実際にインスタンス化されたものである。
一文字パーサ
anychar_p あらゆる一文字に一致する(ヌル終端: '\0' を含む)
alnum_p アルファベットと数字に一致する
alpha_p アルファベットに一致する
blank_p 空白とタブに一致する
cntrl_p 制御文字に一致する
digit_p 数字に一致する
graph_p 空白以外の印字可能な文字に一致する
lower_p 小文字に一致する
print_p 印字可能な文字に一致する
punct_p 句読点に一致する
space_p 空白、タブ、復帰および改行に一致する
upper_p 大文字に一致する
xdigit_p 16 進数文字に一致する

否定 ~

chlitrangeanychar_palnum_p などの一文字パーサの否定を表現できる。例えば:

    ~ch_p('x')

これは 'x' を除くあらゆる文字にマッチする。 文字パーサの二重否定は否定をキャンセルする。 ~~alpha_palpha_p と等価である。

eol_p

行末( CR ないし LF 、あるいはその組み合わせ)にマッチする。

nothing_p

どれにもマッチせず、常にマッチに失敗する。

end_p

入力の終端にマッチする (入力が尽きたときに長さ 0 の文字列とマッチし、成功を返す)。

epsilon_p と eps_p

厳密にはプリミティブパーサではないが、 epsilon_peps_p はヌル文字列にマッチし、長さ 0 の文字列へのマッチを返す:

    epsilon_p // 常に長さ 0 の文字列へのマッチを返す

イプシロンはパーサジェネレータとしても働く。この役割では引数として、引数なしの関数/ファンクタか、他のパーサを取る。 それらは空の(長さ 0 の)マッチか、あるいは失敗を報告するパーサを構築する。

関数/ファンクタが偽を評価するか、収容されたパーサが失敗を報告した場合、失敗が報告される。 そうでなければ空のマッチが報告される。

epsilon_peps_p によって構築されたパーサに対する ~ 演算子が定義されている。 これは、報告された結果を補完することで否定を行う。 ~~eps_p(x)eps_p(x) と同一である。

例:

    epsilon_p('0') >> oct_p // 実際には '0' は ch_p('0') であることに注意 

ここでイプシロンは構文的な述語として使われている。 oct_p は先行する '0' が現れた場合のみ構文解析される。 イプシロンの内側に先行する '0' を包むことで、パーサが入力からなにも消費しないようにしている。 もし '0' が現れれば、 epsilon_p は長さ 0 のマッチが成功したと報告する。 oct_p についてより詳しくは数値セクションで学ぶ。 ここでは、 8 進数の数を構文解析するプリミティブパーサであると言えば十分だろう。



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