Search code examples
c++boost-spirit-qiboost-variantboost-fusion

boost::spirit arithmetic formulas parser fails to compile


I am trying to write a spirit parser for arithmetic expression which fills an abstract syntax tree. The parser compiles if I am not trying to fill the AST, but fails (with one 24K error) in the current version. I am using clang++ 3.5.0 with -std=c++11, and running on Ubuntu 14.4.

#include <string>
#include <vector>
#include <utility>

#include <boost/spirit/include/qi.hpp>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/adapted.hpp>

#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>

using std::string;
using std::vector;
using std::pair;

using boost::spirit::qi::grammar;
using boost::spirit::qi::space_type;
using boost::spirit::qi::rule;

struct Term; // forward dec
typedef boost::recursive_wrapper<Term> RWTerm;
typedef pair<char, RWTerm> OpAndRWTerm;
typedef pair<RWTerm, vector<OpAndRWTerm> > Expr;
typedef boost::variant<Expr, double> Factor;
typedef pair<char, Factor> OpAndFactor;
struct Term : public pair<Factor, vector<OpAndFactor> >{};

template<typename It>
struct formula_parser : grammar<It, Expr(), space_type> {
  formula_parser() : formula_parser::base_type(expr_rule) {
    using boost::spirit::qi::double_;
    using boost::spirit::ascii::char_;

    factor_rule %= double_ | parenthesis_rule;
    parenthesis_rule %= '(' >> expr_rule >> ')';
    op_and_factor_rule %= char_("/*") >> factor_rule;
    term_rule %= factor_rule >> *op_and_factor_rule;
    op_and_term_rule %= char_("+-") >> term_rule;
    expr_rule %= term_rule >> *op_and_term_rule;
  }
  rule<It, OpAndRWTerm(), space_type> op_and_term_rule;
  rule<It, Expr(), space_type> expr_rule;
  rule<It, OpAndFactor(), space_type> op_and_factor_rule;
  rule<It, RWTerm(), space_type> term_rule;
  rule<It, Expr(), space_type> parenthesis_rule;
  rule<It, Factor(), space_type> factor_rule;
};

int main() {
  formula_parser<string::const_iterator> grammar;
}

What I understand from the error message is that fusion mixes up the Types Factor and RWTerm in the rule term_rule.

what am I doing wrong ?


Solution

  • It compiles for me if I change two things:

    1. Since Term inherits from std::pair, Term is a new type. For this reason you need to apply BOOST_FUSION_ADAPT_STRUCT to Term, regardless whether this has been already done for std::pair in <boost/fusion/adapted/std_pair.hpp>:

      BOOST_FUSION_ADAPT_STRUCT(
          Term,
          (Factor, first)
          (std::vector<OpAndFactor>, second)
      )
      

      Alternatively, you could make Term a standalone struct with two members and then apply BOOST_FUSION_ADAPT_STRUCT on that:

      struct Term { Factor f; std::vector<OpAndFactor>  o;};
      
      BOOST_FUSION_ADAPT_STRUCT(
          Term,
          (Factor, f)
          (std::vector<OpAndFactor>, o)
      )
      

      By the way: You have to fully qualify std::vector here, because the following will not compile:

      using std::vector;
      BOOST_FUSION_ADAPT_STRUCT(
          Term,
          (Factor, f)
          (vector<OpAndFactor>, o)
      )
      
    2. Use Term instead of RWTerm when declaring term_rule:

      rule<It, Term(), space_type> term_rule;
      

    full code:

    #include <string>
    #include <vector>
    #include <utility>
    
    #include <boost/spirit/include/qi.hpp>
    
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/adapted.hpp>
    #include <boost/fusion/include/adapted.hpp>
    
    #include <boost/variant/variant.hpp>
    #include <boost/variant/recursive_wrapper.hpp>
    
    using boost::spirit::qi::grammar;
    using boost::spirit::qi::space_type;
    using boost::spirit::qi::rule;
    
    struct Term; // forward dec
    typedef boost::recursive_wrapper<Term> RWTerm;
    typedef std::pair<char, RWTerm> OpAndRWTerm;
    typedef std::pair<RWTerm, std::vector<OpAndRWTerm> > Expr;
    typedef boost::variant<Expr, double> Factor;
    typedef std::pair<char, Factor> OpAndFactor;
    struct Term : public std::pair<Factor, std::vector<OpAndFactor> >{};
    
    BOOST_FUSION_ADAPT_STRUCT(
        Term,
        (Factor, first)
        (std::vector<OpAndFactor>, second)
    )
    
    
    template<typename It>
    struct formula_parser : grammar<It, Expr(), space_type> {
      formula_parser() : formula_parser::base_type(expr_rule) {
        using boost::spirit::qi::double_;
        using boost::spirit::ascii::char_;
    
        factor_rule %= double_ | parenthesis_rule;
        parenthesis_rule %= '(' >> expr_rule >> ')';
        op_and_factor_rule %= char_("/*") >> factor_rule;
        term_rule %= factor_rule >> *op_and_factor_rule;
        op_and_term_rule %= char_("+-") >> term_rule;
        expr_rule %= term_rule >> *op_and_term_rule;
      }
      rule<It, OpAndRWTerm(), space_type> op_and_term_rule;
      rule<It, Expr(), space_type> expr_rule;
      rule<It, OpAndFactor(), space_type> op_and_factor_rule;
      rule<It, Term(), space_type> term_rule;
      rule<It, Expr(), space_type> parenthesis_rule;
      rule<It, Factor(), space_type> factor_rule;
    };
    
    int main() {
      formula_parser<std::string::const_iterator> grammar;
    }
    

    live example