Как я могу использовать полиморфные атрибуты с парсерами boost:: spirit:: qi?
Я хотел бы, чтобы мой синтаксический анализатор boost::spirit был способен анализировать файл, преобразовывать проанализированные правила в различные типы и выдавать вектор, содержащий все найденные совпадения. Все типы, которые передаются в качестве атрибутов, должны быть унаследованы от базового типа, например:
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapt_struct.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/foreach.hpp>
struct CommandBase
{
virtual void commandAction()
{
std::cout << "This is a base command. You should never see this!" << std::endl;
//Boost::spirit seems to get mad if I make this purely virtual. Clearly I'm doing it wrong.
}
};
struct CommandTypeA : public CommandBase
{
int valueA;
int valueB;
virtual void commandAction()
{
std::cout << "CommandType A! ValueA: " << valueA << " ValueB: " << valueB << std::endl;
}
};
struct CommandTypeB : public CommandBase
{
double valueA;
std::vector<char> valueB;
virtual void commandAction()
{
std::cout << "CommandType B! valueA: " << valueA << " string: " << std::string(valueB.begin(), valueB.end()) << std::endl;
}
};
struct CommandTypeC : public CommandBase
{
//Represents a sort of "subroutine" type where multiple commands can be grouped together
std::vector<char> labelName;
std::vector<boost::shared_ptr<CommandBase> > commands;
virtual void commandAction()
{
std::cout << "Subroutine: " << std::string(labelName.start(), labelName.end())
<< " has " << commands.size() << " commands:" << std::endl;
BOOST_FOREACH(boost::shared_ptr<CommandBase> c, commands)
{
c->commandAction();
}
}
};
Теперь, моя попытка синтаксический код:
namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
using qi::lit_;
BOOST_FUSION_ADAPT_STRUCT(
CommandTypeA,
(int, valueA)
(int, valueB)
)
BOOST_FUSION_ADAPT_STRUCT(
CommandTypeB,
(double, valueA)
(std::vector<char>, valueB)
)
BOOST_FUSION_ADAPT_STRUCT(
CommandTypeC,
(std::vector<char>, labelName)
(std::vector<boost::shared_ptr<CommandBase> >, commands)
)
template<typename Iterator, typename Skipper = ascii::space_type>
struct CommandParser : qi::grammar<Iterator, std::vector<boost::shared_ptr<CommandBase> >(), Skipper>
{
public:
CommandParser() : CommandParser()::base_type(commands)
{
CommandARule = qi::int_ >> qi::int_ >> lit("CMD_A");
CommandBRule = qi::int_ >> +(qi::char_) >> lit("CMD_B");
CommandCRule = qi::char_(':') >> lexeme[+(qi::char_ - ';' - ascii::space) >> +ascii::space] >> commands >> qi::char_(';');
commands = +(CommandARule | CommandBRule | CommandCRule);
}
protected:
qi::rule<Iterator, boost::shared_ptr<CommandTypeA>, Skipper> CommandARule;
qi::rule<Iterator, boost::shared_ptr<CommandTypeB>, Skipper> CommandBRule;
qi::rule<Iterator, boost::shared_ptr<CommandTypeC>, Skipper> CommandCRule;
qi::rule<Iterator, std::vector<boost::shared_ptr<CommandBase> >, Skipper> commands;
};
std::vector<boost::shared_ptr<CommandBase> > commandList;
bool success = qi::phrase_parse(StartIterator, EndIterator, CommandParser, ascii::space, commandList);
BOOST_FOREACH(boost::shared_ptr<CommandBase> c, commandList)
{
c->commandAction();
}
Теперь этот код определенно не будет компилироваться, но я надеюсь, что он донесет суть того, что я пытаюсь сделать.
Главное зависание-это кажется, что правила qi:: хотят излучать реальную структуру, а не ссылку на нее.
Мой вопрос таков:
Можно ли заставить qi:: rule выдавать ссылку, совместимую с полиморфизмом, как я пытаюсь (если да, то как), и является ли это лучшим подходом для того, что я пытаюсь выполнить (а именно список исполняемых объектов, представляющих разбираемые команды и их параметры)?
1 ответ:
Дух намного дружелюбнее к компилятивному времени-полиморфизму
Но давайте предположим, что вы действительно хотите сделать старомодный полиморфизм...typedef variant<Command1, Command2, Command3> Command;
Однако простое обновление полиморфных объектов на лету во время синтаксического анализа-это верный способ
- сделайте ваш парсер раздутым семантическими действиями
- создать много утечек памяти при обратном отслеживании в правилах грамматики
- сделайте синтаксический анализ ужасно медленным (потому что у вас есть все виды динамического анализа). распределение продолжается).
- хуже всего то, что ничего из этого не будет оптимизировано, даже если вы на самом деле не передаете ссылку на атрибут в API верхнего уровня
parse
. (Обычно вся обработка атрибутов" волшебным образом " испаряется во время компиляции, что очень полезно для проверки входного формата)Таким образом, вы захотите создать держатель для объектов вашего базового командного класса или производного. Сделайте держатель удовлетворяет RuleOfZero и получить фактическую стоимость путем тип стирания.
(Помимо решения "случайной" сложности и ограничения w. r. t.рекультивации памяти, бонус к этой абстракции заключается в том, что вы все еще можете выбрать статическую обработку хранилища, поэтому вы экономите [много] времени в выделении кучи.)
Я посмотрю на ваш образец, чтобы увидеть, смогу ли я быстро продемонстрировать его.Вот что я имею в виду с классом' holder ' (добавьте виртуальный деструктор в
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; };
Как вы можете видеть, я выбрал. Я не мог заставитьunique_ptr
для simples семантика владения здесь (Avariant
позволит избежать некоторых накладных расходов на распределение в качестве оптимизации позже)unique_ptr
работать с духом, потому что дух просто не осознает движения. (Дух X3 будет).Мы можем тривиально реализовать стираемый тип
AnyCommand
на основании этого держателя: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; };
Таким образом, теперь вы можете "назначить" любую команду любой команде и использовать ее "полиморфно" через держатель, даже если держатель и любая команда имеют идеальные значение-семантика.
Этот пример грамматики будет делать:
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; }
С правилами, определенными следующим образом:
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;
Это довольно восхитительное сочетание семантики значения и полиморфизма времени выполнения:)
Главный тест
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"; } }
Отпечатки пальцев:
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
Смотрите все это жить на Колиру