Search code examples
c++boost-spiritboost-spirit-qi

cannot construct std::string from placeholder in Boost.Spirit


I have started working on a Boost.Spirit based simple parser that will parse a C++-like file (the only C++-ish part is the built-in template types; e.g. map<string, smart_ptr<int>> name_object_map; - but this is built-in the compiler, and user cannot declare template classes). Nevertheless, the grammar is intended to contain data structure declarations, and not expressions, except the constant expressions that are used for initialization of enumerator declarations; enum E { a = 4 * 5 + 3 }; is valid. This is not currently a problem for me, because I couldn't parse E the way I want yet :)

I have made the following yesterday, after reading the docs and examples, but it doesn't compile:

#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_char_class.hpp>
#include <cassert>
#include <memory>
#include <string>
#include <utility>

struct context {};

class foo {
  std::string name;
  const context *ctx;

public:
  foo(const std::string &name, const context *ctx) : name(name), ctx(ctx) {}
};

using foo_ref = std::shared_ptr<foo>;

template <typename Iterator>
struct skipper : boost::spirit::qi::grammar<Iterator> {
  skipper() : skipper::base_type(start) {
    using namespace boost::spirit;
    qi::char_type char_;
    ascii::space_type space;

    start = space                             // tab/space/cr/lf
            | "/*" >> *(char_ - "*/") >> "*/" // C-style comments
        ;
  }

  boost::spirit::qi::rule<Iterator> start;
};

template <typename Iterator>
struct the_parser : boost::spirit::qi::grammar<Iterator, std::vector<foo_ref>(),
                                               skipper<Iterator>> {
  the_parser() : the_parser::base_type(start), current_context(&root) {
    using namespace boost::spirit;
    namespace phx = boost::phoenix;

    identifier = qi::lexeme[qi::alpha >> *qi::alnum];
    start = *(foo_decl); // currently, no semantic action attached.
                         // This will create the root decl in ast.

    foo_decl = (lit("foo") >> identifier)[qi::_val = std::make_shared<foo>(
                                               qi::_1, current_context)] >>
               qi::char_('{') >> qi::char_('}') >> qi::char_(';');
    BOOST_SPIRIT_DEBUG_NODES((identifier)(start)(foo_decl));
  }
  boost::spirit::qi::rule<Iterator, std::string(), skipper<Iterator>>
      identifier;
  boost::spirit::qi::rule<Iterator, std::vector<foo_ref>(), skipper<Iterator>>
      start;
  boost::spirit::qi::rule<Iterator, foo_ref(), skipper<Iterator>> foo_decl;
  context root;
  const context *current_context;
};

int main() {
  the_parser<std::string::const_iterator> parser;
  std::vector<foo_ref> root;

  const std::string content = "foo john_doe { };";
  auto first = content.cbegin(), last = content.cend();
  bool r = boost::spirit::qi::phrase_parse(
      first, last, parser, skipper<std::string::const_iterator>(), root);
  assert(r && first == last);
}

Compiling this with clang on Mac (the first line is std::make_shared):

error: no matching constructor for initialization of 'foo'
          __second_(_VSTD::forward<_Args2>(_VSTD::get<_I2>(__second_args))...)
          ^         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
note: candidate constructor not viable: no known conversion from 'const boost::phoenix::actor<boost::spirit::argument<0> >' to 'const std::string' (aka
  'const basic_string<char, char_traits<char>, allocator<char> >') for 1st argument
  foo(const std::string &name, const context *ctx) : name(name), ctx(ctx) {}
  ^

In foo_decls semantic action, it cannot construct a foo (through std::make_shared), because the first attribute's result cannot be converted to std::string. However, if I add a class member std::string s, and do this instead, it works:

foo_decl = (lit("foo") >> identifier)[boost::phoenix::ref(s) = qi::_1] >>
                 qi::char_('{') >> qi::char_('}') >> qi::char_(';');

Likewise, if I try to std::cout it, I can see john_doe is printed.

If I bind a member function call with phoenix, it also works:

