如何使用`boost :: spirit`将语法解析为`std :: set`?

时间:2015-10-28 15:06:43

标签: c++ boost-spirit

TL; DR

如何将boost::spirit语法的结果解析为std::set

完整的问题陈述

作为学习如何使用boost::spirit的练习,我正在为X.500 / LDAP专有名称设计解析器。语法可以在RFC-1779中以BNF格式找到。

我"展开"它并将其翻译成boost::spirit规则。这是第一步。基本上,DN是一组RDN(相对专有名称),它们本身就是(Key,Value)对的元组。

我考虑使用

typedef std::unordered_map<std::string, std::string> rdn_type;

代表每个RDN。然后将RDN收集到std::set<rdn_type>

我的问题是,通过boost::spirit的(好)文档,我没有找到如何填充集合。

我的当前代码可以在github上找到,我会尽可能地优化它。

开始撒旦的舞蹈来召唤这个最受欢迎的北极熊:p

当前代码

为了有一个全方位的问题,我在这里添加了一个代码的副本,它有点长,所以我把它放在最后:)

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;

typedef std::unordered_map<std::string, std::string> dn_key_value_map;

template <typename Iterator>
struct dn_grammar_common : public qi::grammar<Iterator, std::multiset<dn_key_value_map>(), ascii::space_type> {
  struct dn_reserved_chars_ : public qi::symbols<char, char> {
    dn_reserved_chars_() {
      add
        ("\\", "\\")
        ("=" , "=")
        ("+" , "+")
        ("," , ",")
        (";" , ";")
        ("#" , "#")
        ("<" , "<")
        (">" , ">")
        ("\"", "\"")
        ("%" , "%");
    }
  } dn_reserved_chars;
  dn_grammar_common() : dn_grammar_common::base_type(dn) {
    // Useful using directives
    using namespace qi::labels;

    // Low level rules
    // Key can only contain alphanumerical characters and dashes
    key = ascii::no_case[qi::lexeme[(*qi::alnum) >> (*(qi::char_('-') >> qi::alnum))]];
    escaped_hex_char = qi::lexeme[(&qi::char_("\\")) >> qi::repeat(2)[qi::char_("0-9a-fA-F")]];
    escaped_sequence = escaped_hex_char |
                      qi::lexeme[(&qi::char_("\\")) >> dn_reserved_chars];
    // Rule for a fully escaped string (used as Attribute Value) => "..."
    quote_string = qi::lexeme[qi::lit('"') >>
      *(escaped_sequence | (qi::char_ - qi::char_("\\\""))) >>
      qi::lit('"')
    ];
    // Rule for an hexa string (used as Attribute Value) => #23AD5D...
    hex_string = (&qi::char_("#")) >> *qi::lexeme[(qi::repeat(2)[qi::char_("0-9a-fA-F")])];

    // Value is either:
    // - A regular string (that can contain escaped sequences)
    // - A fully escaped string (that can also contain escaped sequences)
    // - An hexadecimal string
    value = (qi::lexeme[*((qi::char_ - dn_reserved_chars) | escaped_sequence)]) |
            quote_string |
            hex_string;

    // Higher level rules
    rdn_pair = key >> '=' >> value;
    // A relative distinguished name consists of a sequence of pairs (Attribute = AttributeValue)
    // Separated with a +
    rdn = rdn_pair % qi::char_("+");
    // The DN is a set of RDNs separated by either a "," or a ";".
    // The two separators can coexist in a given DN, though it is not
    // recommended practice.
    dn = rdn % (qi::char_(",;"));
  }
  qi::rule<Iterator, std::set<dn_key_value_map>(), ascii::space_type> dn;
  qi::rule<Iterator, dn_key_value_map(), ascii::space_type> rdn;
  qi::rule<Iterator, std::pair<std::string, std::string>(), ascii::space_type> rdn_pair;
  qi::rule<Iterator, std::string(), ascii::space_type> key, value, hex_string, quote_string;
  qi::rule<Iterator, std::string(), ascii::space_type> escaped_hex_char, escaped_sequence;
};

