51

ブーストスピリットセマンティックアクションに関するこの記事では、

実際には、さらに2つの引数が渡されます。パーサーコンテキストとブール値の「ヒット」パラメーターへの参照です。パーサーコンテキストは、セマンティックアクションがルールの右側のどこかにアタッチされている場合にのみ意味があります。これについては、まもなく詳細を確認します。セマンティックアクション内でブール値をfalseに設定すると、遡及的に一致が無効になり、パーサーが失敗します。

すべて問題ありませんが、他のパラメーター(パーサーコンテキストとヒットブール値)を使用するセマンティックアクションとして関数オブジェクトを渡す例を見つけようとしましたが、見つかりませんでした。フェニックスのブードゥーをかろうじて理解することができないので、通常の関数または関数オブジェクトを使用した例を見てみたいと思います

4

1 に答える 1

65

これは、気とフェニックスのインターフェースに到達するため、非常に良い質問(およびワームの缶)です。私も例を見たことがないので、この方向に少し記事を拡張します。

あなたが言うように、セマンティックアクションの関数は最大3つのパラメータを取ることができます

  1. 一致した属性-記事で説明されています
  2. コンテキスト-qi-phoenixインターフェースが含まれています
  3. 一致フラグ-一致状態を操作します

一致フラグ

記事に記載されているように、式がルールの一部でない限り、2番目のパラメーターは意味がないため、3番目のパラメーターから始めましょう。ただし、この使用には、2番目のパラメーターのプレースホルダーが引き続き必要ですboost::fusion::unused_type。したがって、3番目のパラメーターを使用するために記事から変更された関数は次のとおりです。

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

void f(int attribute, const boost::fusion::unused_type& it, bool& mFlag){
    //output parameters
    std::cout << "matched integer: '" << attribute << "'" << std::endl
              << "match flag: " << mFlag << std::endl;

    //fiddle with match flag
    mFlag = false;
}

namespace qi = boost::spirit::qi;

int main(void){
   std::string input("1234 6543");
   std::string::const_iterator begin = input.begin(), end = input.end();

   bool returnVal = qi::phrase_parse(begin, end, qi::int_[f], qi::space);

   std::cout << "return: " << returnVal << std::endl;
   return 0;
}

出力:

一致した整数: '1234'
一致フラグ:1
戻り値:0

この例では、一致を不一致に切り替えるだけで、パーサーの出力に反映されます。hkaiserによると、ブースト1.44以上で一致フラグをfalseに設定すると、通常の方法で一致が失敗します。代替案が定義されている場合、パーサーはバックトラックして、予想どおりにそれらを一致させようとします。ただし、boost <= 1.43では、Spiritバグがバックトラックを防ぎ、奇妙な動作を引き起こします。これを確認するには、フェニックスインクルードboost/spirit/include/phoenix.hppを追加し、式を次のように変更します。

qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]

qi :: intパーサーが失敗すると、代替のqi ::digitが「1」の入力の先頭に一致することが期待されますが、出力は次のとおりです。

一致した整数: '1234'
一致フラグ:1
6
リターン:1

これ6は、入力の2番目のintの最初の桁であり、代替がスキッパーを使用してバックトラックなしで取得されることを示します。代替案に基づいて、試合は成功したと見なされることにも注意してください。

Boost 1.44がリリースされると、一致フラグは、パーサーシーケンスで表現するのが難しい一致基準を適用するのに役立ちます。一致フラグは、_passプレースホルダーを使用してフェニックス式で操作できることに注意してください。

コンテキストパラメータ

より興味深いパラメーターは、qi-phoenixインターフェース、またはqiの用語では、セマンティックアクションのコンテキストを含む2番目のパラメーターです。これを説明するために、最初にルールを調べます。

rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>

コンテキストパラメーターは、属性、Arg1、... ArgN、およびqi :: localsテンプレートパラメーターを具体化し、boost :: spirit::contextテンプレートタイプにラップされます。この属性は関数パラメーターとは異なります。関数パラメーター属性は解析された値ですが、この属性はルール自体の値です。セマンティックアクションは、前者を後者にマップする必要があります。可能なコンテキストタイプの例を次に示します(フェニックス式に相当するものが示されています)。

using namespace boost;
spirit::context<              //context template
    fusion::cons<             
        int&,                 //return int attribute (phoenix: _val)
        fusion::cons<
            char&,            //char argument1       (phoenix: _r1)
            fusion::cons<
                float&,       //float argument2      (phoenix: _r2) 
                fusion::nil   //end of cons list
            >,
        >,
    >,
    fusion::vector2<          //locals container
        char,                 //char local           (phoenix: _a)
        unsigned int          //unsigned int local   (phoenix: _b)
    > 
>

return属性と引数リストは、lispスタイルのリスト(consリスト)の形式をとることに注意してください。関数内でこれらの変数にアクセスするには、fusion :: at <>()を使用してstructテンプレートのattributeまたはlocalsメンバーにアクセスします。contextたとえば、コンテキスト変数の場合con

//assign return attribute
fusion::at_c<0>(con.attributes) = 1;

