2010-06-20 73 views
9

我试图解析C函数像树这样的表达式如下(使用Spirit Parser Framework):解析与升压灵语法

F(A() , B(GREAT(SOME , NOT)) , C(YES)) 

在此,我想使用在以下三个规则语法:

template< typename Iterator , typename ExpressionAST > 
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), space_type> { 

    InputGrammar() : InputGrammar::base_type() { 
     tag = (qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9"))[ push_back(at_c<0>(qi::_val) , qi::_1) ]; 
     command = tag [ at_c<0>(qi::_val) = at_c<0>(qi::_1) ] >> "(" >> (*instruction >> ",") 
             [ push_back(at_c<1>(qi::_val) , qi::_1) ] >> ")"; 
     instruction = (command | tag) [qi::_val = qi::_1]; 
    } 
    qi::rule< Iterator , ExpressionAST() , space_type > tag; 
    qi::rule< Iterator , ExpressionAST() , space_type > command; 
    qi::rule< Iterator , ExpressionAST() , space_type > instruction; 
}; 

发现我的标签规则只是试图捕捉到的表达式(在“功能”的名称)中使用的标识符。还请注意,标记规则的签名会返回ExpressionAST而不是std::string,如同大多数示例中一样。我想这样做的原因实际上很简单:我讨厌使用变体,如果可能的话,我会避免它们。我想,保留蛋糕和吃它太好了。

命令应该以标记(当前节点的名称,AST节点的第一个字符串字段)和可变数量的括号括起来的参数开始,每个参数可以是标记本身或另一个命令。

但是,这个例子根本不起作用。它编译和一切,但在运行时它不能解析我所有的测试字符串。而真正让我恼火的是我无法弄清楚如何修复它,因为我无法真正调试上面的代码,至少在这个词的传统意义上是如此。基本上我能看到的唯一方法就是修正上面的代码,就是知道我做错了什么。

所以,问题是我不知道上面的代码有什么问题。你如何定义上面的语法?

ExpressionAST型我使用的是:

struct MockExpressionNode { 
    std::string name; 
    std::vector<MockExpressionNode> operands; 

    typedef std::vector<MockExpressionNode>::iterator iterator; 
    typedef std::vector<MockExpressionNode>::const_iterator const_iterator; 

    iterator begin() { return operands.begin(); } 
    const_iterator begin() const { return operands.begin(); } 
    iterator end() { return operands.end(); } 
    const_iterator end() const { return operands.end(); } 

    bool is_leaf() const { 
     return (operands.begin() == operands.end()); 
    } 
}; 

BOOST_FUSION_ADAPT_STRUCT(
    MockExpressionNode, 
    (std::string, name) 
    (std::vector<MockExpressionNode>, operands) 
) 
+0

我最近发现的一点是,C和C++标识符的名字中可能有'$'字符。因此,a-z,A-Z,0-9(除了第一个字符),_和$在C/C++标识符中都是有效的。 – Cthutu 2013-11-20 15:47:53

+2

@Cthutu MSVC允许在标识符中使用重音字符。这并不意味着它符合标准。 – 2013-11-20 23:02:10

+0

更重要的是,您试图制作@Cthutu的重点是什么?标识符是否有短缺?你的编译器不正确地支持名字空间吗? – sehe 2013-11-20 23:03:50

回答

11

至于调试,它可以使用正常的休息和观赏方法。这是由你如何格式化规则,但困难。如果您按照精神示例进行格式化(每行一个解析器,每行一个凤凰语句),断点将更具信息性。

您的数据结构没有办法将A()SOME区分开来,因为它们都是叶子(如果我错过了某些东西,请告诉我)。从您的变体评论中,我不认为这是您的意图,因此为了区分这两种情况,我在MockExpressionNode中添加了bool commandFlag成员变量(对于A()为true,对于SOME为false),并添加了相应的融合适配器行。

对于代码而言,您需要将开始规则传递给基类的构造,即:

InputGrammar() : InputGrammar::base_type(instruction) {...} 

这是语法的切入点,就是为什么你没有得到解析的任何数据。我很惊讶它编译没有它,我认为语法类型需要匹配第一条规则的类型。即使如此,这是一个便利的惯例。

对于tag规则,实际上有两个分析器qi::char_("a-zA-Z_"),其与_1型char*qi::char_("a-zA-Z_0-9")其_2与类型(基本上)vector<char>。它不可能强迫这些转换成字符串没有autorules,但它可以通过将一个规则,每个字符解析来实现:

tag = qi::char_("a-zA-Z_") 
     [ at_c<0>(qi::_val) = qi::_1 ]; 
    >> *qi::char_("a-zA-Z_0-9")   //[] has precedence over *, so _1 is 
     [ at_c<0>(qi::_val) += qi::_1 ]; // a char rather than a vector<char> 

然而,它更清洁,让精神做这种转换。所以定义一个新的规则:

qi::rule< Iterator , std::string(void) , ascii::space_type > identifier; 
identifier %= qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9"); 

不要担心它;)。然后,标签变成

