【问题标题】:Parsing a grammar with Boost Spirit使用 Boost Spirit 解析语法
【发布时间】:2011-03-05 22:10:34
【问题描述】:

我正在尝试解析类似于树表达式的 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)
)

【问题讨论】:

  • 最近我发现 C 和 C++ 标识符的名称中可以包含“$”字符。因此 a-z、A-Z、0-9(第一个字符除外)、_ 和 $ 在 C/C++ 标识符中有效。
  • @Cthutu MSVC 允许在标识符中使用重音字符。并不意味着它符合标准。
  • 更重要的是,你试图让@Cthutu 有什么意义?标识符是否短缺?您的编译器是否不正确支持命名空间?

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


【解决方案1】:

就调试而言,可以使用正常的中断和监视方法。但是,您如何格式化规则使这变得困难。如果您按照精神示例进行格式化(~每行一个解析器,每行一个凤凰语句),断点将提供更多信息。

您的数据结构无法区分 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&lt;char&gt;。在没有自动规则的情况下不可能将它们强制转换为字符串,但可以通过将规则附加到每个解析的字符来完成:

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 &gt;&gt; ",")[ push_back( at_c&lt;1&gt;(qi::_val) , qi::_1 ) ] 存在一些问题。这将解析零个或多个指令规则,后跟一个“,”。它还尝试 push_back 一个 vector&lt;MockExpressionNode&gt; (不知道为什么这也编译了,可能因为缺少启动规则而没有实例化?)。我认为您想要以下内容(修改标识符):

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 &gt;&gt; *("," &gt;&gt; instruction)。 phoenix 表达式然后只是将向量直接分配给结构成员,但您也可以将操作直接附加到指令匹配并使用 push_back。

指令规则没问题,我就提一下,相当于instruction %= (command|tag)。

最后一件事,如果 A() 和 SOME 之间实际上没有区别(即你的原始结构没有 commandFlag),你可以只使用自动规则来编写这个解析器:

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

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

【讨论】:

  • 嗨 AcademicRobot,出色的帖子。我花了几天时间回复,只是因为有太多关于运营商的内容需要消化,我没有真正阅读过文档。还试图用 qi::_val.setAsFlag(); 替换您的 commandFlag 设置器;但显然 _val 的类型与 ExpressionAST 不同,而是某种演员凤凰包装器
  • @lurscher - 很高兴你发现它有帮助。是的,qi::_val 将评估为 ExpressionAST,但实际上不是那种类型。要调用成员函数,您必须使用 phoenix bind(对于 memfun void setAsFlag(bool flag)):phoenix::bind(&amp;ExpressionAST::setAsFlag, qi::_val, true)。
  • 我在使用带有 (instruction % ",") 规则的 push_back 时遇到问题,我在这里发布了一个后续问题 stackoverflow.com/questions/3115420/…
  • @lursher, void* - 有点作用。好的一面是它完全类型安全的模板忍术。缺点是它的模板忍术(参考错误消息)。
猜你喜欢
  • 2022-11-16
  • 2017-10-30
  • 2021-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多