ok/jj
1
0
Fork 0
forked from mirrors/jj

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
This commit is contained in:
Yuya Nishihara 2024-05-18 12:27:52 +09:00
parent 5061d4b831
commit 7be4a0a560
2 changed files with 117 additions and 2 deletions

View file

@ -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<ExpressionNode<'i>>),
}
impl<'i> FoldableExpression<'i> for ExpressionKind<'i> {
fn fold<F>(self, folder: &mut F, span: pest::Span<'i>) -> Result<Self, F::Error>
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)

View file

@ -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<F>(self, folder: &mut F, span: pest::Span<'i>) -> Result<Self, F::Error>
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<ExpressionNode<'i, T>, 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<T, Self::Error>;
/// Transforms function call.
fn fold_function_call(
&mut self,
function: Box<FunctionCallNode<'i, T>>,
span: pest::Span<'i>,
) -> Result<T, Self::Error>;
}
/// Transforms list of `nodes` by using `folder`.
pub fn fold_expression_nodes<'i, F, T>(
folder: &mut F,
nodes: Vec<ExpressionNode<'i, T>>,
) -> Result<Vec<ExpressionNode<'i, T>>, 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<FunctionCallNode<'i, T>, 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<R> {
@ -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.