How to Use Polymorphic Attributes with Boost::Spirit::Qi Parsers

How can I use polymorphic attributes with boost::spirit::qi parsers?

Spirit is a lot friendlier to compiletime-polymorphism

typedef variant<Command1, Command2, Command3> Command;

But, let's suppose you really want to do the old-fashioned polymorphism thing...

Just newing-up the polymorphic objects on the fly during parsing, however, is a sure-fire way to

  • make your parser bloated with semantic actions
  • create lot of memory leaks on back-tracking in the grammar rules
  • make parsing awesomely slow (because you have all manner of dynamic allocation going on).
  • Worst of all, none of this would be optimized away, even when you're not actually passing an attribute reference into the top-level parse API. (Usually, all attribute handling "magically" vaporizes at compile-time, which is very useful for input format validation)

So you'll want to create a holder for objects of your base-command class, or derived. Make the holder satisfy RuleOfZero and get the actual value out by type erasure.

(Beyond solving the "accidental" complexity and limits w.r.t. memory reclamation, a bonus to this abstraction is that you you can still opt to handle the storage statically, so you save [a lot] of time in heap allocations.)

I'll look at your sample to see whether I can demonstrate it quickly.

Here is what I mean with a 'holder' class (add a virtual destructor to CommandBase!):

struct CommandHolder
{
template <typename Command> CommandHolder(Command cmd)
: storage(new concrete_store<Command>{ std::move(cmd) }) { }

operator CommandBase&() { return storage->get(); }
private:
struct base_store {
virtual ~base_store() {};
virtual CommandBase& get() = 0;
};
template <typename T> struct concrete_store : base_store {
concrete_store(T v) : wrapped(std::move(v)) { }
virtual CommandBase& get() { return wrapped; }
private:
T wrapped;
};

boost::shared_ptr<base_store> storage;
};

As you can see I opted for unique_ptr for simples ownership semantics here (a variant would avoid some allocation overhead as an optimization later). I couldn't make unique_ptr work with Spirit because Spirit is simply not move-aware. (Spirit X3 will be).

We can trivially implement a type-erased AnyCommand based on this holder:

struct AnyCommand : CommandBase
{
template <typename Command> AnyCommand(Command cmd)
: holder(std::move(cmd)) { }

virtual void commandAction() override {
static_cast<CommandBase&>(holder).commandAction();
}
private:
CommandHolder holder;
};

So now you can "assign" any command to an AnyCommand and use it "polymorphically" through the holder, even though the holder and AnyCommand have perfect value-semantics.

This sample grammar will do:

CommandParser() : CommandParser::base_type(commands)
{
using namespace qi;
CommandARule = int_ >> int_ >> "CMD_A";
CommandBRule = double_ >> lexeme[+(char_ - space)] >> "CMD_B";
CommandCRule = ':' >> lexeme [+graph - ';'] >> commands >> ';';

command = CommandARule | CommandBRule | CommandCRule;
commands = +command;
}

With the rules defined as:

qi::rule<Iterator, CommandTypeA(),            Skipper> CommandARule;
qi::rule<Iterator, CommandTypeB(), Skipper> CommandBRule;
qi::rule<Iterator, CommandTypeC(), Skipper> CommandCRule;
qi::rule<Iterator, AnyCommand(), Skipper> command;
qi::rule<Iterator, std::vector<AnyCommand>(), Skipper> commands;

This is quite a delightful mix of value-semantics and runtime-polymorphism :)

The test main of

int main()
{
std::string const input =
":group \n"
" 3.14 π CMD_B \n"
" -42 42 CMD_A \n"
" -inf -∞ CMD_B \n"
" +inf +∞ CMD_B \n"
"; \n"
"99 0 CMD_A";

auto f(begin(input)), l(end(input));

std::vector<AnyCommand> commandList;
CommandParser<std::string::const_iterator> p;
bool success = qi::phrase_parse(f, l, p, qi::space, commandList);

if (success) {
BOOST_FOREACH(AnyCommand& c, commandList) {
c.commandAction();
}
} else {
std::cout << "Parsing failed\n";
}

if (f!=l) {
std::cout << "Remaining unparsed input '" << std::string(f,l) << "'\n";
}
}

Prints:

Subroutine: group has 4 commands:
CommandType B! valueA: 3.14 string: π
CommandType A! ValueA: -42 ValueB: 42
CommandType B! valueA: -inf string: -∞
CommandType B! valueA: inf string: +∞
CommandType A! ValueA: 99 ValueB: 0

See it all Live On Coliru

Parsing inherited struct with boost spirit

