c++boostboost-spiritqi

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


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?


Solution

  • 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:

    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