foo_decl = (lit("foo") >> identifier)[qi::_val =
                   boost::phoenix::bind(&the_parser, this, qi::_1)] >>
           qi::char_('{') >> qi::char_('}') >> qi::char_(';');

foo_ref make_foo(const std::string &n) {
  return std::make_shared(n, current_context);
}

These last three workarounds mean that there is an implicit conversion sequence from decltype(qi::_1) to std::string; isn't this correct?

I would be very happy, if you could show me my mistake or the gap in my understanding of how semantic actions, and placeholders work. It looks very strange to me as to why std::make_shared doesn't work.

Thank you!


Solution

  • First of all:

    Using the phoenix::function from the first link:

    Live On Coliru

    #define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <cassert>
    #include <memory>
    #include <string>
    #include <utility>
    
    namespace {
        template <typename T> struct make_shared_f {
            template <typename... A> struct result { typedef std::shared_ptr<T> type; };
    
            template <typename... A> typename result<A...>::type operator()(A &&... a) const {
                return std::make_shared<T>(std::forward<A>(a)...);
            }
        };
    
        template <typename T> using make_shared_ = boost::phoenix::function<make_shared_f<T> >;
    }
    
    struct context {};
    
    class foo {
        std::string name;
        const context *ctx;
    
      public:
        foo(const std::string &name, const context *ctx) : name(name), ctx(ctx) {}
    };
    
    using foo_ref = std::shared_ptr<foo>;
    
    template <typename Iterator> struct skipper : boost::spirit::qi::grammar<Iterator> {
        skipper() : skipper::base_type(start) {
            using namespace boost::spirit;
            qi::char_type char_;
            ascii::space_type space;
    
            start = space                             // tab/space/cr/lf
                    | "/*" >> *(char_ - "*/") >> "*/" // C-style comments
                ;
        }
    
        boost::spirit::qi::rule<Iterator> start;
    };
    
    template <typename Iterator>
    struct the_parser : boost::spirit::qi::grammar<Iterator, std::vector<foo_ref>(), skipper<Iterator> > {
        the_parser() : the_parser::base_type(start), current_context(&root) {
            using namespace boost::spirit;
            namespace phx = boost::phoenix;
    
            identifier = qi::alpha >> *qi::alnum;
            // This will create the root decl in ast.
    
            foo_decl = ("foo" >> identifier) [qi::_val = make_shared_<foo>{}(qi::_1, current_context)] >>
                       '{' >> '}' >> ';';
    
            start      = *foo_decl; // currently, no semantic action attached.
            BOOST_SPIRIT_DEBUG_NODES((identifier)(start)(foo_decl));
        }
        boost::spirit::qi::rule<Iterator, std::string()> identifier;
        boost::spirit::qi::rule<Iterator, foo_ref(), skipper<Iterator> > foo_decl;
        boost::spirit::qi::rule<Iterator, std::vector<foo_ref>(), skipper<Iterator> > start;
        context root;
        const context *current_context;
    };
    
    int main() {
        the_parser<std::string::const_iterator> parser;
        std::vector<foo_ref> root;
    
        const std::string content = "foo johndoe { };";
        auto first = content.cbegin(), last = content.cend();
        bool r = boost::spirit::qi::phrase_parse(first, last, parser, skipper<std::string::const_iterator>(), root);
        if (r)
            std::cout << "success\n";
        else
            std::cout << "failed\n";
    
        if (first != last)
            std::cout << "remaining unparsed: '" << std::string(first,last) << "'\n";
    
    }
    

    Prints

    success
    

    Along with the debug output of

    <start>
      <try>foo johndoe { };</try>
      <foo_decl>
        <try>foo johndoe { };</try>
        <identifier>
          <try>johndoe { };</try>
          <success> { };</success>
          <attributes>[[j, o, h, n, d, o, e]]</attributes>
        </identifier>
        <success></success>
        <attributes>[0x60600000ebb0]</attributes>
      </foo_decl>
      <foo_decl>
        <try></try>
        <fail/>
      </foo_decl>
      <success></success>
      <attributes>[[0x60600000ebb0]]</attributes>
    </start>