Reputation: 6821
I am trying to compile a parser with the following rules:
else_statement =
lit("else") > statement;
if_statement =
lit("if") >> '(' >> expression >> ')' >> statement >> -else_statement;
The attribute of else_statement
is statement
, as is the statement
rule that it consumes. The attribute of if_statement
is a struct with members respectively types expression
, statement
and an optional statement
(boost::optional<statement>
).
Using the following BOOST_FUSION_ADAPT_STRUCT
's:
BOOST_FUSION_ADAPT_STRUCT(ast::statement, m_statement_node)
BOOST_FUSION_ADAPT_STRUCT(ast::if_statement, m_condition, m_then, m_else)
where m_statement_node
is a boost::variant
for different statements that are possible.
I'd expect that if an else_statement
is present, it will be put into the boost::optional<statement>
, since the attribute of else_statement
is statement
. And this does work if I comment out the lit("else") >
in the else_statement
rule! But with the lit("else")
present something strange happens: now boost::spirit is trying to fit a statement
into the member of the optional statement
(the boost::variant) which obviously won't compile because that only takes an A or B.
The resulting compile error looks like this:
/usr/include/boost/variant/variant.hpp:1534:38: error: no matching function for call to ‘boost::variant<ast::A, ast::B>::initializer::initialize(void*, const ast::statement&)’
What am I doing wrong? How can I solve this?
Below a complete test snippet that shows the error (and compiles when the lit("else") >
is comment out).
// File: so.cpp
// Compile as: g++ -std=c++11 so.cpp
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/optional/optional_io.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace ast
{
struct A { int a; friend std::ostream& operator<<(std::ostream& os, A const&) { return os << "A"; } };
struct B { int b; friend std::ostream& operator<<(std::ostream& os, B const&) { return os << "B"; } };
struct expression { int e; friend std::ostream& operator<<(std::ostream& os, expression const&) { return os << "expression"; } };
using statement_node = boost::variant<A, B>;
struct statement
{
statement_node m_statement_node;
friend std::ostream& operator<<(std::ostream& os, statement const& statement)
{ return os << "STATEMENT:" << statement.m_statement_node; }
};
struct if_statement
{
expression m_condition;
statement m_then;
boost::optional<statement> m_else;
friend std::ostream& operator<<(std::ostream& os, if_statement const& if_statement)
{
os << "IF_STATEMENT:" << if_statement.m_condition << "; " << if_statement.m_then;
if (if_statement.m_else)
os << "; " << if_statement.m_else;
return os;
}
};
} // namespace ast
BOOST_FUSION_ADAPT_STRUCT(ast::expression, e)
BOOST_FUSION_ADAPT_STRUCT(ast::A, a)
BOOST_FUSION_ADAPT_STRUCT(ast::B, b)
BOOST_FUSION_ADAPT_STRUCT(ast::statement, m_statement_node)
BOOST_FUSION_ADAPT_STRUCT(ast::if_statement, m_condition, m_then, m_else)
namespace client
{
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
template <typename Iterator>
class test_grammar : public qi::grammar<Iterator, ast::if_statement(), qi::space_type>
{
private:
template<typename T> using rule = qi::rule<Iterator, T(), qi::space_type>;
rule<ast::A> a;
rule<ast::B> b;
rule<ast::statement> statement;
rule<ast::statement> else_statement;
rule<ast::if_statement> if_statement;
rule<int> expression;
public:
test_grammar() : test_grammar::base_type(if_statement, "result_grammar")
{
using namespace qi;
statement =
a | b;
else_statement =
lit("else") > statement;
if_statement =
lit("if") >> '(' >> expression >> ')' >> statement >> -else_statement;
expression =
int_;
a = 'A';
b = 'B';
BOOST_SPIRIT_DEBUG_NODES(
(statement)
(else_statement)
(if_statement)
(expression)
(a)
(b)
);
}
};
} // namespace client
int main()
{
std::string const input{"if (1) A B"};
using iterator_type = std::string::const_iterator;
using test_grammar = client::test_grammar<iterator_type>;
namespace qi = boost::spirit::qi;
test_grammar program;
iterator_type iter{input.begin()};
iterator_type const end{input.end()};
ast::if_statement out;
bool r = qi::phrase_parse(iter, end, program, qi::space, out);
if (!r || iter != end)
{
std::cerr << "Parsing failed." << std::endl;
return 1;
}
std::cout << "Parsed: " << out << std::endl;
}
Upvotes: 2
Views: 103
Reputation: 393829
Automatic attribute propagation rules have some trouble with Fusion sequences that consist of a single element. You can work around it here by declaring:
rule<ast::statement_node> statement;
(changing from ast::statement
to ast::statement_node
).
This works: Live On Coliru
The more tedious workaround is to avoid having a single-element fusion sequence there. You can add a dummy field to statement
:
struct statement
{
statement_node m_statement_node;
int dummy;
friend std::ostream& operator<<(std::ostream& os, statement const& statement)
{ return os << "STATEMENT:" << statement.m_statement_node; }
};
BOOST_FUSION_ADAPT_STRUCT(ast::statement, m_statement_node, dummy)
And then add a value for it:
statement = (a | b) >> attr(42);
This removes confusion as well.
// File: so.cpp
// Compile as: g++ -std=c++11 so.cpp
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/optional/optional_io.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace ast
{
struct A { int a; friend std::ostream& operator<<(std::ostream& os, A const&) { return os << "A"; } };
struct B { int b; friend std::ostream& operator<<(std::ostream& os, B const&) { return os << "B"; } };
struct expression { int e; friend std::ostream& operator<<(std::ostream& os, expression const&) { return os << "expression"; } };
using statement_node = boost::variant<A, B>;
struct statement
{
statement_node m_statement_node;
int dummy;
friend std::ostream& operator<<(std::ostream& os, statement const& statement)
{ return os << "STATEMENT:" << statement.m_statement_node; }
};
struct if_statement
{
expression m_condition;
statement m_then;
boost::optional<statement> m_else;
friend std::ostream& operator<<(std::ostream& os, if_statement const& if_statement)
{
os << "IF_STATEMENT:" << if_statement.m_condition << "; " << if_statement.m_then;
if (if_statement.m_else)
os << "; " << if_statement.m_else;
return os;
}
};
} // namespace ast
BOOST_FUSION_ADAPT_STRUCT(ast::expression, e)
BOOST_FUSION_ADAPT_STRUCT(ast::A, a)
BOOST_FUSION_ADAPT_STRUCT(ast::B, b)
BOOST_FUSION_ADAPT_STRUCT(ast::statement, m_statement_node, dummy)
BOOST_FUSION_ADAPT_STRUCT(ast::if_statement, m_condition, m_then, m_else)
namespace client
{
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
template <typename Iterator>
class test_grammar : public qi::grammar<Iterator, ast::if_statement(), qi::space_type>
{
private:
template<typename T> using rule = qi::rule<Iterator, T(), qi::space_type>;
rule<ast::A> a;
rule<ast::B> b;
rule<ast::statement> statement;
rule<ast::statement> else_statement;
rule<ast::if_statement> if_statement;
rule<int> expression;
public:
test_grammar() : test_grammar::base_type(if_statement, "result_grammar")
{
using namespace qi;
statement = (a | b) >> attr(42);
else_statement = lit("else") > statement;
if_statement = lit("if") >> '(' >> expression >> ')' >> statement >> -else_statement;
expression = int_;
a = 'A' >> attr(1);
b = 'B' >> attr(2);
BOOST_SPIRIT_DEBUG_NODES( (statement) (else_statement) (if_statement) (expression) (a) (b));
}
};
} // namespace client
int main()
{
for (std::string const input : {
"if (1) A else B",
})
{
using iterator_type = std::string::const_iterator;
using test_grammar = client::test_grammar<iterator_type>;
namespace qi = boost::spirit::qi;
test_grammar program;
iterator_type iter = input.begin(), end = input.end();
ast::if_statement out;
bool r = qi::phrase_parse(iter, end, program, qi::space, out);
if (!r || iter != end)
{
std::cerr << "Parsing failed." << std::endl;
return 1;
}
std::cout << "Parsed: " << out << std::endl;
}
}
Prints
Parsed: IF_STATEMENT:expression; STATEMENT:A; STATEMENT:B
Note, though, that you get the same confusion back if you mistakenly make the else_statement
rule also the same length:
else_statement = lit("else") > statement > attr(42); // this is wrong
Of course that doesn't actually make sense, but the error message DOES help in explaining what the real problem under the hood is (if the "upstream" fusion sequence appears "compatible" then it is "deconstructed" for propagation). The relevant comment in qi/nonterminal/rule.hpp:
// do up-stream transformation, this integrates the results
// back into the original attribute value, if appropriate
traits::post_transform(attr_param, attr_);
Upvotes: 1