use std::collections::HashMap; use serde::{de, Serialize, Deserialize, Deserializer}; use crate::{ parameterexpression::ParameterExpression, parameter::{Parameter, ParameterType}, componentwrapper::{ComponentWrapper, ComponentType}, components::Serialise, Template, Result as AnyResult, aerr }; type ArgumentDef = (String, ParameterType, Option); #[derive(Debug, PartialEq, Clone)] pub struct Assemblage { pub arguments: Vec, pub components: Vec<(ComponentType, HashMap)>, pub save: bool, pub extract: Vec<(String, ComponentType, String)> } impl Assemblage { pub fn validate(&self) -> AnyResult<()> { for (comptype, parameters) in &self.components { for paramname in comptype.parameters() { let _param = parameters.get(paramname).ok_or(aerr!("missing parameter {} for component {:?}", paramname, comptype))?; // todo: validate parameter types } } Ok(()) } fn prepare_arguments(&self, args: &[Parameter], kwargs: &HashMap) -> AnyResult> { let mut arguments: HashMap<&str, Parameter> = HashMap::new(); for (idx, (name, typ, def)) in self.arguments.iter().enumerate() { let value: Option = { if let Some(val) = kwargs.get(name) { Some(val.clone()) } else if let Some(val) = args.get(idx) { Some(val.clone()) } else if let Some(val) = def { Some(val.clone()) } else { None } }; let param = value.ok_or(aerr!("argument <{:?}> has no value", (idx, (name, typ, def))))?; if param.paramtype() != *typ { return Err(aerr!( "argument has incorrect type: {:?}, {:?}, {:?}", (idx, (name, typ, def)), param.paramtype(), param )); } arguments.insert(name, param); } Ok(arguments) } pub fn instantiate(&self, template: &Template) -> AnyResult>{ let args = &template.args; let kwargs = &template.kwargs; let mut components: Vec = Vec::new(); let arguments = self.prepare_arguments(args, kwargs)?; for (comptype, compparams) in &self.components { let mut compargs: HashMap<&str, Parameter> = HashMap::new(); for (name, param) in compparams { compargs.insert(name.as_str(), param.evaluate(&arguments, template).ok_or(aerr!("argument not found"))?); } components.push(ComponentWrapper::load_component(*comptype, compargs)?); } if template.should_save() && self.save { components.push(ComponentWrapper::Serialise(Serialise{template: template.clone(), extract: self.extract.clone() })); } Ok(components) } } #[macro_export] macro_rules! compmap { {$($name: ident: $val: expr),*} => {{ #[allow(unused_imports)] use crate::fromtoparameter::FromToParameter; #[allow(unused_mut)] let mut h = std::collections::HashMap::new(); $( h.insert(stringify!($name).to_string(), crate::parameterexpression::ParameterExpression::Constant($val.to_parameter())); )* h }} } impl<'de> Deserialize<'de> for Assemblage { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let AssemblageSave{arguments, mut components, save, extract, name, sprite, height, flags, substitute} = AssemblageSave::deserialize(deserializer)?; if let Some(f) = flags { components.push((ComponentType::Flags, compmap!{flags: f})); } if let Some(spr) = sprite { components.push((ComponentType::Visible, compmap!{ sprite: spr.clone(), height: height.ok_or(de::Error::custom("height must be included in assemblage when sprite is included"))?, name: name.unwrap_or(spr) })); } if let Some(sub) = substitute { components.push((ComponentType::Substitute, compmap!{into: sub})); } Ok(Assemblage { arguments: arguments.into_iter() .map(|arg| match arg { ArgumentDefSave::Long(name, typ, def) => (name, typ, Some(def)), ArgumentDefSave::Short(name, typ) => (name, typ, None) }) .collect(), components, save, extract: extract.into_iter().map(|(k, (t, v))| (k, t, v)).collect() }) } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] enum ArgumentDefSave{ Long(String, ParameterType, Parameter), Short(String, ParameterType) } #[derive(Debug, Clone, PartialEq, Deserialize)] struct AssemblageSave { #[serde(default)] pub arguments: Vec, #[serde(default)] pub components: Vec<(ComponentType, HashMap)>, #[serde(default="return_true")] pub save: bool, #[serde(default)] pub extract: HashMap, pub name: Option, pub sprite: Option, pub height: Option, pub flags: Option>, pub substitute: Option