セマンティックアクション

セマンティックアクションは以下の形式を持つ: expression[action]

最終的には、文法を定義し、対応するパーサを生成した後で、 なんらかの出力を生成し、構文解析以外の何らかの作業をする必要がある。 もちろん、入力が文法と一致するかチェックすることだけを欲するのでない限りの話だが、それは非常に稀なケースである。

必要なのは、入力ストリームを構文解析する過程で構文解析器が文法を辿る際に何をすべきなのか指示する機構である。 この機構はセマンティックアクションによって整備されている。

セマンティックアクションは構文解析の階層構造におけるあらゆるレベルのあらゆる式に 取り付けることができる。 アクションは C / C++ 関数または関数オブジェクトであり、 それが取り付けられた特定のコンテキストに一致する箇所が見つかると呼び出される。 アクション関数はフックとしてパーサに提供され、例えば以下のように使われることができる:

パーサから出力を生成する (例えば AST など)
警告やエラーを報告する
シンボルテーブルを管理する

ジェネリックなセマンティックアクション

ジェネリックなセマンティックアクションとは、次のインターフェイスと互換性のあるあらゆる非メンバ関数や関数オブジェクトである:

    void f(IteratorT first, IteratorT last);

ここで IteratorT は用いられるイテレータの型、 first は現在の入力を指し、 last は入力の終端の一つ後ろを指す( STL のイテレータの範囲と同様)。 ファンクタは上と同じシグネチャの operator() メンバを持つべきである:

    struct my_functor
    {
        void operator()(IteratorT first, IteratorT last) const;
    };

マッチしている入力の一部を指しているイテレータたちが関数/ファンクタへ渡される。

例:

    void
    my_action(char const* first, char const* last)
    {
        std::string str(first, last);
        std::cout << str << std::endl;
    }

    rule<> myrule = (a | b | *(c >> d))[&my_action];

my_action 関数は、構文解析中に式 (a | b | *(c >> d) が 入力ストリームの一部にマッチすればいつでも呼び出される。 二つのイテレータ first および last が関数に渡される。 これらのイテレータはそれぞれ一致の見つかった入力ストリームの始まりと終わりの場所を指している。

const 性:

ファンクタを用いる場合、 operator()const であるべき点に注意してほしい。 これはファンクタが変更不能であることを意図する。 アクションが呼ばれた時に更新されるなんらかのメンバ変数を持たせたいと望む人もいるだろうが、これは良い考えではない。 第一に、ファンクタは軽量であったほうが良い。 ファンクタは何度も渡されるので、もしファンクタが重ければ多くのオーバーヘッドを招く。 第二に、ファンクタは値渡しされる。 そのため、最終的にパーサに取り付けられる実際のファンクタオブジェクトは 利用者によって与えられたもともとのインスタンスであるとは保証されない。 これが意味することは、ファンクタの状態変化は利用者が渡したもともとのファンクタに影響を及ぼさないということである。なぜならそれはもとのファンクタとは別個のコピーであるからだ。 もしファンクタがいくつかの状態変数を更新する必要があるなら、それはよくあるケースなのだが、 外部データへの参照を用いる方がよい。 次の例はどのようにこれが為されるかを示している:

    struct my_functor
    {
        my_functor(std::string& str_)
        : str(str_) {}

        void
        operator()(IteratorT first, IteratorT last) const
        {
            str.assign(first, last);
        }

        std::string& str;
    };

完全な例:

以下は、セマンティックアクションで拡張された電卓である:

    namespace
    {
        void    do_int(char const* str, char const* end)
        {
            string  s(str, end);
            cout << "PUSH(" << s << ')' << endl;
        }

        void    do_add(char const*, char const*)    { cout << "ADD\n"; }
        void    do_subt(char const*, char const*)   { cout << "SUBTRACT\n"; }
        void    do_mult(char const*, char const*)   { cout << "MULTIPLY\n"; }
        void    do_div(char const*, char const*)    { cout << "DIVIDE\n"; }
        void    do_neg(char const*, char const*)    { cout << "NEGATE\n"; }
    }

セマンティックアクションを用いて文法を補強する:

    struct calculator : public grammar<calculator>
    {
        template <typename ScannerT>
        struct definition
        {
            definition(calculator const& self)
            {
                expression
                    =   term
                        >> *(   ('+' >> term)[&do_add]
                            |   ('-' >> term)[&do_subt]
                            )
                    ;

                term =
                    factor
                        >> *(   ('*' >> factor)[&do_mult]
                            |   ('/' >> factor)[&do_div]
                            )
                        ;

                factor
                    =   lexeme_d[(+digit_p)[&do_int]]
                    |   '(' >> expression >> ')'
                    |   ('-' >> factor)[&do_neg]
                    |   ('+' >> factor)
                    ;
            }

            rule<ScannerT> expression, term, factor;

            rule<ScannerT> const&
            start() const { return expression; }
        };
    };

例として、式 (-1 + 2) * (3 + -4) を与えると、 expression ルールは期待される出力を生成するだろう:

-1
2
ADD
3
-4
ADD
MULT

ところで、これは与えられた式の逆ポーランド記法( RPN )であり、 いくつかの基本的な電卓や Forth 言語を思い出させてくれる。

完全なソースコードはここから見ることが出来る。 これは Spirit 配布物の一部である。
[ libs/spirit/example/fundamental/calc/calc_plain.cpp を参照 ]

特殊化されたアクション

一般に、セマンティックアクションは first と last というイテレータの組を受ける。 アクション関数やファンクタはマッチした生成物を表現する未処理のデータを入力から直接受け取る。 だがデータをその処理された形で渡したいと思う状況もある。 具体的な例は数値パーサである。 未処理のデータを数値パーサに取り付けられたセマンティックアクションに渡し、 パーサによって為されたことを単に捨ててしまうのは賢明ではない。 ここは実際に構文解析された数字を渡したいところである。

セマンティックアクションである関数およびファンクタのシグネチャは それが取り付けられたパーサに依存して変化する。 次の表は特有のシグネチャを受け付けるパーサの一覧である。 特定のパーサ型のドキュメントで明示的に宣言されていない限り、 このリストに含まれていないパーサは、デフォルトでは上で説明した汎用のシグネチャを期待する。

数値アクション

適用対象:

uint_p
int_p
ureal_p
real_p

関数のシグネチャ:

    void func(NumT val);


ファンクタのシグネチャ:

    struct ftor
    {
        void operator()(NumT val) const;
    };

ここで NumTintlongfloatdouble 等の あらゆるプリミティブな数値型、あるいは big_int のようなユーザが定義した数値型である。 NumTuint_pint_pureal_p あるいは real_p の テンプレートパラメータとして使われた型と同じものである。 構文解析された数値が関数/ファンクタに渡される。

文字アクション

適用対象:

chlit, ch_p
range, range_p
anychar
alnum, alpha
cntrl, digit
graph, lower
print, punct
space, upper
xdigit

関数のシグネチャ:

    void func(CharT ch);

ファンクタのシグネチャ:

    struct ftor
    {
        void operator()(CharT ch) const;
    };

ここで CharT は構文解析で使われたイテレータの value_type である。 例えば char const* イテレータの value_typechar である。 一致した文字が関数/ファンクタに渡される。

アクションの重ね合わせ

アクションは重ね合わせることができる。 重ね合わされたアクションも元の関数/ファンクタのインタフェースを継承する。 例えば:

    uint_p[fa][fb][fc]

ここで、ファンクタ fafb および fc はすべて void operator()(unsigned n) const というシグネチャを期待する。

ディレクティブとアクション

ディレクティブは、それが囲い込む相手の関数/ファンクタインタフェースを継承する。 例:

    as_lower_d[ch_p('x')][f]

ここで、ファンクタ f は、使用されているイテレータが char const* であれば void operator()(char ch) const というシグネチャを期待する。

テンプレート化されたファンクタ

ジェネリック機能の恩恵を得るために、 ファンクタの operator() メンバをテンプレートにするとはしばしば良いことである。 そうすれば、引数の型に対して適切であると期待される振る舞いをするかどうかは、もはや考慮しなくてもよい。 例えば、ジェネリックなセマンティックアクションの引数として char const* と直書きするよりも、 それをテンプレートメンバ関数にする方が良い。 そうすれば、あらゆる型のイテレータを受け付けることができる:

    struct my_functor
    {
        template <typename IteratorT>
        void operator()(IteratorT first, IteratorT last) const;
    };

ただし、これはファンクタに対してのみ可能であることに注意して欲しい。 このことは、ファンクタがプレーンな関数より優れていることを明確に示している。



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