jinja : parse unary +/- before variables (#29244)

* jinja : parse unary +/- before variables

Lexer already emits unary_operator for -n / +n, and runtime executes
unary -. Parse them at multiplicative precedence so slices like
items[:-n] and GigaChat indent[:-indent_factor] work.

* jinja : keep filters/tests outside unary operands

Unary +/- must bind only the primary/postfix operand so -n|abs is
(-n)|abs, not -(n|abs). Add unary + and filter/test regression coverage.

Signed-off-by: sinksilk <[email protected]>

---------

Signed-off-by: sinksilk <[email protected]>
This commit is contained in:
Si Chen
2026-09-23 13:29:45 +02:00
committed by GitHub
parent ee3ecce05c
commit 4e416ee730
3 changed files with 59 additions and 1 deletions
+11 -1
View File
@@ -437,7 +437,8 @@ private:
}
statement_ptr parse_filter_expression() {
auto operand = parse_call_member_expression();
// Filters/tests bind outside unary so -n|abs is (-n)|abs, not -(n|abs).
auto operand = parse_unary_expression();
while (is(token::pipe)) {
size_t start_pos = current;
++current; // consume pipe
@@ -448,6 +449,15 @@ private:
return operand;
}
statement_ptr parse_unary_expression() {
if (is(token::unary_operator)) {
size_t start_pos = current;
auto op = next();
return mk_stmt<unary_expression>(start_pos, op, parse_unary_expression());
}
return parse_call_member_expression();
}
statement_ptr parse_call_member_expression() {
// Handle member expressions recursively
auto member = parse_member_expression(parse_primary_expression());
+5
View File
@@ -450,6 +450,11 @@ value unary_expression::execute_impl(context & ctx) const {
} else {
throw std::runtime_error("Unary - operator requires numeric operand");
}
} else if (op.value == "+") {
if (is_val<value_int>(operand_val) || is_val<value_float>(operand_val)) {
return operand_val;
}
throw std::runtime_error("Unary + operator requires numeric operand");
}
throw std::runtime_error("Unknown unary operator '" + op.value + "'");
+43
View File
@@ -458,6 +458,49 @@ static void test_expressions(testing & t) {
"['b']"
);
test_template(t, "array slice negative variable",
"{{ items[:-n]|string }}",
{{"items", json::array({"a", "b", "c"})}, {"n", 1}},
"['a', 'b']"
);
test_template(t, "array slice negative variable indent",
"{{ indent[:-indent_factor] }}",
{{"indent", " "}, {"indent_factor", 2}},
" "
);
test_template(t, "unary minus variable",
"{{ -n }}",
{{"n", 3}},
"-3"
);
test_template(t, "unary plus variable",
"{{ +n }}",
{{"n", -3}},
"-3"
);
test_template(t, "unary plus float",
"{{ +x }}",
{{"x", -1.5}},
"-1.5"
);
// Unary binds tighter than filter: -n|abs == (-n)|abs, not -(n|abs)
test_template(t, "unary minus then abs filter",
"{{ -n|abs }}",
{{"n", -3}},
"3"
);
test_template(t, "unary minus then number test",
"{{ -n is number }}",
{{"n", 3}},
"True"
);
test_template(t, "array slice step",
"{{ items[::2]|string }}",
{{"items", json::array({"a", "b", "c"})}},