//get the second rule argument
float arg2 = fusion::at_c<2>(con.attributes);

//assign the first local
fusion::at_c<1>(con.locals) = 42;

2番目の引数を使用するように記事の例を変更するには、関数定義とphrase_parse呼び出しを変更します。

...
typedef 
    boost::spirit::context<
        boost::fusion::cons<int&, boost::fusion::nil>, 
        boost::fusion::vector0<> 
    > f_context;
void f(int attribute, const f_context& con, bool& mFlag){
   std::cout << "matched integer: '" << attribute << "'" << std::endl
             << "match flag: " << mFlag << std::endl;

   //assign output attribute from parsed value    
   boost::fusion::at_c<0>(con.attributes) = attribute;
}
...
int matchedInt;
qi::rule<std::string::const_iterator,int(void),ascii::space_type> 
    intRule = qi::int_[f];
qi::phrase_parse(begin, end, intRule, ascii::space, matchedInt);
std::cout << "matched: " << matchedInt << std::endl;
....

これは、解析された値を出力属性値にマップするだけの非常に単純な例ですが、拡張機能はかなり明白なはずです。コンテキスト構造体テンプレートのパラメーターをルールの出力、入力、およびローカルタイプと一致させるだけです。解析されたタイプ/値と出力タイプ/値の間のこのタイプの直接一致は、ルールを定義するときの%=代わりに、自動ルールを使用して自動的に実行できることに注意してください。=

qi::rule<std::string::const_iterator,int(void),ascii::space_type> 
    intRule %= qi::int_;

IMHO、各アクションの関数を書くことは、簡潔で読みやすいフェニックス式の同等物と比較して、かなり退屈です。ブードゥーの視点には共感しますが、フェニックスをしばらく使ってみれば、セマンティクスと構文はそれほど難しくありません。

編集:Phoenixを使用したルールコンテキストへのアクセス

コンテキスト変数は、パーサーがルールの一部である場合にのみ定義されます。パーサーは、入力を消費する任意の式であると考えてください。ルールは、パーサー値(qi :: _ 1)をルール値(qi :: _ val)に変換します。たとえば、qi :: valにPOD解析値から構築する必要のあるクラス型がある場合、違いは自明ではないことがよくあります。以下は簡単な例です。

入力の一部が3つのCSV整数(x1, x2, x3)のシーケンスであり、これら3つの整数の算術関数(f = x0 +(x1 + x2)* x3)のみを処理するとします。ここで、x0は他の場所で取得された値です。1つのオプションは、整数を読み取って関数を計算するか、あるいはフェニックスを使用して両方を実行することです。

この例では、出力属性(関数値)、入力(x0)、およびローカル(ルールを持つ個々のパーサー間で情報を渡すため)を持つ1つのルールを使用します。これが完全な例です。

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

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

int main(void){
   std::string input("1234, 6543, 42");
   std::string::const_iterator begin = input.begin(), end = input.end();

   qi::rule<
      std::string::const_iterator,
      int(int),                    //output (_val) and input (_r1)
      qi::locals<int>,             //local int (_a)
      ascii::space_type
   >
      intRule =
            qi::int_[qi::_a = qi::_1]             //local = x1
         >> ","
         >> qi::int_[qi::_a += qi::_1]            //local = x1 + x2
         >> ","
         >> qi::int_
            [
               qi::_val = qi::_a*qi::_1 + qi::_r1 //output = local*x3 + x0
            ];

   int ruleValue, x0 = 10;
   qi::phrase_parse(begin, end, intRule(x0), ascii::space, ruleValue);
   std::cout << "rule value: " << ruleValue << std::endl;
   return 0;
}

または、すべてのintをベクトルとして解析し、関数を単一のセマンティックアクションで評価することもできます(%以下はリスト演算子であり、ベクトルの要素にはphoenix :: atでアクセスします)。

namespace ph = boost::phoenix;
...
    qi::rule<
        std::string::const_iterator,
        int(int),
        ascii::space_type
    >
    intRule =
        (qi::int_ % ",")
        [
            qi::_val = (ph::at(qi::_1,0) + ph::at(qi::_1,1))
                      * ph::at(qi::_1,2) + qi::_r1
        ];
....

上記の場合、入力が正しくない場合(3つではなく2つのint)、実行時に問題が発生する可能性があるため、解析値の数を明示的に指定することをお勧めします。これにより、不正な入力に対して解析が失敗します。以下では、、、_1および_2を使用_3して、1番目、2番目、および3番目の一致値を参照しています。

(qi::int_ >> "," >> qi::int_ >> "," >> qi::int_)
[
    qi::_val = (qi::_1 + qi::_2) * qi::_3 + qi::_r1
];

これは不自然な例ですが、あなたにアイデアを与えるはずです。フェニックスのセマンティックアクションは、入力から直接複雑なオブジェクトを作成するのに非常に役立ちます。これが可能なのは、セマンティックアクション内でコンストラクターとメンバー関数を呼び出すことができるためです。

于 2010-06-18T07:34:49.260 に答える