I would like my boost::spirit-based parser to be able to parse a file, convert the parsed rules into different types, and emit a vector containing all of the matches it foun
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
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 . I couldn't make unique_ptr
for simples ownership semantics here (a variant
would avoid some allocation overhead as an optimization later)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