如何使用 `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 格式的语法
我"unrolled"它并将它翻译成boost::spirit
规则。这是第一步。基本上,DN 是一组 RDN(相对可分辨名称),它们本身是(键,值)对的元组。
我考虑使用
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
让我试着让它编译
好的
您的启动规则不兼容
qi::rule<Iterator, std::multiset<dn_key_value_map>(), ascii::space_type> dn;
符号table应该映射到字符串,而不是字符
struct dn_reserved_chars_ : public qi::symbols<char, std::string> {
或您应该将映射值更改为字符文字。
Why do you use this, instead of char_("\=+,;#<>\"%")
?
更新
我已经完成了对语法的复习(纯粹是从实现的角度来看,所以我还没有真正阅读 RFC 来检查假设)。
I created a pull request here: https://github.com/Rerito/pkistore/pull/1
一般注意事项
- 无序映射不是排序的table,所以使用
map<string,string>
- 外部集合在技术上不是 RFC 中的集合(?),使它成为
向量(也使相对域名之间的输出
更符合输入顺序)
- 去除了迷信包含(融合set/map完全
与 std::set/map 无关。只需要 std_pair.hpp 地图即可工作)
语法规则:
symbols<char,char>
需要 char
个值(不是 "."
,而是 '.'
)
许多简化
- 删除
&char_(...)
个实例(它们不匹配任何东西,它是
只是一个断言)
- 去除阳痿
no_case[]
- 删除了不必要的
lexeme[]
指令;大多数已经实现
通过从规则声明中删除船长
- 完全删除了一些规则声明(规则定义并不复杂
足以保证产生的间接费用),例如
hex_string
使得 key
需要至少一个字符(未检查规范)。
注意如何
key = ascii::no_case[qi::lexeme[(*qi::alnum) >> (*(qi::char_('-') >> qi::alnum))]];
变成了
key = raw[ alnum >> *(alnum | '-') ];
raw
表示输入序列会被逐字反映
(而不是逐个字符地构建副本)
在 value
上重新排序的分支(未检查,但我打赌 unqouted
字符串基本上会吃掉其他所有东西)
- 使 hexchar 使用
qi::int_parser<char, 16, 2, 2>
公开实际数据
测试
添加了一个测试程序 test.cpp,基于 rfc 中的示例部分
(3.).
添加了一些我自己设计的更复杂的示例。
未完待续
待办事项:查看
上的实际规则和要求的规范
- 转义特殊字符
在各种格式中包含空格(包括换行符)
字符串口味:
- hex #xxxx 字符串可能允许换行(对我来说有意义)
- 未加引号的字符串可能不会(同上)
也启用可选BOOST_SPIRIT_DEBUG
还将船长设置为语法内部(安全!)
还制作了一个方便的免费功能,使解析器可用
不泄露实施细节 (Qi)
现场演示
//#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 Inbc., C=US
-----------
CN -> J. Smi%th
OU -> Sa+les
-----------
O -> Wid,;get In;c.
-----------
C -> US
TL;DR
如何将 boost::spirit
语法的结果解析为 std::set
?
完整的问题陈述
作为学习如何使用 boost::spirit
的练习,我正在为 X.500/LDAP 专有名称设计解析器。可以在 RFC-1779.
我"unrolled"它并将它翻译成boost::spirit
规则。这是第一步。基本上,DN 是一组 RDN(相对可分辨名称),它们本身是(键,值)对的元组。
我考虑使用
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
让我试着让它编译
好的
您的启动规则不兼容
qi::rule<Iterator, std::multiset<dn_key_value_map>(), ascii::space_type> dn;
符号table应该映射到字符串,而不是字符
struct dn_reserved_chars_ : public qi::symbols<char, std::string> {
或您应该将映射值更改为字符文字。
Why do you use this, instead of
char_("\=+,;#<>\"%")
?
更新
我已经完成了对语法的复习(纯粹是从实现的角度来看,所以我还没有真正阅读 RFC 来检查假设)。
I created a pull request here: https://github.com/Rerito/pkistore/pull/1
一般注意事项
- 无序映射不是排序的table,所以使用
map<string,string>
- 外部集合在技术上不是 RFC 中的集合(?),使它成为 向量(也使相对域名之间的输出 更符合输入顺序)
- 去除了迷信包含(融合set/map完全 与 std::set/map 无关。只需要 std_pair.hpp 地图即可工作)
- 无序映射不是排序的table,所以使用
语法规则:
symbols<char,char>
需要char
个值(不是"."
,而是'.'
)许多简化
- 删除
&char_(...)
个实例(它们不匹配任何东西,它是 只是一个断言) - 去除阳痿
no_case[]
- 删除了不必要的
lexeme[]
指令;大多数已经实现 通过从规则声明中删除船长 - 完全删除了一些规则声明(规则定义并不复杂
足以保证产生的间接费用),例如
hex_string
使得
key
需要至少一个字符(未检查规范)。 注意如何key = ascii::no_case[qi::lexeme[(*qi::alnum) >> (*(qi::char_('-') >> qi::alnum))]];
变成了
key = raw[ alnum >> *(alnum | '-') ];
raw
表示输入序列会被逐字反映 (而不是逐个字符地构建副本)在
value
上重新排序的分支(未检查,但我打赌 unqouted 字符串基本上会吃掉其他所有东西)- 使 hexchar 使用
qi::int_parser<char, 16, 2, 2>
公开实际数据
- 删除
测试
添加了一个测试程序 test.cpp,基于 rfc 中的示例部分 (3.).
添加了一些我自己设计的更复杂的示例。
未完待续
待办事项:查看
上的实际规则和要求的规范- 转义特殊字符
在各种格式中包含空格(包括换行符) 字符串口味:
- hex #xxxx 字符串可能允许换行(对我来说有意义)
- 未加引号的字符串可能不会(同上)
也启用可选
BOOST_SPIRIT_DEBUG
还将船长设置为语法内部(安全!)
还制作了一个方便的免费功能,使解析器可用 不泄露实施细节 (Qi)
现场演示
//#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 Inbc., C=US
-----------
CN -> J. Smi%th
OU -> Sa+les
-----------
O -> Wid,;get In;c.
-----------
C -> US