So there's two questions here, I feel:

  • How to adapt derived classes Can I use BOOST_FUSION_ADAPT_STRUCT with inherited stuff?
  • How can I use polymorphic attributes with boost::spirit::qi parsers?

Update

Small demo of approach 2 in the context of this question:

Live On Wandbox

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
#include <map>
#include <iomanip> // std::quoted

struct Base {}; // dynamic polymorphism not required for us now, no problem if you want it

struct A : Base {
int value;
void operator()() const { std::cout << "Hello A: " << value << std::endl; };
};

struct B : Base {
float value;
void operator()() const { std::cout << "Hello B: " << value << std::endl; };
};

using Node = boost::variant<A, B>;
struct BaseContainer {
using Map = std::multimap<std::string, Node>;
Map container;
};

BOOST_FUSION_ADAPT_STRUCT(A, value)
BOOST_FUSION_ADAPT_STRUCT(B, value)
BOOST_FUSION_ADAPT_STRUCT(BaseContainer, container)

namespace qi = boost::spirit::qi;

template <typename It>
struct Parser : qi::grammar<It, BaseContainer()> {
Parser() : Parser::base_type(start) {
using namespace qi;
_key = lexeme['"' >> *('\\' >> char_ | ~char_('"')) >> '"'];
_a_node = "A(" >> int_ >> ")";
_b_node = "B(" >> float_ >> ")";
_node = _a_node | _b_node;
_pair = '{' >> _key >> ',' >> _node >> '}';
_container = '{' >> -(_pair % ',') >> '}';

start = skip(space) [ _container ];

BOOST_SPIRIT_DEBUG_NODES((start)(_container)(_pair)(_key)(_node)(_a_node)(_b_node))
}
private:
qi::rule<It, BaseContainer()> start;

// lexeme
qi::rule<It, std::string()> _key;

using Skipper = qi::space_type;
using Pair = std::pair<std::string, Node>;

qi::rule<It, BaseContainer::Map(), Skipper> _container;
qi::rule<It, Pair(), Skipper> _pair;
qi::rule<It, Node(), Skipper> _node;
qi::rule<It, A(), Skipper> _a_node;
qi::rule<It, B(), Skipper> _b_node;
};

int main() {
Parser<std::string::const_iterator> const p;
for (std::string const input : {
R"({})",
R"({ { "one", A(42) } })",
R"({ { "two", B(3.14) } })",
R"({ { "three", A( -42 ) }, { "four", B( -3.14 ) } })",
})
{
std::cout << "-------\n";
std::cout << "Parsing " << input << "\n";
auto f = begin(input), l = end(input);

BaseContainer result;
if (qi::parse(f, l, p, result)) {
for (auto const& [k,v] : result.container) {
std::cout << " Key " << std::quoted(k) << ": ";
boost::apply_visitor([](auto const& node) { node(); }, v);
}
} else {
std::cout << "Parse failed\n";
}

if (f!=l) {
std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << "\n";
}
}
}

Prints

-------
Parsing {}
-------
Parsing { { "one", A(42) } }
Key "one": Hello A: 42
-------
Parsing { { "two", B(3.14) } }
Key "two": Hello B: 3.14
-------
Parsing { { "three", A( -42 ) }, { "four", B( -3.14 ) } }
Key "four": Hello B: -3.14
Key "three": Hello A: -42

Boost Spirit eliminate left recursion from simple addition operator

There are a number of things about your example.

First off, you use auto with Spirit Qi expressions. That's not valid, and leads to UB:

  • Assigning parsers to auto variables
  • also boost spirit qi parser failed in release and pass in debug
  • undefined behaviour somewhere in boost::spirit::qi::phrase_parse
  • boost spirit V2 qi bug associated with optimization level

Next off, you chose to use polymorphic Ast nodes. That's possible but likely not efficient:

  • How can I use polymorphic attributes with boost::spirit::qi parsers?
  • Semantic actions runs multiple times in boost::spirit parsing
  • but also making a vector of shared pointers from Spirit Qi which you may have found

Finally, there's the left recursion, since your expression starts with itself, leading to infinite recursion. The only way to solve it is to split your productions up into "levels" of expressions. This also aids in generating the desired operator precedence:

 expression = term >> char_("-+") >> term;
term = factor >> char_("*/%") >> factor;
factor = simple >> char_("^") >> simple;

In your case, I'd suggest:

    simple
= qi::double_
[_val = make_shared_<DoubleNode>()(_1)];
;

expression
= (simple >> '+' >> expression)
[_val = make_shared_<AddNode>()(_1, _2)]
| simple
;

