use crate::core::environment::{expr_desc, RunSignal};
use crate::core::eval;
use crate::core::exec::ExecOutcome;
use crate::core::{CallTarget, Element};
use crate::error::{Result, TemplateError};
use crate::span::Span;
use crate::template::{TModel, TemplateDirectiveBody};
use std::collections::HashMap;
use std::rc::Rc;
pub struct UnifiedCall {
pub callee: CallTarget,
pub args: Vec<(String, crate::core::Expr)>,
pub body: Option<Vec<Element>>,
pub body_params: Vec<String>,
pub span: Span,
}
impl UnifiedCall {
#[allow(clippy::too_many_arguments)]
pub fn new(
callee: CallTarget,
args: Vec<(String, crate::core::Expr)>,
body: Option<Vec<Element>>,
body_params: Vec<String>,
span: Span,
) -> Self {
UnifiedCall {
callee,
args,
body,
body_params,
span,
}
}
pub(crate) fn exec(&self, env: &mut crate::core::Environment) -> Result<ExecOutcome> {
exec_call_impl(
env,
&self.callee,
&self.args,
self.body.clone(),
self.body_params.clone(),
self.span,
)
}
}
pub(crate) fn exec_call_impl(
env: &mut crate::core::Environment,
callee: &CallTarget,
args: &[(String, crate::core::Expr)],
body: Option<Vec<Element>>,
body_params: Vec<String>,
call_span: crate::span::Span,
) -> Result<ExecOutcome> {
let tm = match callee {
CallTarget::Name(name) => {
if let Some(mv) = env.get_macro(name) {
return call_macro(env, &mv, args, body, body_params);
}
match env.get_variable(name) {
Ok(tm) => tm,
Err(e) => {
return Err(crate::core::environment::attach_location(
e,
&env.current_template_name,
crate::span::Span::new(call_span.line, call_span.col.saturating_add(2)),
))
}
}
}
CallTarget::Namespaced { ns, name } => {
let nsm = env.get_variable(ns)?;
match env.as_namespace(&nsm) {
Some(nsr) => nsr
.get_member(name)
.ok_or_else(|| TemplateError::invalid_reference(format!("{ns}.{name}")))?,
None => eval::eval(
env,
&crate::core::Expr::new(
crate::core::ExprKind::Dot {
target: Box::new(crate::core::Expr::new(
crate::core::ExprKind::Ident(ns.clone()),
crate::span::Span::new(0, 0),
)),
name: name.clone(),
},
crate::span::Span::new(0, 0),
),
)?,
}
}
CallTarget::Expr(e) => eval::eval(env, e)?,
};
if let Some(mv) = env.as_macro(&tm) {
return call_macro(env, &mv, args, body, body_params);
}
if let Some(d) = &tm.directive {
let mut params = HashMap::new();
for (k, e) in args {
if !k.is_empty() {
params.insert(k.clone(), eval::eval(env, e)?);
}
}
let mut loop_vars: Vec<TModel> = vec![TModel::nothing(); body_params.len()];
let has_body = body.is_some();
let call_body = CallBody {
elements: body.unwrap_or_default(),
};
let body_ref: Option<&dyn TemplateDirectiveBody> =
if has_body { Some(&call_body) } else { None };
d.execute(env, ¶ms, &mut loop_vars, body_ref)?;
return Ok(ExecOutcome::Done);
}
if let Some(ttm) = env.as_transform(&tm) {
let mut params = HashMap::new();
for (k, e) in args {
if !k.is_empty() {
params.insert(k.clone(), eval::eval(env, e)?);
}
}
let body_elems: &[crate::core::Element] = body.as_deref().unwrap_or(&[]);
let signal = ttm.transform_with_body(env, ¶ms, body_elems)?;
return match signal {
RunSignal::Returned(v) => Ok(ExecOutcome::ReturnValue(v)),
_ => Ok(ExecOutcome::Done),
};
}
let call_name = match callee {
CallTarget::Name(name) => name.clone(),
CallTarget::Namespaced { ns, name } => format!("{ns}.{name}"),
CallTarget::Expr(e) => expr_desc(e),
};
Err(TemplateError::misc(format!(
"The value of {call_name} is not a macro or user-defined directive (it's a {})",
tm.type_name
)))
}
pub(crate) fn call_macro(
env: &mut crate::core::Environment,
mv: &Rc<crate::core::environment::MacroValue>,
args: &[(String, crate::core::Expr)],
body: Option<Vec<Element>>,
body_params: Vec<String>,
) -> Result<ExecOutcome> {
if mv.def.is_function {
return Err(TemplateError::misc(format!(
"Routine \"{}\" is a function, not a directive. Functions can only be called from expressions, like in ${{f()}}.",
mv.def.name
)));
}
let r = env.invoke_macro(mv, args, body, body_params)?;
match r {
RunSignal::Completed => Ok(ExecOutcome::Done),
RunSignal::Returned(v) => Ok(ExecOutcome::ReturnValue(v)),
}
}
pub struct CallBody {
elements: Vec<Element>,
}
impl TemplateDirectiveBody for CallBody {
fn render(&self, env: &mut crate::core::Environment) -> Result<()> {
env.run_elements(&self.elements)
}
}