Search code examples
c++boost-spiritboost-spirit-x3

Spirit X3, two rules do not compile after being combined into one


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>.


Solution

  • 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) >> '}';