使用 boost::spirit::qi 解析带分隔符的数字

Using boost::spirit::qi to parse numbers with separators

我正在尝试使用 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;
}

如果您真的想要以"nice"的方式进行操作,则必须将其破解成numeric_utils.hpp中的extract_int

更好的是,您希望将其作为一种策略 class,就像 real_parser 使用的 real_policies 一样。因为只是将更多分支与现有的 通用 整数处理代码混合只会使事情复杂化,并有可能减慢 any 整数解析。

我没有这样做过。但是,我 在这里有一个概念验证方法:

请注意,由于上述原因,这没有经过充分测试,不适合认真使用,但您可以将其用作灵感。您可能只想复制整个 uint_parser 指令并将其粘贴到您的 Spirit Repository 位置 .


补丁

  1. 比较简单。如果您定义 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
    

    唯一真正的复杂性来自“透视:在该翻译单元中进行的优化。

  2. 有一个相当随意的选择(禁止)允许在前导零中使用下划线。我选择这样做:

    #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
    
  3. 最后,uderscores 计入MinDigitsMaxDigits限制

演示

下面的测试程序演示了一些东西。 注意分支的重新排序。

#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

上市

用于在 SO:

上保存的完整补丁(在 boost-1.57.0 标签上)
commit 24b16304f436bfd0f6e2041b2b7be0c8677c7e75
Author: Seth Heeren <sgheeren@gmail.com>
Date:   Thu Mar 19 01:44:55 2015 +0100

    

    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

噢。没有人应该为 Spirit 解析器上下文等实现细节操心 ,除非您正在扩展库并实现自己的解析器指令

在那之前,phoenix::function<>phoenix::bind 甚至 BOOST_PHOENIX_ADAPT_FUNCTION 对任何人来说都足够了。

这里有两种方法可以解决您的问题,无需对库进行任何修补。

  1. 直接解析 Live On Coliru

    这可以看作 "naive" 仅使用 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;
    
  2. 使用 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)
    
  3. 使用std::strtoul Live On Coliru

    当然,上面的内容可能有点"complex",因为它要求您处理整数运算和数字解码的具体细节。

    此外,"naive" 方法会做一些重复的工作,以防文字是像“101_101”这样的十进制值。它将计算 子结果 的十六进制、八进制 二进制分支,然后才意识到它是十进制。

    所以我们可以改变顺序:

    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;
    

    如您所见,唯一复杂的是首先从范围中删除 _