Как я могу использовать полиморфные атрибуты с парсерами 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 6

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 семантика владения здесь (A variant позволит избежать некоторых накладных расходов на распределение в качестве оптимизации позже) . Я не мог заставить 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

Смотрите все это жить на Колиру