mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-19 19:08:08 +00:00
templater: add != operator as user would probably expect that it exists
Some checks are pending
binaries / Build binary artifacts (linux-aarch64-gnu, ubuntu-24.04, aarch64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-aarch64-musl, ubuntu-24.04, aarch64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-gnu, ubuntu-24.04, x86_64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-musl, ubuntu-24.04, x86_64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (macos-aarch64, macos-14, aarch64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (macos-x86_64, macos-13, x86_64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (win-x86_64, windows-2022, x86_64-pc-windows-msvc) (push) Waiting to run
nix / flake check (macos-14) (push) Waiting to run
nix / flake check (ubuntu-latest) (push) Waiting to run
build / build (, macos-13) (push) Waiting to run
build / build (, macos-14) (push) Waiting to run
build / build (, ubuntu-latest) (push) Waiting to run
build / build (, windows-latest) (push) Waiting to run
build / build (--all-features, ubuntu-latest) (push) Waiting to run
build / Build jj-lib without Git support (push) Waiting to run
build / Check protos (push) Waiting to run
build / Check formatting (push) Waiting to run
build / Check that MkDocs can build the docs (push) Waiting to run
build / Check that MkDocs can build the docs with Poetry 1.8 (push) Waiting to run
build / cargo-deny (advisories) (push) Waiting to run
build / cargo-deny (bans licenses sources) (push) Waiting to run
build / Clippy check (push) Waiting to run
Codespell / Codespell (push) Waiting to run
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Some checks are pending
binaries / Build binary artifacts (linux-aarch64-gnu, ubuntu-24.04, aarch64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-aarch64-musl, ubuntu-24.04, aarch64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-gnu, ubuntu-24.04, x86_64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-musl, ubuntu-24.04, x86_64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (macos-aarch64, macos-14, aarch64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (macos-x86_64, macos-13, x86_64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (win-x86_64, windows-2022, x86_64-pc-windows-msvc) (push) Waiting to run
nix / flake check (macos-14) (push) Waiting to run
nix / flake check (ubuntu-latest) (push) Waiting to run
build / build (, macos-13) (push) Waiting to run
build / build (, macos-14) (push) Waiting to run
build / build (, ubuntu-latest) (push) Waiting to run
build / build (, windows-latest) (push) Waiting to run
build / build (--all-features, ubuntu-latest) (push) Waiting to run
build / Build jj-lib without Git support (push) Waiting to run
build / Check protos (push) Waiting to run
build / Check formatting (push) Waiting to run
build / Check that MkDocs can build the docs (push) Waiting to run
build / Check that MkDocs can build the docs with Poetry 1.8 (push) Waiting to run
build / cargo-deny (advisories) (push) Waiting to run
build / cargo-deny (bans licenses sources) (push) Waiting to run
build / Clippy check (push) Waiting to run
Codespell / Codespell (push) Waiting to run
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
This commit is contained in:
parent
b556fc6093
commit
6739dccc6d
6 changed files with 46 additions and 22 deletions
|
@ -14,8 +14,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
|
||||
### New features
|
||||
|
||||
* Templates now support the `==` logical operator for `Boolean`, `Integer`, and
|
||||
`String` types.
|
||||
* Templates now support the `==` and `!=` logical operators for `Boolean`,
|
||||
`Integer`, and `String` types.
|
||||
|
||||
* The `jj desc` and `jj st` aliases are now hidden to not interfere with shell
|
||||
completion. They remain available.
|
||||
|
|
|
@ -41,10 +41,11 @@ concat_op = { "++" }
|
|||
logical_or_op = { "||" }
|
||||
logical_and_op = { "&&" }
|
||||
logical_eq_op = { "==" }
|
||||
logical_ne_op = { "!=" }
|
||||
logical_not_op = { "!" }
|
||||
negate_op = { "-" }
|
||||
prefix_ops = _{ logical_not_op | negate_op }
|
||||
infix_ops = _{ logical_or_op | logical_and_op | logical_eq_op }
|
||||
infix_ops = _{ logical_or_op | logical_and_op | logical_eq_op | logical_ne_op }
|
||||
|
||||
function = { identifier ~ "(" ~ whitespace* ~ function_arguments ~ whitespace* ~ ")" }
|
||||
keyword_argument = { identifier ~ whitespace* ~ "=" ~ whitespace* ~ template }
|
||||
|
|
|
@ -638,7 +638,7 @@ fn build_binary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
let out = lhs.and_then(move |l| Ok(l && rhs.extract()?));
|
||||
Ok(L::wrap_boolean(out))
|
||||
}
|
||||
BinaryOp::LogicalEq => {
|
||||
BinaryOp::LogicalEq | BinaryOp::LogicalNe => {
|
||||
let lhs = build_expression(language, diagnostics, build_ctx, lhs_node)?;
|
||||
let rhs = build_expression(language, diagnostics, build_ctx, rhs_node)?;
|
||||
let lty = lhs.type_name();
|
||||
|
@ -647,7 +647,11 @@ fn build_binary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
let message = format!(r#"Cannot compare expressions of type "{lty}" and "{rty}""#);
|
||||
TemplateParseError::expression(message, span)
|
||||
})?;
|
||||
Ok(L::wrap_boolean(out))
|
||||
match op {
|
||||
BinaryOp::LogicalEq => Ok(L::wrap_boolean(out)),
|
||||
BinaryOp::LogicalNe => Ok(L::wrap_boolean(out.map(|eq| !eq))),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1728,14 +1732,14 @@ mod tests {
|
|||
env.add_keyword("description", || L::wrap_string(Literal("".to_owned())));
|
||||
env.add_keyword("empty", || L::wrap_boolean(Literal(true)));
|
||||
|
||||
insta::assert_snapshot!(env.parse_err(r#"description ()"#), @r#"
|
||||
insta::assert_snapshot!(env.parse_err(r#"description ()"#), @r"
|
||||
--> 1:13
|
||||
|
|
||||
1 | description ()
|
||||
| ^---
|
||||
|
|
||||
= expected <EOI>, `++`, `||`, `&&`, or `==`
|
||||
"#);
|
||||
= expected <EOI>, `++`, `||`, `&&`, `==`, or `!=`
|
||||
");
|
||||
|
||||
insta::assert_snapshot!(env.parse_err(r#"foo"#), @r###"
|
||||
--> 1:1
|
||||
|
@ -1787,10 +1791,10 @@ mod tests {
|
|||
|
|
||||
= Cannot compare expressions of type "Boolean" and "Integer"
|
||||
"#);
|
||||
insta::assert_snapshot!(env.parse_err(r#"true == 'a'"#), @r#"
|
||||
insta::assert_snapshot!(env.parse_err(r#"true != 'a'"#), @r#"
|
||||
--> 1:1
|
||||
|
|
||||
1 | true == 'a'
|
||||
1 | true != 'a'
|
||||
| ^---------^
|
||||
|
|
||||
= Cannot compare expressions of type "Boolean" and "String"
|
||||
|
@ -1803,10 +1807,10 @@ mod tests {
|
|||
|
|
||||
= Cannot compare expressions of type "Integer" and "Boolean"
|
||||
"#);
|
||||
insta::assert_snapshot!(env.parse_err(r#"1 == 'a'"#), @r#"
|
||||
insta::assert_snapshot!(env.parse_err(r#"1 != 'a'"#), @r#"
|
||||
--> 1:1
|
||||
|
|
||||
1 | 1 == 'a'
|
||||
1 | 1 != 'a'
|
||||
| ^------^
|
||||
|
|
||||
= Cannot compare expressions of type "Integer" and "String"
|
||||
|
@ -1819,10 +1823,10 @@ mod tests {
|
|||
|
|
||||
= Cannot compare expressions of type "String" and "Boolean"
|
||||
"#);
|
||||
insta::assert_snapshot!(env.parse_err(r#"'a' == 1"#), @r#"
|
||||
insta::assert_snapshot!(env.parse_err(r#"'a' != 1"#), @r#"
|
||||
--> 1:1
|
||||
|
|
||||
1 | 'a' == 1
|
||||
1 | 'a' != 1
|
||||
| ^------^
|
||||
|
|
||||
= Cannot compare expressions of type "String" and "Integer"
|
||||
|
@ -2054,10 +2058,16 @@ mod tests {
|
|||
insta::assert_snapshot!(env.render_ok(r#"false && true"#), @"false");
|
||||
insta::assert_snapshot!(env.render_ok(r#"true == true"#), @"true");
|
||||
insta::assert_snapshot!(env.render_ok(r#"true == false"#), @"false");
|
||||
insta::assert_snapshot!(env.render_ok(r#"true != true"#), @"false");
|
||||
insta::assert_snapshot!(env.render_ok(r#"true != false"#), @"true");
|
||||
insta::assert_snapshot!(env.render_ok(r#"1 == 1"#), @"true");
|
||||
insta::assert_snapshot!(env.render_ok(r#"1 == 2"#), @"false");
|
||||
insta::assert_snapshot!(env.render_ok(r#"1 != 1"#), @"false");
|
||||
insta::assert_snapshot!(env.render_ok(r#"1 != 2"#), @"true");
|
||||
insta::assert_snapshot!(env.render_ok(r#"'a' == 'a'"#), @"true");
|
||||
insta::assert_snapshot!(env.render_ok(r#"'a' == 'b'"#), @"false");
|
||||
insta::assert_snapshot!(env.render_ok(r#"'a' != 'a'"#), @"false");
|
||||
insta::assert_snapshot!(env.render_ok(r#"'a' != 'b'"#), @"true");
|
||||
|
||||
insta::assert_snapshot!(env.render_ok(r#" !"" "#), @"true");
|
||||
insta::assert_snapshot!(env.render_ok(r#" "" || "a".lines() "#), @"true");
|
||||
|
|
|
@ -75,6 +75,7 @@ impl Rule {
|
|||
Rule::logical_or_op => Some("||"),
|
||||
Rule::logical_and_op => Some("&&"),
|
||||
Rule::logical_eq_op => Some("=="),
|
||||
Rule::logical_ne_op => Some("!="),
|
||||
Rule::logical_not_op => Some("!"),
|
||||
Rule::negate_op => Some("-"),
|
||||
Rule::prefix_ops => None,
|
||||
|
@ -377,6 +378,8 @@ pub enum BinaryOp {
|
|||
LogicalAnd,
|
||||
/// `==`
|
||||
LogicalEq,
|
||||
/// `!=`
|
||||
LogicalNe,
|
||||
}
|
||||
|
||||
pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
|
||||
|
@ -507,7 +510,8 @@ 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::infix(Rule::logical_eq_op, Assoc::Left))
|
||||
.op(Op::infix(Rule::logical_eq_op, Assoc::Left)
|
||||
| Op::infix(Rule::logical_ne_op, Assoc::Left))
|
||||
.op(Op::prefix(Rule::logical_not_op) | Op::prefix(Rule::negate_op))
|
||||
});
|
||||
PRATT
|
||||
|
@ -528,6 +532,7 @@ fn parse_expression_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode
|
|||
Rule::logical_or_op => BinaryOp::LogicalOr,
|
||||
Rule::logical_and_op => BinaryOp::LogicalAnd,
|
||||
Rule::logical_eq_op => BinaryOp::LogicalEq,
|
||||
Rule::logical_ne_op => BinaryOp::LogicalNe,
|
||||
r => panic!("unexpected infix operator rule {r:?}"),
|
||||
};
|
||||
let lhs = Box::new(lhs?);
|
||||
|
@ -856,8 +861,8 @@ mod tests {
|
|||
parse_normalized("(!(x.f())) || (!(g()))"),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_normalized("!x.f() == !x.f() || !g() == !g()"),
|
||||
parse_normalized("((!(x.f())) == (!(x.f()))) || ((!(g())) == (!(g())))"),
|
||||
parse_normalized("!x.f() == !x.f() || !g() != !g()"),
|
||||
parse_normalized("((!(x.f())) == (!(x.f()))) || ((!(g())) != (!(g())))"),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_normalized("x.f() || y == y || z"),
|
||||
|
@ -867,6 +872,10 @@ mod tests {
|
|||
parse_normalized("x || y == y && z.h() == z"),
|
||||
parse_normalized("x || ((y == y) && ((z.h()) == z))"),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_normalized("x == y || y != z && !z"),
|
||||
parse_normalized("(x == y) || ((y != z) && (!z))"),
|
||||
);
|
||||
|
||||
// Logical operator bounds more tightly than concatenation. This might
|
||||
// not be so intuitive, but should be harmless.
|
||||
|
@ -882,6 +891,10 @@ mod tests {
|
|||
parse_normalized(r"x == y ++ z"),
|
||||
parse_normalized(r"(x == y) ++ z"),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_normalized(r"x != y ++ z"),
|
||||
parse_normalized(r"(x != y) ++ z"),
|
||||
);
|
||||
|
||||
// Expression span
|
||||
assert_eq!(parse_template(" ! x ").unwrap().span.as_str(), "! x");
|
||||
|
|
|
@ -25,15 +25,15 @@ fn test_templater_parse_error() {
|
|||
let repo_path = test_env.env_root().join("repo");
|
||||
let render_err = |template| test_env.jj_cmd_failure(&repo_path, &["log", "-T", template]);
|
||||
|
||||
insta::assert_snapshot!(render_err(r#"description ()"#), @r#"
|
||||
insta::assert_snapshot!(render_err(r#"description ()"#), @r"
|
||||
Error: Failed to parse template: Syntax error
|
||||
Caused by: --> 1:13
|
||||
|
|
||||
1 | description ()
|
||||
| ^---
|
||||
|
|
||||
= expected <EOI>, `++`, `||`, `&&`, or `==`
|
||||
"#);
|
||||
= expected <EOI>, `++`, `||`, `&&`, `==`, or `!=`
|
||||
");
|
||||
|
||||
// Typo
|
||||
test_env.add_config(
|
||||
|
|
|
@ -31,8 +31,8 @@ The following operators are supported.
|
|||
* `x.f()`: Method call.
|
||||
* `-x`: Negate integer value.
|
||||
* `!x`: Logical not.
|
||||
* `x == y`: Logical equal. Operands must be either `Boolean`, `Integer`, or
|
||||
`String`.
|
||||
* `x == y`, `x != y`: Logical equal/not equal. Operands must be either
|
||||
`Boolean`, `Integer`, or `String`.
|
||||
* `x && y`: Logical and, short-circuiting.
|
||||
* `x || y`: Logical or, short-circuiting.
|
||||
* `x ++ y`: Concatenate `x` and `y` templates.
|
||||
|
|
Loading…
Reference in a new issue