From 7be4a0a5609e46956d37e4d782607d609dd0910e Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sat, 18 May 2024 12:27:52 +0900 Subject: [PATCH] dsl_util: add visitor-like API primarily designed for alias substitution The templater implementation of FoldableExpression is a stripped-down version of expand_node(). It's visitor-like because I'm going to write generic alias substitution rules over abstract expression types (template, revset, fileset.) Naming comes from rustc. https://rust-unofficial.github.io/patterns/patterns/creational/fold.html --- cli/src/template_parser.rs | 50 ++++++++++++++++++++++++++- lib/src/dsl_util.rs | 69 +++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/cli/src/template_parser.rs b/cli/src/template_parser.rs index 04d186354..c898b27fd 100644 --- a/cli/src/template_parser.rs +++ b/cli/src/template_parser.rs @@ -18,7 +18,8 @@ use std::{error, mem}; use itertools::Itertools as _; use jj_lib::dsl_util::{ self, collect_similar, AliasDeclaration, AliasDeclarationParser, AliasExpandError, - AliasExpandableExpression, AliasId, AliasesMap, InvalidArguments, StringLiteralParser, + AliasExpandableExpression, AliasId, AliasesMap, ExpressionFolder, FoldableExpression, + InvalidArguments, StringLiteralParser, }; use once_cell::sync::Lazy; use pest::iterators::{Pair, Pairs}; @@ -272,6 +273,53 @@ pub enum ExpressionKind<'i> { AliasExpanded(AliasId<'i>, Box>), } +impl<'i> FoldableExpression<'i> for ExpressionKind<'i> { + fn fold(self, folder: &mut F, span: pest::Span<'i>) -> Result + where + F: ExpressionFolder<'i, Self> + ?Sized, + { + match self { + ExpressionKind::Identifier(name) => folder.fold_identifier(name, span), + ExpressionKind::Boolean(_) | ExpressionKind::Integer(_) | ExpressionKind::String(_) => { + Ok(self) + } + ExpressionKind::Unary(op, arg) => { + let arg = Box::new(folder.fold_expression(*arg)?); + Ok(ExpressionKind::Unary(op, arg)) + } + ExpressionKind::Binary(op, lhs, rhs) => { + let lhs = Box::new(folder.fold_expression(*lhs)?); + let rhs = Box::new(folder.fold_expression(*rhs)?); + Ok(ExpressionKind::Binary(op, lhs, rhs)) + } + ExpressionKind::Concat(nodes) => Ok(ExpressionKind::Concat( + dsl_util::fold_expression_nodes(folder, nodes)?, + )), + ExpressionKind::FunctionCall(function) => folder.fold_function_call(function, span), + ExpressionKind::MethodCall(method) => { + // Method call is syntactically different from function call. + let method = Box::new(MethodCallNode { + object: folder.fold_expression(method.object)?, + function: dsl_util::fold_function_call_args(folder, method.function)?, + }); + Ok(ExpressionKind::MethodCall(method)) + } + ExpressionKind::Lambda(lambda) => { + let lambda = Box::new(LambdaNode { + params: lambda.params, + params_span: lambda.params_span, + body: folder.fold_expression(lambda.body)?, + }); + Ok(ExpressionKind::Lambda(lambda)) + } + ExpressionKind::AliasExpanded(id, subst) => { + let subst = Box::new(folder.fold_expression(*subst)?); + Ok(ExpressionKind::AliasExpanded(id, subst)) + } + } + } +} + impl<'i> AliasExpandableExpression<'i> for ExpressionKind<'i> { fn identifier(name: &'i str) -> Self { ExpressionKind::Identifier(name) diff --git a/lib/src/dsl_util.rs b/lib/src/dsl_util.rs index ed2f987bd..e2989d4f7 100644 --- a/lib/src/dsl_util.rs +++ b/lib/src/dsl_util.rs @@ -130,6 +130,73 @@ pub struct InvalidArguments<'i> { pub span: pest::Span<'i>, } +/// Expression item that can be transformed recursively by using `folder: F`. +pub trait FoldableExpression<'i>: Sized { + /// Transforms `self` by applying the `folder` to inner items. + fn fold(self, folder: &mut F, span: pest::Span<'i>) -> Result + where + F: ExpressionFolder<'i, Self> + ?Sized; +} + +/// Visitor-like interface to transform AST nodes recursively. +pub trait ExpressionFolder<'i, T: FoldableExpression<'i>> { + /// Transform error. + type Error; + + /// Transforms the expression `node`. By default, inner items are + /// transformed recursively. + fn fold_expression( + &mut self, + node: ExpressionNode<'i, T>, + ) -> Result, Self::Error> { + let ExpressionNode { kind, span } = node; + let kind = kind.fold(self, span)?; + Ok(ExpressionNode { kind, span }) + } + + /// Transforms identifier. + fn fold_identifier(&mut self, name: &'i str, span: pest::Span<'i>) -> Result; + + /// Transforms function call. + fn fold_function_call( + &mut self, + function: Box>, + span: pest::Span<'i>, + ) -> Result; +} + +/// Transforms list of `nodes` by using `folder`. +pub fn fold_expression_nodes<'i, F, T>( + folder: &mut F, + nodes: Vec>, +) -> Result>, F::Error> +where + F: ExpressionFolder<'i, T> + ?Sized, + T: FoldableExpression<'i>, +{ + nodes + .into_iter() + .map(|node| folder.fold_expression(node)) + .try_collect() +} + +/// Transforms function call arguments by using `folder`. +pub fn fold_function_call_args<'i, F, T>( + folder: &mut F, + function: FunctionCallNode<'i, T>, +) -> Result, F::Error> +where + F: ExpressionFolder<'i, T> + ?Sized, + T: FoldableExpression<'i>, +{ + Ok(FunctionCallNode { + name: function.name, + name_span: function.name_span, + args: fold_expression_nodes(folder, function.args)?, + args_span: function.args_span, + }) +} + /// Helper to parse string literal. #[derive(Debug)] pub struct StringLiteralParser { @@ -267,7 +334,7 @@ pub trait AliasDeclarationParser { } /// Expression item that supports alias substitution. -pub trait AliasExpandableExpression<'i>: Sized { +pub trait AliasExpandableExpression<'i>: FoldableExpression<'i> { /// Wraps identifier. fn identifier(name: &'i str) -> Self; /// Wraps function call.