mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-28 15:34:22 +00:00
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:
parent
7c6ddf9773
commit
9dc68af4f9
1 changed files with 146 additions and 53 deletions
|
@ -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 {
|
||||
assert_eq!(pair.as_rule(), Rule::literal);
|
||||
let mut result = String::new();
|
||||
|
@ -180,88 +217,87 @@ fn parse_string_literal(pair: Pair<Rule>) -> String {
|
|||
result
|
||||
}
|
||||
|
||||
/*
|
||||
fn parse_term<'a, C: 'a>(
|
||||
pair: Pair<Rule>,
|
||||
parse_keyword: &impl Fn(Pair<Rule>) -> TemplateParseResult<PropertyAndLabels<'a, C>>,
|
||||
) -> TemplateParseResult<Expression<'a, C>> {
|
||||
fn parse_function_call_node(pair: Pair<Rule>) -> TemplateParseResult<FunctionCallNode> {
|
||||
assert_eq!(pair.as_rule(), Rule::function);
|
||||
let mut inner = pair.into_inner();
|
||||
let name = inner.next().unwrap();
|
||||
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);
|
||||
let mut inner = pair.into_inner();
|
||||
let expr = inner.next().unwrap();
|
||||
let span = expr.as_span();
|
||||
let primary = match expr.as_rule() {
|
||||
Rule::literal => {
|
||||
let text = parse_string_literal(expr);
|
||||
let term = PropertyAndLabels(Property::String(Box::new(Literal(text))), vec![]);
|
||||
Expression::Property(term)
|
||||
ExpressionNode::new(ExpressionKind::String(text), span)
|
||||
}
|
||||
Rule::integer_literal => {
|
||||
let value = expr.as_str().parse().map_err(|err| {
|
||||
TemplateParseError::with_span(
|
||||
TemplateParseErrorKind::ParseIntError(err),
|
||||
expr.as_span(),
|
||||
)
|
||||
TemplateParseError::with_span(TemplateParseErrorKind::ParseIntError(err), span)
|
||||
})?;
|
||||
let term = PropertyAndLabels(Property::Integer(Box::new(Literal(value))), vec![]);
|
||||
Expression::Property(term)
|
||||
ExpressionNode::new(ExpressionKind::Integer(value), span)
|
||||
}
|
||||
Rule::identifier => Expression::Property(parse_keyword(expr)?),
|
||||
Rule::identifier => ExpressionNode::new(ExpressionKind::Identifier(expr.as_str()), span),
|
||||
Rule::function => {
|
||||
let mut inner = expr.into_inner();
|
||||
let name = inner.next().unwrap();
|
||||
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)?
|
||||
let function = parse_function_call_node(expr)?;
|
||||
ExpressionNode::new(ExpressionKind::FunctionCall(function), span)
|
||||
}
|
||||
Rule::template => parse_template_rule(expr, parse_keyword)?,
|
||||
Rule::template => parse_template_node(expr)?,
|
||||
other => panic!("unexpected term: {other:?}"),
|
||||
};
|
||||
match primary {
|
||||
Expression::Property(property) => {
|
||||
parse_method_chain(property, inner, parse_keyword).map(Expression::Property)
|
||||
}
|
||||
Expression::Template(template) => {
|
||||
if let Some(chain) = inner.next() {
|
||||
assert_eq!(chain.as_rule(), Rule::function);
|
||||
let name = chain.into_inner().next().unwrap();
|
||||
Err(TemplateParseError::no_such_method("Template", &name))
|
||||
} else {
|
||||
Ok(Expression::Template(template))
|
||||
}
|
||||
}
|
||||
}
|
||||
inner.try_fold(primary, |object, chain| {
|
||||
assert_eq!(chain.as_rule(), Rule::function);
|
||||
let span = chain.as_span();
|
||||
let method = MethodCallNode {
|
||||
object: Box::new(object),
|
||||
function: parse_function_call_node(chain)?,
|
||||
};
|
||||
Ok(ExpressionNode::new(
|
||||
ExpressionKind::MethodCall(method),
|
||||
span,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_template_rule<'a, C: 'a>(
|
||||
pair: Pair<Rule>,
|
||||
parse_keyword: &impl Fn(Pair<Rule>) -> TemplateParseResult<PropertyAndLabels<'a, C>>,
|
||||
) -> TemplateParseResult<Expression<'a, C>> {
|
||||
fn parse_template_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode> {
|
||||
assert_eq!(pair.as_rule(), Rule::template);
|
||||
let span = pair.as_span();
|
||||
let inner = pair.into_inner();
|
||||
let mut expressions: Vec<_> = inner
|
||||
.map(|term| parse_term(term, parse_keyword))
|
||||
.try_collect()?;
|
||||
if expressions.len() == 1 {
|
||||
Ok(expressions.pop().unwrap())
|
||||
let mut nodes: Vec<_> = inner.map(parse_term_node).try_collect()?;
|
||||
if nodes.len() == 1 {
|
||||
Ok(nodes.pop().unwrap())
|
||||
} else {
|
||||
let templates = expressions.into_iter().map(|x| x.into_template()).collect();
|
||||
Ok(Expression::Template(Box::new(ListTemplate(templates))))
|
||||
Ok(ExpressionNode::new(ExpressionKind::List(nodes), span))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_template_str<'a, C: 'a>(
|
||||
template_text: &str,
|
||||
parse_keyword: impl Fn(Pair<Rule>) -> TemplateParseResult<PropertyAndLabels<'a, C>>,
|
||||
) -> TemplateParseResult<Expression<'a, C>> {
|
||||
/// Parses text into AST nodes. No type/name checking is made at this stage.
|
||||
fn parse_template(template_text: &str) -> TemplateParseResult<ExpressionNode> {
|
||||
let mut pairs: Pairs<Rule> = TemplateParser::parse(Rule::program, template_text)?;
|
||||
let first_pair = pairs.next().unwrap();
|
||||
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 {
|
||||
parse_template_rule(first_pair, &parse_keyword)
|
||||
parse_template_node(first_pair)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
enum Property<'a, I> {
|
||||
String(Box<dyn TemplateProperty<I, Output = String> + 'a>),
|
||||
|
@ -874,6 +910,8 @@ pub fn parse_commit_template<'a>(
|
|||
workspace_id: &WorkspaceId,
|
||||
template_text: &str,
|
||||
) -> 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| {
|
||||
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]
|
||||
fn test_function_call_syntax() {
|
||||
// Trailing comma isn't allowed for empty argument
|
||||
|
|
Loading…
Reference in a new issue