1 个答案:

答案 0 :(得分:2)

我怀疑你只需要fusion/adapted/std_pair.hpp

让我试着让它编译

确定

  1. 您的开始规则不兼容

     qi::rule<Iterator, std::multiset<dn_key_value_map>(), ascii::space_type> dn;
    
  2. 符号表应映射到字符串,而不是char

    struct dn_reserved_chars_ : public qi::symbols<char, std::string> {
    

    您应该将映射值更改为char literals。

      

    为什么使用此代替char_("\\=+,;#<>\"%")

  3. 更新

    完成了我对语法的评论(纯粹从实现的角度来看,所以我还没有真正阅读RFC来检查假设)。

      

    我在这里创建了一个拉取请求:https://github.com/Rerito/pkistore/pull/1

    1. 一般说明

      • 无序地图不可排序,因此使用map<string,string>
      • 外部集合在技术上不是RFC中的集合(?),使其成为 vector(也在相对域名之间输出) 更多地符合输入顺序)
      • 删除了迷信包含(Fusion set / map完全是 与std :: set / map无关。只需要std_pair.hpp就可以使地图工作了)
    2. 语法规则:

      • symbols<char,char>需要char个值(不是".",而是'.'
      • 许多简化

        • 删除&char_(...)个实例(它们不匹配任何内容,它们 只是一个断言)
        • 删除无能的no_case[]
        • 删除了不必要的lexeme[]指令;大部分都已实现 通过从规则声明中删除队长
        • 完全删除了一些规则声明(规则def并不复杂 足以保证所产生的开销,例如hex_string
        • make key至少需要一个字符(未检查规格)。 注意

          key = ascii::no_case[qi::lexeme[(*qi::alnum) >> (*(qi::char_('-') >> qi::alnum))]];
          

          成了

          key = raw[ alnum >> *(alnum | '-') ];
          

          raw表示输入序列将逐字反映 (而不是按字符构建复制字符)

        • 重新排序value上的分支(未选中,但我打赌未查询 字符串基本上会吃其他所有东西)

        • 使用qi::int_parser<char, 16, 2, 2>
        • 使hexchar暴露实际数据
    3. 测试

      根据rfc中的Examples部分添加了一个test.cpp测试程序 (3)。

      添加了一些我自己设计的更复杂的例子。

    4. 松散结束

      待办事项:查看有关

      的实际规则和要求的规范
      • 逃避特殊字符
      • 在各种内容中包含空格(包括换行符) 字符串风味:

        • hex #xxxx字符串可能允许换行符(对我来说很有意义)
        • 未加引号的字符串可能不是(同上)

      同时启用了可选BOOST_SPIRIT_DEBUG

      还使船长内部的语法(安全!)

      还提供了一个方便的免费功能,使解析器可用 没有泄露实施细节(齐)

    5. 现场演示

      <强> Live On Coliru

      //#include "dn_parser.hpp"
      //#define BOOST_SPIRIT_DEBUG
      #include <boost/fusion/adapted/std_pair.hpp>
      #include <boost/spirit/include/qi.hpp>
      #include <map>
      #include <set>
      
      namespace pkistore {
          namespace parsing {
      
          namespace qi      = boost::spirit::qi;
          namespace ascii   = boost::spirit::ascii;
      
          namespace ast {
              typedef std::map<std::string, std::string> rdn;
              typedef std::vector<rdn> dn;
          }
      
          template <typename Iterator>
          struct dn_grammar_common : public qi::grammar<Iterator, ast::dn()> {
              dn_grammar_common() : dn_grammar_common::base_type(start) {
                  using namespace qi;
      
                  // syntax as defined in rfc1779
                  key          = raw[ alnum >> *(alnum | '-') ];
      
                  char_escape  = '\\' >> (hexchar | dn_reserved_chars);
                  quote_string = '"' >> *(char_escape | (char_ - dn_reserved_chars)) >> '"' ;
      
                  value        =  quote_string 
                               | '#' >> *hexchar
                               | *(char_escape | (char_ - dn_reserved_chars))
                               ;
      
                  rdn_pair     = key >> '=' >> value;
      
                  rdn          = rdn_pair % qi::char_("+");
                  dn           = rdn % qi::char_(",;");
      
                  start        = skip(qi::ascii::space) [ dn ];
      
                  BOOST_SPIRIT_DEBUG_NODES((start)(dn)(rdn)(rdn_pair)(key)(value)(quote_string)(char_escape))
              }
      
          private:
              qi::int_parser<char, 16, 2, 2> hexchar;
      
              qi::rule<Iterator, ast::dn()> start;
      
              qi::rule<Iterator, ast::dn(), ascii::space_type> dn;
              qi::rule<Iterator, ast::rdn(), ascii::space_type> rdn;
              qi::rule<Iterator, std::pair<std::string, std::string>(), ascii::space_type> rdn_pair;
      
              qi::rule<Iterator, std::string()> key, value, quote_string;
              qi::rule<Iterator, char()>        char_escape;
      
              struct dn_reserved_chars_ : public qi::symbols<char, char> {
                  dn_reserved_chars_() {
                      add ("\\", '\\') ("\"", '"')
                          ("=" , '=')  ("+" , '+')
                          ("," , ',')  (";" , ';')
                          ("#" , '#')  ("%" , '%')
                          ("<" , '<')  (">" , '>')
                          ;
                  }
              } dn_reserved_chars;
          };
      
          } // namespace parsing
      
          static parsing::ast::dn parse(std::string const& input) {
              using It = std::string::const_iterator;
      
              pkistore::parsing::dn_grammar_common<It> const g;
      
              It f = input.begin(), l = input.end();
              pkistore::parsing::ast::dn parsed;
      
              bool ok = boost::spirit::qi::parse(f, l, g, parsed);
      
              if (!ok || (f!=l))
                  throw std::runtime_error("dn_parse failure");
      
              return parsed;
          }
      } // namespace pkistore
      
      int main() {
          for (std::string const input : {
                  "OU=Sales + CN=J. Smith, O=Widget Inc., C=US",
                  "OU=#53616c6573",
                  "OU=Sa\\+les + CN=J. Smi\\%th, O=Wid\\,\\;get In\\3bc., C=US",
                  //"CN=Marshall T. Rose, O=Dover Beach Consulting, L=Santa Clara,\nST=California, C=US",
                  //"CN=FTAM Service, CN=Bells, OU=Computer Science,\nO=University College London, C=GB",
                  //"CN=Markus Kuhn, O=University of Erlangen, C=DE",
                  //"CN=Steve Kille,\nO=ISODE Consortium,\nC=GB",
                  //"CN=Steve Kille ,\n\nO =   ISODE Consortium,\nC=GB",
                  //"CN=Steve Kille, O=ISODE Consortium, C=GB\n",
              })
          {
              auto parsed = pkistore::parse(input);
      
              std::cout << "===========\n" << input << "\n";
              for(auto const& dn : parsed) {
                  std::cout << "-----------\n";
                  for (auto const& kv : dn) {
                      std::cout << "\t" << kv.first << "\t->\t" << kv.second << "\n";
                  }
              }
          }
      }
      

      打印:

      ===========
      OU=Sales + CN=J. Smith, O=Widget Inc., C=US
      -----------
          CN  ->  J. Smith
          OU  ->  Sales 
      -----------
          O   ->  Widget Inc.
      -----------
          C   ->  US
      ===========
      OU=#53616c6573
      -----------
          OU  ->  Sales
      ===========
      OU=Sa\+les + CN=J. Smi\%th, O=Wid\,\;get In\3bc., C=US
      -----------
          CN  ->  J. Smi%th
          OU  ->  Sa+les 
      -----------
          O   ->  Wid,;get In;c.
      -----------
          C   ->  US