Constraining the existing Boost.Spirit real_parser (with a policy)

后端 未结 1 1365
走了就别回头了
走了就别回头了 2020-12-04 00:48

I want to parse a float, but not allow NaN values, so I generate a policy which inherits from the default policy and create a real_parser with it:



        
相关标签:
1条回答
  • 2020-12-04 01:10

    It seems I am so close, i.e. just a few changes to the double_ parser and I'd be done. This would probably be a lot more maintainable than adding a new grammar, since all the other parsing is done that way. – toting 7 hours ago

    Even more maintainable would be to not write another parser at all.

    You basically want to parse a floating point numbers (Spirit has got you covered) but apply some validations afterward. I'd do the validations in a semantic action:

    raw [ double_ [_val = _1] ] [ _pass = !isnan_(_val) && px::size(_1)<=4 ]
    

    That's it.

    Explanations

    Anatomy:

    • double_ [_val = _1] parses a double and assigns it to the exposed attribute as usual¹
    • raw [ parser ] matches the enclosed parser but exposes the raw source iterator range as an attribute
    • [ _pass = !isnan_(_val) && px::size(_1)<=4 ] - the business part!

      This semantic action attaches to the raw[] parser. Hence

      • _1 now refers to the raw iterator range that already parsed the double_
      • _val already contains the "cooked" value of a successful match of double_
      • _pass is a Spirit context flag that we can set to false to make parsing fail.

    Now the only thing left is to tie it all together. Let's make a deferred version of ::isnan:

    boost::phoenix::function<decltype(&::isnan)> isnan_(&::isnan);
    

    We're good to go.

    Test Program

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <cmath>
    #include <iostream>
    
    int main ()
    {
        using It = std::string::const_iterator;
    
        auto my_fpnumber = [] { // TODO encapsulate in a grammar struct
            using namespace boost::spirit::qi;
            using boost::phoenix::size;
    
            static boost::phoenix::function<decltype(&::isnan)> isnan_(&::isnan);
    
            return rule<It, double()> (
                    raw [ double_ [_val = _1] ] [ _pass = !isnan_(_val) && size(_1)<=4 ]
                );
        }();
    
        for (std::string const s: { "1.23", ".123", "2.e6", "inf", "3.2323", "nan" })
        {
            It f = s.begin(), l = s.end();
    
            double result;
            if (parse(f, l, my_fpnumber, result))
                std::cout << "Parse success:  '" << s << "' -> " << result << "\n";
            else
                std::cout << "Parse rejected: '" << s << "' at '" << std::string(f,l) << "'\n";
        }
    }
    

    Prints

    Parse success:  '1.23' -> 1.23
    Parse success:  '.123' -> 0.123
    Parse success:  '2.e6' -> 2e+06
    Parse success:  'inf' -> inf
    Parse rejected: '3.2323' at '3.2323'
    Parse rejected: 'nan' at 'nan'
    

    ¹ The assignment has to be done explicitly here because we use semantic actions and they normally suppress automatic attribute propagation

    0 讨论(0)
提交回复
热议问题