mirror of
https://github.com/martinvonz/jj.git
synced 2025-02-08 13:39:56 +00:00
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:
parent
0efd2aa316
commit
b31f75bc94
2 changed files with 109 additions and 143 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue