ブーストスピリットで文法を解析します
-
28-09-2019 - |
質問
私は次のようなツリー式のようなc機能を解析しようとしています(を使用して スピリットパーサーフレームワーク):
F( A() , B( GREAT( SOME , NOT ) ) , C( YES ) )
このために、私は次の文法で3つのルールを使用しようとしています。
template< typename Iterator , typename ExpressionAST >
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), space_type> {
InputGrammar() : InputGrammar::base_type( ) {
tag = ( qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9") )[ push_back( at_c<0>(qi::_val) , qi::_1 ) ];
command = tag [ at_c<0>(qi::_val) = at_c<0>(qi::_1) ] >> "(" >> (*instruction >> ",")
[ push_back( at_c<1>(qi::_val) , qi::_1 ) ] >> ")";
instruction = ( command | tag ) [qi::_val = qi::_1];
}
qi::rule< Iterator , ExpressionAST() , space_type > tag;
qi::rule< Iterator , ExpressionAST() , space_type > command;
qi::rule< Iterator , ExpressionAST() , space_type > instruction;
};
私のタグルールは、式(「関数」名)で使用されている識別子をキャプチャしようとするだけであることに注意してください。また、タグルールの署名が戻ることに注意してください ExpressionAST
aの代わりに std::string
, 、ほとんどの例のように。このようにやりたい理由は実際には非常に簡単です。バリアントの使用は嫌いで、可能であれば避けてください。ケーキを保管して食べるのも素晴らしいことだと思います。
コマンドは、タグ(現在のノードの名前、ASTノードの最初の文字列フィールド)と、括弧で囲まれたさまざまな数の引数で開始する必要があります。各引数は、タグ自体または別のコマンドにすることができます。
ただし、この例はまったく機能しません。それはすべてをコンパイルしますが、実行時にはすべてのテスト文字列を解析できません。そして、私を本当に悩ませるのは、少なくとも言葉の伝統的な意味では、上記のコードを本当にデバッグできないので、それを修正する方法を理解できないということです。基本的に、上記のコードを修正できる唯一の方法は、私が間違っていることを知ることです。
ですから、問題は、上記のコードの何が悪いのかわからないということです。上記の文法をどのように定義しますか?
ExpressionAST
使用しているタイプIは次のとおりです。
struct MockExpressionNode {
std::string name;
std::vector< MockExpressionNode > operands;
typedef std::vector< MockExpressionNode >::iterator iterator;
typedef std::vector< MockExpressionNode >::const_iterator const_iterator;
iterator begin() { return operands.begin(); }
const_iterator begin() const { return operands.begin(); }
iterator end() { return operands.end(); }
const_iterator end() const { return operands.end(); }
bool is_leaf() const {
return ( operands.begin() == operands.end() );
}
};
BOOST_FUSION_ADAPT_STRUCT(
MockExpressionNode,
(std::string, name)
(std::vector<MockExpressionNode>, operands)
)
解決
デバッグに関しては、通常の休憩と監視アプローチを使用することが可能です。ただし、これはルールをどのようにフォーマットしたかによって困難になります。スピリットの例(〜1行ごとに1つのパーサー、1行ごとに1つのフェニックスステートメント)ごとにフォーマットすると、ブレークポイントははるかに有益です。
データ構造には区別する方法がありません A()
から SOME
それらは両方とも葉であるということです(私が何かが足りないかどうか教えてください)。あなたのバリアントのコメントから、私はこれがあなたの意図ではないと思いますので、これらの2つのケースを区別するために、私は追加しました bool commandFlag
mockexpressionnodeからmember変数(true for A()
と偽り SOME
)、対応するフュージョンアダプターラインを使用。
具体的には、コードの場合、開始ルールをベースコンストラクターに渡す必要があります。
InputGrammar() : InputGrammar::base_type(instruction) {...}
これが文法のエントリポイントであり、データが解析されていない理由です。それなしでコンパイルされたことに驚いています。最初のルールのタイプと一致するために文法タイプが必要だと思いました。それでも、これは従うべき便利なコンベンションです。
のために tag
ルール、実際には2つのパーサーがあります qi::char_("a-zA-Z_")
, 、タイプの_1です char
と *qi::char_("a-zA-Z_0-9")
タイプの_2(基本的に) vector<char>
. 。これらをオートルールなしで弦に強制することはできませんが、解析された各charにルールを取り付けることで実行できます。
tag = qi::char_("a-zA-Z_")
[ at_c<0>(qi::_val) = qi::_1 ];
>> *qi::char_("a-zA-Z_0-9") //[] has precedence over *, so _1 is
[ at_c<0>(qi::_val) += qi::_1 ]; // a char rather than a vector<char>
しかし、スピリットにこの変換をさせるのはもっときれいです。したがって、新しいルールを定義します。
qi::rule< Iterator , std::string(void) , ascii::space_type > identifier;
identifier %= qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9");
心配しないでください;)。その後、タグはなります
tag = identifier
[
at_c<0>(qi::_val) = qi::_1,
ph::at_c<2>(qi::_val) = false //commandFlag
]
コマンドの場合、最初の部分は問題ありませんが、 (*instruction >> ",")[ push_back( at_c<1>(qi::_val) , qi::_1 ) ]
. 。これにより、ゼロまたは複数の命令ルールに続いて「、」が続きます。また、aをプッシュしようとします vector<MockExpressionNode>
(これがなぜコンパイルされたのかわからないが、開始ルールの欠落のためにインスタンス化されないかもしれない?)次のことが必要だと思います(識別子の変更を伴う):
command =
identifier
[
ph::at_c<0>(qi::_val) = qi::_1,
ph::at_c<2>(qi::_val) = true //commandFlag
]
>> "("
>> -(instruction % ",")
[
ph::at_c<1>(qi::_val) = qi::_1
]
>> ")";
これは、オプションの演算子を使用します -
およびリスト演算子 %
, 、後者は同等です instruction >> *("," >> instruction)
. 。フェニックス式は、ベクトルを構造メンバーに直接割り当てるだけですが、アクションを命令マッチに直接添付してPush_backを使用することもできます。
命令ルールは問題ありません、私はそれが同等であることを言うだけです instruction %= (command|tag)
.
最後に、実際に区別がない場合 A()
と SOME
(つまり、元の構造はありません commandFlag
)、このパーサーを使用してこのパーサーを作成できます。
template< typename Iterator , typename ExpressionAST >
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), ascii::space_type> {
InputGrammar() : InputGrammar::base_type( command ) {
identifier %=
qi::char_("a-zA-Z_")
>> *qi::char_("a-zA-Z_0-9");
command %=
identifier
>> -(
"("
>> -(command % ",")
>> ")");
}
qi::rule< Iterator , std::string(void) , ascii::space_type > identifier;
qi::rule< Iterator , ExpressionAST(void) , ascii::space_type > command;
};
これは、入力を密接にモデル化するフュージョンラップ構造を使用することの大きな利点です。