我试图使用boost :: spirit :: qi进行一些解析。它实际上进展顺利,我成功地设法根据后缀在各种基础上解析数字。示例:123,c12h,777o,110101b。
然后,我想添加允许完全忽略的分隔符的功能,以允许解析123_456或1101_0011b之类的值。我尝试使用跳过解析器,但我非常怀疑我完全误解了它是如何被使用的。它编译得很好,但我试图让它忽略下划线完全没有任何意义。任何关于如何使这做我想做的建议将不胜感激。我的测试代码包含在下面:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
using qi::_val;
using qi::_1;
using qi::skip;
using qi::uint_parser;
using ascii::char_;
template <typename Iterator>
struct unsigned_parser : qi::grammar<Iterator, uint64_t()> {
unsigned_parser() : unsigned_parser::base_type(start) {
uint_parser<uint64_t, 10> dec_parser;
uint_parser<uint64_t, 16> hex_parser;
uint_parser<uint64_t, 8> oct_parser;
uint_parser<uint64_t, 2> bin_parser;
start = skip(char_('_'))[
/* binary with suffix */
(bin_parser[_val=_1] >> char_("bByY"))
/* octal with suffix */
| (oct_parser[_val=_1] >> char_("qQoO"))
/* hexadecimal with suffix */
| (hex_parser[_val=_1] >> char_("hHxX"))
/* decimal with optional suffix */
| (dec_parser[_val=_1] >> -char_("dDtT"))
];
}
qi::rule<Iterator, uint64_t()> start;
};
int main(int argv, const char *argc[]) {
typedef std::string::const_iterator iter;
unsigned_parser<iter> up;
uint64_t val;
if (argv != 2) {
std::cerr << "Usage: " << argc[0] << " <input>" << std::endl;
return 1;
}
std::string test(argc[1]);
iter i = test.begin();
iter end = test.end();
bool rv = parse(i, end, up, val);
if (rv && i == end) {
std::cout << "Succeeded: " << val << std::endl;
return 0;
}
if (rv) {
std::cout << "Failed partial parse: " << val << std::endl;
return 1;
}
std::cout << "Failed." << std::endl;
return 1;
}
答案 0 :(得分:4)
噢。除非您正在扩展库并实现自己的解析器指令,否则任何人都不必为像解析器上下文这样的实现细节而烦恼。
在此之前,phoenix::function<>
,phoenix::bind
甚至BOOST_PHOENIX_ADAPT_FUNCTION
对任何人都应该充足。
以下是您的问题的两种方法,没有任何补丁库。
直接解析 Live On Coliru
这可以被视为&#34;天真&#34;使用Qi和简单的语义动作解析不同样式的整数的方法:
start =
eps [_val=0] >> +(char_("0-9a-fA-F") [ _val = _val*16 + _decode(_1) ] | '_')>> char_("hHxX") /* hexadecimal with suffix */
| eps [_val=0] >> +(char_("0-7") [ _val = _val* 8 + _decode(_1) ] | '_')>> char_("qQoO") /* octal with suffix */
| eps [_val=0] >> +(char_("01") [ _val = _val* 2 + _decode(_1) ] | '_')>> char_("bByY") /* binary with suffix */
| eps [_val=0] >> +(char_("0-9") [ _val = _val*10 + _decode(_1) ] | '_')>> -char_("dDtT") /* decimal with optional suffix */
;
当然,您会想知道_decode
的样子。那么你自己定义它:
struct decode {
template <typename> struct result { typedef int type; };
template <typename Ch> int operator()(Ch ch) const {
if (ch>='0' && ch<='9') return ch - '0';
if (ch>='a' && ch<='z') return ch - 'a' + 10;
if (ch>='A' && ch<='Z') return ch - 'A' + 10;
assert(false);
}
};
boost::phoenix::function<decode> _decode;
使用BOOST_PHOENIX_ADAPT_FUNCTION
宏 Live On Coliru
您可以使用宏
,而不是定义函数对象int decode(char ch) {
if (ch>='0' && ch<='9') return ch - '0';
if (ch>='a' && ch<='z') return ch - 'a' + 10;
if (ch>='A' && ch<='Z') return ch - 'A' + 10;
assert(false);
}
BOOST_PHOENIX_ADAPT_FUNCTION(int, _decode, decode, 1)
使用std::strtoul
Live On Coliru
当然,以上可能是一个复杂的&#34;因为它需要你处理整数算术和数字解码的细节。
此外,&#34;天真&#34;如果文字是十进制值,例如&#34; 101_101&#34;,则会进行一些重复的工作。它将计算十六进制,八进制和二进制分支的子结果,然后才能实现它是小数。
所以我们可以改变订单:
start =
(raw[+char_("_0-9a-fA-F")] >> char_("hHxX")) [ _val = _strtoul(_1,16) ] /* hexadecimal with suffix */
| (raw[+char_("_0-7")] >> char_("qQoO")) [ _val = _strtoul(_1, 8) ] /* octal with suffix */
| (raw[+char_("_01")] >> char_("bByY")) [ _val = _strtoul(_1, 2) ] /* binary with suffix */
| (raw[+char_("_0-9")] >> -char_("dDtT")) [ _val = _strtoul(_1,10) ] /* decimal with optional suffix */
;
您再次对我们如何实施_evaluate
感到好奇?它是一个函数,它从raw
(它是一个迭代器范围)和基数中获取合成属性,这个属性当时是明确的:
struct strtoul_f {
template <typename, typename> struct result { typedef uint64_t type; };
template <typename Raw, typename Int> uint64_t operator()(Raw raw, Int base) const {
std::string s(raw.begin(), raw.end());
s.erase(std::remove(s.begin(), s.end(), '_'), s.end());
char *f(&s[0]), *l(f+s.size());
return std::strtoul(f, &l, base);
}
};
boost::phoenix::function<strtoul_f> _strtoul;
如您所见,唯一的复杂性是首先从范围中移除_
。
答案 1 :(得分:3)
如果真的希望以“好”方式执行此操作,则必须将其破解为numeric_utils.hpp中的extract_int
。
更好的是,您希望将其设为策略类,与real_policies
使用的real_parser
非常相似。因为只使用现有的通用整数处理代码混合更多分支只会使其复杂化,并且有可能减慢任何整数解析速度。
我没有这样做。但是,我做在这里有一个概念验证方法:
请注意,由于上述原因,这项测试未经过充分测试,不适合严肃使用,但您可以将其作为灵感来使用。您可能只想将uint_parser
指令复制为整体,并将其粘贴在 Spirit Repository位置。
这是相对简单的。如果您定义ALLOW_SO_UNDERSCORE_HACK
,您将获得插入到循环展开宏中的下划线的旁路:
#if defined(ALLOW_SO_UNDERSCORE_HACK)
# define SPIRIT_SO_SKIP_UNDERSCORE_HACK() \
if ('_' == *it) { \
++it; \
continue; \
}
#else
# define SPIRIT_SO_SKIP_UNDERSCORE_HACK()
#endif
唯一真正的复杂性来自于“透视:在该翻译单元中进行的优化。
有一个相当随意的选择(dis)允许下划线成为领先的零。我选择这样做:
#if defined(ALLOW_SO_UNDERSCORE_HACK)
// skip leading zeros
for(;it != last;++it) {
if ('0' == *it && leading_zeros < MaxDigits) {
++leading_zeros;
continue;
} else if ('_' == *it) {
continue;
}
break;
}
#else
最后,uderscores 未计入MinDigits
和MaxDigits
限制
以下测试程序演示了一些事情。 注意分支的重新排序。
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
template <typename Iterator>
struct unsigned_parser : qi::grammar<Iterator, uint64_t()> {
unsigned_parser() : unsigned_parser::base_type(start) {
using namespace qi;
uint_parser<uint64_t, 10> dec_parser;
uint_parser<uint64_t, 16> hex_parser;
uint_parser<uint64_t, 8> oct_parser;
uint_parser<uint64_t, 2> bin_parser;
start = eps(false)
| (hex_parser >> omit[ char_("hHxX")]) /* hexadecimal with suffix */
| (oct_parser >> omit[ char_("qQoO")]) /* octal with suffix */
| (bin_parser >> omit[ char_("bByY")]) /* binary with suffix */
| (dec_parser >> omit[-char_("dDtT")]) /* decimal with optional suffix */
;
}
qi::rule<Iterator, uint64_t()> start;
};
int main(int argv, const char *argc[]) {
typedef std::string::const_iterator iter;
unsigned_parser<iter> up;
for (auto const& test : std::vector<std::string>(argc+1, argc+argv)) {
iter i = test.begin(), end = test.end();
uint64_t val;
bool rv = parse(i, end, up, val);
std::cout << (rv?"Successful":"Failed") << " parse: '" << test << "' -> " << val << "\n";
if (i != end)
std::cout << " ** Remaining unparsed: '" << std::string(i,end) << "'\n";
}
}
如果使用命令行参数123_456 123456 1_bh 0_010Q 1010_1010_0111_0111_b
调用它,它将打印:
Successful parse: '123_456' -> 123456
Successful parse: '123456' -> 123456
Successful parse: '1_bh' -> 27
Successful parse: '0_010Q' -> 8
Successful parse: '1010_1010_0111_0111_b' -> 43639
完整补丁(在boost-1.57.0
标签上)以便在SO上保存:
commit 24b16304f436bfd0f6e2041b2b7be0c8677c7e75
Author: Seth Heeren <sgheeren@gmail.com>
Date: Thu Mar 19 01:44:55 2015 +0100
http://stackoverflow.com/questions/29132809/using-boostspiritqi-to-parse-numbers-with-separators
rough patch for exposition of my answer only
diff --git a/include/boost/spirit/home/qi/numeric/detail/numeric_utils.hpp b/include/boost/spirit/home/qi/numeric/detail/numeric_utils.hpp
index 5137f87..1ced164 100644
--- a/include/boost/spirit/home/qi/numeric/detail/numeric_utils.hpp
+++ b/include/boost/spirit/home/qi/numeric/detail/numeric_utils.hpp
@@ -262,10 +262,21 @@ namespace boost { namespace spirit { namespace qi { namespace detail
///////////////////////////////////////////////////////////////////////////
// extract_int: main code for extracting integers
///////////////////////////////////////////////////////////////////////////
+#if defined(ALLOW_SO_UNDERSCORE_HACK)
+# define SPIRIT_SO_SKIP_UNDERSCORE_HACK() \
+ if ('_' == *it) { \
+ ++it; \
+ continue; \
+ }
+#else
+# define SPIRIT_SO_SKIP_UNDERSCORE_HACK()
+#endif
+
#define SPIRIT_NUMERIC_INNER_LOOP(z, x, data) \
if (!check_max_digits<MaxDigits>::call(count + leading_zeros) \
|| it == last) \
break; \
+ SPIRIT_SO_SKIP_UNDERSCORE_HACK() \
ch = *it; \
if (!radix_check::is_valid(ch) || !extractor::call(ch, count, val)) \
break; \
@@ -301,12 +312,25 @@ namespace boost { namespace spirit { namespace qi { namespace detail
std::size_t leading_zeros = 0;
if (!Accumulate)
{
+#if defined(ALLOW_SO_UNDERSCORE_HACK)
+ // skip leading zeros
+ for(;it != last;++it) {
+ if ('0' == *it && leading_zeros < MaxDigits) {
+ ++leading_zeros;
+ continue;
+ } else if ('_' == *it) {
+ continue;
+ }
+ break;
+ }
+#else
// skip leading zeros
while (it != last && *it == '0' && leading_zeros < MaxDigits)
{
++it;
++leading_zeros;
}
+#endif
}
typedef typename
@@ -366,6 +390,7 @@ namespace boost { namespace spirit { namespace qi { namespace detail
#define SPIRIT_NUMERIC_INNER_LOOP(z, x, data) \
if (it == last) \
break; \
+ SPIRIT_SO_SKIP_UNDERSCORE_HACK() \
ch = *it; \
if (!radix_check::is_valid(ch)) \
break; \
@@ -399,12 +424,25 @@ namespace boost { namespace spirit { namespace qi { namespace detail
std::size_t count = 0;
if (!Accumulate)
{
+#if defined(ALLOW_SO_UNDERSCORE_HACK)
+ // skip leading zeros
+ for(;it != last;++it) {
+ if ('0' == *it) {
+ ++count;
+ continue;
+ } else if ('_' == *it) {
+ continue;
+ }
+ break;
+ }
+#else
// skip leading zeros
while (it != last && *it == '0')
{
++it;
++count;
}
+#endif
if (it == last)
{
@@ -472,6 +510,7 @@ namespace boost { namespace spirit { namespace qi { namespace detail
};
#undef SPIRIT_NUMERIC_INNER_LOOP
+#undef SPIRIT_SO_SKIP_UNDERSCORE_HACK
///////////////////////////////////////////////////////////////////////////
// Cast an signed integer to an unsigned integer