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

67 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 answers

0
sehe 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
nail steiger 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.