Of course, you can be simpler and slightly less inefficient here:

    expression 
= simple [_val = _1]
>> *(('+' >> expression)
[_val = make_shared_<AddNode>()(_val, _0)])
;

Full Demo

Live On Coliru

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

namespace { // https://stackoverflow.com/a/21565350/85371

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 AstNode {
virtual ~AstNode() = default;
};
using AstNodePtr = std::shared_ptr<AstNode>;
struct DoubleNode : AstNode {
DoubleNode(double) {}
};
struct AddNode : AstNode {
AddNode(AstNodePtr, AstNodePtr) {}
};

#include <iomanip>

namespace qi = boost::spirit::qi;

template<typename Iterator>
struct simple_grammar : qi::grammar<Iterator, AstNodePtr()> {

simple_grammar() : simple_grammar::base_type(start) {
using namespace qi::labels;

simple
= qi::double_
[_val = make_shared_<DoubleNode>()(_1)];
;

expression
= simple [_val = _1]
>> *(('+' >> expression)
[_val = make_shared_<AddNode>()(_val, _1)])
;

start = qi::skip(qi::space) [ expression ];
BOOST_SPIRIT_DEBUG_NODES((start)(expression)(simple))
}
private:
qi::rule<Iterator, AstNodePtr()> start;
qi::rule<Iterator, AstNodePtr(), qi::space_type> expression;
// implicit lexemes
qi::rule<Iterator, AstNodePtr()> simple;
};

int main() {
simple_grammar<std::string::const_iterator> g;

for (std::string const input : {
"1 + 2",
"3.14"
})
{
auto f = begin(input), l = end(input);
AstNodePtr ast;
if (qi::parse(f, l, g, ast)) {
std::cout << "Succeeded: " << boost::core::demangle(typeid(*ast).name()) << "\n";
} else {
std::cout << "Failed\n";
}

if (f!=l)
std::cout << "Remaining unparsed " << std::quoted(std::string(f,l)) << "\n";
}
}

Prints

Succeeded: AddNode
Succeeded: DoubleNode

With Boost.spirit, is there any way to pass additional argument to attribute constructor?

There is no direct way, but you can probably explicitely initialize things:

qi::rule<It, optional<S>(), Skipper> myrule;

myrule %=
qi::eps [ _val = phoenix::construct<S>(42) ] >>
int_parser<S()>;

However, since you are returning it from the int_parser, my intuition says that default-initialization should be appropriate (or perhaps the type S doesn't have a single, clear, responsibility?).

Edit

In response to the comment, it looks like you want this:

T someTvalue;    

myrule = qi::int_
[ qi::_val = phx::construct<S>(someTvalue, qi::_1) ];

Or, if someTvalue is a variable outside the grammar constructor, and may change value during execution of the parser (and it lives long enough!), you could do

myrule = qi::int_ 
[ qi::_val = phx::construct<S>(phx::ref(someTvalue), qi::_1) ];

Hope that helps

Synthesize to smart pointers with Boost Spirit X3

You can do semantic actions, or you can define traits for you custom types. However, see here Semantic actions runs multiple times in boost::spirit parsing (especially the two links there) - basically, consider not doing that.

I need to parse a complex AST, and it would be impossible to allocate this AST on heap memory

This somewhat confusing statement leads me to the logical conclusion that you merely need to allocate from a shared memory segment instead.

In the good old spirit of Rule Of Zero you could make a value-wrapper that does the allocation using whatever method you prefer and still enjoy automatic attribute propagation with "value semantics" (which will server as mere "handles" for the actual object in shared memory).

If you need any help getting this set up, feel free to post a new question.

How to use a pointer as token attribute in Boost::Spirit::Lex?

Okay. Don't take this badly. Reading your sample (kudos for including a self-contained example! This saves a ton of time) I can't help but feeling that you've somehow stumbled on the worst possible cross-section of anti-patterns in Spirit Qi.

  1. You're using a polymorphic AST:

    • How can I use polymorphic attributes with boost::spirit::qi parsers?
    • Semantic actions runs multiple times in boost::spirit parsing
    • Parsing inherited struct with boost spirit
  2. You're using semantic actions. As a rule this already misses the sweet spot for embedded grammars, which is why I linked 126 answers to Boost Spirit: "Semantic actions are evil"?.

    However, that's even just talking about semantic actions for Qi. You also use them for Lex:

    self +=
    spaces[([](auto& start, auto& end, auto& matched, auto& id,
    auto& ctx) { matched = lex::pass_flags::pass_ignore; })];

    Which is then further complicated by not using Phoenix, e.g.:

    self += spaces[lex::_pass = lex::pass_flags::pass_ignore];

    Which does exactly the same but with about 870% less noise and equal amounts of evil magic.

  3. The other semantic action tops it all:

    self += number[(
    [](auto& start, auto& end, auto& matched, auto& id, auto& ctx) {
    auto val = new Number();
    auto iter = start;
    qi::parse(iter, end, qi::long_long, val->_val);
    ctx.set_value(val);
    })];

    Besides having all the problems already listed, it literally makes a fractal out of things by calling Qi from a Lex semantic action. Of course, this wants to be:

    self += number[lex::_val = phx::new_<Number>(/*magic*/)];

    But that magic doesn't exist. My gut feeling is that your issue that the Lexer shouldn't be concerned with AST types at all. At this point I feel that the lexer could/should be something like

    using TokenTypes = boost::mpl::vector<uint64_t>;
    using Iterator = std::string::const_iterator; // NOTE const_

    struct Lexer : lex::lexer<actor_lexer<token<Iterator, TokenTypes>>> {
    lex::token_def<> spaces;
    lex::token_def<uint64_t> number;

    Lexer() : spaces{R"(\s+)"}, number{R"(\d+)"} {
    self += '(';
    self += ')';
    self += spaces[lex::_pass = lex::pass_flags::pass_ignore];
    self += number;
    }
    };

    That is, if it should exist at all.

That's the structural assessment. Let me apply simplifications to the Qi grammar along the same lines, just so we can reason about the code:

struct Parser : qi::grammar<Lexer::iterator_type, Object*()> {
Parser() : base_type(elem) {
using namespace qi::labels;

static constexpr qi::_a_type _list{};
const auto _objs = phx::bind(&List::_objs, _list);

list = ( //
'(' >> //
*(elem[phx::push_back(_objs, _1)]) //
>> ')' //
)[_val = phx::new_<List>(_list)];

elem //
= list[_val = _1] //
| lexer.number[_val = phx::new_<Number>(_1)];
}

Lexer lexer; // TODO FIXME excess scope

private:
using It = Lexer::iterator_type;
qi::rule<It, Object*(), qi::locals<List>> list;
qi::rule<It, Object*()> elem;
};

Note how I made the local List instead of List*, to just slightly reduce the chance of memory leaks. I guess for efficiency you could try to make Phoenix do move-semantics for you:

 [_val = phx::new_<List>(phx::static_cast_<List&&>(_list))];

But at that point I wouldn't trust all the expression templates to do what you want and go to the more elaborate (even assuming c++17):

phx::function move_new = [](List& l) { return new List(std::move(l)); };

list = ( //
'(' >> //
*(elem[phx::push_back(_objs, _1)]) //
>> ')' //
)[_val = move_new(_list)];

Now we arrive at a workable demo:

Live On Coliru

int main() {
Parser parser;

for (std::string const line : {
"",
"42",
"()",
"(1 2 3)",
"(1 (44 55 () 66) 3)",
}) {
auto begin = line.begin();
Object* obj = nullptr;
if (lex::tokenize_and_parse(begin, line.end(), parser.lexer, parser,
obj)) {
obj->print(std::cout << std::quoted(line) << " -> ");
delete obj;
} else {
std::cout << std::quoted(line) << " -> FAILED";
}
std::cout << std::endl;
}
}

Printing

"" -> FAILED
"42" -> 42
"()" -> ()
"(1 2 3)" -> (1 2 3 )
"(1 (44 55 () 66) 3)" -> (1 (44 55 () 66 ) 3 )

Note that this simple test program ALREADY leaks 11 objects, for a total of 224 bytes. That's not even complicating things with error-handling or backtracking rules.

That's craziness. You could of course fix it with smart pointers, but that just further complicates everything while making sure performance will be very poor.

Further Simplifications

I would stop using Lex and dynamic polymorphism:

No More Lex:

The only "value" Lex is adding here is skipping spaces. Qi is very capable (see e.g. Boost spirit skipper issues for variations on that theme), so we'll use skip(space)[] instead:

Live On Coliru

#include <boost/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

struct Object {
virtual ~Object() = default;
virtual void print(std::ostream& out) const = 0;

friend std::ostream& operator<<(std::ostream& os, Object const& o) { return o.print(os), os; }
};

struct Number : Object {
Number(uint64_t v = 0) : _val(v) {}
int64_t _val;
virtual void print(std::ostream& out) const override { out << _val; }
};

struct List : Object {
std::vector<Object*> _objs;

virtual void print(std::ostream& out) const override {
out << '(';
for (auto&& el : _objs)
out << ' ' << *el;
out << ')';
}
};

namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;

template <typename It>
struct Parser : qi::grammar<It, Object*()> {
Parser() : Parser::base_type(start) {
using namespace qi::labels;

static constexpr qi::_a_type _list{};
const auto _objs = phx::bind(&List::_objs, _list);

phx::function move_new = [](List& l) { return new List(std::move(l)); };

list = ( //
'(' >> //
*(elem[phx::push_back(_objs, _1)]) //
>> ')' //
)[_val = move_new(_list)];

elem //
= list[_val = _1] //
| qi::uint_[_val = phx::new_<Number>(_1)] //
;

start = qi::skip(qi::space)[elem];
}

private:
qi::rule<It, Object*(), qi::space_type, qi::locals<List>> list;
qi::rule<It, Object*(), qi::space_type> elem;

// lexemes
qi::rule<It, Object*()> start;
};

int main() {
Parser<std::string::const_iterator> const parser;

for (std::string const line : {
"",
"42",
"()",
"(1 2 3)",
"(1 (44 55 () 66) 3)",
}) {
Object* obj = nullptr;
if (parse(line.begin(), line.end(), parser >> qi::eoi, obj)) {
std::cout << std::quoted(line) << " -> " << *obj;
} else {
std::cout << std::quoted(line) << " -> FAILED";
}
delete obj;
std::cout << std::endl;
}
}

Still leaking like C++ went out of fashion, but at least doing so in 20 fewer LoC and half the compile time.

Static Polymorphism

Hiding all the raw pointer stuff (or avoiding it completely, depending on the exact AST requirements):

using Number = uint64_t;
using Object = boost::make_recursive_variant< //
Number, //
std::vector<boost::recursive_variant_>>::type;

using List = std::vector<Object>;

For ease of supplying operator<< I moved them into an AST namespace below.

The parser goes down to:

template <typename It> struct Parser : qi::grammar<It, AST::Object()> {
Parser() : Parser::base_type(start) {
list = '(' >> *elem >> ')';
elem = list | qi::uint_;

start = qi::skip(qi::space)[elem];
}
private:
qi::rule<It, AST::List(), qi::space_type> list;
qi::rule<It, AST::Object(), qi::space_type> elem;
qi::rule<It, AST::Object()> start;
};

No more lex, no more phoenix, no more leaks, no more manual semantic actions. Just, expressive code.

Live Demo

Live On Coliru

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

namespace AST {
struct Number {
uint64_t v;
Number(uint64_t v = 0) : v(v){};
};

using Object = boost::make_recursive_variant< //
Number, //
std::vector<boost::recursive_variant_>>::type;

using List = std::vector<Object>;

std::ostream& operator<<(std::ostream& os, Number const& n) {
return os << n.v;
}
std::ostream& operator<<(std::ostream& os, List const& l) {
os << '(';
for (auto& el : l)
os << ' ' << el;
return os << ')';
}
} // namespace AST

namespace qi = boost::spirit::qi;

template <typename It> struct Parser : qi::grammar<It, AST::Object()> {
Parser() : Parser::base_type(start) {
list = '(' >> *elem >> ')';
elem = list | qi::uint_;

start = qi::skip(qi::space)[elem];
}
private:
qi::rule<It, AST::List(), qi::space_type> list;
qi::rule<It, AST::Object(), qi::space_type> elem;
qi::rule<It, AST::Object()> start;
};

int main() {
Parser<std::string::const_iterator> const parser;

for (std::string const line : {
"",
"42",
"()",
"(1 2 3)",
"(1 (44 55 () 66) 3)",
}) {
AST::Object obj;
if (parse(line.begin(), line.end(), parser >> qi::eoi, obj))
std::cout << std::quoted(line) << " -> " << obj << "\n";
else
std::cout << std::quoted(line) << " -> FAILED\n";
}
}

Prints

"" -> FAILED
"42" -> 42
"()" -> ()
"(1 2 3)" -> ( 1 2 3)
"(1 (44 55 () 66) 3)" -> ( 1 ( 44 55 () 66) 3)

But this time, without leaking memory. And also, it now compiles fast enough that Compiler Explorer can also handle it.

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

First of all:

  • I'd steer cleer from smart pointers in Spirit

    • making a vector of shared pointers from Spirit Qi

    • How can I use polymorphic attributes with boost::spirit::qi parsers?

  • 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:

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));
}


Related Topics



Leave a reply



Submit