2014-04-25 48 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

相关问题