簡體   English   中英

如何使用`boost :: spirit`將語法解析為`std :: set`?

[英]How to parse a grammar into a `std::set` using `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上找到,我試圖盡可能地優化它。

開始撒旦舞蹈召喚SO最受歡迎的北極熊: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;
};

我懷疑你只需要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文字。

    為什么使用它而不是char_("\\\\=+,;#<>\\"%")

更新

完成了我對語法的評論(純粹從實現的角度來看,所以我實際上還沒有閱讀RFC來檢查假設)。

我在這里創建了一個拉取請求: https//github.com/Rerito/pkistore/pull/1

  1. 一般注意事項

    • 無序映射不可排序,因此使用map<string,string>
    • 外部集合在技術上不是RFC中的集合(?),使其成為一個向量(也使得相對域名之間的輸出更多地對應於輸入順序)
    • 刪除迷信包含(融合集/地圖與std :: set / map完全無關。只需要std_pair.hpp即可使地圖工作)
  2. 語法規則:

    • symbols<char,char>需要char值(不是"."而是'.'
    • 許多簡化

      • remove &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. 測試

    添加了測試程序test.cpp,基於rfc(3。)中的Examples部分。

    添加了一些我自己設計的更復雜的例子。

  4. 松散的結束

    要做的事情:查看有關實際規則和要求的規范

    • 逃避特殊人物
    • 在各種字符串風格中包含空格(包括換行符):

      • 十六進制#xxxx字符串可能允許換行符(對我來說很有意義)
      • 不帶引號的字符串可能不是(同上)

    還啟用了可選的BOOST_SPIRIT_DEBUG

    還使船長內部的語法(安全!)

    還提供了一個方便免費的功能,使解析器可用而不會泄漏實現細節(Qi)

現場演示

住在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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM