vendredi 23 juin 2017

Boost spirit compile error for trivial grammar

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.

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;
}

Aucun commentaire:

Enregistrer un commentaire