1

I am using boost::spirit::qi to parse a number that can be optionally followed by metric prefixes. For example, "10k" for 10000. The k should be treated in a case insensitive fashion. I can simply match the prefix using:

#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

using iter = std::string::const_iterator;

class MyGrammar : public qi::grammar<iter>
{
public:

    MyGrammar()
    : MyGrammar::base_type(start)
    {
        start = qi::double_ >> suffix | qi::double_;
        suffix = qi::char_('K') | qi::char_('k');
    }

    qi::rule<iter> suffix; 
    qi::rule<iter> start;
};

What I would like to do is to use the above code to not only parse/match the format, but to use whatever method is appropriate to convert the suffix (e.g., k) to a numerical multiplier and return a double.

How does one achieve this using boost qi?

user3814483
  • 292
  • 1
  • 2
  • 13

2 Answers2

1

What Nail said works. I'd suggest using symbols to be more efficient and potentially more correct (e.g. with overlapping suffixes). Here's an example to parse time units:

  • Custom validate function to parse std::chrono::milliseconds via Boost program options

     int magnitude;
     clock::duration factor;
    
     namespace qi = boost::spirit::qi;
     qi::symbols<char, clock::duration> unit;
     unit.add("s",1s)("ms",1ms)("us",1us)("µs",1us)("m",1min)("h",1h);
    
     if (parse(s.begin(), s.end(), qi::int_ >> (unit|qi::attr(1s)) >> qi::eoi, magnitude, factor))
         v = duration {magnitude * factor};
     else
         throw po::invalid_option_value(s);
    

In your example I'd write something like Live On Coliru

#include <boost/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>

namespace qi = boost::spirit::qi;

template <typename It> struct MyGrammar : qi::grammar<It, double()> {
    MyGrammar() : MyGrammar::base_type(start) {
        using namespace qi::labels;

        factor.add("k", 1e3)("m", 1e6)("g", 1e9)("t", 1e12)("p", 1e15)("z", 1e18);
        optunit = qi::no_case[factor] | qi::attr(1.0);
        start   = (qi::double_ >> optunit)[_val = _1 * _2];
    }

  private:
    qi::symbols<char, double> factor;
    qi::rule<It, double()>    start, optunit;
};

int main() {
    MyGrammar<char const*> g;

    for (double v; std::string_view s : {"1.23", "1.23k", "1.23T"})
        if (parse(begin(s), end(s), g, v))
            std::cout << quoted(s) << " -> " << v << std::endl;
        else
            std::cout << quoted(s) << " -> FAIL" << std::endl;

}

Printing

"1.23" -> 1.23
"1.23k" -> 1230
"1.23T" -> 1.23e+12
sehe
  • 374,641
  • 47
  • 450
  • 633
0

In your case, you could attach a semantic action to your 'suffix' rule that multiplies the parsed number by 1000 whenever a 'k' or 'K' gets matched. To keep track of the parsed number , you'll need to adjust your rules a bit to include a double as the attribute, so you can access it in your semantic actions.

Here's what that would look like:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phoenix = boost::phoenix;

using iter = std::string::const_iterator;

class MyGrammar : public qi::grammar<iter , double()>
{
public:

    MyGrammar()
    : MyGrammar::base_type(start)
    {
        using qi::_val;
        using qi::_1;

        start = (qi::double_[_val = _1] >> suffix[_val *= _1]) | qi::double_[_val = _1];
        suffix = qi::no_case['k'][_val = 1000];
    }

    qi::rule<iter , double()> suffix; 
    qi::rule<iter , double()> start;
};

This uses boost::phoenix operators to create the semantic actions. In the start rule, _val = _1 sets the attribute of the rule (in this case , a double) to the parsed number. Then, _val *= _1 in the suffix rule multiplies that number by 1000.

The qi::no_case['k'] part is a nice way of handling both 'k' and 'K' , without repeating yourself.

  • 1
    This answer looks like it was generated by an AI (like ChatGPT), not by an actual human being. You should be aware that [posting AI-generated output is officially **BANNED** on Stack Overflow](https://meta.stackoverflow.com/q/421831). If this answer was indeed generated by an AI, then I strongly suggest you delete it before you get yourself into even bigger trouble: **WE TAKE PLAGIARISM SERIOUSLY HERE.** Please read: [Why posting GPT and ChatGPT generated answers is not currently acceptable](https://stackoverflow.com/help/gpt-policy). – tchrist Jul 08 '23 at 18:16
  • 1
    @tchrist Out of curiosity, what are signs this might be AI generated? I consider myself subject expert here (see [top users](https://stackoverflow.com/tags/boost-spirit/topusers)) and I see multiple reasons to assume the answer was written by a human. Also interested in the the - seemingly unrelated - plagiary accusation. Are you using specific tools to detect plagiary? – sehe Jul 09 '23 at 01:06
  • 3
    @sehe For one thing, it's a great deal to type in the 4 minutes between when the question was posted and this answer was. For another, it's the first in a sequence of 7 answers that are all that way. You can't get around to seeing a question, thinking about an answer, devising and testing a solution, and writing it up in that amount of time. Go look at his other answers and check the exact timings and word counts, and you'll see it's simply not credible that a human did this—probably not even once at 90 words per minute or whatnot, and certainly not 7 times! There are other signs as well. – tchrist Jul 09 '23 at 03:14
  • 1
    @tchrist Those are excellent observations. As you know I know exactly how long it takes to answer a Qi question with code :) Thanks for sharing your insight. I was focusing on the wrong things (like, spurious spacing/interpunction that I assumed would not happen with AI output, or specific choice of wording). – sehe Jul 09 '23 at 10:13