boost :: spirit :: qi Expectation Parser和parser对意外行为进行分组

时间:2012-04-30 05:38:47

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

我希望有人能够通过我在精神解析中使用>>>运算符的无知来发光。

我有一个工作语法,顶级规则看起来像

test = identifier >> operationRule >> repeat(1,3)[any_string] >> arrow >> any_string >> conditionRule;

它依赖于属性来自动将解析后的值分配给融合自适应的结构(即增强元组)。

但是,我知道,一旦我们匹配operationRule,我们必须继续或失败(即我们不希望允许回溯尝试以identifier开头的其他规则。)

test = identifier >> 
           operationRule > repeat(1,3)[any_string] > arrow > any_string > conditionRule;

这会导致一个神秘的编译器错误('boost::Container' : use of class template requires template argument list)。 Futz有点以及以下编译:

test = identifier >> 
           (operationRule > repeat(1,3)[any_string] > arrow > any_string > conditionRule);

但属性设置不再有效 - 我的数据结构在解析后包含垃圾。这可以通过添加[at_c<0>(_val) = _1]之类的操作来修复,但这看起来有点笨拙 - 以及根据提升文档减慢速度。

所以,我的问题是

  1. 是否值得阻止反向跟踪?
  2. 为什么我需要分组运算符()
  3. 上面的最后一个示例是否真的在operationRule匹配后停止了回溯(我怀疑不是,似乎如果(...)内的整个解析器无法回溯将被允许)?
  4. 如果上一个问题的答案是/ no /,如何构建一个允许回溯的规则,如果operation是/不匹配,但是一旦操作/匹配后不允许回溯?
  5. 为什么分组操作符会破坏属性语法 - 需要操作?
  6. 我意识到这是一个相当广泛的问题 - 任何指向正确方向的提示都将受到高度赞赏!

1 个答案:

