use crate::parser::KVPair; use crate::parser::{Filter, OwnedLiteral, RValue}; use crate::renderer::context_element::CompareContextElement; use crate::renderer::context_element::ContextElement; use crate::renderer::walking::owned_walk_path; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; use crate::renderer::WalkError; use crate::renderer::Walkable; use std::{cmp::Ordering, collections::HashMap}; /// Copy the data from an RValue to an Owned struct /// /// In order to get comparisons to work for our `ContextElement` trait /// objects, we need to be able to use `std::any::Any`. Unfortunately, /// `Any` requires that the structs do not have a lifetime (so they /// will have a `'static` lifetime. This means that we cannot have a /// `<'a>` appended to the struct type, so the struct cannot contain /// any borrows. Rather than impose the copy cost in the parser, we /// are imposing the cost of copying the data in the renderer because /// the parser has no reason to not be able to reference data from the /// input string. #[derive(Clone, Debug)] pub enum OwnedRValue { RVPath(OwnedPath), RVLiteral(OwnedLiteral), } #[derive(Clone, Debug, PartialEq)] pub struct OwnedPath { pub keys: Vec, } impl From<&RValue<'_>> for OwnedRValue { fn from(original: &RValue<'_>) -> Self { match original { RValue::RVLiteral(literal) => OwnedRValue::RVLiteral(literal.clone()), RValue::RVPath(path) => OwnedRValue::RVPath(OwnedPath { keys: path.keys.iter().map(|k| k.to_string()).collect(), }), } } } #[derive(Debug)] pub struct ParametersContext { params: HashMap, breadcrumbs: Vec>, } impl ParametersContext { pub fn new(breadcrumbs: &Vec<&dyn ContextElement>, params: &Vec) -> ParametersContext { let owned_params: HashMap = params .iter() .map(|kvpair| (kvpair.key.to_string(), OwnedRValue::from(&kvpair.value))) .collect(); let owned_breadcrumbs: Vec> = breadcrumbs.iter().map(|ce| ce.clone_to_box()).collect(); ParametersContext { params: owned_params, breadcrumbs: owned_breadcrumbs, } } } impl ContextElement for ParametersContext {} impl Renderable for ParametersContext { fn render(&self, _filters: &Vec) -> Result { // TODO: Would this even ever be called? Won't matter, but I'd // like to know. Since it is injected 1 above the current // context, we wouldn't be able to access it with `{.}`. Ok("[object Object]".to_owned()) } } impl Loopable for ParametersContext { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { // TODO: Would this even ever be called? Won't matter, but I'd // like to know. Since it is injected 1 above the current // context, we wouldn't be able to access it with `{.}`. vec![self] } } impl Walkable for ParametersContext { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?; match rval { OwnedRValue::RVPath(path) => owned_walk_path(&self.breadcrumbs, &path.keys), OwnedRValue::RVLiteral(literal) => Ok(literal), } } } impl Clone for ParametersContext { fn clone(&self) -> Self { let new_params: HashMap = self .params .iter() .map(|(k, v)| (k.clone(), v.clone())) .collect(); let new_breadcrumbs: Vec> = self .breadcrumbs .iter() .map(|bread| bread.clone_to_box()) .collect(); ParametersContext { params: new_params, breadcrumbs: new_breadcrumbs, } } } impl CompareContextElement for ParametersContext { fn equals(&self, other: &dyn ContextElement) -> bool { // TODO: Does this ever happen? perhaps I should have a panic here. false } fn partial_compare(&self, other: &dyn ContextElement) -> Option { // TODO: Does this ever happen? perhaps I should have a panic here. None } } impl ContextElement for OwnedLiteral {} impl Renderable for OwnedLiteral { fn render(&self, _filters: &Vec) -> Result { match self { OwnedLiteral::LString(text) => Ok(text.clone()), OwnedLiteral::LPositiveInteger(num) => Ok(num.to_string()), } } } impl Loopable for OwnedLiteral { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { match self { OwnedLiteral::LString(text) => { if text.is_empty() { Vec::new() } else { vec![self] } } OwnedLiteral::LPositiveInteger(num) => vec![self], } } } impl Walkable for OwnedLiteral { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { Err(WalkError::CantWalk) } } impl CompareContextElement for OwnedLiteral { fn equals(&self, other: &dyn ContextElement) -> bool { // println!("equals literal {:?} | {:?}", 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::LString(other_text)) => { &self_num.to_string() == other_text } (OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => { self_text == &other_num.to_string() } ( OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => self_num == other_num, }, } } fn partial_compare(&self, other: &dyn ContextElement) -> Option { // println!("partial_compare literal {:?} | {:?}", 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 => 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) { (OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => { self_text.partial_cmp(other_text) } (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LString(other_text)) => { self_num.to_string().partial_cmp(other_text) } (OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => { self_text.partial_cmp(&other_num.to_string()) } ( OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => self_num.partial_cmp(other_num), }, } } }