用Boost Spirit解析语法

时间:2010-06-20 03:51:22

标签: c++ parsing boost-spirit boost-spirit-qi

我正在尝试解析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)
)

1 个答案:

答案 0 :(得分:12)

就调试而言,它可以使用正常的break和watch方法。尽管如何格式化规则使这变得困难。如果您根据精神示例进行格式化(每行〜一个解析器,每行一个凤凰语句),断点将提供更多信息。

您的数据结构无法区分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(基本上){{1 }}。不可能将这些强制转换为没有autorule的字符串,但可以通过将规则附加到每个已解析的char来完成:

vector<char>

然而,让精神做这种转换更加清洁。因此,定义一个新规则:

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 ] 有几个问题。这将解析零或多个指令规则,后跟“,”。它还尝试push_back一个(*instruction >> ",")[ push_back( at_c<1>(qi::_val) , qi::_1 ) ](不知道为什么这个编译,也许没有实例化,因为缺少启动规则?)。我想你想要以下(标识修改):

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 ] >> ")"; 和列表运算符-,后者等同于%。然后,phoenix表达式只是将向量直接赋给结构成员,但您也可以将操作直接附加到指令匹配并使用push_back。

指令规则没问题,我只想提一下它等同于instruction >> *("," >> instruction)

最后一件事,如果instruction %= (command|tag)A()之间没有区别(即你的原始结构没有SOME),你可以只使用autorules编写这个解析器:

commandFlag

这是使用融合包裹结构的大好处,该结构可以密切地模拟输入。