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_decl
s 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!