forked from mirrors/jj
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 {
|
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
|
||||||
|
|
Loading…
Reference in a new issue