2

I'm currently building an expression parser in Boost.Parser. It should be able to do

  • Parentheses ( ( ... ) ) and abs ( | ... |)
  • Unary minus
  • Power ( x^y)
  • (other arithmetic operations, not relevant for the question).

Here's my set-up so far:

class ExpressionAst { /* ... */ };
class NumberNode : public ExpressionAst { /* ... */ };
class UnaryOpNode : public ExpressionAST { /* ... */ };
class BinaryOpNode : public ExpressionAST { /* ... */ };

bp::rule<struct number_tag, SharedExprPtr> number = "number";
bp::rule<struct primary_tag, SharedExprPtr> primary = "primary";
bp::rule<struct unary_tag, SharedExprPtr> unary = "unary";
bp::rule<struct power_tag, SharedExprPtr> power = "power";
bp::rule<struct expression_tag, SharedExprPtr> expression = "expression";

auto make_unary_minus_node = [](auto& ctx) {
    auto attr = _attr(ctx);
    _val(ctx) = std::make_shared<UnaryOpNode>(std::move(attr), UnaryOpNode::Op::Negate);
};

auto make_parentheses_node = [](auto& ctx) { _val(ctx) = _attr(ctx); };

auto make_abs_node = [](auto& ctx) {
    auto attr = _attr(ctx);
    _val(ctx) = std::make_shared<UnaryOpNode>(std::move(attr), UnaryOpNode::Op::Abs);
};

auto const number_def = (-sign >> (hex_literal | octal_literal | binary_literal | decimal_literal) >> -quantifier)[make_number_node];

auto const primary_def = number //
    | ('(' >> expression >> ')')[make_parentheses_node] //
    | ('|' >> expression >> '|')[make_abs_node];

auto const unary_def = ('-' >> unary)[make_unary_minus_node] //
    | primary;

auto const power_def = (unary >> -('^' >> power))[make_power_node];

auto const expression_def = power;

BOOST_PARSER_DEFINE_RULES(number, primary, unary, power, expression);

Now it comes to the implementation of my make_power_node semantic action:

auto make_power_node = [](auto& ctx) {
    using attr = std::remove_cvref_t<decltype(_attr(ctx))>;
    static_assert(!std::is_same_v<attr, SharedExprPtr>);

    // Make compiler happy for now
    _val(ctx) = std::make_shared<BinaryOpNode>(nullptr, nullptr, BinaryOpNode::Op::Pow);
};

I would've expected _attr(ctx) to just be a tuple<SharedExprPtr, std::optional<SharedExprPtr>>, and to just be able to destructure it. This doesn't seem to be the case. What am I doing wrong?

1 Answer 1

4

I agree that this looks to be a bug in the library.

For comparison: unary >> -('^' >> unary) works fine. Regardless your grammar can be expressed a bit more elegantly:

auto const power_def = unary[propagate] >> *(('^' >> unary)[make_power_node]);

Then your semantic action could look like:

auto make_power_node = [](auto& ctx) {
    _val(ctx) = std::make_shared<BinaryOpNode>(_val(ctx), _attr(ctx), BinaryOpNode::Op::Pow);
};
Sign up to request clarification or add additional context in comments.

3 Comments

Here's my compilation harness, with potentially interesting choices you might want to look at godbolt.org/z/8eYKj7458
Thanks so much! "Your grammar can be expressed more elegantly" -> Why is that one more "elegant" than the other?
Because it involves iteration instead of recursion and as such more directly expresses the repeated grammar production. It's subjective of course. It also involves a less complex semantic action, avoiding the need to destructure any attributes.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.