Search code examples
c++boost-spiritboost-spirit-qi

Semantic actions calling lambda


I'm trying to parse a time string using boost spirit and not sure why this doesn't work.

auto fill_ts_nanos = [&t] (int h, int m, int s, int ms) -> int
                                { t.tv_nsec = ( ( h * 3600 + m * 60 + s ) * 1000 + ms ) * 1000000; return t.tv_sec; };
auto fill_suffix   = [&suffix] (string &s) { suffix=s; };

auto parse_ok = qi::parse(input.begin(), input.end(),
               ( qi::int_ >> qi::char_(":") >> qi::int_ >> qi::char_(":") >> 
                 qi::int_ >> qi::char_(".") >> qi::int_ ) 
                 [boost::bind(fill_ts_nanos, qi::_1, qi::_3, qi::_5, qi::_7                
                >> qi::char_(",") >> qi::as_string[*qi::char_][fill_suffix]  ;

A sample input is "04:00:00.512,2251812698588658"


Solution

  • After guessing a lot of details (e.g. what the type of t is supposed to be), here's the fixed code with some debug output:

    Live On Coliru

    Note: I fixed the signed-ness of the numbers as well as I changed the types to prevent overflow.

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <ctime>
    #include <chrono>
    #include <iomanip>
    
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    using namespace std::chrono_literals;
    using namespace qi::labels;
    
    int main() {
        timespec t;
        std::string suffix;
    
        auto fill_ts_nanos = [&t](int h, unsigned m, unsigned s, unsigned ms) -> long {
            t = {};
            t.tv_nsec = ((h * 3600 + m * 60 + s) * 1000 + ms) * 1000000l;
            return t.tv_sec;
        };
    
        auto fill_suffix = [&suffix](std::string &s) { suffix = s; };
    
        std::string const input = "04:00:00.512,2251812698588658";
    
        auto parse_ok = qi::parse(input.begin(), input.end(),
                   (qi::int_ >> ':' >> qi::uint_ >> ':' >> qi::uint_ >> '.' >> qi::uint_) 
                     [px::bind(fill_ts_nanos, _1, _2, _3, _4) ]
                    >> ',' >> qi::as_string[*qi::char_]
                     [fill_suffix] );
    
        std::printf("%lld.%.9ld\n", (long long)t.tv_sec, t.tv_nsec);
    
        auto ns = t.tv_nsec * 1ns;
    
        std::cout << std::fixed << std::setprecision(6);
        std::cout << "hours: " << (ns / 1.0h) << "\n";
        std::cout << "minutes: " << (ns / 1.0min) << "\n";
        std::cout << "seconds: " << (ns / 1.0s) << "\n";
    
        std::cout << "suffix: " << suffix << "\n";
    
        return parse_ok? 0:255;
    }
    

    Prints

    0.14400512000000
    hours: 4.000142
    minutes: 240.008533
    seconds: 14400.512000
    suffix: 2251812698588658
    

    Suggestions

    I'd try to simplify this by a lot, e.g., by creating a rule:

    qi::rule<It, long()> timespec_ = 
           (qi::int_ >> ':' >> qi::uint_ >> ':' >> qi::uint_ >> '.' >> qi::uint_) 
           [ _val = ((_1 * 3600 + _2 * 60 + _3) * 1000 + _4) * 1000000l ];
    

    Which means you can then parse with nothing else but:

    timespec t {};
    std::string suffix;
    
    It f = input.begin(), l = input.end();
    
    parse(f, l, timespec_ >> ',' >> *qi::char_, t.tv_nsec, suffix);
    

    This has the same output:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <ctime>
    #include <chrono>
    #include <iomanip>
    
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    using namespace std::chrono_literals;
    using namespace qi::labels;
    
    using It = std::string::const_iterator;
    
    qi::rule<It, long()> timespec_ = 
           (qi::int_ >> ':' >> qi::uint_ >> ':' >> qi::uint_ >> '.' >> qi::uint_) 
           [ _val = ((_1 * 3600 + _2 * 60 + _3) * 1000 + _4) * 1000000l ];
    
    int main() {
        std::string const input = "04:00:00.512,2251812698588658";
    
        timespec t {};
        std::string suffix;
    
        It f = input.begin(), l = input.end();
    
        if (parse(f, l, timespec_ >> ',' >> *qi::char_, t.tv_nsec, suffix)) {
            std::printf("%lld.%.9ld\n", (long long)t.tv_sec, t.tv_nsec);
    
            auto ns = t.tv_nsec * 1ns;
    
            std::cout << std::fixed << std::setprecision(6);
            std::cout << "hours: " << (ns / 1.0h) << "\n";
            std::cout << "minutes: " << (ns / 1.0min) << "\n";
            std::cout << "seconds: " << (ns / 1.0s) << "\n";
    
            std::cout << "suffix: " << suffix << "\n";
        } else {
            std::cout << "Parse failed\n";
        }
    
        if (f!=l) {
            std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
        }
    }