Merge branch 'remove_lifetime_from_context_elements' into dust_helpers

This commit is contained in:
Tom Alexander 2020-05-10 23:43:19 -04:00
commit 3c85717952
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
7 changed files with 311 additions and 97 deletions

View File

@ -1,5 +1,6 @@
extern crate nom; extern crate nom;
use crate::renderer::CompareContextElement;
use parser::Filter; use parser::Filter;
use renderer::compile_template; use renderer::compile_template;
use renderer::CompiledTemplate; use renderer::CompiledTemplate;
@ -137,3 +138,24 @@ impl Loopable for serde_json::Value {
} }
} }
} }
impl CompareContextElement for serde_json::Value {
fn equals(&self, other: &dyn ContextElement) -> bool {
// Handle other serde_json::Value
match other.to_any().downcast_ref::<Self>() {
None => (),
Some(other_json_value) => return self == other_json_value,
}
// Handle string literals
match other.to_any().downcast_ref::<String>() {
None => (),
Some(other_string) => return self.as_str().map_or(false, |s| s == other_string),
}
// Handle numeric literals
match other.to_any().downcast_ref::<u64>() {
None => (),
Some(other_num) => return self.as_u64().map_or(false, |n| n == *other_num),
}
false
}
}

View File

@ -2,11 +2,11 @@ use nom::branch::alt;
use nom::bytes::complete::escaped_transform; use nom::bytes::complete::escaped_transform;
use nom::bytes::complete::is_a; use nom::bytes::complete::is_a;
use nom::bytes::complete::is_not; use nom::bytes::complete::is_not;
use nom::bytes::complete::{tag, take_until, take_until_parser_matches}; use nom::bytes::complete::{tag, take_until, take_until_parser_matches, take_while};
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::multispace0; use nom::character::complete::multispace0;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::{space0, space1}; use nom::character::complete::{digit1, space0, space1};
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::opt; use nom::combinator::opt;
@ -122,6 +122,7 @@ pub struct Partial<'a> {
pub enum RValue<'a> { pub enum RValue<'a> {
RVPath(Path<'a>), RVPath(Path<'a>),
RVString(String), RVString(String),
RVPositiveInteger(u64),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -210,11 +211,23 @@ fn path(i: &str) -> IResult<&str, Path> {
))(i) ))(i)
} }
/// Just digits, no signs or decimals
fn postitive_integer_literal(i: &str) -> IResult<&str, u64> {
map(
verify(
map(digit1, |number_string: &str| number_string.parse::<u64>()),
|parse_result| parse_result.is_ok(),
),
|parsed_number| parsed_number.unwrap(),
)(i)
}
/// Either a literal or a path to a value /// Either a literal or a path to a value
fn rvalue(i: &str) -> IResult<&str, RValue> { fn rvalue(i: &str) -> IResult<&str, RValue> {
alt(( alt((
map(path, RValue::RVPath), map(path, RValue::RVPath),
map(quoted_string, RValue::RVString), map(quoted_string, RValue::RVString),
map(postitive_integer_literal, RValue::RVPositiveInteger),
))(i) ))(i)
} }
@ -929,6 +942,29 @@ mod tests {
); );
} }
#[test]
fn test_literals() {
assert_eq!(
dust_tag(r#"{>foo a="foo" b=179/}"#),
Ok((
"",
DustTag::DTPartial(Partial {
name: "foo".to_owned(),
params: vec![
KVPair {
key: "a",
value: RValue::RVString("foo".to_owned())
},
KVPair {
key: "b",
value: RValue::RVPositiveInteger(179)
}
]
})
))
);
}
#[test] #[test]
fn test_helper() { fn test_helper() {
assert_eq!( assert_eq!(

View File

@ -1,9 +1,13 @@
use crate::parser::Filter; use crate::parser::Filter;
use crate::renderer::errors::RenderError; use crate::renderer::errors::RenderError;
use crate::renderer::errors::WalkError; use crate::renderer::errors::WalkError;
use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
pub trait ContextElement: Debug + Walkable + Renderable + Loopable {} pub trait ContextElement:
Debug + Walkable + Renderable + Loopable + CloneIntoBoxedContextElement + CompareContextElement
{
}
pub trait Walkable { pub trait Walkable {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>; fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>;
@ -26,8 +30,32 @@ pub trait Loopable {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement>; fn get_loop_elements(&self) -> Vec<&dyn ContextElement>;
} }
// impl PartialEq<dyn ContextElement> for dyn ContextElement { pub trait CastToAny {
// fn eq(&self, other: &dyn ContextElement) -> bool { fn to_any(&self) -> &dyn Any;
// todo!() }
// }
// } pub trait CompareContextElement: CastToAny {
fn equals(&self, other: &dyn ContextElement) -> bool;
}
pub trait CloneIntoBoxedContextElement {
fn clone_to_box(&self) -> Box<dyn ContextElement>;
}
impl<C: 'static + ContextElement + Clone> CloneIntoBoxedContextElement for C {
fn clone_to_box(&self) -> Box<dyn ContextElement> {
Box::new(self.clone())
}
}
impl<C: 'static + ContextElement> CastToAny for C {
fn to_any(&self) -> &dyn Any {
self
}
}
impl<'a, 'b> PartialEq<&'b dyn ContextElement> for &'a dyn ContextElement {
fn eq(&self, other: &&'b dyn ContextElement) -> bool {
self.equals(*other)
}
}

View File

@ -7,6 +7,8 @@ mod parameters_context;
mod renderer; mod renderer;
mod walking; mod walking;
pub use context_element::CloneIntoBoxedContextElement;
pub use context_element::CompareContextElement;
pub use context_element::ContextElement; pub use context_element::ContextElement;
pub use context_element::Loopable; pub use context_element::Loopable;
pub use context_element::Renderable; pub use context_element::Renderable;

View File

@ -1,7 +1,8 @@
use crate::parser::KVPair; use crate::parser::KVPair;
use crate::parser::{Filter, RValue}; use crate::parser::{Filter, RValue};
use crate::renderer::context_element::CompareContextElement;
use crate::renderer::context_element::ContextElement; use crate::renderer::context_element::ContextElement;
use crate::renderer::walking::walk_path; use crate::renderer::walking::owned_walk_path;
use crate::renderer::Loopable; use crate::renderer::Loopable;
use crate::renderer::RenderError; use crate::renderer::RenderError;
use crate::renderer::Renderable; use crate::renderer::Renderable;
@ -9,31 +10,66 @@ use crate::renderer::WalkError;
use crate::renderer::Walkable; use crate::renderer::Walkable;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Clone, Debug)] /// Copy the data from an RValue to an Owned struct
pub struct ParametersContext<'a> { ///
params: HashMap<&'a str, &'a RValue<'a>>, /// In order to get comparisons to work for our `ContextElement` trait
breadcrumbs: &'a Vec<&'a dyn ContextElement>, /// 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, PartialEq)]
pub enum OwnedRValue {
RVPath(OwnedPath),
RVString(String),
RVPositiveInteger(u64),
} }
impl<'a> ParametersContext<'a> { #[derive(Clone, Debug, PartialEq)]
pub fn new( pub struct OwnedPath {
breadcrumbs: &'a Vec<&'a dyn ContextElement>, pub keys: Vec<String>,
params: &'a Vec<KVPair<'a>>, }
) -> ParametersContext<'a> {
let param_map = params impl From<&RValue<'_>> for OwnedRValue {
fn from(original: &RValue<'_>) -> Self {
match original {
RValue::RVString(text) => OwnedRValue::RVString(text.to_owned()),
RValue::RVPath(path) => OwnedRValue::RVPath(OwnedPath {
keys: path.keys.iter().map(|k| k.to_string()).collect(),
}),
RValue::RVPositiveInteger(num) => OwnedRValue::RVPositiveInteger(*num),
}
}
}
#[derive(Debug)]
pub struct ParametersContext {
params: HashMap<String, OwnedRValue>,
breadcrumbs: Vec<Box<dyn ContextElement>>,
}
impl ParametersContext {
pub fn new(breadcrumbs: &Vec<&dyn ContextElement>, params: &Vec<KVPair>) -> ParametersContext {
let owned_params: HashMap<String, OwnedRValue> = params
.iter() .iter()
.map(|pair: &KVPair<'a>| (pair.key, &pair.value)) .map(|kvpair| (kvpair.key.to_string(), OwnedRValue::from(&kvpair.value)))
.collect(); .collect();
let owned_breadcrumbs: Vec<Box<dyn ContextElement>> =
breadcrumbs.iter().map(|ce| ce.clone_to_box()).collect();
ParametersContext { ParametersContext {
params: param_map, params: owned_params,
breadcrumbs: breadcrumbs, breadcrumbs: owned_breadcrumbs,
} }
} }
} }
impl<'a> ContextElement for ParametersContext<'a> {} impl ContextElement for ParametersContext {}
impl<'a> Renderable for ParametersContext<'a> { impl Renderable for ParametersContext {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> { fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: Would this even ever be called? Won't matter, but I'd // TODO: Would this even ever be called? Won't matter, but I'd
// like to know. Since it is injected 1 above the current // like to know. Since it is injected 1 above the current
@ -42,7 +78,7 @@ impl<'a> Renderable for ParametersContext<'a> {
} }
} }
impl<'a> Loopable for ParametersContext<'a> { impl Loopable for ParametersContext {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
// TODO: Would this even ever be called? Won't matter, but I'd // TODO: Would this even ever be called? Won't matter, but I'd
// like to know. Since it is injected 1 above the current // like to know. Since it is injected 1 above the current
@ -51,16 +87,43 @@ impl<'a> Loopable for ParametersContext<'a> {
} }
} }
impl<'a> Walkable for ParametersContext<'a> { impl Walkable for ParametersContext {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?; let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?;
match rval { match rval {
RValue::RVPath(path) => walk_path(self.breadcrumbs, &path.keys), OwnedRValue::RVPath(path) => owned_walk_path(&self.breadcrumbs, &path.keys),
RValue::RVString(text) => Ok(text), OwnedRValue::RVString(text) => Ok(text),
OwnedRValue::RVPositiveInteger(num) => Ok(num),
} }
} }
} }
impl Clone for ParametersContext {
fn clone(&self) -> Self {
let new_params: HashMap<String, OwnedRValue> = self
.params
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let new_breadcrumbs: Vec<Box<dyn ContextElement>> = 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
}
}
impl ContextElement for String {} impl ContextElement for String {}
impl Renderable for String { impl Renderable for String {
@ -84,3 +147,47 @@ impl Walkable for String {
Err(WalkError::CantWalk) Err(WalkError::CantWalk)
} }
} }
impl CompareContextElement for String {
fn equals(&self, other: &dyn ContextElement) -> bool {
// If its a String 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::<Self>() {
None => other.equals(self),
Some(other_string) => self == other_string,
}
}
}
impl ContextElement for u64 {}
impl Renderable for u64 {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
Ok(self.to_string())
}
}
impl Loopable for u64 {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
vec![self]
}
}
impl Walkable for u64 {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(WalkError::CantWalk)
}
}
impl CompareContextElement for u64 {
fn equals(&self, other: &dyn ContextElement) -> bool {
// If its a u64 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::<Self>() {
None => other.equals(self),
Some(other_num) => self == other_num,
}
}
}

