2014-04-25 28 views
6

Chciałbym, aby mój parser :: parser ducha był w stanie przetworzyć plik, przekonwertować przeanalizowane reguły na różne typy i wysłać wektor zawierający wszystkie znalezione dopasowania. Wszystkich typów, które są emitowane jako atrybuty powinny być dziedziczone od typu bazy, na przykład:Jak korzystać z polimorficznych atrybutów przy pomocy parowników boost :: spirit :: qi?

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

Teraz mój próbował kod parsera:

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

Teraz ten kod zdecydowanie wygrał” t skompilować, ale mam nadzieję, że to ma sens dla tego, co próbuję zrobić.

Główne rozłączenie polega na tym, że reguły qi :: wydają się chcieć emitować rzeczywistą strukturę, a nie odniesienia do niej.

Moje pytanie brzmi zatem:

Czy to możliwe, aby wymusić qi :: regułę emitować odniesienie polimorfizmu kompatybilne jakbym próbuje (jeśli tak, to w jaki sposób) i jest to najlepsze podejście do tego, co Próbuję wykonać (mianowicie listę obiektów wykonywalnych reprezentujących analizowane polecenia i ich parametry)?

+1

Wiesz, że możesz użyć 'std :: string' chociaż parser naraża' std :: vector '? To jedna z wbudowanych transformacji atrybutów. – sehe

Odpowiedz

4

Duch jest dużo bardziej przyjazne dla compiletime-polimorfizm

typedef variant<Command1, Command2, Command3> Command; 

Ale załóżmy, że naprawdę chcą zrobić staromodny polimorfizmu rzecz ...

Wystarczy newing-up obiektów polimorficzne w locie podczas parsowania jednak to najpewniejszy sposób, aby

  • uczynić parser nadęty z działaniami semantycznych
  • tworzy wiele wycieków pamięci w śledzeniu wstecz w regułach gramatyki
  • parsowanie niesamowicie wolno (ponieważ masz cały czas alokację dynamiczną).
  • Co najgorsze, żadna z nich nie zostanie zoptymalizowana, nawet jeśli nie przekazujesz odwołania do atrybutu do najwyższego poziomu API parse. (Zwykle cała obsługa atrybutów "magicznie" wyparowuje w czasie kompilacji, co jest bardzo przydatne przy walidacji formatu wejściowego)

Więc będziesz chciał utworzyć uchwyt dla obiektów klasy bazowej lub wyprowadzonych . Upewnij się, że posiadacz spełnia RuleOfZero i otrzymasz rzeczywistą wartość przez typ wymazania.

(Poza rozwiązaniem "przypadkowej" złożoności i ograniczeniem rekultywacji pamięci, premia do tej abstrakcji polega na tym, że nadal możesz zdecydować się na obsługę pamięci masowej statycznie, dzięki czemu oszczędzasz [dużo] czasu w alokacjach sterty.)

Przyjrzę się Twojej próbce, aby sprawdzić, czy mogę ją szybko pokazać.

Oto, co mam na myśli z klasą "posiadacza" (dodaj wirtualny destruktor do 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; 
}; 

Jak widać Zdecydowałem unique_ptr dla semantyki simples własności tutaj (a variant by uniknąć jakiś narzut przydziału jako optymalizacji później) . Nie mogłem sprawić, by unique_ptr pracował z Duchem, ponieważ Duch nie jest po prostu ruchomy. (Duch X3 będzie).

Możemy trywialnie zaimplementować typu skasowaneAnyCommand na podstawie tego uchwytu:

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

Więc teraz można „przypisać” dowolne polecenie do AnyCommand i używać go „polimorficznie” przez posiadacza, nawet chociaż posiadacz i AnyCommand mają doskonałą semantykę wartości.

Gramatyka próbka zrobi:

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

z zasadami określonymi jako:

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; 

Jest to dość wykwintna mieszanka wartościujących semantyki i runtime-polimorfizmu :)

Podstawowy test z

Wydruki:

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 

zobaczyć to wszystko Live On Coliru

+0

Ponieważ uważasz, że polimorfizm w czasie kompilacji z wariantem boost :: jest lepszym podejściem, czy mógłbyś to również rozwinąć? – stix

+0

Hehehe. Przypuszczam, że zrobiłaby to jedna z wielu odpowiedzi (http://stackoverflow.com/search?tab=votes&q=user%3a85371%20%5bboost-spirit%5d%20variant) :) Chciałbym wymyślić rozwiązanie mostkowania czasu kompilacji (Spirit) vs polimorficznego (Domain Model) mostu później, aby zobaczyć, że może działać. – sehe

+1

@stix Zaktualizowałem odpowiedź za pomocą próbki, która używa właściciela do zarządzania alokacją/czasem trwania rzeczywistych poleceń, jednocześnie eksponując czystą semantykę wartości dla gramatyki: zobacz ** [Live On Coliru] (http: // /coliru.stacked-crooked.com/a/0aa334ae7c42375f)** – sehe