revset: extract helper that parses function call

I'm going to add keyword arguments support to templater.
This commit is contained in:
Yuya Nishihara 2024-10-16 21:13:06 +09:00
parent ca5d119c10
commit 2413fffcf9
2 changed files with 85 additions and 51 deletions

View file

@ -20,6 +20,7 @@ use std::fmt;
use std::slice;
use itertools::Itertools as _;
use pest::iterators::Pair;
use pest::iterators::Pairs;
use pest::RuleType;
@ -428,6 +429,76 @@ impl<R: RuleType> StringLiteralParser<R> {
}
}
/// Helper to parse function call.
#[derive(Debug)]
pub struct FunctionCallParser<R> {
/// Function name.
pub function_name_rule: R,
/// List of positional and keyword arguments.
pub function_arguments_rule: R,
/// Pair of parameter name and value.
pub keyword_argument_rule: R,
/// Parameter name.
pub argument_name_rule: R,
/// Value expression.
pub argument_value_rule: R,
}
impl<R: RuleType> FunctionCallParser<R> {
/// Parses the given `pair` as function call.
pub fn parse<'i, T, E: From<InvalidArguments<'i>>>(
&self,
pair: Pair<'i, R>,
// parse_name can be defined for any Pair<'_, R>, but parse_value should
// be allowed to construct T by capturing Pair<'i, R>.
parse_name: impl Fn(Pair<'i, R>) -> Result<&'i str, E>,
parse_value: impl Fn(Pair<'i, R>) -> Result<ExpressionNode<'i, T>, E>,
) -> Result<FunctionCallNode<'i, T>, E> {
let (name_pair, args_pair) = pair.into_inner().collect_tuple().unwrap();
assert_eq!(name_pair.as_rule(), self.function_name_rule);
assert_eq!(args_pair.as_rule(), self.function_arguments_rule);
let name_span = name_pair.as_span();
let args_span = args_pair.as_span();
let function_name = parse_name(name_pair)?;
let mut args = Vec::new();
let mut keyword_args = Vec::new();
for pair in args_pair.into_inner() {
let span = pair.as_span();
if pair.as_rule() == self.argument_value_rule {
if !keyword_args.is_empty() {
return Err(InvalidArguments {
name: function_name,
message: "Positional argument follows keyword argument".to_owned(),
span,
}
.into());
}
args.push(parse_value(pair)?);
} else if pair.as_rule() == self.keyword_argument_rule {
let (name_pair, value_pair) = pair.into_inner().collect_tuple().unwrap();
assert_eq!(name_pair.as_rule(), self.argument_name_rule);
assert_eq!(value_pair.as_rule(), self.argument_value_rule);
let name_span = name_pair.as_span();
let arg = KeywordArgument {
name: parse_name(name_pair)?,
name_span,
value: parse_value(value_pair)?,
};
keyword_args.push(arg);
} else {
panic!("unexpected argument rule {pair:?}");
}
}
Ok(FunctionCallNode {
name: function_name,
name_span,
args,
keyword_args,
args_span,
})
}
}
/// Map of symbol and function aliases.
#[derive(Clone, Debug, Default)]
pub struct AliasesMap<P, V> {

View file

@ -42,8 +42,8 @@ use crate::dsl_util::AliasesMap;
use crate::dsl_util::Diagnostics;
use crate::dsl_util::ExpressionFolder;
use crate::dsl_util::FoldableExpression;
use crate::dsl_util::FunctionCallParser;
use crate::dsl_util::InvalidArguments;
use crate::dsl_util::KeywordArgument;
use crate::dsl_util::StringLiteralParser;
#[derive(Parser)]
@ -54,6 +54,13 @@ const STRING_LITERAL_PARSER: StringLiteralParser<Rule> = StringLiteralParser {
content_rule: Rule::string_content,
escape_rule: Rule::string_escape,
};
const FUNCTION_CALL_PARSER: FunctionCallParser<Rule> = FunctionCallParser {
function_name_rule: Rule::function_name,
function_arguments_rule: Rule::function_arguments,
keyword_argument_rule: Rule::keyword_argument,
argument_name_rule: Rule::identifier,
argument_value_rule: Rule::expression,
};
impl Rule {
/// Whether this is a placeholder rule for compatibility with the other
@ -613,7 +620,11 @@ fn parse_primary_node(pair: Pair<Rule>) -> Result<ExpressionNode, RevsetParseErr
let expr = match first.as_rule() {
Rule::expression => return parse_expression_node(first.into_inner()),
Rule::function => {
let function = Box::new(parse_function_call_node(first)?);
let function = Box::new(FUNCTION_CALL_PARSER.parse(
first,
|pair| Ok(pair.as_str()),
|pair| parse_expression_node(pair.into_inner()),
)?);
ExpressionKind::FunctionCall(function)
}
Rule::string_pattern => {
@ -668,55 +679,6 @@ fn parse_as_string_literal(pair: Pair<Rule>) -> String {
}
}
fn parse_function_call_node(pair: Pair<Rule>) -> Result<FunctionCallNode, RevsetParseError> {
assert_eq!(pair.as_rule(), Rule::function);
let (name_pair, args_pair) = pair.into_inner().collect_tuple().unwrap();
assert_eq!(name_pair.as_rule(), Rule::function_name);
assert_eq!(args_pair.as_rule(), Rule::function_arguments);
let name_span = name_pair.as_span();
let args_span = args_pair.as_span();
let function_name = name_pair.as_str();
let mut args = Vec::new();
let mut keyword_args = Vec::new();
for pair in args_pair.into_inner() {
let span = pair.as_span();
match pair.as_rule() {
Rule::expression => {
if !keyword_args.is_empty() {
return Err(InvalidArguments {
name: function_name,
message: "Positional argument follows keyword argument".to_owned(),
span,
}
.into());
}
args.push(parse_expression_node(pair.into_inner())?);
}
Rule::keyword_argument => {
let mut pairs = pair.into_inner();
let name = pairs.next().unwrap();
let expr = pairs.next().unwrap();
assert_eq!(name.as_rule(), Rule::identifier);
assert_eq!(expr.as_rule(), Rule::expression);
let arg = KeywordArgument {
name: name.as_str(),
name_span: name.as_span(),
value: parse_expression_node(expr.into_inner())?,
};
keyword_args.push(arg);
}
r => panic!("unexpected argument rule {r:?}"),
}
}
Ok(FunctionCallNode {
name: function_name,
name_span,
args,
keyword_args,
args_span,
})
}
pub type RevsetAliasesMap = AliasesMap<RevsetAliasParser, String>;
#[derive(Clone, Debug, Default)]
@ -855,6 +817,7 @@ mod tests {
use assert_matches::assert_matches;
use super::*;
use crate::dsl_util::KeywordArgument;
#[derive(Debug)]
struct WithRevsetAliasesMap(RevsetAliasesMap);