Boost Spirit X3 AST does not work with semantic actions when using a separate rule definition and instantiation

I am trying to use Boost Spirit X3 with semantic actions when analyzing the structure in AST. If I use a rule without a separate definition and instantiation, it works fine, for example:

#include <vector> #include <string> #include <iostream> #include <boost/fusion/include/adapt_struct.hpp> #include <boost/spirit/home/x3.hpp> namespace ast { struct ast_struct { int number; std::vector<int> numbers; }; } BOOST_FUSION_ADAPT_STRUCT( ast::ast_struct, (int, number) (std::vector<int>, numbers) ) namespace x3 = boost::spirit::x3; using namespace std; void parse( const std::string &data ) { string::const_iterator begin = data.begin(); string::const_iterator end = data.end(); unsigned n(0); auto f = [&n]( auto &ctx ) { n = x3::_attr(ctx); }; ast::ast_struct ast; bool r = x3::parse( begin, end, x3::int_[f] >> +( x3::omit[+x3::blank] >> x3::int_ ), ast ); if ( r && begin == end ) { cout << "n: " << n << ", "; std::copy(ast.numbers.begin(), ast.numbers.end(), std::ostream_iterator<int>(std::cout << ast.numbers.size() << " elements: ", " ")); cout << endl; } else cout << "Parse failed" << endl; } int main() { parse( "3 1 2 3" ); parse( "4 1 2 3 4" ); return 0; } 

Execution of the above code (compiled with the flags -std = C ++ 14) displays the expected result:

 n: 3, 3 elements: 1 2 3 n: 4, 4 elements: 1 2 3 4 

Now I am trying to make my Spirit X3 parser more or less the same as calc 9 example from Boost Spirit X3, but this does not work:

  • ast.hxx: defines an abstract syntax tree.
  • grammar.hxx: user interface that exposes parser methods.
  • grammar.cxx: creates rules.
  • grammar_def.hxx: parser grammar definition.
  • config.hxx: parser configuration.
  • main.cxx: an example of using the parser.

ast.hxx:

 #ifndef AST_HXX #define AST_HXX #include <vector> #include <boost/fusion/include/adapt_struct.hpp> namespace ast { struct ast_struct { int number; std::vector<int> numbers; }; } BOOST_FUSION_ADAPT_STRUCT( ast::ast_struct, (int, number) (std::vector<int>, numbers) ) #endif 

grammar.hxx:

 #ifndef GRAMMAR_HXX #define GRAMMAR_HXX #include "ast.hxx" #include <boost/spirit/home/x3.hpp> namespace parser { namespace x3 = boost::spirit::x3; using my_rule_type = x3::rule<class my_rule_class, ast::ast_struct>; BOOST_SPIRIT_DECLARE( my_rule_type ); const my_rule_type &get_my_rule(); } #endif 

grammar.cxx:

 #include "grammar_def.hxx" #include "config.hxx" namespace parser { BOOST_SPIRIT_INSTANTIATE( my_rule_type, iterator_type, context_type ) } 

grammar_def.hxx:

 #ifndef GRAMMAR_DEF_HXX #define GRAMMAR_DEF_HXX #include <iostream> #include <boost/spirit/home/x3.hpp> #include "grammar.hxx" #include "ast.hxx" namespace parser { namespace x3 = boost::spirit::x3; const my_rule_type my_rule( "my_rule" ); unsigned n; auto f = []( auto &ctx ) { n = x3::_attr(ctx); }; auto my_rule_def = x3::int_[f] >> +( x3::omit[+x3::blank] >> x3::int_ ); BOOST_SPIRIT_DEFINE( my_rule ) const my_rule_type &get_my_rule() { return my_rule; } } #endif 

config.hxx:

 #ifndef CONFIG_HXX #define CONFIG_HXX #include <string> #include <boost/spirit/home/x3.hpp> namespace parser { namespace x3 = boost::spirit::x3; using iterator_type = std::string::const_iterator; using context_type = x3::unused_type; } #endif 

main.cxx:

 #include "ast.hxx" #include "grammar.hxx" #include "config.hxx" #include <iostream> #include <boost/spirit/home/x3.hpp> #include <string> namespace x3 = boost::spirit::x3; using namespace std; void parse( const std::string &data ) { parser::iterator_type begin = data.begin(); parser::iterator_type end = data.end(); ast::ast_struct ast; cout << "Parsing [" << string(begin,end) << "]" << endl; bool r = x3::parse( begin, end, parser::get_my_rule(), ast ); if ( r && begin == end ) { std::copy(ast.numbers.begin(), ast.numbers.end(), std::ostream_iterator<int>(std::cout << ast.numbers.size() << " elements: ", " ")); cout << endl; } else cout << "Parse failed" << endl; } int main() { parse( "3 1 2 3" ); parse( "4 1 2 3 4" ); return 0; } 

