web-dev-qa-db-ja.com

ブーストスピリットセマンティックアクションパラメータ

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

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

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

52
lurscher

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

あなたが言うように、 セマンティックアクション の関数は最大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バグがバックトラックを防ぎ、奇妙な動作を引き起こします。これを確認するには、phoenix include boost/spirit/include/phoenix.hppを追加し、式を次のように変更します。

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

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

一致した整数: '1234' 
一致フラグ:1 
 6 
 return:1 

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

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

コンテキストパラメータ

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

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スタイルのリスト(a cons list )の形式をとることに注意してください。関数内でこれらの変数にアクセスするには、fusion :: at <>()を使用して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をベクトルとして解析し、関数を1つのセマンティックアクションで評価することもできます(以下の%はリスト演算子であり、ベクトルの要素には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
];

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

66
academicRobot