View File

@ -235,6 +235,7 @@ impl<'a> DustRenderer<'a> {
Some(rval) => match rval { Some(rval) => match rval {
RValue::RVString(text) => Ok(text), RValue::RVString(text) => Ok(text),
RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys), RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys),
RValue::RVPositiveInteger(num) => Ok(num),
}, },
}; };
let right_side: Result<&dyn ContextElement, WalkError> = let right_side: Result<&dyn ContextElement, WalkError> =
@ -243,16 +244,26 @@ impl<'a> DustRenderer<'a> {
Some(rval) => match rval { Some(rval) => match rval {
RValue::RVString(text) => Ok(text), RValue::RVString(text) => Ok(text),
RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys), RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys),
RValue::RVPositiveInteger(num) => Ok(num),
}, },
}; };
// let x = WalkError::CantWalk; if left_side == right_side {
// let y = WalkError::CantWalk; match &parameterized_block.contents {
// if x == y { None => return Ok("".to_owned()),
// panic!("placeholder"); Some(body) => {
// } let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
// if left_side.unwrap() == right_side.unwrap() { return Ok(rendered_content);
// panic!("placeholder"); }
// } }
} else {
match &parameterized_block.else_contents {
None => return Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
return Ok(rendered_content);
}
}
}
} }
_ => (), // TODO: Implement the rest _ => (), // TODO: Implement the rest
} }
@ -281,88 +292,76 @@ mod tests {
use crate::renderer::context_element::Loopable; use crate::renderer::context_element::Loopable;
use crate::renderer::context_element::Renderable; use crate::renderer::context_element::Renderable;
use crate::renderer::context_element::Walkable; use crate::renderer::context_element::Walkable;
use crate::renderer::CompareContextElement;
impl ContextElement for u32 {} impl<I: 'static + ContextElement + Clone> ContextElement for HashMap<String, I> {}
impl ContextElement for &str {}
impl<I: ContextElement> ContextElement for HashMap<&str, I> {}
impl Renderable for u32 { impl<I: ContextElement> Renderable for HashMap<String, I> {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: handle the filters
Ok(self.to_string())
}
}
impl Renderable for &str {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: handle the filters
Ok(self.to_string())
}
}
impl<I: ContextElement> Renderable for HashMap<&str, I> {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> { fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: handle the filters // TODO: handle the filters
Ok("[object Object]".to_owned()) Ok("[object Object]".to_owned())
} }
} }
impl<I: ContextElement> Walkable for HashMap<&str, I> { impl<I: ContextElement> Walkable for HashMap<String, I> {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
let child = self.get(segment).ok_or(WalkError::CantWalk)?; let child = self.get(segment).ok_or(WalkError::CantWalk)?;
Ok(child) Ok(child)
} }
} }
impl Walkable for &str { impl<I: 'static + ContextElement + Clone> Loopable for HashMap<String, I> {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(WalkError::CantWalk)
}
}
impl Walkable for u32 {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(WalkError::CantWalk)
}
}
impl Loopable for &str {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
if self.is_empty() {
Vec::new()
} else {
vec![self]
}
}
}
impl Loopable for u32 {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
vec![self] vec![self]
} }
} }
impl<I: ContextElement> Loopable for HashMap<&str, I> { impl<I: 'static + ContextElement + Clone> CompareContextElement for HashMap<String, I> {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { fn equals(&self, other: &dyn ContextElement) -> bool {
vec![self] false
} }
} }
#[test] #[test]
fn test_walk_path() { fn test_walk_path() {
let context: HashMap<&str, &str> = let context: HashMap<String, String> = [
[("cat", "kitty"), ("dog", "doggy"), ("tiger", "murderkitty")] ("cat".to_string(), "kitty".to_string()),
("dog".to_string(), "doggy".to_string()),
("tiger".to_string(), "murderkitty".to_string()),
]
.iter() .iter()
.cloned() .cloned()
.collect(); .collect();
let number_context: HashMap<&str, u32> = [("cat", 1), ("dog", 2), ("tiger", 3)] let number_context: HashMap<String, u64> = [
("cat".to_string(), 1),
("dog".to_string(), 2),
("tiger".to_string(), 3),
]
.iter() .iter()
.cloned() .cloned()
.collect(); .collect();
let deep_context: HashMap<&str, HashMap<&str, &str>> = [ let deep_context: HashMap<String, HashMap<String, String>> = [
("cat", [("food", "meat")].iter().cloned().collect()), (
("dog", [("food", "meat")].iter().cloned().collect()), "cat".to_string(),
("tiger", [("food", "people")].iter().cloned().collect()), [("food".to_string(), "meat".to_string())]
.iter()
.cloned()
.collect(),
),
(
"dog".to_string(),
[("food".to_string(), "meat".to_string())]
.iter()
.cloned()
.collect(),
),
(
"tiger".to_string(),
[("food".to_string(), "people".to_string())]
.iter()
.cloned()
.collect(),
),
] ]
.iter() .iter()
.cloned() .cloned()

View File

@ -51,3 +51,23 @@ pub fn walk_path<'a>(
} }
Err(WalkError::CantWalk) Err(WalkError::CantWalk)
} }
pub fn owned_walk_path<'a>(
breadcrumbs: &'a Vec<Box<dyn ContextElement>>,
path: &Vec<String>,
) -> Result<&'a dyn ContextElement, WalkError> {
let path_reference: Vec<&str> = path.iter().map(|p| &p[..]).collect();
for context in breadcrumbs.iter().rev() {
match walk_path_from_single_level(context.as_ref(), &path_reference) {
// If no walking was done at all, keep looping
WalkResult::NoWalk => {}
// If we partially walked then stop trying to find
// anything
WalkResult::PartialWalk => {
return Err(WalkError::CantWalk);
}
WalkResult::FullyWalked(new_context) => return Ok(new_context),
}
}
Err(WalkError::CantWalk)
}