答案 0 :(得分:5)

  1. 是否值得阻止回溯?

    绝对。一般来说,防止反向跟踪是提高解析器性能的可靠方法。

    • 减少使用(负面)前瞻(操作员!,操作员 - 以及某些操作员&amp;)
    • 命令分支(运算符|,运算符||,运算符^和某些运算符* / - / +),以便首先排序最频繁/可能的分支,或者最后尝试最昂贵的分支

    使用期望点(>)基本上不会减少回溯:它只会禁止它。这将启用有针对性的错误消息,防止无用的“解析到未知”。

  2. 为什么我需要分组operator ()

    我不确定。我有一张支票using my what_is_the_attr helpers from here

    • ident >> op >> repeat(1,3)[any] >> "->" >> any
      合成属性:

      fusion::vector4<string, string, vector<string>, string>
      
    • ident >> op > repeat(1,3)[any] > "->" > any
      合成属性:

      fusion::vector3<fusion::vector2<string, string>, vector<string>, string>
      

    我没有找到 need 使用括号(事物编译)对子表达式进行分组,但显然需要修改DataT以匹配更改的布局。

    typedef boost::tuple<
        boost::tuple<std::string, std::string>, 
        std::vector<std::string>, 
        std::string
    > DataT;
    
  3. 下面的完整代码显示了我喜欢如何使用改编的结构。

    1. 我的上述示例是否真的在操作规则匹配后停止回溯(我怀疑不是,似乎如果(...)中的整个解析器无法回溯将被允许)?

      绝对。如果未满足期望,则抛出qi::expectation_failure<>异常。这默认情况下中止解析。您可以将qi :: on_error用于retryfailacceptrethrowMiniXML example has very good examples on using expectation points with qi::on_error

    2. 如果上一个问题的答案是/ no /, 如何构建一个允许回溯的规则,如果操作是/不匹配,但不允许回溯一旦操作/匹配?

    3. 为什么分组操作符会破坏属性语法 - 需要操作?

      它不会破坏属性语法,只是更改了暴露的类型。因此,如果将适当的属性引用绑定到规则/语法,则不需要语义操作。现在,我觉得应该有没有分组的方法,所以让我试一试(最好是你的简短自我样本)。 我确实没有找到这样的需求 。我添加了一个完整的示例来帮助您了解我的测试中发生了什么,而不是使用语义操作。

    4. 完整代码

      完整代码显示了5个场景:

      • 选项1:原始没有期望

        (无相关更改)

      • 选项2:满怀期待

        对DataT使用修改后的typedef(如上所示)

      • 选项3:适应结构,没有期望

        将用户定义的结构与BOOST_FUSION_ADAPT_STRUCT一起使用

      • 选项4:适应结构,带有期望

        修改OPTION 3中的改编结构

      • 选项5:前瞻黑客

        这个利用'聪明'(?)hack ,将所有>>纳入预期,并预先检测到operationRule - 匹配的存在。这当然不是最理想的,但允许您保持DataT不被修改,而不使用语义操作。

      显然,在编译之前将OPTION定义为所需的值。

      #include <boost/spirit/include/qi.hpp>
      #include <boost/spirit/include/karma.hpp>
      #include <boost/spirit/include/phoenix.hpp>
      #include <boost/fusion/adapted.hpp>
      #include <iostream>
      
      namespace qi    = boost::spirit::qi; 
      namespace karma = boost::spirit::karma; 
      
      #ifndef OPTION
      #define OPTION 5
      #endif
      
      #if OPTION == 1 || OPTION == 5 // original without expectations (OR lookahead hack)
          typedef boost::tuple<std::string, std::string, std::vector<std::string>, std::string> DataT;
      #elif OPTION == 2 // with expectations
          typedef boost::tuple<boost::tuple<std::string, std::string>, std::vector<std::string>, std::string> DataT;
      #elif OPTION == 3 // adapted struct, without expectations
          struct DataT
          {
              std::string identifier, operation;
              std::vector<std::string> values;
              std::string destination;
          };
      
          BOOST_FUSION_ADAPT_STRUCT(DataT, (std::string, identifier)(std::string, operation)(std::vector<std::string>, values)(std::string, destination));
      #elif OPTION == 4 // adapted struct, with expectations
          struct IdOpT
          {
              std::string identifier, operation;
          };
          struct DataT
          {
              IdOpT idop;
              std::vector<std::string> values;
              std::string destination;
          };
      
          BOOST_FUSION_ADAPT_STRUCT(IdOpT, (std::string, identifier)(std::string, operation));
          BOOST_FUSION_ADAPT_STRUCT(DataT, (IdOpT, idop)(std::vector<std::string>, values)(std::string, destination));
      #endif
      
      template <typename Iterator>
      struct test_parser : qi::grammar<Iterator, DataT(), qi::space_type, qi::locals<char> >
      {
          test_parser() : test_parser::base_type(test, "test")
          {
              using namespace qi;
      
              quoted_string = 
                     omit    [ char_("'\"") [_a =_1] ]             
                  >> no_skip [ *(char_ - char_(_a))  ]
                   > lit(_a); 
      
              any_string = quoted_string | +qi::alnum;
      
              identifier = lexeme [ alnum >> *graph ];
      
              operationRule = string("add") | "sub";
              arrow = "->";
      
      #if OPTION == 1 || OPTION == 3   // without expectations
              test = identifier >> operationRule >> repeat(1,3)[any_string] >> arrow >> any_string;
      #elif OPTION == 2 || OPTION == 4 // with expectations
              test = identifier >> operationRule  > repeat(1,3)[any_string]  > arrow  > any_string;
      #elif OPTION == 5                // lookahead hack
              test = &(identifier >> operationRule) > identifier > operationRule > repeat(1,3)[any_string] > arrow > any_string;
      #endif
          }
      
          qi::rule<Iterator, qi::space_type/*, qi::locals<char> */> arrow;
          qi::rule<Iterator, std::string(), qi::space_type/*, qi::locals<char> */> operationRule;
          qi::rule<Iterator, std::string(), qi::space_type/*, qi::locals<char> */> identifier;
          qi::rule<Iterator, std::string(), qi::space_type, qi::locals<char> > quoted_string, any_string;
          qi::rule<Iterator, DataT(),       qi::space_type, qi::locals<char> > test;
      };
      
      int main()
      {
          std::string str("addx001 add 'str1'   \"str2\"       ->  \"str3\"");
          test_parser<std::string::const_iterator> grammar;
          std::string::const_iterator iter = str.begin();
          std::string::const_iterator end  = str.end();
      
          DataT data;
          bool r = phrase_parse(iter, end, grammar, qi::space, data);
      
          if (r)
          {
              using namespace karma;
              std::cout << "OPTION " << OPTION << ": " << str << " --> ";
      #if OPTION == 1 || OPTION == 3 || OPTION == 5 // without expectations (OR lookahead hack)
              std::cout << format(delimit[auto_ << auto_ << '[' << auto_ << ']' << " --> " << auto_], data) << "\n";
      #elif OPTION == 2 || OPTION == 4 // with expectations
              std::cout << format(delimit[auto_ << '[' << auto_ << ']' << " --> " << auto_], data) << "\n";
      #endif
          }
          if (iter!=end)
              std::cout << "Remaining: " << std::string(iter,end) << "\n";
      }
      

      所有选项的输出:

      for a in 1 2 3 4 5; do g++ -DOPTION=$a -I ~/custom/boost/ test.cpp -o test$a && ./test$a; done
      OPTION 1: addx001 add 'str1'   "str2"       ->  "str3" --> addx001 add [ str1 str2 ]  -->  str3 
      OPTION 2: addx001 add 'str1'   "str2"       ->  "str3" --> addx001 add [ str1 str2 ]  -->  str3 
      OPTION 3: addx001 add 'str1'   "str2"       ->  "str3" --> addx001 add [ str1 str2 ]  -->  str3 
      OPTION 4: addx001 add 'str1'   "str2"       ->  "str3" --> addx001 add [ str1 str2 ]  -->  str3 
      OPTION 5: addx001 add 'str1'   "str2"       ->  "str3" --> addx001 add [ str1 str2 ]  -->  str3