2

I have the following boost::spirit::qi parser rule:

namespace qi = boost::spirit::qi;
qi::rule<Iterator, BroadbandCurve(), Skipper> Cmd_BBNSET;


Cmd_BBNSET = +(qi::float_ >> qi::float_) >> qi::int_ >> qi::int_ >> lit("BBNSET");

I'm trying to get it to emit the following attribute:

struct FreqLevelPair
{
    float freq;
    float dbLevel;
};
BOOST_FUSION_ADAPT_STRUCT(
    FreqLevelPair,
    (float, freq)
    (float, dbLevel)
)

struct BroadbandCurve
{
    std::vector<FreqLevelPair> freqPairs;
    int numFreqPairs; //Ignored since we can just count the number of pairs outright...
    int curveNum; //ID number
};
BOOST_FUSION_ADAPT_STRUCT(
    BroadbandCurve,
    (std::vector<FreqLevelPair>, freqPairs)
    (int, numFreqPairs)
    (int, curveNum)
)

As you can see, I'm attempting to parse one or more pairs of floats, followed by two ints, followed by the literal "BBNSET." All of this code compiles, however when I attempt to parse a valid BBNSET command of the form:

0.0 80.0 50.0 25.0 100.0 10.0 3 0 BBNSET

the parse fails. I'm unable to determine why. I've attempted wrapping the float pairs in a lexeme directive, and changing the + to a *, but no matter what I've attempted, the command still fails to parse, despite compiling without a problem.

What am I doing wrong, and will this rule emit the attribute as expected once it is parsing correctly?

stix
  • 1,140
  • 13
  • 36
  • Please make your sample a SSCCE next time (as [in my answer](http://coliru.stacked-crooked.com/a/26b01c59cc585605)...). See also [my comment here](http://stackoverflow.com/questions/23276879/what-is-the-correct-way-to-use-boostqirule-with-boost-fusion-adapt-struct#comment35633188_23276879) – sehe Apr 29 '14 at 15:53

2 Answers2

3

I suspect your issue is actually with Recursive Descent Parsers in general.

Cmd_BBNSET = +(qi::float_ >> qi::float_) >> qi::int_ >> qi::int_ >> lit("BBNSET");

Let's walk this:

0.0 80.0 50.0 25.0 100.0 10.0 3 0 BBNSET
^
Matches +(qi::float_ >> qi::float_)

0.0 80.0 50.0 25.0 100.0 10.0 3 0 BBNSET
         ^
         Matches +(qi::float_ >> qi::float_)

0.0 80.0 50.0 25.0 100.0 10.0 3 0 BBNSET
                   ^
                   Matches +(qi::float_ >> qi::float_)

0.0 80.0 50.0 25.0 100.0 10.0 3 0 BBNSET
                              ^
                          !!! Matches +(qi::float_ >> qi::float_) !!!

0.0 80.0 50.0 25.0 100.0 10.0 3 0 BBNSET
                                  ^
                                  Does not match qi::int_.
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • Actually, that suggested fix might not work... I'm not sure if `3.4` matches `qi::int_` and just parses to the `.`. – Bill Lynch Apr 29 '14 at 15:48
  • It will indeed. +1 though. It doesn't often happen I get sniped to a [tag:boost-spirit] answer. And with excellent explanation. My answer fixes the issue, though I don't have much time to look at it from all angles :) – sehe Apr 29 '14 at 15:49
  • @sehe: I was hoping you'd make it into here to note the correct method for fixing it. I didn't really like my proposed fix :) – Bill Lynch Apr 29 '14 at 15:58
3

Sharth was really quick to note the cause.

The solution, IMO is to use strict_real_policies

See it Live On Coliru

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

struct FreqLevelPair
{
    float freq;
    float dbLevel;
};

BOOST_FUSION_ADAPT_STRUCT(
    FreqLevelPair,
    (float, freq)
    (float, dbLevel)
)

struct BroadbandCurve
{
    std::vector<FreqLevelPair> freqPairs;
    int numFreqPairs; //Ignored since we can just count the number of pairs outright...
    int curveNum; //ID number
};

BOOST_FUSION_ADAPT_STRUCT(
    BroadbandCurve,
    (std::vector<FreqLevelPair>, freqPairs)
    (int, numFreqPairs)
    (int, curveNum)
)

int main()
{
    typedef std::string::const_iterator Iterator;
    typedef qi::space_type Skipper;

    qi::real_parser<double, qi::strict_real_policies<double> > strict_real_;

    qi::rule<Iterator, BroadbandCurve(), Skipper> Cmd_BBNSET;
    Cmd_BBNSET = +(strict_real_ >> strict_real_) >> qi::int_ >> qi::int_ >> qi::lit("BBNSET");

    std::string const input("0.0 80.0 50.0 25.0 100.0 10.0 3 0 BBNSET");
    auto f(input.begin()), l(input.end());
    bool ok = qi::phrase_parse(f, l, Cmd_BBNSET, qi::space);

    if (ok)
    {
        std::cout << "Parse succeeded\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
  • Thanks for the help. I had assumed this would work, since in my limited testing it seemed to resolve the problem. However, in the wild the parser is having trouble on actual files where the decimal point isn't specified (`i.e. 0 50 200 100 2 0 BBNSET`). I'm assuming this is because of the strict_real requirement imposed on the parser. Is it possible to resolve this so that the decimal point is optional? – stix Apr 29 '14 at 21:43
  • Your grammar appears to be ambiguous. Your initial rule /appeared/ to hinge around the fact that ints are distinguished from floats. If that's not the case, there's only one recourse: parse everything tentatively and interpret the `numFreqPairs` value as soon as you figured out where it lives. There is a reason why file formats tend to put length fields in the header, and you've just discovered why. – sehe Apr 29 '14 at 21:50
  • 1
    @stix Okay, that took a little longer: http://coliru.stacked-crooked.com/a/04baceeb4b13b2f4 – sehe Apr 29 '14 at 22:37
  • Thanks loads! That worked flawlessly with the exception that I had to turn attr.push_back({a, b}); into an explicit FreqLevelPair because of my compiler. :) – stix Apr 30 '14 at 15:15