templater: introduce AST structs

This prepares for template aliases support #1190. Unlike revset, template
expressions can be of various types, whereas alias substitution will process
untyped nodes. That's one reason that ExpressionNode is closer to parsed tree
than evaluatable Property structs. Another reason is that it's uneasy to split
name/type checking into "parsing" and "building property function" stages.

We could do alias expansion at once while building Property functions, but
that would make testing harder because Property isn't Debug + PartialEq.
This commit is contained in:
Yuya Nishihara 2023-02-11 20:24:26 +09:00
parent 7c6ddf9773
commit 9dc68af4f9

View file

@ -160,6 +160,43 @@ impl error::Error for TemplateParseError {
} }
} }
/// AST node without type or name checking.
#[derive(Clone, Debug, PartialEq)]
struct ExpressionNode<'i> {
kind: ExpressionKind<'i>,
span: pest::Span<'i>,
}
impl<'i> ExpressionNode<'i> {
fn new(kind: ExpressionKind<'i>, span: pest::Span<'i>) -> Self {
ExpressionNode { kind, span }
}
}
#[derive(Clone, Debug, PartialEq)]
enum ExpressionKind<'i> {
Identifier(&'i str),
Integer(i64),
String(String),
List(Vec<ExpressionNode<'i>>),
FunctionCall(FunctionCallNode<'i>),
MethodCall(MethodCallNode<'i>),
}
#[derive(Clone, Debug, PartialEq)]
struct FunctionCallNode<'i> {
name: &'i str,
name_span: pest::Span<'i>,
args: Vec<ExpressionNode<'i>>,
args_span: pest::Span<'i>,
}
#[derive(Clone, Debug, PartialEq)]
struct MethodCallNode<'i> {
object: Box<ExpressionNode<'i>>,
function: FunctionCallNode<'i>,
}
fn parse_string_literal(pair: Pair<Rule>) -> String { fn parse_string_literal(pair: Pair<Rule>) -> String {
assert_eq!(pair.as_rule(), Rule::literal); assert_eq!(pair.as_rule(), Rule::literal);
let mut result = String::new(); let mut result = String::new();
@ -180,88 +217,87 @@ fn parse_string_literal(pair: Pair<Rule>) -> String {
result result
} }
/* fn parse_function_call_node(pair: Pair<Rule>) -> TemplateParseResult<FunctionCallNode> {
fn parse_term<'a, C: 'a>( assert_eq!(pair.as_rule(), Rule::function);
pair: Pair<Rule>, let mut inner = pair.into_inner();
parse_keyword: &impl Fn(Pair<Rule>) -> TemplateParseResult<PropertyAndLabels<'a, C>>, let name = inner.next().unwrap();
) -> TemplateParseResult<Expression<'a, C>> { let args_pair = inner.next().unwrap();
let args_span = args_pair.as_span();
assert_eq!(name.as_rule(), Rule::identifier);
assert_eq!(args_pair.as_rule(), Rule::function_arguments);
let args = args_pair
.into_inner()
.map(parse_template_node)
.try_collect()?;
Ok(FunctionCallNode {
name: name.as_str(),
name_span: name.as_span(),
args,
args_span,
})
}
fn parse_term_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode> {
assert_eq!(pair.as_rule(), Rule::term); assert_eq!(pair.as_rule(), Rule::term);
let mut inner = pair.into_inner(); let mut inner = pair.into_inner();
let expr = inner.next().unwrap(); let expr = inner.next().unwrap();
let span = expr.as_span();
let primary = match expr.as_rule() { let primary = match expr.as_rule() {
Rule::literal => { Rule::literal => {
let text = parse_string_literal(expr); let text = parse_string_literal(expr);
let term = PropertyAndLabels(Property::String(Box::new(Literal(text))), vec![]); ExpressionNode::new(ExpressionKind::String(text), span)
Expression::Property(term)
} }
Rule::integer_literal => { Rule::integer_literal => {
let value = expr.as_str().parse().map_err(|err| { let value = expr.as_str().parse().map_err(|err| {
TemplateParseError::with_span( TemplateParseError::with_span(TemplateParseErrorKind::ParseIntError(err), span)
TemplateParseErrorKind::ParseIntError(err),
expr.as_span(),
)
})?; })?;
let term = PropertyAndLabels(Property::Integer(Box::new(Literal(value))), vec![]); ExpressionNode::new(ExpressionKind::Integer(value), span)
Expression::Property(term)
} }
Rule::identifier => Expression::Property(parse_keyword(expr)?), Rule::identifier => ExpressionNode::new(ExpressionKind::Identifier(expr.as_str()), span),
Rule::function => { Rule::function => {
let mut inner = expr.into_inner(); let function = parse_function_call_node(expr)?;
let name = inner.next().unwrap(); ExpressionNode::new(ExpressionKind::FunctionCall(function), span)
let args_pair = inner.next().unwrap();
assert_eq!(name.as_rule(), Rule::identifier);
assert_eq!(args_pair.as_rule(), Rule::function_arguments);
parse_global_function(name, args_pair, parse_keyword)?
} }
Rule::template => parse_template_rule(expr, parse_keyword)?, Rule::template => parse_template_node(expr)?,
other => panic!("unexpected term: {other:?}"), other => panic!("unexpected term: {other:?}"),
}; };
match primary { inner.try_fold(primary, |object, chain| {
Expression::Property(property) => { assert_eq!(chain.as_rule(), Rule::function);
parse_method_chain(property, inner, parse_keyword).map(Expression::Property) let span = chain.as_span();
} let method = MethodCallNode {
Expression::Template(template) => { object: Box::new(object),
if let Some(chain) = inner.next() { function: parse_function_call_node(chain)?,
assert_eq!(chain.as_rule(), Rule::function); };
let name = chain.into_inner().next().unwrap(); Ok(ExpressionNode::new(
Err(TemplateParseError::no_such_method("Template", &name)) ExpressionKind::MethodCall(method),
} else { span,
Ok(Expression::Template(template)) ))
} })
}
}
} }
fn parse_template_rule<'a, C: 'a>( fn parse_template_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode> {
pair: Pair<Rule>,
parse_keyword: &impl Fn(Pair<Rule>) -> TemplateParseResult<PropertyAndLabels<'a, C>>,
) -> TemplateParseResult<Expression<'a, C>> {
assert_eq!(pair.as_rule(), Rule::template); assert_eq!(pair.as_rule(), Rule::template);
let span = pair.as_span();
let inner = pair.into_inner(); let inner = pair.into_inner();
let mut expressions: Vec<_> = inner let mut nodes: Vec<_> = inner.map(parse_term_node).try_collect()?;
.map(|term| parse_term(term, parse_keyword)) if nodes.len() == 1 {
.try_collect()?; Ok(nodes.pop().unwrap())
if expressions.len() == 1 {
Ok(expressions.pop().unwrap())
} else { } else {
let templates = expressions.into_iter().map(|x| x.into_template()).collect(); Ok(ExpressionNode::new(ExpressionKind::List(nodes), span))
Ok(Expression::Template(Box::new(ListTemplate(templates))))
} }
} }
fn parse_template_str<'a, C: 'a>( /// Parses text into AST nodes. No type/name checking is made at this stage.
template_text: &str, fn parse_template(template_text: &str) -> TemplateParseResult<ExpressionNode> {
parse_keyword: impl Fn(Pair<Rule>) -> TemplateParseResult<PropertyAndLabels<'a, C>>,
) -> TemplateParseResult<Expression<'a, C>> {
let mut pairs: Pairs<Rule> = TemplateParser::parse(Rule::program, template_text)?; let mut pairs: Pairs<Rule> = TemplateParser::parse(Rule::program, template_text)?;
let first_pair = pairs.next().unwrap(); let first_pair = pairs.next().unwrap();
if first_pair.as_rule() == Rule::EOI { if first_pair.as_rule() == Rule::EOI {
Ok(Expression::Template(Box::new(Literal(String::new())))) let span = first_pair.as_span();
Ok(ExpressionNode::new(ExpressionKind::List(Vec::new()), span))
} else { } else {
parse_template_rule(first_pair, &parse_keyword) parse_template_node(first_pair)
} }
} }
*/
enum Property<'a, I> { enum Property<'a, I> {
String(Box<dyn TemplateProperty<I, Output = String> + 'a>), String(Box<dyn TemplateProperty<I, Output = String> + 'a>),
@ -874,6 +910,8 @@ pub fn parse_commit_template<'a>(
workspace_id: &WorkspaceId, workspace_id: &WorkspaceId,
template_text: &str, template_text: &str,
) -> TemplateParseResult<Box<dyn Template<Commit> + 'a>> { ) -> TemplateParseResult<Box<dyn Template<Commit> + 'a>> {
// TODO: use AST node to build expression tree
parse_template(template_text)?;
let expression = parse_template_str(template_text, |pair| { let expression = parse_template_str(template_text, |pair| {
parse_commit_keyword(repo, workspace_id, pair) parse_commit_keyword(repo, workspace_id, pair)
})?; })?;
@ -890,6 +928,61 @@ mod tests {
}) })
} }
/// Drops auxiliary data of AST so it can be compared with other node.
fn normalize_tree(node: ExpressionNode) -> ExpressionNode {
fn empty_span() -> pest::Span<'static> {
pest::Span::new("", 0, 0).unwrap()
}
fn normalize_list(nodes: Vec<ExpressionNode>) -> Vec<ExpressionNode> {
nodes.into_iter().map(normalize_tree).collect()
}
fn normalize_function_call(function: FunctionCallNode) -> FunctionCallNode {
FunctionCallNode {
name: function.name,
name_span: empty_span(),
args: normalize_list(function.args),
args_span: empty_span(),
}
}
let normalized_kind = match node.kind {
ExpressionKind::Identifier(_)
| ExpressionKind::Integer(_)
| ExpressionKind::String(_) => node.kind,
ExpressionKind::List(nodes) => ExpressionKind::List(normalize_list(nodes)),
ExpressionKind::FunctionCall(function) => {
ExpressionKind::FunctionCall(normalize_function_call(function))
}
ExpressionKind::MethodCall(method) => {
let object = Box::new(normalize_tree(*method.object));
let function = normalize_function_call(method.function);
ExpressionKind::MethodCall(MethodCallNode { object, function })
}
};
ExpressionNode {
kind: normalized_kind,
span: empty_span(),
}
}
#[test]
fn test_parse_tree_eq() {
assert_eq!(
normalize_tree(parse_template(r#" commit_id.short(1 ) description"#).unwrap()),
normalize_tree(parse_template(r#"commit_id.short( 1 ) (description)"#).unwrap()),
);
assert_ne!(
normalize_tree(parse_template(r#" "ab" "#).unwrap()),
normalize_tree(parse_template(r#" "a" "b" "#).unwrap()),
);
assert_ne!(
normalize_tree(parse_template(r#" "foo" "0" "#).unwrap()),
normalize_tree(parse_template(r#" "foo" 0 "#).unwrap()),
);
}
#[test] #[test]
fn test_function_call_syntax() { fn test_function_call_syntax() {
// Trailing comma isn't allowed for empty argument // Trailing comma isn't allowed for empty argument