forked from mirrors/jj
revsets: add support for function syntax
This adds `parents(foo)` and `ancestors(foo)` as alternative ways of writing `:foo` and `*:foo`. I haven't added support for for whitespace yet; the parsing is very strict. The error messages will also need to be improved later.
This commit is contained in:
parent
2d6325b0f4
commit
88904e2b63
3 changed files with 131 additions and 9 deletions
|
@ -13,12 +13,31 @@
|
|||
// limitations under the License.
|
||||
|
||||
symbol = @{ (ASCII_ALPHANUMERIC | "@" | "/" | ".")+ }
|
||||
literal_string = { "\"" ~ (!"\"" ~ ANY)+ ~ "\"" }
|
||||
|
||||
parents = { ":" }
|
||||
ancestors = { "*:" }
|
||||
|
||||
function_name = @{ ASCII_ALPHANUMERIC+ }
|
||||
// The grammar accepts a string literal or an expression for function
|
||||
// arguments. We then decide when walking the parse tree if we
|
||||
// should interpret the string value of the literal string or the expression
|
||||
// as a string or an expression, or maybe as an integer. For example,
|
||||
// parents(foo) might interpret "foo" as an expression, while limit(*:foo,5)
|
||||
// might interpret its second argument as an integer. Also, parents("foo")
|
||||
// might be disallowed, just as description(heads()) might be.
|
||||
function_argument = {
|
||||
literal_string
|
||||
| expression
|
||||
}
|
||||
function_arguments = {
|
||||
(function_argument ~ ",")* ~ function_argument
|
||||
| ""
|
||||
}
|
||||
|
||||
expression = {
|
||||
parents ~ expression
|
||||
| ancestors ~ expression
|
||||
| function_name ~ "(" ~ function_arguments ~ ")"
|
||||
| symbol
|
||||
}
|
||||
|
|
|
@ -87,7 +87,11 @@ pub struct RevsetParser;
|
|||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum RevsetParseError {
|
||||
#[error("{0}")]
|
||||
RevsetParseError(String),
|
||||
SyntaxError(String),
|
||||
#[error("Revset function \"{0}\" doesn't exist")]
|
||||
NoSuchFunction(String),
|
||||
#[error("Invalid arguments to revset function \"{name}\": {message}")]
|
||||
InvalidFunctionArguments { name: String, message: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -100,9 +104,7 @@ pub enum RevsetExpression {
|
|||
fn parse_expression_rule(mut pairs: Pairs<Rule>) -> Result<RevsetExpression, RevsetParseError> {
|
||||
let first = pairs.next().unwrap();
|
||||
match first.as_rule() {
|
||||
Rule::symbol => {
|
||||
Ok(RevsetExpression::Symbol(first.as_str().to_owned()))
|
||||
}
|
||||
Rule::symbol => Ok(RevsetExpression::Symbol(first.as_str().to_owned())),
|
||||
Rule::parents => {
|
||||
let expression = pairs.next().unwrap();
|
||||
Ok(RevsetExpression::Parents(Box::new(parse_expression_rule(
|
||||
|
@ -115,9 +117,74 @@ fn parse_expression_rule(mut pairs: Pairs<Rule>) -> Result<RevsetExpression, Rev
|
|||
parse_expression_rule(expression.into_inner())?,
|
||||
)))
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected revset parse rule: {:?}", first);
|
||||
Rule::function_name => {
|
||||
let name = first.as_str().to_owned();
|
||||
let argument_pairs = pairs.next().unwrap().into_inner();
|
||||
parse_function_expression(name, argument_pairs)
|
||||
}
|
||||
_ => {
|
||||
panic!("unxpected revset parse rule: {:?}", first.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_function_expression(
|
||||
name: String,
|
||||
mut argument_pairs: Pairs<Rule>,
|
||||
) -> Result<RevsetExpression, RevsetParseError> {
|
||||
let arg_count = argument_pairs.clone().count();
|
||||
match name.as_str() {
|
||||
"parents" => {
|
||||
if arg_count == 1 {
|
||||
Ok(RevsetExpression::Parents(Box::new(
|
||||
parse_function_argument_to_expression(
|
||||
&name,
|
||||
argument_pairs.next().unwrap().into_inner(),
|
||||
)?,
|
||||
)))
|
||||
} else {
|
||||
Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name,
|
||||
message: "Expected 1 argument".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
"ancestors" => {
|
||||
if arg_count == 1 {
|
||||
Ok(RevsetExpression::Ancestors(Box::new(
|
||||
parse_function_argument_to_expression(
|
||||
&name,
|
||||
argument_pairs.next().unwrap().into_inner(),
|
||||
)?,
|
||||
)))
|
||||
} else {
|
||||
Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name,
|
||||
message: "Expected 1 argument".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Err(RevsetParseError::NoSuchFunction(name)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_function_argument_to_expression(
|
||||
name: &str,
|
||||
mut pairs: Pairs<Rule>,
|
||||
) -> Result<RevsetExpression, RevsetParseError> {
|
||||
// Make a clone of the pairs for error messages
|
||||
let pairs_clone = pairs.clone();
|
||||
let first = pairs.next().unwrap();
|
||||
assert!(pairs.next().is_none());
|
||||
match first.as_rule() {
|
||||
Rule::expression => Ok(parse_expression_rule(first.into_inner())?),
|
||||
_ => Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name: name.to_string(),
|
||||
message: format!(
|
||||
"Expected function argument of type expression, found: {}",
|
||||
pairs_clone.as_str()
|
||||
),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,8 +193,8 @@ pub fn parse(revset_str: &str) -> Result<RevsetExpression, RevsetParseError> {
|
|||
let first = pairs.next().unwrap();
|
||||
assert!(pairs.next().is_none());
|
||||
if first.as_span().end() != revset_str.len() {
|
||||
return Err(RevsetParseError::RevsetParseError(format!(
|
||||
"Failed to parse revset {} past position {}",
|
||||
return Err(RevsetParseError::SyntaxError(format!(
|
||||
"Failed to parse revset \"{}\" past position {}",
|
||||
revset_str,
|
||||
first.as_span().end()
|
||||
)));
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
use jujube_lib::commit_builder::CommitBuilder;
|
||||
use jujube_lib::repo::RepoRef;
|
||||
use jujube_lib::revset::{
|
||||
evaluate_expression, parse, resolve_symbol, RevsetError, RevsetExpression,
|
||||
evaluate_expression, parse, resolve_symbol, RevsetError, RevsetExpression, RevsetParseError,
|
||||
};
|
||||
use jujube_lib::store::{CommitId, MillisSinceEpoch, Signature, Timestamp};
|
||||
use jujube_lib::testutils;
|
||||
|
@ -247,6 +247,42 @@ fn test_parse_revset() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_revset_function() {
|
||||
assert_eq!(
|
||||
parse("parents(@)"),
|
||||
Ok(RevsetExpression::Parents(Box::new(
|
||||
RevsetExpression::Symbol("@".to_string())
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("parents(\"@\")"),
|
||||
Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name: "parents".to_string(),
|
||||
message: "Expected function argument of type expression, found: \"@\"".to_string()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
parse("ancestors(parents(@))"),
|
||||
Ok(RevsetExpression::Ancestors(Box::new(
|
||||
RevsetExpression::Parents(Box::new(RevsetExpression::Symbol("@".to_string())))
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("parents(@"),
|
||||
Err(RevsetParseError::SyntaxError(
|
||||
"Failed to parse revset \"parents(@\" past position 7".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("parents(@,@)"),
|
||||
Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name: "parents".to_string(),
|
||||
message: "Expected 1 argument".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fn resolve_commit_ids(repo: RepoRef, revset_str: &str) -> Vec<CommitId> {
|
||||
let expression = parse(revset_str).unwrap();
|
||||
evaluate_expression(repo, &expression)
|
||||
|
|
Loading…
Reference in a new issue