mirror of
https://github.com/jj-vcs/jj.git
synced 2025-12-23 06:01:01 +00:00
revset: add unary negate (or set complement) operator '~y'
Because a unary negation node '~y' is more primitive than the corresponding difference node 'x~y', '~y' is easier to deal with while rewriting the tree. That's the main reason to add RevsetExpression::NotIn node. As we have a NotIn node, it makes sense to add an operator for that. This patch reuses '~' token, which I feel intuitive since the other set operators looks like bitwise ops. Another option is '!'. The unary '~' operator has the highest precedence among the set operators, but they are lower than the ranges. This might be counter intuitive, but useful because a prefix range ':x' can be negated without parens. Maybe we can remove the redundant infix operator 'x ~ y', but it isn't decided yet.
This commit is contained in:
parent
2e725270e4
commit
48d10d648c
5 changed files with 53 additions and 2 deletions
|
|
@ -57,6 +57,7 @@ only symbols.
|
|||
* `x & y`: Revisions that are in both `x` and `y`.
|
||||
* `x | y`: Revisions that are in either `x` or `y` (or both).
|
||||
* `x ~ y`: Revisions that are in `x` but not in `y`.
|
||||
* `~x`: Revisions that are not in `x`.
|
||||
* `x-`: Parents of `x`.
|
||||
* `x+`: Children of `x`.
|
||||
* `:x`: Ancestors of `x`, including the commits in `x` itself.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ range_ops = _{ dag_range_op | range_op }
|
|||
range_pre_ops = _{ dag_range_pre_op | range_pre_op }
|
||||
range_post_ops = _{ dag_range_post_op | range_post_op }
|
||||
|
||||
negate_op = { "~" }
|
||||
union_op = { "|" }
|
||||
intersection_op = { "&" }
|
||||
difference_op = { "~" }
|
||||
|
|
@ -68,7 +69,8 @@ range_expression = _{
|
|||
}
|
||||
|
||||
expression = {
|
||||
whitespace* ~ range_expression ~ whitespace* ~ (infix_op ~ whitespace* ~ range_expression ~ whitespace*)*
|
||||
whitespace* ~ (negate_op ~ whitespace*)* ~ range_expression ~ whitespace*
|
||||
~ (infix_op ~ whitespace* ~ (negate_op ~ whitespace*)* ~ range_expression ~ whitespace*)*
|
||||
}
|
||||
|
||||
program = _{ SOI ~ expression ~ EOI }
|
||||
|
|
|
|||
|
|
@ -367,6 +367,7 @@ pub enum RevsetExpression {
|
|||
predicate: RevsetFilterPredicate,
|
||||
},
|
||||
Present(Rc<RevsetExpression>),
|
||||
NotIn(Rc<RevsetExpression>),
|
||||
Union(Rc<RevsetExpression>, Rc<RevsetExpression>),
|
||||
Intersection(Rc<RevsetExpression>, Rc<RevsetExpression>),
|
||||
Difference(Rc<RevsetExpression>, Rc<RevsetExpression>),
|
||||
|
|
@ -487,6 +488,11 @@ impl RevsetExpression {
|
|||
})
|
||||
}
|
||||
|
||||
/// Commits that are not in `self`, i.e. the complement of `self`.
|
||||
pub fn negated(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
|
||||
Rc::new(RevsetExpression::NotIn(self.clone()))
|
||||
}
|
||||
|
||||
/// Commits that are in `self` or in `other` (or both).
|
||||
pub fn union(
|
||||
self: &Rc<RevsetExpression>,
|
||||
|
|
@ -686,6 +692,7 @@ fn parse_expression_rule(
|
|||
.op(Op::infix(Rule::union_op, Assoc::Left))
|
||||
.op(Op::infix(Rule::intersection_op, Assoc::Left)
|
||||
| Op::infix(Rule::difference_op, Assoc::Left))
|
||||
.op(Op::prefix(Rule::negate_op))
|
||||
// Ranges can't be nested without parentheses. Associativity doesn't matter.
|
||||
.op(Op::infix(Rule::dag_range_op, Assoc::Left) | Op::infix(Rule::range_op, Assoc::Left))
|
||||
.op(Op::prefix(Rule::dag_range_pre_op) | Op::prefix(Rule::range_pre_op))
|
||||
|
|
@ -696,6 +703,7 @@ fn parse_expression_rule(
|
|||
PRATT
|
||||
.map_primary(|primary| parse_primary_rule(primary, state))
|
||||
.map_prefix(|op, rhs| match op.as_rule() {
|
||||
Rule::negate_op => Ok(rhs?.negated()),
|
||||
Rule::dag_range_pre_op | Rule::range_pre_op => Ok(rhs?.ancestors()),
|
||||
r => panic!("unexpected prefix operator rule {r:?}"),
|
||||
})
|
||||
|
|
@ -1103,6 +1111,9 @@ fn transform_expression_bottom_up(
|
|||
RevsetExpression::Present(candidates) => {
|
||||
transform_rec(candidates, f).map(RevsetExpression::Present)
|
||||
}
|
||||
RevsetExpression::NotIn(complement) => {
|
||||
transform_rec(complement, f).map(RevsetExpression::NotIn)
|
||||
}
|
||||
RevsetExpression::Union(expression1, expression2) => {
|
||||
transform_rec_pair((expression1, expression2), f).map(
|
||||
|(expression1, expression2)| RevsetExpression::Union(expression1, expression2),
|
||||
|
|
@ -1801,6 +1812,11 @@ pub fn evaluate_expression<'repo>(
|
|||
Err(RevsetError::NoSuchRevision(_)) => Ok(Box::new(EagerRevset::empty())),
|
||||
r @ Err(RevsetError::AmbiguousIdPrefix(_) | RevsetError::StoreError(_)) => r,
|
||||
},
|
||||
RevsetExpression::NotIn(complement) => {
|
||||
let set1 = RevsetExpression::All.evaluate(repo, workspace_ctx)?;
|
||||
let set2 = complement.evaluate(repo, workspace_ctx)?;
|
||||
Ok(Box::new(DifferenceRevset { set1, set2 }))
|
||||
}
|
||||
RevsetExpression::Union(expression1, expression2) => {
|
||||
let set1 = expression1.evaluate(repo, workspace_ctx)?;
|
||||
let set2 = expression2.evaluate(repo, workspace_ctx)?;
|
||||
|
|
@ -1947,6 +1963,10 @@ mod tests {
|
|||
heads: wc_symbol.clone()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
foo_symbol.negated(),
|
||||
Rc::new(RevsetExpression::NotIn(foo_symbol.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
foo_symbol.union(&wc_symbol),
|
||||
Rc::new(RevsetExpression::Union(
|
||||
|
|
@ -2013,6 +2033,12 @@ mod tests {
|
|||
Ok(wc_symbol.range(&RevsetExpression::visible_heads()))
|
||||
);
|
||||
assert_eq!(parse("foo..bar"), Ok(foo_symbol.range(&bar_symbol)));
|
||||
// Parse the "negate" operator
|
||||
assert_eq!(parse("~ foo"), Ok(foo_symbol.negated()));
|
||||
assert_eq!(
|
||||
parse("~ ~~ foo"),
|
||||
Ok(foo_symbol.negated().negated().negated())
|
||||
);
|
||||
// Parse the "intersection" operator
|
||||
assert_eq!(parse("foo & bar"), Ok(foo_symbol.intersection(&bar_symbol)));
|
||||
// Parse the "union" operator
|
||||
|
|
@ -2057,6 +2083,11 @@ mod tests {
|
|||
Ok(foo_symbol.children().children().children())
|
||||
);
|
||||
// Set operator associativity/precedence
|
||||
assert_eq!(parse("~x|y").unwrap(), parse("(~x)|y").unwrap());
|
||||
assert_eq!(parse("x&~y").unwrap(), parse("x&(~y)").unwrap());
|
||||
assert_eq!(parse("x~~y").unwrap(), parse("x~(~y)").unwrap());
|
||||
assert_eq!(parse("x~~~y").unwrap(), parse("x~(~(~y))").unwrap());
|
||||
assert_eq!(parse("~x:y").unwrap(), parse("~(x:y)").unwrap());
|
||||
assert_eq!(parse("x|y|z").unwrap(), parse("(x|y)|z").unwrap());
|
||||
assert_eq!(parse("x&y|z").unwrap(), parse("(x&y)|z").unwrap());
|
||||
assert_eq!(parse("x|y&z").unwrap(), parse("x|(y&z)").unwrap());
|
||||
|
|
@ -2342,6 +2373,10 @@ mod tests {
|
|||
Rc::new(RevsetExpression::Present(RevsetExpression::branches()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
optimize(parse("~branches() & all()").unwrap()),
|
||||
RevsetExpression::branches().negated()
|
||||
);
|
||||
assert_eq!(
|
||||
optimize(parse("(branches() & all()) | (all() & tags())").unwrap()),
|
||||
RevsetExpression::branches().union(&RevsetExpression::tags())
|
||||
|
|
|
|||
|
|
@ -1734,6 +1734,12 @@ fn test_evaluate_expression_difference(use_git: bool) {
|
|||
let commit4 = graph_builder.commit_with_parents(&[&commit3]);
|
||||
let commit5 = graph_builder.commit_with_parents(&[&commit2]);
|
||||
|
||||
// Difference from all
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), &format!("~:{}", commit5.id().hex())),
|
||||
vec![commit4.id().clone(), commit3.id().clone()]
|
||||
);
|
||||
|
||||
// Difference between ancestors
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
|
|
@ -1749,6 +1755,13 @@ fn test_evaluate_expression_difference(use_git: bool) {
|
|||
),
|
||||
vec![commit5.id().clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("~:{} & :{}", commit4.id().hex(), commit5.id().hex())
|
||||
),
|
||||
vec![commit5.id().clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fn test_syntax_error() {
|
|||
1 | x &
|
||||
| ^---
|
||||
|
|
||||
= expected dag_range_pre_op, range_pre_op, or primary
|
||||
= expected dag_range_pre_op, range_pre_op, negate_op, or primary
|
||||
"###);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue