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

时间:2015-03-18 21:14:35

标签: c++ boost-spirit boost-spirit-qi

我试图使用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;
}

2 个答案:

答案 0 :(得分:4)

噢。除非您正在扩展库并实现自己的解析器指令,否则任何人都不必为像解析器上下文这样的实现细节而烦恼。

在此之前,phoenix::function<>phoenix::bind甚至BOOST_PHOENIX_ADAPT_FUNCTION对任何人都应该充足。

以下是您的问题的两种方法,没有任何补丁库。

  1. 直接解析 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;
    
  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

    当然,以上可能是一个复杂的&#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位置


补丁

  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. 有一个相当随意的选择(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
    
  3. 最后,uderscores 计入MinDigitsMaxDigits限制

  4. 样本

    以下测试程序演示了一些事情。 注意分支的重新排序。

    #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
    

    LISTING

    完整补丁(在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