From b31f75bc94c4552454ab929b54b9f37340231fac Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sat, 18 May 2024 12:44:48 +0900 Subject: [PATCH] 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. --- cli/src/template_parser.rs | 144 +------------------------------------ lib/src/dsl_util.rs | 108 ++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 143 deletions(-) diff --git a/cli/src/template_parser.rs b/cli/src/template_parser.rs index b3a6de3e7..4b5cfc8fd 100644 --- a/cli/src/template_parser.rs +++ b/cli/src/template_parser.rs @@ -592,148 +592,6 @@ impl AliasDefinitionParser for TemplateAliasParser { } } -/// Expand aliases recursively. -pub fn expand_aliases<'i>( - node: ExpressionNode<'i>, - aliases_map: &'i TemplateAliasesMap, -) -> TemplateParseResult> { - #[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> { - // 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>, - state: State<'_, 'i>, - ) -> TemplateParseResult>> { - nodes - .into_iter() - .map(|node| expand_node(node, state)) - .try_collect() - } - - fn expand_function_call<'i>( - function: FunctionCallNode<'i>, - state: State<'_, 'i>, - ) -> TemplateParseResult> { - 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> { - 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> { 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. diff --git a/lib/src/dsl_util.rs b/lib/src/dsl_util.rs index 225a89c6f..ce58a220c 100644 --- a/lib/src/dsl_util.rs +++ b/lib/src/dsl_util.rs @@ -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

, + /// Stack of aliases and local parameters currently expanding. + states: Vec>, +} + +#[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 = 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 { + // 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 = T, Error = E>, + E: AliasExpandError, +{ + type Error = E; + + fn fold_identifier(&mut self, name: &'i str, span: pest::Span<'i>) -> Result { + 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>, + span: pest::Span<'i>, + ) -> Result { + 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

, +) -> Result, P::Error> +where + T: AliasExpandableExpression<'i> + Clone, + P: AliasDefinitionParser = 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(name: &str, candidates: I) -> Vec where