Compiling main.cxx and grammar.cxx (flags: -std = C ++ 14) and running the code above the fingerprints:

 Parsing [3 1 2 3] 0 elements: Parsing [4 1 2 3 4] 0 elements: 

Sorry for the long source code, I tried to make it as small as possible.

Please note that I have the use of the global variable unsigned n, it will be used with a custom repeat directive (see here and one of the solutions here ). To focus the question, I removed some of the repetition from this question, so although I could remove the semantic action in this example, this is not a possible solution.

I would appreciate help in identifying this problem, it is not clear to me why the above code does not work. Thank you in advance.

+5
source share
1 answer

I have to admit that actually restoring your sample was too much for me (call me lazy ...).

However, I know the answer and the trick to make your life easier.

Answer

Semantic actions on the definition of a rule prohibit the automatic distribution of attributes. From Qi docs (the same goes for X3, but I always lose the link to the docs):

r = p; Rule definition
This is equivalent to r% = p (see below) if semantic actions are not tied anywhere in p.

r% = p; Auto Rule Definition
The p attribute must be compatible with the synthesized r attribute. When p is successful, its attribute automatically propagates to the r synthesized attribute.

Trick

In this case, you can enter the state (link n ) using the x3::with<> directive. So you do not have a global namespace ( n ) and can make the parser reentrant, thread safe, etc.

Here, my β€œsimplifier” perceives things in one file:

 namespace parsing { x3::rule<struct parser, ast::ast_struct> parser {"parser"}; struct state_tag { }; auto record_number = [](auto &ctx) { unsigned& n = x3::get<state_tag>(ctx); n = x3::_attr(ctx); }; auto parser_def = x3::rule<struct parser_def, ast::ast_struct> {} %= x3::int_[record_number] >> +(x3::omit[+x3::blank] >> x3::int_); BOOST_SPIRIT_DEFINE(parser) } 

Tip : run the demo with = instead of %= to see the difference in behavior!

Note that get<state_tag>(ctx) returns reference_wrapper<unsigned> only because we use the parser as follows:

 void parse(const std::string &data) { using namespace std; ast::ast_struct ast; unsigned n; auto parser = x3::with<parsing::state_tag>(ref(n)) [parsing::parser] >> x3::eoi; if (x3::parse(data.begin(), data.end(), parser, ast)) { cout << "n: " << n << ", "; copy(ast.numbers.begin(), ast.numbers.end(), ostream_iterator<int>(cout << ast.numbers.size() << " elements: ", " ")); cout << "\n"; } else cout << "Parse failed\n"; } 

Demo version

Live on coliru

 #include <boost/fusion/include/adapt_struct.hpp> #include <boost/spirit/home/x3.hpp> #include <iostream> namespace ast { struct ast_struct { int number; std::vector<int> numbers; }; } BOOST_FUSION_ADAPT_STRUCT(ast::ast_struct, number, numbers) namespace x3 = boost::spirit::x3; namespace parsing { x3::rule<struct parser, ast::ast_struct> parser {"parser"}; struct state_tag { }; auto record_number = [](auto &ctx) { unsigned& n = x3::get<state_tag>(ctx); // note: returns reference_wrapper<T> n = x3::_attr(ctx); }; auto parser_def = x3::rule<struct parser_def, ast::ast_struct> {} %= x3::int_[record_number] >> +(x3::omit[+x3::blank] >> x3::int_); BOOST_SPIRIT_DEFINE(parser) } void parse(const std::string &data) { using namespace std; ast::ast_struct ast; unsigned n = 0; auto parser = x3::with<parsing::state_tag>(ref(n)) [parsing::parser] >> x3::eoi; if (x3::parse(data.begin(), data.end(), parser, ast)) { cout << "n: " << n << ", "; copy(ast.numbers.begin(), ast.numbers.end(), ostream_iterator<int>(cout << ast.numbers.size() << " elements: ", " ")); cout << "\n"; } else cout << "Parse failed\n"; } int main() { parse("3 1 2 3"); parse("4 1 2 3 4"); } 

Print

 n: 3, 3 elements: 1 2 3 n: 4, 4 elements: 1 2 3 4 
+6
source

Source: https://habr.com/ru/post/1236869/


All Articles