dsl_util: introduce visitor-based generic alias substitution

The original expand_node() body is migrated as follows:
- Identifier -> fold_identifier()
- FunctionCall -> fold_function_call()

expand_defn() now manages states stack by itself, which simplifies lifetime
parameters.
This commit is contained in:
Yuya Nishihara 2024-05-18 12:44:48 +09:00
parent 0efd2aa316
commit b31f75bc94
2 changed files with 109 additions and 143 deletions

View file

@ -592,148 +592,6 @@ impl AliasDefinitionParser for TemplateAliasParser {
}
}
/// Expand aliases recursively.
pub fn expand_aliases<'i>(
node: ExpressionNode<'i>,
aliases_map: &'i TemplateAliasesMap,
) -> TemplateParseResult<ExpressionNode<'i>> {
#[derive(Clone, Copy, Debug)]
struct State<'a, 'i> {
aliases_map: &'i TemplateAliasesMap,
aliases_expanding: &'a [AliasId<'a>],
locals: &'a HashMap<&'a str, ExpressionNode<'i>>,
}
fn expand_defn<'i>(
id: AliasId<'i>,
defn: &'i str,
locals: &HashMap<&str, ExpressionNode<'i>>,
span: pest::Span<'i>,
state: State<'_, 'i>,
) -> TemplateParseResult<ExpressionKind<'i>> {
// The stack should be short, so let's simply do linear search and duplicate.
if state.aliases_expanding.contains(&id) {
return Err(TemplateParseError::recursive_expansion(id, span));
}
let mut aliases_expanding = state.aliases_expanding.to_vec();
aliases_expanding.push(id);
let expanding_state = State {
aliases_map: state.aliases_map,
aliases_expanding: &aliases_expanding,
locals,
};
// Parsed defn could be cached if needed.
TemplateAliasParser
.parse_definition(defn)
.and_then(|node| expand_node(node, expanding_state))
.map(|node| ExpressionKind::alias_expanded(id, Box::new(node)))
.map_err(|e| e.within_alias_expansion(id, span))
}
fn expand_list<'i>(
nodes: Vec<ExpressionNode<'i>>,
state: State<'_, 'i>,
) -> TemplateParseResult<Vec<ExpressionNode<'i>>> {
nodes
.into_iter()
.map(|node| expand_node(node, state))
.try_collect()
}
fn expand_function_call<'i>(
function: FunctionCallNode<'i>,
state: State<'_, 'i>,
) -> TemplateParseResult<FunctionCallNode<'i>> {
Ok(FunctionCallNode {
name: function.name,
name_span: function.name_span,
args: expand_list(function.args, state)?,
args_span: function.args_span,
})
}
fn expand_node<'i>(
node: ExpressionNode<'i>,
state: State<'_, 'i>,
) -> TemplateParseResult<ExpressionNode<'i>> {
let ExpressionNode { kind, span } = node;
let kind = match kind {
ExpressionKind::Identifier(name) => {
if let Some(subst) = state.locals.get(name) {
let id = AliasId::Parameter(name);
ExpressionKind::alias_expanded(id, Box::new(subst.clone()))
} else if let Some((id, defn)) = state.aliases_map.get_symbol(name) {
let locals = HashMap::new(); // Don't spill out the current scope
expand_defn(id, defn, &locals, span, state)?
} else {
ExpressionKind::identifier(name)
}
}
ExpressionKind::Boolean(_) | ExpressionKind::Integer(_) | ExpressionKind::String(_) => {
kind
}
ExpressionKind::Unary(op, arg) => {
let arg = Box::new(expand_node(*arg, state)?);
ExpressionKind::Unary(op, arg)
}
ExpressionKind::Binary(op, lhs, rhs) => {
let lhs = Box::new(expand_node(*lhs, state)?);
let rhs = Box::new(expand_node(*rhs, state)?);
ExpressionKind::Binary(op, lhs, rhs)
}
ExpressionKind::Concat(nodes) => ExpressionKind::Concat(expand_list(nodes, state)?),
ExpressionKind::FunctionCall(function) => {
if let Some((id, params, defn)) = state.aliases_map.get_function(function.name) {
if function.args.len() != params.len() {
return Err(InvalidArguments {
name: function.name,
message: format!("Expected {} arguments", params.len()),
span: function.args_span,
}
.into());
}
// Resolve arguments in the current scope, and pass them in to the alias
// expansion scope.
let args = expand_list(function.args, state)?;
let locals = params.iter().map(|s| s.as_str()).zip(args).collect();
expand_defn(id, defn, &locals, span, state)?
} else {
let function = Box::new(expand_function_call(*function, state)?);
ExpressionKind::function_call(function)
}
}
ExpressionKind::MethodCall(method) => {
let method = Box::new(MethodCallNode {
object: expand_node(method.object, state)?,
function: expand_function_call(method.function, state)?,
});
ExpressionKind::MethodCall(method)
}
ExpressionKind::Lambda(lambda) => {
let lambda = Box::new(LambdaNode {
params: lambda.params,
params_span: lambda.params_span,
body: expand_node(lambda.body, state)?,
});
ExpressionKind::Lambda(lambda)
}
ExpressionKind::AliasExpanded(id, subst) => {
// Just in case the original tree contained AliasExpanded node.
let subst = Box::new(expand_node(*subst, state)?);
ExpressionKind::AliasExpanded(id, subst)
}
};
Ok(ExpressionNode { kind, span })
}
let state = State {
aliases_map,
aliases_expanding: &[],
locals: &HashMap::new(),
};
expand_node(node, state)
}
/// Parses text into AST nodes, and expands aliases.
///
/// No type/name checking is made at this stage.
@ -742,7 +600,7 @@ pub fn parse<'i>(
aliases_map: &'i TemplateAliasesMap,
) -> TemplateParseResult<ExpressionNode<'i>> {
let node = parse_template(template_text)?;
expand_aliases(node, aliases_map)
dsl_util::expand_aliases(node, aliases_map)
}
/// Applies the given function if the `node` is a string literal.

