X3: plain into variant of single element tuple
Bockeman opened this issue · 6 comments
This code does not compile
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
template <typename T>
struct as_type{
template <typename E>
constexpr auto operator[](E e) const {return x3::rule<struct _, T> {} = e;}
};
template <typename T>
static inline constexpr as_type<T> as;
namespace ast {
using namespace std;
struct tagged_string : std::string {};
enum token_enum {lcomment, rcomment};
struct tagged_token {token_enum token;};
struct comment_item : x3::variant<tagged_string, tagged_token>
{using base_type::base_type;using base_type::operator=;};
struct comment : std::vector<comment_item>{
using std::vector<comment_item>::vector;
using std::vector<comment_item>::operator=;
};
struct start_item : x3::variant<comment>
{using base_type::base_type; using base_type::operator=;};
struct start : std::vector<start_item>{using std::vector<start_item>::vector;};
}
BOOST_FUSION_ADAPT_STRUCT(ast::tagged_token, token)
namespace grammar {
x3::symbols<ast::token_enum> lcomment_token;
x3::symbols<ast::token_enum> rcomment_token;
x3::rule<struct lcomment, ast::tagged_token> const tagged_lcomment = "tagged_lcomment";
x3::rule<struct rcomment, ast::tagged_token> const tagged_rcomment = "tagged_rcomment";
x3::rule<struct text, ast::tagged_string> const tagged_text = "tagged_text";
x3::rule<struct comment, ast::comment> const comment = "comment";
x3::rule<struct start_rule, ast::start> const start_rule = "start_rule";
auto const tagged_lcomment_def = lcomment_token; // /*
auto const tagged_rcomment_def = rcomment_token; // */
auto const tagged_text_def = *(x3::char_ - rcomment_token); // not */
// Compile fails:
auto const comment_def = lcomment_token > tagged_text > rcomment_token; // /* ... */
// Compiles ok, but ast is not correctly assembled:
//auto const comment_def = (x3::rule<class id>{}=lcomment_token) > tagged_text > tagged_rcomment;// /* ... */
// Compiles ok, and ast is ok:
//auto const comment_def = as<ast::tagged_token>[lcomment_token] > tagged_text > tagged_rcomment;// /* ... */
//auto const comment_def = tagged_lcomment > tagged_text > tagged_rcomment; // /* ... */
auto const start_rule_def = x3::no_skip[+comment]; // 1 ... ;
BOOST_SPIRIT_DEFINE(tagged_lcomment, tagged_rcomment, tagged_text, comment, start_rule);
}
int main() {
char const* iter = "/** /**/", * const end = iter + std::strlen(iter);
ast::start ast;
grammar::lcomment_token.add ("/*", ast::lcomment);
grammar::rcomment_token.add ("*/", ast::rcomment);
bool ok = parse(iter, end, grammar::start_rule, ast) && iter==end;
if(ok)BOOST_ASSERT(3==boost::get<ast::comment>(ast[0].var).size());
return !ok;
}
It seems the only way to successfully compile is to wrap the token (x3::symbols<>
) into a rule (either inline, or with a separate rule definition). Is this an X3 implementation problem, or is it beyond the anticipated capabilities of X3?
Don't know variant or single element tuple or both, but not x3::symbols:
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace ast {
struct tagged_token { int token; };
}
BOOST_FUSION_ADAPT_STRUCT(ast::tagged_token, token)
int main() {
namespace x3 = boost::spirit::x3;
char const* iter = "/** /**/", * const end = iter + std::strlen(iter);
x3::variant<ast::tagged_token> x;
x3::parse(iter, end, x3::attr(int{}), x);
}
It seems that not the single element variant is the problem. It depends on the number of struct/class members. No member works, two or more members works but not one member:
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <variant>
struct no_member {};
struct one_member {
int a;
};
BOOST_FUSION_ADAPT_STRUCT(one_member, a)
struct two_members {
int a{};
int b{};
};
BOOST_FUSION_ADAPT_STRUCT(two_members, a, b)
int main()
{
namespace x3 = boost::spirit::x3;
char const* iter = "", * const end = iter + std::strlen(iter);
std::variant<no_member, one_member, two_members> x;
x3::parse(iter, end, x3::attr(no_member{}), x); // This line compiles
x3::parse(iter, end, x3::attr(one_member{}), x); // This line failes
x3::parse(iter, end, x3::attr(two_members{}), x); // This line compiles
}
Is there a workaround available for this behaviour?
Use boost::variant instead of std::variant.
The problem, I'd guess, is:
https://www.boost.org/doc/libs/1_81_0/boost/spirit/home/x3/support/traits/is_variant.hpp
doesn't consider std::variant as a variant.
OOPS! Adding obvious change to is_variant.hpp not enough. With the obvious change, now getting, using std::variant:
C:/msys64/home/evansl/prog_dev/boost.org/1_80_0download/download/boost/spirit/home/x3/support/traits/variant_has_substitute.hpp:23:40: error: no type named 'types' in 'std::variant<no_member, one_member, two_members>'
typedef typename variant_type::types types;
~~~~~~~~~~~~~~~~~~~~~~~^~~~~
OOPS! Adding obvious change to is_variant.hpp not enough. With the obvious change, now getting, using std::variant:
C:/msys64/home/evansl/prog_dev/boost.org/1_80_0download/download/boost/spirit/home/x3/support/traits/variant_has_substitute.hpp:23:40: error: no type named 'types' in 'std::variant<no_member, one_member, two_members>' typedef typename variant_type::types types; ~~~~~~~~~~~~~~~~~~~~~~~^~~~~
However, changing obvious portion of variant_has_substitute.hpp to:
template <typename Variant, typename T>
struct variant_has_substitute_impl
;
template <template<typename...>typename Variant, typename T, typename... Elements>
struct variant_has_substitute_impl< Variant<Elements...>, T>
{
// Find a type from the Variant that can be a substitute for T.
// return true_ if one is found, else false_
typedef Variant<Elements...> variant_type;
typedef fusion::vector<Elements...> types;
and #including <boost/fusion/container.hpp> before seems to solve problem.
@cppljevans Thank you for your response. Replacing std::variant
with boost::variant
(or x3::variant
) did the trick!
Sorry for confusing with the original post of @Bockeman.