1

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"

sehe
  • 374,641
  • 47
  • 450
  • 633
Danny
  • 391
  • 2
  • 12
  • you're using boost bind (in a lazy actor), and in the other action you are not (why). The syntax is botched beyond repair anyways (you brackets aren't balanced). I don't really know what's being asked. Can you make it much simpler and smaller? Start from the simplest thing that works and when it stops working, ask about that (unless, of course, you understand it at that point) – sehe Jul 07 '18 at 20:35

2 Answers2

2

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";
    }
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Oh so i have to use phoenix::bind with placeholders _1 from phoenix as well? Thanks @sehe! you're the guru when it comes to spirit :) – Danny Jul 08 '18 at 05:43
  • and indeed you've guessed most of the details correctly!! – Danny Jul 08 '18 at 05:44
  • one additional question... is there a difference between `>> ':' >>` and `>> qi::char_(":") >>` ? – Danny Jul 08 '18 at 05:46
  • Oh actually this works too `auto parse_ok = qi::parse(input.begin(), input.end(), ( qi::int_ >> qi::char_(":") >> qi::int_ >> qi::char_(":") >> qi::int_ >> qi::char_(".") >> qi::int_ ) [ phoenix::bind(fill_ts_nanos, qi::_1, qi::_3, qi::_5, qi::_7)]` Rather strange, i swear i've swapped boost::bind with phoenix::bind before and thought the compile still fails. weird. – Danny Jul 08 '18 at 05:53
  • `:` in context has the same effect as `qi::lit(':')` so it doesn't have an attribute (just matches the literal). – sehe Jul 08 '18 at 10:57
0

In my desperation I've also figured out how the container version works. See below,

auto test_fn = [&t](auto c) {
            t.tv_nsec = ( ( at_c<0>(c) * 3600 + 
                            at_c<2>(c)  * 60  + 
                            at_c<4>(c)  ) * 1000 + 
                            at_c<6>(c)  ) * 1000000; 

auto parse_ok = qi::parse(input.begin(), input.end(),
              ( qi::int_ >> qi::char_(":") >> qi::int_ >> qi::char_(":") >> 
                qi::int_ >> qi::char_(".") >> qi::int_ )[ test_fn ]

Admittedly, it's quite ugly.

Danny
  • 391
  • 2
  • 12
  • 1
    Flip that around: [not using semantic actions](https://stackoverflow.com/questions/8259440/boost-spirit-semantic-actions-are-evil) at all: http://coliru.stacked-crooked.com/a/781ea7da57ed8e99 – sehe Jul 08 '18 at 12:26
  • 1
    Or using the "experimental" X3 version http://coliru.stacked-crooked.com/a/07e0025d2c0365ba – sehe Jul 08 '18 at 12:46