Juan Dent
Juan Dent

Reputation: 449

I am using Boost.Spirit to use a boost::tuple to retrieve the attributes of a parser with no success

see this snippet of code:

                     using qi::int_;
                      using qi::phrase_parse;
                      using qi::_1;
                      using ascii::space;
                      
                      std::string s = "50 43.3 77.44";   
                      first = s.begin();
                      last = s.end();
boost::tuple<boost::optional<double>, boost::optional<int>> to;

bool r = phrase_parse(first, last,
(
double_ || int_
),
space, to);                  // r == true

auto a = to.get<0>(); // this works catches the 50 from the input
auto aa = *a;
auto b = to.get<1>();      // this does not work even though 43 is parsed correctly
auto bb = *b;             // EXCEPTION

I have tried placing this strange behaviour the [email protected] but no one answers

The code is very small. I would truly appreciate if someone tries to compile and runs it!

Regards, Juan

Upvotes: 0

Views: 78

Answers (1)

sehe
sehe

Reputation: 393114

qi::double_ | qi::int_ parses alternatives, not a sequence.

In other words, I'd expect you to use qi::double_ | qi::int_ to parse variant<double, int> (or compatible).

In contrast, use qi::double_ >> qi::int_ to parse tuple<double, int> (or compatible).

Now, double_ || int_ is a weird one: it's an optional/alternative sequence: https://www.boost.org/doc/libs/1_74_0/libs/spirit/doc/html/spirit/qi/reference/operator/sequential_or.html. So... what do you expect to happen?

What Is The Goal?

It looks like you're parsing space-separated numbers. There's 3 of them. But you parse into a tuple of two. With an alternative sequence. Of which both branches conflict (any integer will parse as a double, and the integer part of a double can parse as an int, as you very well know, because you claim "even though 43 is parsed correctly").

