Using semantic actions (or another method) to operate on parsed value and return new value

95 Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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
4
On

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.