4
votes

I have a more complex rule, but this one will suffice for this question (I hope). Consider the rule:

result = double_ >> *(char_ > int_);

where result is declared in terms of a struct result in namespace ast:

qi::rule<Iterator, ast::result(), qi::space_type> result;

Then how does ast::result have to look like? According to the boost::spirit docs (http://www.boost.org/doc/libs/develop/libs/spirit/doc/html/spirit/abstracts/attributes/compound_attributes.html), the attribute of char_ > int_ is tuple<char, int> or std::pair<char, int>. So, I tried:

namespace ast
{

using second_type = std::vector<std::pair<char, int>>;

struct result
{
  double first;
  second_type second;
};

} // namespace ast

in addition to

BOOST_FUSION_ADAPT_STRUCT(
  ast::result,
  (double, first),
  (ast::second_type, second)
)

But this gives me the compile error:

error: no matching function for call to 'std::pair<char, int>::pair(const char&)'

This rule is simple, creating the AST struct that the result will be stored in should be simple too... but how?

Here is a complete test program with my attempt:

 #include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <iostream>
#include <string>
#include <vector>

namespace ast
{

using second_type = std::vector<std::pair<char, int>>;

struct result
{
  double first;
  second_type second;

  friend std::ostream& operator<<(std::ostream& os, result const& result);
};

std::ostream& operator<<(std::ostream& os, second_type::value_type val)
{
  return os << val.first << ' ' << val.second;
}

std::ostream& operator<<(std::ostream& os, result const& result)
{
  os << result.first;
  for (auto& i : result.second)
      os << ' ' << i;
  return os;
}

} // namespace ast

BOOST_FUSION_ADAPT_STRUCT(
  ast::result,
  (double, first),
  (ast::second_type, second)
)

namespace client
{

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

template <typename Iterator>
class test_grammar : public qi::grammar<Iterator, ast::result(), qi::space_type>
{
 private:
  qi::rule<Iterator, ast::result(), qi::space_type> result;

 public:
  test_grammar() : test_grammar::base_type(result, "result_grammar")
  {
    using qi::double_;
    using qi::char_;
    using qi::int_;

    result = double_ >> *(char_ > int_);
  }
};

} // namespace client

int main()
{
  std::string const input{"3.4 a 5 b 6 c 7"};
  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::result 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;
}
2

2 Answers

3
votes

SirGuy changed the AST to suit the default synthesized attributes. At the cost of, indeed complicating the AST.

However, you could leverage attribute compatibity rules by adapting std::pair. In fact, that is as simple as including 1 header:

#include <boost/fusion/include/std_pair.hpp>

Then, everything compiles without change, printing:

Parsed: 3.4 a 5 b 6 c 7
1
votes

I made the following changes:

#include <boost/fusion/tuple.hpp>

using second_type = std::vector<boost::fusion::tuple<char, int>>;

std::ostream& operator<<(std::ostream& os, second_type::value_type val)
{
    return os << boost::fusion::get<0>(val) << ' ' << boost::fusion::get<1>(val);
}

and the result compiled for me. There are most certainly other solutions available too.