I want to parse a recursive grammar with Boost.Spirit x3, but it fails with a template instantiation depth problem.
The grammar looks like :
value: int | float | char | tuple
int: "int: " int_
float: "float: " real_
char: "char: " char_
tuple: "tuple: [" value* "]"
Here is a contained example:
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>
struct value: std::variant<int,float,std::vector<value>>
{
using std::variant<int,float,std::vector<value>>::variant;
value& operator=(float) { return *this; }
value& operator=(int) { return *this; }
value& operator=(std::vector<value>) { return *this; }
};
using namespace boost::fusion;
namespace x3 = boost::spirit::x3;
using x3::skip;
using x3::int_;
using x3::real_parser;
using x3::char_;
x3::rule<class value_, value> const value_ = "value";
x3::rule<class o_tuple_, std::vector<value>> o_tuple_ = "tuple";
using float_p = real_parser<float, x3::strict_real_policies<float>>;
const auto o_tuple__def = "tuple: " >> skip(boost::spirit::x3::space)["[" >> value_ % "," >> "]"];
BOOST_SPIRIT_DEFINE(o_tuple_)
const auto value__def
= ("float: " >> float_p())
| ("int: " >> int_)
| o_tuple_
;
BOOST_SPIRIT_DEFINE(value_)
int main()
{
std::string str;
value val;
using boost::spirit::x3::parse;
auto first = str.cbegin(), last = str.cend();
bool r = parse(first, last, value_, val);
}
This works if the line | o_tuple_
is commented (eg no recursion).
This is a common problem with recursive in X3. It's yet unresolved.
I think I understand the issue is because of
x3::skip
alters the context object¹. Indeed, dropping that makes the thing compile, and successfully parse some trivial test cases:However, obviously the following do not parse without the skipper:
Now, I venture that you can get rid of the problem by applying the skipper at the top level (which means that the context is identical for all rules involved in the instantiation "cycle"). If you do, you will at once start accepting more flexible whitespace in the input:
None of these would have parsed with the original approach, even if it had compiled successfully.
What it takes
Here's some of the tweaks I made to the code.
removed the impotent assignment operators
value::operator=
(I don't know why you had them)add code to print a debug dump of any
value
:Drop the skipper and split out keywords from
:
interpunction:Now, the crucial step: add the skipper at toplevel:
Create nice test driver
main()
:Live Demo
See it Live On Coliru
Prints
¹ other directives do too, like
x3::with<>
. The problem would be that the context gets extended on each instantiation level, instead of "modified" to get the original context type back, and ending the instantiation cycle.