I'm at a loss what you're trying to achieve, as most people would never say that "43 is parsed correctly" since there is no number 43 in the input (that's only part of 43.3).

Let's Demo The Options

Because it's very hard to guess what you were actually trying to parse here, let's avoid choosing and just demonstrate all approaches starting from your example:

Live On Coliru

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/lexical_cast.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;

std::string const sample = "50 43.3 77.44";

template <typename T>
static std::string debug_string(T const& v, std::string const& sep = "") {
    return boost::lexical_cast<std::string>(v) + sep;
}

template <typename... T>
static auto debug_string(std::tuple<T...> const& v, std::string const& sep = "") {
    std::ostringstream oss;
    oss << "( ";
    auto out = [&oss](auto const&... elements) {
        (oss << ... << debug_string(elements, " ")) << ")";
    };

    std::apply(out, v);
    return oss.str() + sep;
}

template <typename Parser, typename... Attrs>
void demo_parse(Parser const& p, Attrs... attrs) {
    auto first = sample.begin();
    auto last = sample.end();

    if (qi::phrase_parse(first, last, p, qi::space, attrs...)) {
        ((std::cout << "Parsed: ") << ... << debug_string(attrs, " ")) << "\n";
    } else {
        std::cout << "Parse failed\n";
    }

    if (first != last) {
        std::cout << "Remaining input: " << std::quoted(std::string(first, last)) << "\n";
    }
    std::cout << "------------------------\n";
}

int main() {
    std::cout << sample << "\n========================\n";
    
    //// as a sequence
    std::cout << "As a sequence" << "\n========================\n";
    // note that int vs double input can be confusing:
    auto parse_sequence = [](auto p) {
        double d = 0; int i = 0;
        demo_parse(p, d, i);
        demo_parse(p, i, d);
        demo_parse(p, std::tuple<double, int>{});
        demo_parse(p, std::tuple<int, double>{});
        demo_parse(p, std::tuple<boost::optional<double>, boost::optional<int> >{});
        demo_parse(p, std::tuple<boost::optional<int>, boost::optional<double> >{});
    };
    parse_sequence(qi::int_ >> qi::double_);
    parse_sequence(qi::double_ >> qi::int_);

    // as an alternative
    std::cout << "As alternatives" << "\n========================\n";
    // - keeping in mind the confusion between double_ and int_, using
    //   strict_real_policies
    qi::real_parser<double, qi::strict_real_policies<double> > sdouble_;
    demo_parse(sdouble_ | qi::int_, boost::variant<int, double>{});
    demo_parse(sdouble_ | qi::int_, boost::variant<double, int>{});
    demo_parse(sdouble_ | qi::int_, boost::optional<boost::variant<int, double>>{});

    // as alternative sequences
    std::cout << "As alternative sequences" << "\n========================\n";
    // - I don't know why this would be useful, and keep in mind the
    //   strict_real_policies again
    demo_parse(sdouble_ || qi::int_, std::tuple<int, double>{});
    demo_parse(sdouble_ || qi::int_, std::tuple<double, int>{});
    demo_parse(sdouble_ || qi::int_, std::tuple<boost::optional<double>, boost::optional<int> >{});

    std::cout << "Alternatives into variant type" << "\n========================\n";
    demo_parse(sdouble_ || qi::int_, boost::variant<int, double>{});
    demo_parse(sdouble_ || qi::int_, boost::variant<double, int>{});
    demo_parse(sdouble_ || qi::int_, boost::optional<boost::variant<double, int>> {});
}
    

Prints

50 43.3 77.44
========================
As a sequence
========================
Parsed: 50 43 
Remaining input: "77.44"
------------------------
Parsed: 50 43.299999999999997 
Remaining input: "77.44"
------------------------
Parsed: ( 50 43 ) 
Remaining input: "77.44"
------------------------
Parsed: ( 50 43.299999999999997 ) 
Remaining input: "77.44"
------------------------
Parsed: (  50  43 ) 
Remaining input: "77.44"
------------------------
Parsed: (  50  43.3 ) 
Remaining input: "77.44"
------------------------
Parsed: 50 43 
Remaining input: ".3 77.44"
------------------------
Parsed: 50 43 
Remaining input: ".3 77.44"
------------------------
Parsed: ( 50 43 ) 
Remaining input: ".3 77.44"
------------------------
Parsed: ( 50 43 ) 
Remaining input: ".3 77.44"
------------------------
Parsed: (  50  43 ) 
Remaining input: ".3 77.44"
------------------------
Parsed: (  50  43 ) 
Remaining input: ".3 77.44"
------------------------
As alternatives
========================
Parsed: 50 
Remaining input: "43.3 77.44"
------------------------
Parsed: 50 
Remaining input: "43.3 77.44"
------------------------
Parsed:  50 
Remaining input: "43.3 77.44"
------------------------
As alternative sequences
========================
Parsed: ( 0 50 ) 
Remaining input: "43.3 77.44"
------------------------
Parsed: ( 0 50 ) 
Remaining input: "43.3 77.44"
------------------------
Parsed: ( --  50 ) 
Remaining input: "43.3 77.44"
------------------------
Alternatives into variant type
========================
Parsed: 0 
Remaining input: "43.3 77.44"
------------------------
Parsed: 0 
Remaining input: "43.3 77.44"
------------------------
Parsed: -- 
Remaining input: "43.3 77.44"
------------------------

Or Maybe: Sane Option #1

That's probably overwhelming, so here's what I think you might have been after: just parsing a sequence of int-or-double:

Live On Coliru

// #define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;

std::string const sample = "50 43.3 77.44";
using It = std::string::const_iterator;

int main() {
    std::cout << sample << "\n========================\n";
    
    using Value = boost::variant<double, int>;
    using Values = std::vector<Value>;
    qi::real_parser<double, qi::strict_real_policies<double> > sdouble_;
    qi::rule<It, Value()> double_or_int = sdouble_ | qi::int_;
    BOOST_SPIRIT_DEBUG_NODE(double_or_int);

    auto first = sample.begin();
    auto last = sample.end();

    Values values;
    if (qi::phrase_parse(first, last, *double_or_int, qi::space, values)) {
        std::cout << "Parsed:";
        for (auto v : values)
            std::cout << " " << v;
        std::cout << "\n";
    } else {
        std::cout << "Parse failed\n";
    }

    if (first != last) {
        std::cout << "Remaining input: " << std::quoted(std::string(first, last)) << "\n";
    }
}

Prints

50 43.3 77.44
========================
Parsed: 50 43.3 77.44

Sane(?) Option #2

Perhaps the clue was in the weirdness of the claim that "43 is parsed correctly". If 43 is parsed "correctly" that means that the periods (".") are NOT decimal separators. So perhaps you want to parse "1 2 . 3 4 . 5 6 . 7 8" style input?

Live On Coliru

#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;

std::string const sample = "50 43.3 77.44";
using It = std::string::const_iterator;

int main() {
    std::cout << sample << "\n========================\n";

    auto first = sample.begin();
    auto last = sample.end();

    using Pair = std::pair<int, int>;
    std::vector<Pair> values;
    if (qi::phrase_parse(first, last, (qi::int_ >> qi::int_) % '.', qi::space, values)) {
        std::cout << "Parsed:";
        for (auto [a,b] : values)
            std::cout << " (" << a << " " << b << ")";
        std::cout << "\n";
    } else {
        std::cout << "Parse failed\n";
    }

    if (first != last) {
        std::cout << "Remaining input: " << std::quoted(std::string(first, last)) << "\n";
    }
}

Prints

50 43.3 77.44
========================
Parsed: (50 43) (3 77)
Remaining input: ".44"

Upvotes: 2

Related Questions