From 83c86ff9b323200d60db4931676577818e183e8b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 14 Jun 2020 17:08:01 -0400 Subject: [PATCH] Move ComparisonNumber into the library and add an OwnedLiteral for booleans. --- src/bin.rs | 112 ++--------------------------- src/parser/parser.rs | 5 ++ src/renderer/comparison_number.rs | 89 +++++++++++++++++++++++ src/renderer/math.rs | 7 ++ src/renderer/mod.rs | 3 + src/renderer/parameters_context.rs | 101 ++++++++++---------------- 6 files changed, 148 insertions(+), 169 deletions(-) create mode 100644 src/renderer/comparison_number.rs diff --git a/src/bin.rs b/src/bin.rs index 4b60db3..1a031fd 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -4,8 +4,10 @@ use crate::renderer::CompareContextElement; use parser::Filter; use parser::OwnedLiteral; use parser::Template; +use renderer::compare_json_numbers; use renderer::compile_template; use renderer::Castable; +use renderer::ComparisonNumber; use renderer::CompileError; use renderer::ContextElement; use renderer::DustRenderer; @@ -399,6 +401,9 @@ impl CompareContextElement for serde_json::Value { Some(OwnedLiteral::LString(other_string)) => { return self.as_str().map_or(false, |s| s == other_string) } + Some(OwnedLiteral::LBoolean(boolean)) => { + return self.equals(&serde_json::Value::Bool(*boolean) as &dyn ContextElement); + } Some(OwnedLiteral::LPositiveInteger(other_num)) => { let other_json_num: serde_json::Number = std::convert::From::from(*other_num); return self @@ -450,7 +455,7 @@ impl CompareContextElement for serde_json::Value { _, Some(OwnedLiteral::LString(other_string)), ) => return self_string.partial_cmp(&other_string), - // Otherwise, convert to numbers are compare them that way + // Otherwise, convert to numbers and compare them that way (_, Some(json_other), _) => return compare_json_numbers(self, json_other), (_, _, Some(literal_other)) => return compare_json_numbers(self, literal_other), _ => panic!("Unimplemented comparison type."), @@ -606,14 +611,6 @@ impl CompareContextElement for serde_json::Value { } } -#[derive(Debug)] -enum ComparisonNumber { - UnsignedInteger(u64), - SignedInteger(i64), - Decimal(f64), - Failure, -} - impl From<&serde_json::Value> for ComparisonNumber { /// Convert from a JSON value to a number for comparison based on /// the logic described at @@ -640,24 +637,6 @@ impl From<&serde_json::Value> for ComparisonNumber { } } -impl From<&String> for ComparisonNumber { - fn from(original: &String) -> Self { - match original.parse::() { - Ok(num) => return ComparisonNumber::UnsignedInteger(num), - Err(_) => (), - }; - match original.parse::() { - Ok(num) => return ComparisonNumber::SignedInteger(num), - Err(_) => (), - }; - match original.parse::() { - Ok(num) => return ComparisonNumber::Decimal(num), - Err(_) => (), - }; - ComparisonNumber::Failure - } -} - impl From<&serde_json::Number> for ComparisonNumber { fn from(original: &serde_json::Number) -> Self { match original.as_u64() { @@ -676,85 +655,6 @@ impl From<&serde_json::Number> for ComparisonNumber { } } -impl From<&OwnedLiteral> for ComparisonNumber { - fn from(original: &OwnedLiteral) -> Self { - match original { - 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) - } - } - } - } -} - -/// Compare json numbers -/// -/// While this function can be called with two strings, it would not -/// make sense because javascript does not use numeric comparisons for -/// strings -fn compare_json_numbers(self_input: S, other_input: O) -> Option -where - S: Into, - O: Into, -{ - let self_number: ComparisonNumber = self_input.into(); - let other_number: ComparisonNumber = other_input.into(); - match (self_number, other_number) { - (ComparisonNumber::Failure, _) => return None, - (_, ComparisonNumber::Failure) => return None, - ( - ComparisonNumber::UnsignedInteger(self_num), - ComparisonNumber::UnsignedInteger(other_num), - ) => return self_num.partial_cmp(&other_num), - ( - ComparisonNumber::UnsignedInteger(self_num), - ComparisonNumber::SignedInteger(other_num), - ) => { - if self_num < std::i64::MAX as u64 { - return (self_num as i64).partial_cmp(&other_num); - } else { - return Some(Ordering::Greater); - } - } - (ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { - return (self_num as f64).partial_cmp(&other_num) - } - - ( - ComparisonNumber::SignedInteger(self_num), - ComparisonNumber::UnsignedInteger(other_num), - ) => { - if other_num < std::i64::MAX as u64 { - return self_num.partial_cmp(&(other_num as i64)); - } else { - return Some(Ordering::Less); - } - } - (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::SignedInteger(other_num)) => { - return self_num.partial_cmp(&other_num) - } - (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { - return (self_num as f64).partial_cmp(&other_num) - } - - (ComparisonNumber::Decimal(self_num), ComparisonNumber::UnsignedInteger(other_num)) => { - return self_num.partial_cmp(&(other_num as f64)) - } - (ComparisonNumber::Decimal(self_num), ComparisonNumber::SignedInteger(other_num)) => { - return self_num.partial_cmp(&(other_num as f64)) - } - (ComparisonNumber::Decimal(self_num), ComparisonNumber::Decimal(other_num)) => { - return self_num.partial_cmp(&other_num) - } - } -} - impl From<&serde_json::Value> for MathNumber { fn from(original: &serde_json::Value) -> Self { match original { diff --git a/src/parser/parser.rs b/src/parser/parser.rs index c25ba08..e35a44b 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -123,6 +123,11 @@ pub enum OwnedLiteral { LPositiveInteger(u64), LNegativeInteger(i64), LFloat(f64), + // Unlike the other OwnedLiterals, booleans cannot occur in DustJS + // templates because true/false are not reserved + // names. Regardless, they are needed here for type casting in the + // renderer. + LBoolean(bool), } #[derive(Debug, PartialEq)] diff --git a/src/renderer/comparison_number.rs b/src/renderer/comparison_number.rs new file mode 100644 index 0000000..d85307f --- /dev/null +++ b/src/renderer/comparison_number.rs @@ -0,0 +1,89 @@ +use std::cmp::Ordering; + +#[derive(Debug)] +pub enum ComparisonNumber { + UnsignedInteger(u64), + SignedInteger(i64), + Decimal(f64), + Failure, +} + +/// Compare json numbers +/// +/// While this function can be called with two strings, it would not +/// make sense because javascript does not use numeric comparisons for +/// strings +pub fn compare_json_numbers(self_input: S, other_input: O) -> Option +where + S: Into, + O: Into, +{ + let self_number: ComparisonNumber = self_input.into(); + let other_number: ComparisonNumber = other_input.into(); + match (self_number, other_number) { + (ComparisonNumber::Failure, _) => return None, + (_, ComparisonNumber::Failure) => return None, + ( + ComparisonNumber::UnsignedInteger(self_num), + ComparisonNumber::UnsignedInteger(other_num), + ) => return self_num.partial_cmp(&other_num), + ( + ComparisonNumber::UnsignedInteger(self_num), + ComparisonNumber::SignedInteger(other_num), + ) => { + if self_num < std::i64::MAX as u64 { + return (self_num as i64).partial_cmp(&other_num); + } else { + return Some(Ordering::Greater); + } + } + (ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { + return (self_num as f64).partial_cmp(&other_num) + } + + ( + ComparisonNumber::SignedInteger(self_num), + ComparisonNumber::UnsignedInteger(other_num), + ) => { + if other_num < std::i64::MAX as u64 { + return self_num.partial_cmp(&(other_num as i64)); + } else { + return Some(Ordering::Less); + } + } + (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::SignedInteger(other_num)) => { + return self_num.partial_cmp(&other_num) + } + (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { + return (self_num as f64).partial_cmp(&other_num) + } + + (ComparisonNumber::Decimal(self_num), ComparisonNumber::UnsignedInteger(other_num)) => { + return self_num.partial_cmp(&(other_num as f64)) + } + (ComparisonNumber::Decimal(self_num), ComparisonNumber::SignedInteger(other_num)) => { + return self_num.partial_cmp(&(other_num as f64)) + } + (ComparisonNumber::Decimal(self_num), ComparisonNumber::Decimal(other_num)) => { + return self_num.partial_cmp(&other_num) + } + } +} + +impl From<&String> for ComparisonNumber { + fn from(original: &String) -> Self { + match original.parse::() { + Ok(num) => return ComparisonNumber::UnsignedInteger(num), + Err(_) => (), + }; + match original.parse::() { + Ok(num) => return ComparisonNumber::SignedInteger(num), + Err(_) => (), + }; + match original.parse::() { + Ok(num) => return ComparisonNumber::Decimal(num), + Err(_) => (), + }; + ComparisonNumber::Failure + } +} diff --git a/src/renderer/math.rs b/src/renderer/math.rs index 8161019..dd972e8 100644 --- a/src/renderer/math.rs +++ b/src/renderer/math.rs @@ -18,6 +18,13 @@ impl From<&OwnedLiteral> for MathNumber { fn from(original: &OwnedLiteral) -> Self { match original { OwnedLiteral::LString(_) => panic!("Strings should not be cast to numbers for math"), + OwnedLiteral::LBoolean(boolean) => { + if *boolean { + MathNumber::Integer(1) + } else { + MathNumber::Integer(0) + } + } OwnedLiteral::LPositiveInteger(num) => { return MathNumber::Integer((*num).try_into().unwrap()) } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 9b1f5b7..739eede 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,6 +1,7 @@ //! This module contains a renderer for a rust implementation of LinkedIn Dust mod breadcrumb_tree; +mod comparison_number; mod context_element; mod errors; mod inline_partial_tree; @@ -11,6 +12,8 @@ mod renderer; mod select_context; mod walking; +pub use comparison_number::compare_json_numbers; +pub use comparison_number::ComparisonNumber; pub use context_element::Castable; pub use context_element::CompareContextElement; pub use context_element::ContextElement; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index a5a4ced..6d21211 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -3,6 +3,8 @@ 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; @@ -160,6 +162,7 @@ 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, @@ -171,6 +174,7 @@ impl Truthiness for OwnedLiteral { 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()), @@ -194,6 +198,9 @@ impl Walkable for OwnedLiteral { impl Sizable for OwnedLiteral { 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)), @@ -258,9 +265,11 @@ impl CompareContextElement for OwnedLiteral { OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LNegativeInteger(other_num), ) => self_num == other_num, - (OwnedLiteral::LString(_self_text), _) | (_, OwnedLiteral::LString(_self_text)) => { - false + (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 } @@ -315,70 +324,12 @@ impl CompareContextElement for OwnedLiteral { }, }, 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) } - ( - OwnedLiteral::LPositiveInteger(self_num), - OwnedLiteral::LPositiveInteger(other_num), - ) => self_num.partial_cmp(other_num), - ( - OwnedLiteral::LNegativeInteger(self_num), - OwnedLiteral::LNegativeInteger(other_num), - ) => self_num.partial_cmp(other_num), - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => { - self_num.partial_cmp(other_num) - } - (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::LNegativeInteger(self_num), OwnedLiteral::LString(other_text)) => { - self_num.to_string().partial_cmp(other_text) - } - (OwnedLiteral::LString(self_text), OwnedLiteral::LNegativeInteger(other_num)) => { - self_text.partial_cmp(&other_num.to_string()) - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LString(other_text)) => { - self_num.to_string().partial_cmp(other_text) - } - (OwnedLiteral::LString(self_text), OwnedLiteral::LFloat(other_num)) => { - self_text.partial_cmp(&other_num.to_string()) - } - (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LFloat(other_num)) => { - (*self_num as f64).partial_cmp(other_num) - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LPositiveInteger(other_num)) => { - self_num.partial_cmp(&(*other_num as f64)) - } - (OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LFloat(other_num)) => { - (*self_num as f64).partial_cmp(other_num) - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LNegativeInteger(other_num)) => { - self_num.partial_cmp(&(*other_num as f64)) - } - ( - OwnedLiteral::LPositiveInteger(self_num), - OwnedLiteral::LNegativeInteger(other_num), - ) => { - if *self_num < std::i64::MAX as u64 { - (*self_num as i64).partial_cmp(other_num) - } else { - Some(Ordering::Greater) - } - } - ( - OwnedLiteral::LNegativeInteger(self_num), - OwnedLiteral::LPositiveInteger(other_num), - ) => { - if *other_num < std::i64::MAX as u64 { - self_num.partial_cmp(&(*other_num as i64)) - } else { - Some(Ordering::Less) - } - } + // Otherwise, convert to numbers and compare them that way + (_, _) => return compare_json_numbers(self, other_literal), }, } } @@ -481,3 +432,27 @@ impl CompareContextElement for OwnedLiteral { .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) + } + } + } + } +}