use crate::parser::{CompositionBlock, Statement, TemplateCall, TemplateSource, ParamValue};
use crate::scope::Scope;
use crate::templates;
use proc_macro2::TokenStream;
use std::collections::HashSet;
use syn::Result;
pub fn process_block(block: CompositionBlock) -> Result<TokenStream> {
let mut scope = Scope::new();
validate_block(&block)?;
for statement in block.statements {
match statement {
Statement::Let { name, value } => {
let resolved = process_template_call(&value, &scope)?;
scope.add_local(name.to_string(), resolved);
}
Statement::Const { attrs, name, value } => {
let resolved = process_template_call(&value, &scope)?;
scope.add_export(attrs, name.to_string(), resolved);
}
}
}
Ok(scope.generate_output())
}
fn validate_block(block: &CompositionBlock) -> Result<()> {
let mut defined_names = HashSet::new();
let mut let_names = HashSet::new();
for statement in &block.statements {
match statement {
Statement::Let { name, value } => {
if !defined_names.insert(name.to_string()) {
return Err(syn::Error::new_spanned(
name,
format!("Duplicate definition of '{}'", name),
));
}
let_names.insert(name.to_string());
validate_references(value, &let_names)?;
}
Statement::Const { name, value, .. } => {
if !defined_names.insert(name.to_string()) {
return Err(syn::Error::new_spanned(
name,
format!("Duplicate definition of '{}'", name),
));
}
validate_references(value, &let_names)?;
}
}
}
Ok(())
}
fn validate_references(call: &TemplateCall, defined: &HashSet<String>) -> Result<()> {
for (_, value) in &call.params {
match value {
ParamValue::Variable(name) => {
if !defined.contains(name) {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
format!("Undefined variable: '{}'", name),
));
}
}
ParamValue::Nested(nested) => {
validate_references(nested, defined)?;
}
ParamValue::Literal(_) => {}
}
}
Ok(())
}
fn process_template_call(call: &TemplateCall, scope: &Scope) -> Result<String> {
let (template_string, engine_name) = match &call.source {
TemplateSource::Name(name) => {
let templates = templates::load_templates();
if let Some(template) = templates.get(name) {
let template_str = template.template.clone();
let engine = template.engine.as_deref().unwrap_or("simple").to_string();
(template_str, engine)
} else {
(name.clone(), "simple".to_string())
}
}
};
let mut resolved_params = std::collections::HashMap::new();
for (key, value) in &call.params {
let resolved_value = match value {
ParamValue::Literal(s) => s.clone(),
ParamValue::Variable(name) => {
scope.get_local(name)
.ok_or_else(|| syn::Error::new(
proc_macro2::Span::call_site(),
format!("Undefined variable: '{}'", name),
))?
.clone()
}
ParamValue::Nested(nested) => {
process_template_call(nested, scope)?
}
};
resolved_params.insert(key.clone(), resolved_value);
}
crate::engines::process(&engine_name, &template_string, &resolved_params)
.map_err(|e| syn::Error::new(proc_macro2::Span::call_site(), e))
}