I defined the output of literal_string as std:: string:
namespace parsesss {
namespace x3 = boost::spirit::x3;
auto const literal_string = x3::rule<class literal_string,std::string>{"literal_string"};
auto const literal_string_def = x3::lexeme['"' >> +(x3::char_ - '"') >> '"'];
BOOST_SPIRIT_DEFINE(literal_string);
}
Then I tested with the following code and the results also met expectations:
std::string test = "\"asdfjalsdjflajsdlfjalsdf\" \"xxxxxxxxxxasdfjalsdjflajsdlfjalsdf\"";
std::vector<std::string> out;
bool r = parse::x3::phrase_parse(test.begin(), test.end(), *(parsesss::literal_string), parse::x3::ascii::space, out);
if(r && (parse::g_iter == parse::g_end)){
std::cout << "parse success!" << std::endl;
}else{
std::cerr << "parse fail!" << std::endl;
}
But when I use variant data structures to store output, std:: string degenerates into char, why is this。
struct constant : x3::variant<long,char,double,float,int>{
using base_type::base_type;
using base_type::operator=;
};
struct last : x3::variant<std::string,char,constant>{
using base_type::base_type;
using base_type::operator=;
};
//
std::string test = "\"asdfjalsdjflajsdlfjalsdf\" \"xxxxxxxxxxasdfjalsdjflajsdlfjalsdf\"";
std::vector<last> out;
bool r = parse::x3::phrase_parse(test.begin(), test.end(), *(parsesss::literal_string), parse::x3::ascii::space, out);
if(r && (parse::g_iter == parse::g_end)){
std::cout << "parse success!" << std::endl;
}else{
std::cerr << "parse fail!" << std::endl;
}
Who can tell me why,
Or where can I find more detailed questions about this aspect on the official website。
The variant attribute can be constructed from a char
, which is preferred.
If you drop char
from the variant element type list, it will work:
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <iomanip>
#include <iostream>
namespace x3 = boost::spirit::x3;
namespace AST {
using x3::variant;
struct constant : variant<long, double, float, int> {
using base_type::base_type;
using base_type::operator=;
};
struct last : variant<std::string, constant> {
using base_type::base_type;
using base_type::operator=;
};
template <typename... T> static std::ostream& operator<<(std::ostream& os, variant<T...> const& v) {
return boost::apply_visitor([&os](auto&& v) -> auto& { return os << v; }, v);
}
}
namespace parsesss {
auto const literal_string = x3::rule<class literal_string, std::string>{"literal_string"};
auto const literal_string_def = x3::lexeme['"' >> *~x3::char_('"') >> '"'];
BOOST_SPIRIT_DEFINE(literal_string);
} // namespace parsesss
int main() {
std::string const test = R"("asdf" "xxx yyy")";
if (std::vector<AST::last> out;
phrase_parse(test.begin(), test.end(), *(parsesss::literal_string) >> x3::eoi, x3::space, out))
for (auto& el : out) {
std::cout << " - " << el << std::endl;
}
else
std::cout << "fail (" << quoted(test) << std::endl;
}
Prints
- asdf
- xxx yyy
I tried hinting the attribute system with traits:
template <> struct x3::traits::is_substitute<AST::last, std::string> : std::true_type {};
But, not unexpectedly, that didn't work.
What you can do instead is to break the compatibility of "char":
struct Char {
char value;
friend std::ostream& operator<<(std::ostream& os, Char ch) { return os << '\'' << ch.value << '\''; }
};
This already works (live). You can do shorter here: (live as well)
enum Char : char;
However, I noticed that these will just give you other trouble when you do want to parse the char literals...
So instead, I took the flight forwards and made some rules to match all the AST nodes.
As a rule of thumb, being explicit about attribute congruence helps. For example, the "naive" expression for constant
will not work:
constant = x3::double_ | x3::int_;
For two reasons:
double_
will eagerly consume integral numbers. We avoid that by substituting strict_real_policies
x3::int_
is ambiguously compatible with either double
or intmax_t
. Now, there will be an x3
numeric parser like x3::long_long
that happens to match intmax_t
, but it's better to be exactconstant
= x3::real_parser<double, x3::strict_real_policies<double>>{}
| x3::int_parser<intmax_t>{}
;
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <iomanip>
#include <iostream>
namespace x3 = boost::spirit::x3;
namespace AST {
using x3::variant;
struct constant : variant<intmax_t, double> {
using base_type::base_type;
using base_type::operator=;
};
struct last : variant<std::string, char, constant> {
using base_type::base_type;
using base_type::operator=;
};
template <typename> constexpr std::string_view tname = "?";
template <> constexpr std::string_view tname<std::string> = "string";
template <> constexpr std::string_view tname<intmax_t> = "integer";
template <> constexpr std::string_view tname<double> = "real";
template <> constexpr std::string_view tname<char> = "character";
template <> constexpr std::string_view tname<constant> = "constant";
template <typename... T> static std::ostream& operator<<(std::ostream& os, variant<T...> const& v) {
return boost::apply_visitor(
[&os](auto const& v) -> auto& {
auto type = tname<std::decay_t<decltype(v)>>;
return os << type << '(' << v << ')';
},
v);
}
}
namespace parsesss {
auto string_lit = x3::rule<class string_lit, std::string>{"string_lit"} =
x3::lexeme['"' >> *~x3::char_('"') >> '"'];
auto char_lit = x3::rule<class char_lit, char>{"char_lit"} =
x3::lexeme["'" >> ('\\' >> x3::char_ | ~x3::char_("'")) >> "'"];
auto constant = x3::rule<class constant, AST::constant>{"constant"} =
x3::real_parser<double, x3::strict_real_policies<double>>{} //
| x3::int_parser<intmax_t>{};
auto last = x3::rule<class last, AST::last>{"last"} =
string_lit | char_lit | constant;
} // namespace parsesss
int main() {
for (std::string const test :
{
R"("asdf" "xxx yyy")",
R"("asdf" 'c' "xxx yyy")",
R"("asdf" '\'' "xxx yyy")",
R"("asdf" '\'' 3.14e4 9999 -9999 -inf +nan 3. "xxx yyy")",
}) //
{
std::cout << "testcase " << quoted(test) << std::endl;
std::vector<AST::last> out;
if (phrase_parse(test.begin(), test.end(), *parsesss::last >> x3::eoi, x3::space, out))
for (auto& el : out)
std::cout << " - " << el << std::endl;
else
std::cout << " - FAIL" << std::endl;
}
}
Printing
testcase "\"asdf\" \"xxx yyy\""
- string(asdf)
- string(xxx yyy)
testcase "\"asdf\" 'c' \"xxx yyy\""
- string(asdf)
- character(c)
- string(xxx yyy)
testcase "\"asdf\" '\\'' \"xxx yyy\""
- string(asdf)
- character(')
- string(xxx yyy)
testcase "\"asdf\" '\\'' 3.14e4 9999 -9999 -inf +nan 3. \"xxx yyy\""
- string(asdf)
- character(')
- constant(real(31400))
- constant(integer(9999))
- constant(integer(-9999))
- constant(real(-inf))
- constant(real(nan))
- constant(real(3))
- string(xxx yyy)