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?
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