tag = identifier 
     [ 
      at_c<0>(qi::_val) = qi::_1, 
      ph::at_c<2>(qi::_val) = false //commandFlag 
     ] 

为命令,第一部分是好的,但那里有与(*instruction >> ",")[ push_back(at_c<1>(qi::_val) , qi::_1) ]几个问题。这将解析零个或多个指令规则,后跟一个“,”。它也试图push_back vector<MockExpressionNode>(不知道为什么这样编译,也许没有实例化,因为缺少启动规则?)。我想你想以下(与标识符修改):

command = 
     identifier 
     [ 
      ph::at_c<0>(qi::_val) = qi::_1, 
      ph::at_c<2>(qi::_val) = true //commandFlag 
     ] 
    >> "(" 
    >> -(instruction % ",") 
     [ 
      ph::at_c<1>(qi::_val) = qi::_1 
     ] 
    >> ")"; 

这将使用可选的运营商-和列表操作符%,后者相当于instruction >> *("," >> instruction)。然后,phoenix表达式将向量直接分配给结构成员,但您也可以直接将操作附加到指令匹配并使用push_back。

指令规则没问题,我只是提及它相当于instruction %= (command|tag)

最后一两件事,如果确实是A()SOME之间没有区别(即你没有commandFlag原来的结构),你可以只使用autorules写这个解析器:

template< typename Iterator , typename ExpressionAST > 
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), ascii::space_type> { 
    InputGrammar() : InputGrammar::base_type(command) { 
     identifier %= 
      qi::char_("a-zA-Z_") 
     >> *qi::char_("a-zA-Z_0-9"); 
     command %= 
      identifier 
     >> -(
      "(" 
     >> -(command % ",") 
     >> ")"); 
    } 
    qi::rule< Iterator , std::string(void) , ascii::space_type > identifier; 
    qi::rule< Iterator , ExpressionAST(void) , ascii::space_type > command; 
}; 

这是大好处使用紧密模拟输入的融合包裹结构。

+0

嗨AcademicRobot,优秀的职位。我花了几天的时间来回答,只是因为有太多的消息想要了解操作员,所以我没有真正阅读过这些文档。也试图用 qi :: _ val.setAsFlag()替换你的commandFlag setter。但显然_val的类型不同于ExpressionAST,但是某种类型的演员凤凰包装 – lurscher 2010-06-22 15:31:33

+1

@lurscher - 很高兴您发现它有帮助。是的,qi :: _ val将评估为ExpressionAST,但实际上并不是这种类型。要调用成员函数,你需要使用phoenix bind(对于memfun'void setAsFlag(bool flag)'):'phoenix :: bind(&ExpressionAST :: setAsFlag,qi :: _ val,true)'。 – academicRobot 2010-06-22 16:06:12

+0

奇怪的是,它有一定的void *味道.. – lurscher 2010-06-22 18:25:21