I am currently learning how to use x3. As the title states, I have had success creating a grammar with a few simple rules, but upon combining two of these rules into one, the code no longer compiles. Here is the code for the AST portion:
namespace x3 = boost::spirit::x3;
struct Expression;
struct FunctionExpression {
std::string functionName;
std::vector<x3::forward_ast<Expression>> inputs;
};
struct Expression: x3::variant<int, double, bool, FunctionExpression> {
using base_type::base_type;
using base_type::operator=;
};
The rules I have created parse input formatted as {rangeMin, rangeMax}
:
rule<struct basic_exp_class, ast::Expression> const
basic_exp = "basic_exp";
rule<struct exp_pair_class, std::vector<ast::Expression>> const
exp_pair = "exp_pair";
rule<struct range_class, ast::FunctionExpression> const
range = "range";
auto const basic_exp_def = double_ | int_ | bool_;
auto const exp_pair_def = basic_expr >> ',' >> basic_expr;
auto const range_def = attr("computeRange") >> '{' >> exp_pair >> '}';
BOOST_SPIRIT_DEFINE(basic_expr, exp_pair_def, range_def);
This code compiles fine. However, if I try to inline the exp_pair
rule into the range_def
rule, like so:
rule<struct basic_exp_class, ast::Expression> const
basic_exp = "basic_exp";
rule<struct range_class, ast::FunctionExpression> const
range = "range";
auto const basic_exp_def = double_ | int_ | bool_;
auto const range_def = attr("computeRange") >> '{' >> (
basic_exp >> ',' >> basic_exp
) >> '}';
BOOST_SPIRIT_DEFINE(basic_expr, range_def);
The code fails to compile with a very long template error, ending with the line:
spirit/include/boost/spirit/home/x3/operator/detail/sequence.hpp:149:9: error: static assertion failed: Size of the passed attribute is less than expected.
static_assert(
^~~~~~~~~~~~~
The header file also includes this comment above the static_assert
:
// If you got an error here, then you are trying to pass
// a fusion sequence with the wrong number of elements
// as that expected by the (sequence) parser.
But I do not see why the code should fail. According to x3's compound attribute rules, the inlined portion in the parenthesis should have an attribute of type vector<ast::Expression>
, making the overall rule have the type tuple<string, vector<ast::Expression>
, so that it would be compatible with ast::FunctionExpression
. The same logic applies the more verbose three-rule version, the only difference being that I specifically declared a rule for the inner part and specifically stated its attribute needed to be of type vector<ast::Expression>
.
Spirit x3 is probably seeing the result of the inlined rule as two separate ast::Expression
instead of the std::vector<ast::Expression>
required by the ast::FunctionExpression
struct.
To solve it we can use a helper as
lambda as mentioned in another answer to specify the return type of a sub-rule.
And the modified range_def would become:
auto const range_def = attr("computeRange") >> '{' >> as<std::vector<ast::Expression>>(basic_exp >> ',' >> basic_exp) >> '}';