use crate::{
directives::{IMPLEMENTED_DIRECTIVES, UNIMPLEMENTED_DIRECTIVES},
error::{EnrichError, XacroError},
parse::{macro_def::MacroProcessor, xml::is_known_xacro_uri},
};
use std::{collections::HashMap, rc::Rc};
use xmltree::{Element, XMLNode};
use super::{
children::expand_children_list,
expand_node,
guards::{BlockGuard, MacroCallGuard, ScopeGuard},
XacroContext,
};
pub(super) fn is_macro_call(
elem: &Element,
macros: &HashMap<String, Rc<crate::parse::macro_def::MacroDefinition>>,
xacro_ns: &str,
) -> bool {
let in_xacro_ns = !xacro_ns.is_empty()
&& elem
.namespace
.as_deref()
.is_some_and(|elem_ns| elem_ns == xacro_ns || is_known_xacro_uri(elem_ns));
if !in_xacro_ns {
return false;
}
let elem_name = elem.name.as_str();
if IMPLEMENTED_DIRECTIVES.contains(&elem_name) || UNIMPLEMENTED_DIRECTIVES.contains(&elem_name)
{
return false;
}
macros.contains_key(&elem.name)
}
pub(super) fn expand_macro_call(
call_elem: &Element,
ctx: &XacroContext,
parent_scope_depth: usize,
) -> Result<Vec<XMLNode>, XacroError> {
let macro_name = &call_elem.name;
let macro_def = ctx
.macros
.borrow()
.get(macro_name)
.cloned()
.ok_or_else(|| XacroError::UndefinedMacro(macro_name.to_string()))?;
let _macro_guard = MacroCallGuard::new(&ctx.macro_call_stack, macro_name.clone());
let loc = ctx.get_location_context();
let mut processed_elem = call_elem.clone();
processed_elem.children =
expand_children_list(core::mem::take(&mut processed_elem.children), ctx)?;
let (args, blocks) = MacroProcessor::collect_macro_args(&processed_elem, ¯o_def)?;
let mut expanded_blocks = HashMap::new();
let mut unexpanded_blocks = blocks;
log::debug!(
"expand_macro_call: macro '{}' received {} blocks from collect_macro_args: {:?}",
macro_name,
unexpanded_blocks.len(),
unexpanded_blocks.keys().collect::<Vec<_>>()
);
for param_name in ¯o_def.param_order {
if macro_def.block_params.contains(param_name) {
let raw_block = unexpanded_blocks.remove(param_name).unwrap();
log::debug!(
"expand_macro_call: expanding block param '{}' (element '<{}>')",
param_name,
raw_block.name
);
let is_lazy = macro_def.lazy_block_params.contains(param_name);
let expanded = if is_lazy {
expand_children_list(raw_block.children, ctx)?
} else {
expand_node(XMLNode::Element(raw_block), ctx)?
};
log::debug!(
"expand_macro_call: block param '{}' {} expanded to {} nodes",
param_name,
if is_lazy { "(lazy)" } else { "(regular)" },
expanded.len()
);
expanded_blocks.insert(param_name.clone(), expanded);
}
}
log::debug!(
"expand_macro_call: pushing {} expanded blocks to stack: {:?}",
expanded_blocks.len(),
expanded_blocks.keys().collect::<Vec<_>>()
);
ctx.properties.push_scope(HashMap::new());
let _scope_guard = ScopeGuard::new(&ctx.properties);
for param_name in ¯o_def.param_order {
if macro_def.block_params.contains(param_name) {
continue; }
if let Some(value) = args.get(param_name) {
ctx.properties
.add_to_current_scope(param_name.clone(), value.clone());
} else {
let param_default = macro_def
.params
.get(param_name)
.expect("Internal logic error: parameter in param_order must exist in params map");
let evaluated = match param_default {
crate::parse::macro_def::ParamDefault::None => {
return Err(XacroError::MissingParameter {
macro_name: macro_def.name.clone(),
param: param_name.clone(),
});
}
crate::parse::macro_def::ParamDefault::Value(default_expr) => {
ctx.properties
.substitute_text(default_expr, Some(&loc))
.with_loc(&loc)?
}
crate::parse::macro_def::ParamDefault::ForwardRequired(forward_name) => {
ctx.properties
.lookup_at_depth(forward_name, parent_scope_depth)
.ok_or_else(|| XacroError::UndefinedPropertyToForward {
macro_name: macro_def.name.clone(),
param: param_name.clone(),
forward_name: forward_name.clone(),
})?
}
crate::parse::macro_def::ParamDefault::ForwardWithDefault(
forward_name,
maybe_default,
) => {
if let Some(parent_value) = ctx
.properties
.lookup_at_depth(forward_name, parent_scope_depth)
{
parent_value
} else if let Some(default_expr) = maybe_default.as_ref() {
ctx.properties
.substitute_text(default_expr, Some(&loc))
.with_loc(&loc)?
} else {
String::new()
}
}
};
ctx.properties
.add_to_current_scope(param_name.clone(), evaluated);
}
}
ctx.block_stack.borrow_mut().push(expanded_blocks);
let _block_guard = BlockGuard::new(&ctx.block_stack);
let content = macro_def.content.clone();
expand_children_list(content.children, ctx)
}