use crate::parser::Filter; use crate::parser::KVPair; use crate::parser::OwnedLiteral; use crate::parser::RValue; use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement; use crate::renderer::comparison_number::compare_json_numbers; use crate::renderer::comparison_number::ComparisonNumber; use crate::renderer::context_element::CompareContextElement; use crate::renderer::context_element::ContextElement; use crate::renderer::context_element::IceResult; use crate::renderer::context_element::IntoContextElement; use crate::renderer::math::MathNumber; use crate::renderer::walking::walk_path; use crate::renderer::Castable; use crate::renderer::DustRenderer; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; use crate::renderer::Sizable; use crate::renderer::Truthiness; use crate::renderer::WalkError; use crate::renderer::Walkable; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::HashMap; use std::convert::TryInto; #[derive(Debug)] pub struct ParametersContext<'a> { parent: Option<&'a ParametersContext<'a>>, params: HashMap<&'a str, (&'a RValue<'a>, Option>)>, } impl<'a> ParametersContext<'a> { pub fn new( renderer: &DustRenderer, breadcrumbs: &'a Vec>, params: &'a Vec, parent: Option<&'a ParametersContext<'a>>, ) -> Self { // If the parameter is a Path, then we resolve it immediately // to a context element because those are resolved using the // breadcrumbs at the time of assignment. // // If the parameter is a template (for example `foo="{bar}"`) // then those are resolved at the time of access rather than // the time of assignment, so we leave them into their // original IntoContextElement state. let rendered_params: HashMap<&'a str, (&'a RValue<'a>, Option>)> = params .iter() .map(|kvpair| { let k = kvpair.key; let v: Option> = match &kvpair.value { RValue::RVLiteral(_owned_literal) => { Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value)) } RValue::RVPath(_path) => kvpair .value .into_context_element(renderer, breadcrumbs) .map(std::convert::From::from), RValue::RVTemplate(_template) => { Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value)) } }; (k, (&kvpair.value, v)) }) .collect(); ParametersContext { parent: parent, params: rendered_params, } } pub fn from_values( parent: Option<&'a ParametersContext<'a>>, params: HashMap<&'a str, (&'a RValue<'a>, Option>)>, ) -> Self { ParametersContext { parent: parent, params: params, } } pub fn contains_key(&self, segment: &str) -> bool { self.params.contains_key(segment) || self .parent .map(|p| p.contains_key(segment)) .unwrap_or(false) } pub fn get_original_rvalue(&self, segment: &str) -> Option<&'a RValue<'a>> { self.params .get(segment) .map(|(rvalue, _bte)| *rvalue) .or_else(|| { self.parent .map(|p| p.get_original_rvalue(segment)) .flatten() }) } } impl<'a> IntoContextElement for ParametersContext<'a> { fn into_context_element<'b>( &'b self, _renderer: &DustRenderer, _breadcrumbs: &'b Vec>, ) -> Option> { panic!("into_context_element cannot be called on pseudo elements"); } } impl<'a> Walkable for ParametersContext<'a> { fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { match self.params.get(segment).map(|(_rvalue, bte)| bte) { Some(Some(bte)) => Ok(bte.borrow()), Some(None) => Err(WalkError::CantWalk), None => self .parent .map(|p| p.walk(segment)) .unwrap_or(Err(WalkError::CantWalk)), } } fn is_pseudo_element(&self) -> bool { true } } impl<'a> IntoContextElement for RValue<'a> { fn into_context_element<'b>( &'b self, renderer: &DustRenderer, breadcrumbs: &'b Vec>, ) -> Option> { match self { RValue::RVLiteral(owned_literal) => Some(IceResult::from_borrowed(owned_literal)), RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys) .map(|ice| ice.into_context_element(renderer, breadcrumbs)) .ok() .flatten(), RValue::RVTemplate(template) => renderer .render_partial_name(template, breadcrumbs) .map(|rendered| OwnedLiteral::LString(rendered)) .ok() .map(|owned_literal| IceResult::from_owned(owned_literal)), } } } impl<'a> Walkable for RValue<'a> { fn walk(&self, _segment: &str) -> Result<&dyn IntoContextElement, WalkError> { Err(WalkError::CantWalk) } } impl ContextElement for OwnedLiteral {} impl Truthiness for OwnedLiteral { fn is_truthy(&self) -> bool { match self { OwnedLiteral::LBoolean(boolean) => *boolean, OwnedLiteral::LString(text) => !text.is_empty(), OwnedLiteral::LPositiveInteger(_num) => true, OwnedLiteral::LNegativeInteger(_num) => true, OwnedLiteral::LFloat(_num) => true, } } } impl Renderable for OwnedLiteral { fn render(&self, _filters: &Vec) -> Result { match self { OwnedLiteral::LBoolean(boolean) => Ok(boolean.to_string()), OwnedLiteral::LString(text) => Ok(text.clone()), OwnedLiteral::LPositiveInteger(num) => Ok(num.to_string()), OwnedLiteral::LNegativeInteger(num) => Ok(num.to_string()), OwnedLiteral::LFloat(num) => Ok(num.to_string()), } } } impl Loopable for OwnedLiteral { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { Vec::new() } } impl Walkable for OwnedLiteral { fn walk(&self, _segment: &str) -> Result<&dyn IntoContextElement, WalkError> { Err(WalkError::CantWalk) } } impl Sizable for OwnedLiteral { fn is_castable(&self) -> bool { match self { OwnedLiteral::LBoolean(_) => false, OwnedLiteral::LFloat(_) => true, OwnedLiteral::LPositiveInteger(_) => true, OwnedLiteral::LNegativeInteger(_) => true, OwnedLiteral::LString(_) => true, } } fn get_size<'a>(&'a self) -> Option> { match self { OwnedLiteral::LBoolean(_) => { Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0))) } OwnedLiteral::LFloat(_) => Some(IceResult::from_borrowed(self)), OwnedLiteral::LPositiveInteger(_) => Some(IceResult::from_borrowed(self)), OwnedLiteral::LNegativeInteger(_) => Some(IceResult::from_borrowed(self)), OwnedLiteral::LString(text) => Some(IceResult::from_owned( OwnedLiteral::LPositiveInteger(text.len().try_into().unwrap()), )), } } } impl Castable for OwnedLiteral { fn cast_to_type<'a>(&'a self, target: &str) -> Option> { match (self, target) { (OwnedLiteral::LString(text), "number") => text .parse::() .map(|num| IceResult::from_owned(OwnedLiteral::LPositiveInteger(num))) .or_else(|_| { text.parse::() .map(|num| IceResult::from_owned(OwnedLiteral::LNegativeInteger(num))) }) .or_else(|_| { text.parse::() .map(|num| IceResult::from_owned(OwnedLiteral::LFloat(num))) }) .ok(), (OwnedLiteral::LBoolean(boolean), "number") => { if *boolean { Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(1))) } else { Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0))) } } (OwnedLiteral::LPositiveInteger(_), "number") => Some(IceResult::from_borrowed(self)), (OwnedLiteral::LNegativeInteger(_), "number") => Some(IceResult::from_borrowed(self)), (OwnedLiteral::LFloat(_), "number") => Some(IceResult::from_borrowed(self)), (OwnedLiteral::LString(_), "string") => Some(IceResult::from_borrowed(self)), (OwnedLiteral::LBoolean(boolean), "string") => Some(IceResult::from_owned( OwnedLiteral::LString(boolean.to_string()), )), (OwnedLiteral::LPositiveInteger(num), "string") => Some(IceResult::from_owned( OwnedLiteral::LString(num.to_string()), )), (OwnedLiteral::LNegativeInteger(num), "string") => Some(IceResult::from_owned( OwnedLiteral::LString(num.to_string()), )), (OwnedLiteral::LFloat(num), "string") => Some(IceResult::from_owned( OwnedLiteral::LString(num.to_string()), )), (OwnedLiteral::LString(text), "boolean") => { if text.is_empty() { Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) } else { Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) } } (OwnedLiteral::LBoolean(_), "boolean") => Some(IceResult::from_borrowed(self)), (OwnedLiteral::LPositiveInteger(num), "boolean") => { if *num == 0 { Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) } else { Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) } } (OwnedLiteral::LNegativeInteger(num), "boolean") => { if *num == 0 { Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) } else { Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) } } (OwnedLiteral::LFloat(num), "boolean") => { if *num == 0.0 || num.is_nan() { Some(IceResult::from_owned(OwnedLiteral::LBoolean(false))) } else { Some(IceResult::from_owned(OwnedLiteral::LBoolean(true))) } } (_, _) => panic!("Unimplemented cast"), } } } impl CompareContextElement for OwnedLiteral { fn equals(&self, other: &dyn ContextElement) -> bool { // println!("Literal equality check {:?} == {:?}", self, other); // If its an OwnedLiteral then compare them directly, // otherwise defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => other.equals(self), Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => { self_text == other_text } ( OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => self_num == other_num, ( OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LNegativeInteger(other_num), ) => self_num == other_num, (OwnedLiteral::LBoolean(self_bool), OwnedLiteral::LBoolean(other_bool)) => { self_bool == other_bool } (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => false, (OwnedLiteral::LBoolean(_), _) | (_, OwnedLiteral::LBoolean(_)) => false, (OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => { self_num == other_num } (OwnedLiteral::LFloat(self_num), OwnedLiteral::LPositiveInteger(other_num)) => { *self_num == (*other_num as f64) } (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LFloat(other_num)) => { (*self_num as f64) == *other_num } (OwnedLiteral::LFloat(self_num), OwnedLiteral::LNegativeInteger(other_num)) => { *self_num == (*other_num as f64) } (OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LFloat(other_num)) => { (*self_num as f64) == *other_num } ( OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LNegativeInteger(other_num), ) => { if *self_num < std::i64::MAX as u64 { (*self_num as i64) == *other_num } else { false } } ( OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => { if *other_num < std::i64::MAX as u64 { *self_num == (*other_num as i64) } else { false } } }, } } fn partial_compare(&self, other: &dyn ContextElement) -> Option { // If its an OwnedLiteral then compare them directly, // otherwise defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => match other.partial_compare(self) { None => None, Some(ord) => match ord { Ordering::Equal => Some(Ordering::Equal), Ordering::Greater => Some(Ordering::Less), Ordering::Less => Some(Ordering::Greater), }, }, Some(other_literal) => match (self, other_literal) { // If they're both strings, compare them directly (OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => { self_text.partial_cmp(other_text) } // Otherwise, convert to numbers and compare them that way (_, _) => return compare_json_numbers(self, other_literal), }, } } fn math_add<'a>(&self, other: &dyn ContextElement) -> Option> { // If its an OwnedLiteral then add them directly, otherwise // defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => other.math_add(self), Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None, (_, _) => (std::convert::Into::::into(self) + std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), }, } } fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option> { // If its an OwnedLiteral then subtract them directly, // otherwise defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => other.math_subtract(self), Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None, (_, _) => (std::convert::Into::::into(self) - std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), }, } } fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option> { // If its an OwnedLiteral then multiply them directly, // otherwise defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => other.math_multiply(self), Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None, (_, _) => (std::convert::Into::::into(self) * std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), }, } } fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option> { // If its an OwnedLiteral then divide them directly, otherwise // defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => other.math_divide(self), Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None, (_, _) => (std::convert::Into::::into(self) / std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), }, } } fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option> { // If its an OwnedLiteral then modulus them directly, // otherwise defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => other.math_modulus(self), Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None, (_, _) => (std::convert::Into::::into(self) % std::convert::Into::::into(other_literal)) .map(IceResult::from_owned), }, } } fn math_abs<'a>(&self) -> Option> { std::convert::Into::::into(self) .math_abs() .map(IceResult::from_owned) } fn math_floor<'a>(&self) -> Option> { std::convert::Into::::into(self) .math_floor() .map(IceResult::from_owned) } fn math_ceil<'a>(&self) -> Option> { std::convert::Into::::into(self) .math_ceil() .map(IceResult::from_owned) } } impl From<&OwnedLiteral> for ComparisonNumber { fn from(original: &OwnedLiteral) -> Self { match original { OwnedLiteral::LBoolean(boolean) => { if *boolean { ComparisonNumber::UnsignedInteger(1) } else { ComparisonNumber::UnsignedInteger(0) } } OwnedLiteral::LPositiveInteger(num) => ComparisonNumber::UnsignedInteger(*num), OwnedLiteral::LNegativeInteger(num) => ComparisonNumber::SignedInteger(*num), OwnedLiteral::LString(text) => text.into(), OwnedLiteral::LFloat(num) => { if num.is_nan() { ComparisonNumber::Failure } else { ComparisonNumber::Decimal(*num) } } } } }