A loose definition of "parsing" would be to transform textual representation to "another" (often, more native) representation.
It doesn't really make sense to "parse" a number into a std::string. What you're seeing is automatic attribute propagation trying very hard to make sense of it (by sticking the parsed number into a string as a character).
That's not what you wanted. Instead, you want to parse the integer value, or the double value. For this, You could simply declare a variant attribute type:
using V = boost::variant<std::string, double, unsigned int>;
qi::rule<std::string::const_iterator, V()>
immediate = double_quoted_string | qi::double_ | qi::uint_;
That's it. Live demo, adding type-checks on the result:
Live On Coliru
#include <iostream>
#include <iomanip>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
using namespace std::string_literals;
int main() {
for (auto&& [str, type] : std::vector {
std::pair("\"hello\""s, typeid(std::string).name()),
{" \" hello \" "s, typeid(std::string).name()},
{" \" hello \"\"stranger\"\" \" "s, typeid(std::string).name()},
{"1"s, typeid(unsigned int).name()},
{"23"s, typeid(unsigned int).name()},
{"456"s, typeid(unsigned int).name()},
{"3.3"s, typeid(double).name()},
{"34.35"s, typeid(double).name()},
}) {
auto iter = str.cbegin(), end = str.cend();
qi::rule<std::string::const_iterator, std::string()> double_quoted_string
= '"' >> *("\"\"" >> qi::attr('"') | ~qi::char_('"')) >> '"';
using V = boost::variant<std::string, double, unsigned int>;
qi::rule<std::string::const_iterator, V()> immediate
= double_quoted_string | qi::double_ | qi::uint_;
std::cout << std::quoted(str) << " ";
V res;
bool r = qi::phrase_parse(iter, end, immediate, qi::blank, res);
bool typecheck = (type == res.type().name());
if (r) {
std::cout << "OK: " << res << " typecheck " << (typecheck?"MATCH":"MISMATCH") << "\n";
} else {
std::cout << "Failed\n";
}
if (iter != end) {
std::cout << "Remaining unparsed: " << std::quoted(std::string(iter, end)) << "\n";
}
std::cout << "----\n";
}
}
Prints
"\"hello\"" OK: hello typecheck MATCH
----
" \" hello \" " OK: hello typecheck MATCH
----
" \" hello \"\"stranger\"\" \" " OK: hello "stranger" typecheck MATCH
----
"1" OK: 1 typecheck MISMATCH
----
"23" OK: 23 typecheck MISMATCH
----
"456" OK: 456 typecheck MISMATCH
----
"3.3" OK: 3.3 typecheck MATCH
----
"34.35" OK: 34.35 typecheck MATCH
----
Note the re-ordering of uint_
after double_
. If you parse integers first, it will parse the integer part of a double until the decimal separator, and then fail to parse the rest. To be more accurate, you may want to use a strict real parser so that only number that actual have a fraction get parsed as doubles. This does limit the range for integral numbers because unsigned int
has a far smaller range than double
.
See Parse int or double using boost spirit (longest_d)
Live On Coliru
qi::rule<std::string::const_iterator, V()> immediate
= double_quoted_string
| qi::real_parser<double, qi::strict_real_policies<double> >{}
| qi::uint_;
Prints
"\"hello\"" OK: hello typecheck MATCH
----
" \" hello \" " OK: hello typecheck MATCH
----
" \" hello \"\"stranger\"\" \" " OK: hello "stranger" typecheck MATCH
----
"1" OK: 1 typecheck MATCH
----
"23" OK: 23 typecheck MATCH
----
"456" OK: 456 typecheck MATCH
----
"3.3" OK: 3.3 typecheck MATCH
----
"34.35" OK: 34.35 typecheck MATCH
----