View file

@ -372,6 +372,114 @@ pub trait AliasExpandError: Sized {
fn within_alias_expansion(self, id: AliasId<'_>, span: pest::Span<'_>) -> Self;
}
/// Expands aliases recursively in tree of `T`.
#[derive(Debug)]
struct AliasExpander<'i, T, P> {
/// Alias symbols and functions that are globally available.
aliases_map: &'i AliasesMap<P>,
/// Stack of aliases and local parameters currently expanding.
states: Vec<AliasExpandingState<'i, T>>,
}
#[derive(Debug)]
struct AliasExpandingState<'i, T> {
id: AliasId<'i>,
locals: HashMap<&'i str, ExpressionNode<'i, T>>,
}
impl<'i, T, P, E> AliasExpander<'i, T, P>
where
T: AliasExpandableExpression<'i> + Clone,
P: AliasDefinitionParser<Output<'i> = T, Error = E>,
E: AliasExpandError,
{
fn expand_defn(
&mut self,
id: AliasId<'i>,
defn: &'i str,
locals: HashMap<&'i str, ExpressionNode<'i, T>>,
span: pest::Span<'i>,
) -> Result<T, E> {
// The stack should be short, so let's simply do linear search.
if self.states.iter().any(|s| s.id == id) {
return Err(E::recursive_expansion(id, span));
}
self.states.push(AliasExpandingState { id, locals });
// Parsed defn could be cached if needed.
let result = self
.aliases_map
.parser
.parse_definition(defn)
.and_then(|node| self.fold_expression(node))
.map(|node| T::alias_expanded(id, Box::new(node)))
.map_err(|e| e.within_alias_expansion(id, span));
self.states.pop();
result
}
}
impl<'i, T, P, E> ExpressionFolder<'i, T> for AliasExpander<'i, T, P>
where
T: AliasExpandableExpression<'i> + Clone,
P: AliasDefinitionParser<Output<'i> = T, Error = E>,
E: AliasExpandError,
{
type Error = E;
fn fold_identifier(&mut self, name: &'i str, span: pest::Span<'i>) -> Result<T, Self::Error> {
if let Some(subst) = self.states.last().and_then(|s| s.locals.get(name)) {
let id = AliasId::Parameter(name);
Ok(T::alias_expanded(id, Box::new(subst.clone())))
} else if let Some((id, defn)) = self.aliases_map.get_symbol(name) {
let locals = HashMap::new(); // Don't spill out the current scope
self.expand_defn(id, defn, locals, span)
} else {
Ok(T::identifier(name))
}
}
fn fold_function_call(
&mut self,
function: Box<FunctionCallNode<'i, T>>,
span: pest::Span<'i>,
) -> Result<T, Self::Error> {
if let Some((id, params, defn)) = self.aliases_map.get_function(function.name) {
if function.args.len() != params.len() {
return Err(E::invalid_arguments(InvalidArguments {
name: function.name,
message: format!("Expected {} arguments", params.len()),
span: function.args_span,
}));
}
// Resolve arguments in the current scope, and pass them in to the alias
// expansion scope.
let args = fold_expression_nodes(self, function.args)?;
let locals = params.iter().map(|s| s.as_str()).zip(args).collect();
self.expand_defn(id, defn, locals, span)
} else {
let function = Box::new(fold_function_call_args(self, *function)?);
Ok(T::function_call(function))
}
}
}
/// Expands aliases recursively.
pub fn expand_aliases<'i, T, P>(
node: ExpressionNode<'i, T>,
aliases_map: &'i AliasesMap<P>,
) -> Result<ExpressionNode<'i, T>, P::Error>
where
T: AliasExpandableExpression<'i> + Clone,
P: AliasDefinitionParser<Output<'i> = T>,
P::Error: AliasExpandError,
{
let mut expander = AliasExpander {
aliases_map,
states: Vec::new(),
};
expander.fold_expression(node)
}
/// Collects similar names from the `candidates` list.
pub fn collect_similar<I>(name: &str, candidates: I) -> Vec<String>
where