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

templater: evaluate logical operator expressions (||, &&, and !)

#2924
This commit is contained in:
Yuya Nishihara 2024-02-05 20:49:06 +09:00
parent 88a1729f8b
commit 948129e88f
3 changed files with 81 additions and 4 deletions

View file

@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### New features
* Templates now support logical operators: `||`, `&&`, `!`
### Fixed bugs
## [0.14.0] - 2024-02-07

View file

@ -18,8 +18,8 @@ use itertools::Itertools as _;
use jj_lib::backend::{Signature, Timestamp};
use crate::template_parser::{
self, ExpressionKind, ExpressionNode, FunctionCallNode, MethodCallNode, TemplateParseError,
TemplateParseResult,
self, BinaryOp, ExpressionKind, ExpressionNode, FunctionCallNode, MethodCallNode,
TemplateParseError, TemplateParseResult, UnaryOp,
};
use crate::templater::{
ConcatTemplate, ConditionalTemplate, IntoTemplate, LabelTemplate, ListPropertyTemplate,
@ -272,6 +272,45 @@ pub struct BuildContext<'i, P> {
local_variables: HashMap<&'i str, &'i (dyn Fn() -> P)>,
}
fn build_unary_operation<'a, L: TemplateLanguage<'a>>(
language: &L,
build_ctx: &BuildContext<L::Property>,
op: UnaryOp,
arg_node: &ExpressionNode,
) -> TemplateParseResult<Expression<L::Property>> {
let property = match op {
UnaryOp::LogicalNot => {
let arg = expect_boolean_expression(language, build_ctx, arg_node)?;
language.wrap_boolean(TemplateFunction::new(arg, |v| !v))
}
};
Ok(Expression::unlabeled(property))
}
fn build_binary_operation<'a, L: TemplateLanguage<'a>>(
language: &L,
build_ctx: &BuildContext<L::Property>,
op: BinaryOp,
lhs_node: &ExpressionNode,
rhs_node: &ExpressionNode,
) -> TemplateParseResult<Expression<L::Property>> {
let property = match op {
BinaryOp::LogicalOr => {
// No short-circuiting supported
let lhs = expect_boolean_expression(language, build_ctx, lhs_node)?;
let rhs = expect_boolean_expression(language, build_ctx, rhs_node)?;
language.wrap_boolean(TemplateFunction::new((lhs, rhs), |(l, r)| l | r))
}
BinaryOp::LogicalAnd => {
// No short-circuiting supported
let lhs = expect_boolean_expression(language, build_ctx, lhs_node)?;
let rhs = expect_boolean_expression(language, build_ctx, rhs_node)?;
language.wrap_boolean(TemplateFunction::new((lhs, rhs), |(l, r)| l & r))
}
};
Ok(Expression::unlabeled(property))
}
fn build_method_call<'a, L: TemplateLanguage<'a>>(
language: &L,
build_ctx: &BuildContext<L::Property>,
@ -803,8 +842,12 @@ pub fn build_expression<'a, L: TemplateLanguage<'a>>(
let property = language.wrap_string(Literal(value.clone()));
Ok(Expression::unlabeled(property))
}
ExpressionKind::Unary(..) => todo!(),
ExpressionKind::Binary(..) => todo!(),
ExpressionKind::Unary(op, arg_node) => {
build_unary_operation(language, build_ctx, *op, arg_node)
}
ExpressionKind::Binary(op, lhs_node, rhs_node) => {
build_binary_operation(language, build_ctx, *op, lhs_node, rhs_node)
}
ExpressionKind::Concat(nodes) => {
let templates = nodes
.iter()
@ -1061,6 +1104,23 @@ mod tests {
= Expected identifier
"###);
insta::assert_snapshot!(env.parse_err(r#"!foo"#), @r###"
--> 1:2
|
1 | !foo
| ^-^
|
= Keyword "foo" doesn't exist
"###);
insta::assert_snapshot!(env.parse_err(r#"true && 123"#), @r###"
--> 1:9
|
1 | true && 123
| ^-^
|
= Expected expression of type "Boolean"
"###);
insta::assert_snapshot!(env.parse_err(r#"description.first_line().foo()"#), @r###"
--> 1:26
|
@ -1219,6 +1279,18 @@ mod tests {
"###);
}
#[test]
fn test_logical_operation() {
let env = TestTemplateEnv::default();
insta::assert_snapshot!(env.render_ok(r#"!false"#), @"true");
insta::assert_snapshot!(env.render_ok(r#"false || !false"#), @"true");
insta::assert_snapshot!(env.render_ok(r#"false && true"#), @"false");
insta::assert_snapshot!(env.render_ok(r#" !"" "#), @"true");
insta::assert_snapshot!(env.render_ok(r#" "" || "a".lines() "#), @"true");
}
#[test]
fn test_list_method() {
let mut env = TestTemplateEnv::default();

View file

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