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!
First of all:
I'd steer cleer from smart pointers in Spirit
I'd steer away from semantic actions if you don't need them (you don't) Boost Spirit: "Semantic actions are evil"?
the problem is with the placeholder: you can't use it statically, you need to use it in a lazy expression (Phoenix Actor)
Using the phoenix::function from the first link:
#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>