ok/jj
1
0
Fork 0
forked from mirrors/jj

templater: parse negative integer as unary operator and literal

Follows up 9702a425e5 "Allow negative numbers in the template grammar."
Since we've added parsing rules for operator expressions, it makes sense to
parse unary '-' as operator.
This commit is contained in:
Yuya Nishihara 2024-02-06 21:57:59 +09:00
parent 5b517b542e
commit e943a5b092
4 changed files with 33 additions and 7 deletions

View file

@ -25,8 +25,8 @@ raw_literal = @{ literal_char+ }
literal = { "\"" ~ (raw_literal | escape)* ~ "\"" }
integer_literal = {
"-"? ~ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*
| "-"? ~ "0"
ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*
| "0"
}
identifier = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
@ -35,7 +35,8 @@ concat_op = { "++" }
logical_or_op = { "||" }
logical_and_op = { "&&" }
logical_not_op = { "!" }
prefix_ops = _{ logical_not_op }
negate_op = { "-" }
prefix_ops = _{ logical_not_op | negate_op }
infix_ops = _{ logical_or_op | logical_and_op }
function = { identifier ~ "(" ~ whitespace* ~ function_arguments ~ whitespace* ~ ")" }

View file

@ -283,6 +283,11 @@ fn build_unary_operation<'a, L: TemplateLanguage<'a>>(
let arg = expect_boolean_expression(language, build_ctx, arg_node)?;
language.wrap_boolean(TemplateFunction::new(arg, |v| !v))
}
UnaryOp::Negate => {
let arg = expect_integer_expression(language, build_ctx, arg_node)?;
// TODO: propagate error on overflow?
language.wrap_integer(TemplateFunction::new(arg, |v| v.saturating_neg()))
}
};
Ok(Expression::unlabeled(property))
}
@ -1147,12 +1152,12 @@ mod tests {
= Method "foo" doesn't exist for type "Integer"
"###);
insta::assert_snapshot!(env.parse_err(r#"(-empty)"#), @r###"
--> 1:2
--> 1:3
|
1 | (-empty)
| ^---
| ^---^
|
= expected <template>
= Expected expression of type "Integer"
"###);
insta::assert_snapshot!(env.parse_err(r#"("foo" ++ "bar").baz()"#), @r###"
@ -1279,6 +1284,21 @@ mod tests {
"###);
}
#[test]
fn test_arithmetic_operation() {
let mut env = TestTemplateEnv::default();
env.add_keyword("i64_min", |language| {
language.wrap_integer(Literal(i64::MIN))
});
insta::assert_snapshot!(env.render_ok(r#"-1"#), @"-1");
insta::assert_snapshot!(env.render_ok(r#"--2"#), @"2");
insta::assert_snapshot!(env.render_ok(r#"-(3)"#), @"-3");
// No panic on integer overflow. Runtime error might be better.
insta::assert_snapshot!(env.render_ok(r#"-i64_min"#), @"9223372036854775807");
}
#[test]
fn test_logical_operation() {
let env = TestTemplateEnv::default();

View file

@ -43,6 +43,7 @@ impl Rule {
Rule::logical_or_op => Some("||"),
Rule::logical_and_op => Some("&&"),
Rule::logical_not_op => Some("!"),
Rule::negate_op => Some("-"),
Rule::prefix_ops => None,
Rule::infix_ops => None,
Rule::function => None,
@ -253,6 +254,8 @@ pub enum ExpressionKind<'i> {
pub enum UnaryOp {
/// `!`
LogicalNot,
/// `-`
Negate,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@ -429,13 +432,14 @@ fn parse_expression_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode
PrattParser::new()
.op(Op::infix(Rule::logical_or_op, Assoc::Left))
.op(Op::infix(Rule::logical_and_op, Assoc::Left))
.op(Op::prefix(Rule::logical_not_op))
.op(Op::prefix(Rule::logical_not_op) | Op::prefix(Rule::negate_op))
});
PRATT
.map_primary(parse_term_node)
.map_prefix(|op, rhs| {
let op_kind = match op.as_rule() {
Rule::logical_not_op => UnaryOp::LogicalNot,
Rule::negate_op => UnaryOp::Negate,
r => panic!("unexpected prefix operator rule {r:?}"),
};
let rhs = Box::new(rhs?);

View file

@ -57,6 +57,7 @@ The following keywords can be used in `jj op log` templates.
The following operators are supported.
* `x.f()`: Method call.
* `-x`: Negate integer value.
* `!x`: Logical not.
* `x && y`: Logical and.
* `x || y`: Logical or.