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.
|
/// Parses text into AST nodes, and expands aliases.
|
||||||
///
|
///
|
||||||
/// No type/name checking is made at this stage.
|
/// No type/name checking is made at this stage.
|
||||||
|
@ -742,7 +600,7 @@ pub fn parse<'i>(
|
||||||
aliases_map: &'i TemplateAliasesMap,
|
aliases_map: &'i TemplateAliasesMap,
|
||||||
) -> TemplateParseResult<ExpressionNode<'i>> {
|
) -> TemplateParseResult<ExpressionNode<'i>> {
|
||||||
let node = parse_template(template_text)?;
|
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.
|
/// 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;
|
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.
|
/// Collects similar names from the `candidates` list.
|
||||||
pub fn collect_similar<I>(name: &str, candidates: I) -> Vec<String>
|
pub fn collect_similar<I>(name: &str, candidates: I) -> Vec<String>
|
||||||
where
|
where
|
||||||
|
|
Loading…
Reference in a new issue