2014-04-25 50 views
6

我想我的基於boost :: spirit的解析器能夠解析文件,將解析的規則轉換爲不同的類型,併發出一個包含所有找到的匹配的向量。所有這一切會放出屬性的類型應該從基類繼承,例如:如何在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(); 
     }   
    } 
}; 

現在,我試圖解析器代碼:

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 :: rules似乎想要發出實際的結構,而不是引用它。

我的問題是這樣的:

是否有可能迫使齊::規則發出多態性兼容參考像我嘗試(如果是,如何),這是最好的方法是什麼我試圖完成(即表示解析命令及其參數的可執行對象列表)?

+1

你知道,即使解析器公開'的std ::矢量'你可以使用'的std :: string'?這是內置屬性轉換的一個 – sehe

回答

4

精神是很多友好到編譯時多態

typedef variant<Command1, Command2, Command3> Command; 

但是,讓我們假設你真的想要做的老式多態性的事情...

只是newing行動的多態對象在分析過程中的蒼蠅,但是,是一個安全可靠的方式來

  • 讓你的解析器臃腫語義動作
  • 在語法規則中創建大量回溯內存泄漏
  • 使解析非常緩慢(因爲您擁有所有動態分配方式)。
  • 最糟糕的是,即使您沒有將屬性參考實際傳遞到頂級parse API,也不會優化這些內容。 (通常,所有屬性處理在編譯時都會「神奇地」蒸發,這對於輸入格式驗證非常有用)。

因此,您需要創建基本命令類或派生對象的持有者。使持有者滿足RuleOfZero並通過類型擦除獲得實際值。 (除了解決「意外」複雜性並限制內存回收之外,這種抽象的好處是您仍然可以選擇靜態地處理存儲,因此您可以在堆分配中節省大量時間。)

我會看看你的樣品,看看我能否快速展示它。

這是我的意思是'持有人'類(添加一個虛擬析構函數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所有權語義這裏(一 variant將避免一些分配開銷爲優化後) 。我無法讓unique_ptr與Spirit一起工作,因爲Spirit根本就沒有移動意識。 (精神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; 
}; 

所以,現在你可以在「分配」的任何命令的AnyCommand並使用「多態」,通過持有它,哪怕儘管持有者和AnyCommand具有完美的價值語義。

此示例語法會做:

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 

看到這一切Live On Coliru

+0

因爲您認爲使用boost :: variant編譯時多態是一種更好的方法,您能否詳細說明一下? – stix

+0

呵呵呵。我想[許多答案]之一(http://stackoverflow.com/search?tab=votes&q=user%3a85371%20%5bboost-spirit%5d%20variant)會做:)我有興趣提出一個編譯時(精神)vs運行時多態(域模型)橋解決方案,只是爲了看到它可以工作。 – sehe

+1

@stix我已經用一個樣例更新了答案,該樣例使用持有者來管理實際命令的分配/生命週期,同時爲語法提供純粹的值語義:請參閱** [Live On Coliru](http:/ /coliru.stacked-crooked.com/a/0aa334ae7c42375f)** – sehe

相關問題