From c5db1cea8f48504dc42d2010636adae141758455 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 11:45:45 -0400 Subject: [PATCH 01/33] Add a test case for the math helper and add else blocks to the select test. --- js/test_cases/helpers_math/README.md | 4 ++ js/test_cases/helpers_math/input1.json | 5 +++ js/test_cases/helpers_math/main.dust | 58 ++++++++++++++++++++++++++ js/test_cases/helpers_select/README.md | 4 ++ js/test_cases/helpers_select/main.dust | 14 +++++++ 5 files changed, 85 insertions(+) create mode 100644 js/test_cases/helpers_math/README.md create mode 100644 js/test_cases/helpers_math/input1.json create mode 100644 js/test_cases/helpers_math/main.dust diff --git a/js/test_cases/helpers_math/README.md b/js/test_cases/helpers_math/README.md new file mode 100644 index 0000000..9984083 --- /dev/null +++ b/js/test_cases/helpers_math/README.md @@ -0,0 +1,4 @@ +Early termination +----------------- + +If the math helper has a body then it stops rendering conditionals after the first conditional that returns true. Rendering an else block does not cause early termination, but else blocks are rendered. diff --git a/js/test_cases/helpers_math/input1.json b/js/test_cases/helpers_math/input1.json new file mode 100644 index 0000000..77959bc --- /dev/null +++ b/js/test_cases/helpers_math/input1.json @@ -0,0 +1,5 @@ +{ + "number_7": 7, + "string_7": "7", + "string_cat": "cat" +} diff --git a/js/test_cases/helpers_math/main.dust b/js/test_cases/helpers_math/main.dust new file mode 100644 index 0000000..5108995 --- /dev/null +++ b/js/test_cases/helpers_math/main.dust @@ -0,0 +1,58 @@ +Bodiless math{~n} +============={~n} +{@math key="7" method="add" operand="4" /}{~n} +{@math key=number_7 method="add" operand="4" /}{~n} +{@math key=string_7 method="add" operand="4" /}{~n} +{@math key=string_cat method="add" operand="4" /}{~n} + +Math with body: dot reference{~n} +============================={~n} +{@math key="7" method="add" operand="4"} + {.}{~n} +{/math} + +Math with body: eq literal{~n} +=========================={~n} +{@math key="7" method="add" operand="4"} + {@eq value=11}math result is 11{:else}math result is not 11{/eq}{~n} +{/math} + +Math with body: eq string{~n} +========================={~n} +{@math key="7" method="add" operand="4"} + {@eq value="11"}math result is "11"{:else}math result is not "11"{/eq}{~n} +{/math} + +Math with body: eq else block{~n} +============================={~n} +{@math key="7" method="add" operand="4"} + {@eq value=12}math result is 12{:else}math result is not 12{/eq}{~n} +{/math} + +Math with body: standalone eq{~n} +============================={~n} +{@math key="7" method="add" operand="4"} + {@eq key=12 value=12}12 is 12{:else}12 is not 12{/eq}{~n} +{/math} + +Math with body: standalone else block{~n} +====================================={~n} +{@math key="7" method="add" operand="4"} + {@eq key=11 value=12}11 is 12{:else}11 is not 12{/eq}{~n} +{/math} + +Math with body: early termination{~n} +============================={~n} +{@math key="7" method="add" operand="4"} + {@eq value=11}math result is 11{:else}math result is not 11{/eq}{~n} + {@eq value=11}math result is 11{:else}math result is not 11{/eq}{~n} +{/math} + +Math with body: early termination else block{~n} +============================================{~n} +{@math key="7" method="add" operand="4"} + {@eq value=12}math result is 12{:else}math result is not 12{/eq}{~n} + {@eq value=11}math result is 11{:else}math result is not 11{/eq}{~n} + {@eq value=12}math result is 12{:else}math result is not 12{/eq}{~n} +{/math} + diff --git a/js/test_cases/helpers_select/README.md b/js/test_cases/helpers_select/README.md index e127586..ba85db9 100644 --- a/js/test_cases/helpers_select/README.md +++ b/js/test_cases/helpers_select/README.md @@ -7,6 +7,10 @@ Early termination Comparisons are done in-order and only the first matching comparison (eq/ne/gt/gte/lt/lte) is evaluated. All other non-comparison elements inside the select tag are still rendered normally. +Matching is terminated even if the first matching comparison has its own key. + +Else blocks are rendered until the first matching comparison but else blocks after that are not rendered. + Default vs none --------------- Default was deprecated as of dust 1.6.0 and no longer does anything. It was deprecated because it was essentially the same as none except that there could only be one per select block and it had to be placed after all the other conditions to make sure they were all false. None is more flexible. diff --git a/js/test_cases/helpers_select/main.dust b/js/test_cases/helpers_select/main.dust index 30a8971..6c2d49f 100644 --- a/js/test_cases/helpers_select/main.dust +++ b/js/test_cases/helpers_select/main.dust @@ -101,6 +101,20 @@ Early termination stand-alone comparison{~n} {/pet_names} {/select}{~n} +Early termination else block{~n} +============================{~n} +{@select key=pet} + {@eq value="dog"}Lets name your pet rover{~n}{:else}Lets not name your pet rover{~n}{/eq} + {@eq value="cat"}Lets name your pet fluffy{~n}{:else}Lets not name your pet fluffy{~n}{/eq} + {@eq value="cat"}Lets name your pet whiskers{~n}{:else}Lets not name your pet whiskers{~n}{/eq} + {@eq value="lizard"}Lets name your pet dave{~n}{:else}Lets not name your pet dave{~n}{/eq} + {@any}{person} has a pet!{~n}{/any} + text not inside a comparison{~n} + {#pet_names} + If your pet was a {type} we'd name it {pet_name}{~n} + {/pet_names} +{/select}{~n} + @any alone{~n} =========={~n} {@any}{person} has a pet!{~n}{/any} From 6877e3d39344f8b792617aace31c8ae967989d8d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 12:03:55 -0400 Subject: [PATCH 02/33] Add parser support for the math helper. --- src/parser/parser.rs | 5 +++++ src/renderer/inline_partial_tree.rs | 3 ++- src/renderer/renderer.rs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 1dfd7a8..015a070 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -47,6 +47,7 @@ pub enum DustTag<'a> { DTHelperSelect(ParameterizedBlock<'a>), DTHelperAny(ParameterizedBlock<'a>), DTHelperNone(ParameterizedBlock<'a>), + DTHelperMath(ParameterizedBlock<'a>), } #[derive(Clone, Debug, PartialEq)] @@ -279,6 +280,10 @@ fn dust_tag_helper(i: &str) -> IResult<&str, DustTag> { parameterized_block("{@", &tag_to_path("none")), DustTag::DTHelperNone, ), + map( + parameterized_block("{@", &tag_to_path("math")), + DustTag::DTHelperMath, + ), ))(i) } diff --git a/src/renderer/inline_partial_tree.rs b/src/renderer/inline_partial_tree.rs index d6cc2e9..f7cbfdf 100644 --- a/src/renderer/inline_partial_tree.rs +++ b/src/renderer/inline_partial_tree.rs @@ -111,7 +111,8 @@ fn extract_inline_partials_from_tag<'a, 'b>( | DustTag::DTHelperLast(parameterized_block) | DustTag::DTHelperSelect(parameterized_block) | DustTag::DTHelperAny(parameterized_block) - | DustTag::DTHelperNone(parameterized_block) => { + | DustTag::DTHelperNone(parameterized_block) + | DustTag::DTHelperMath(parameterized_block) => { match ¶meterized_block.contents { None => (), Some(body) => extract_inline_partials_from_body(blocks, &body), diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index eedfd07..b8104a7 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -644,6 +644,7 @@ impl<'a> DustRenderer<'a> { } _ => return Ok("".to_owned()), }, + DustTag::DTHelperMath(parameterized_block) => {} } Ok("".to_owned()) From 8daa746da68a7dbb3e6bfe8f01b91f7d1d58f588 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 13:16:25 -0400 Subject: [PATCH 03/33] Initial structure for the perform_math_operation function. --- js/test_cases/helpers_math/README.md | 5 ++ js/test_cases/helpers_math/input1.json | 4 +- js/test_cases/helpers_math/main.dust | 63 ++++++++++++++++++++-- src/renderer/renderer.rs | 73 +++++++++++++++++++++++++- 4 files changed, 138 insertions(+), 7 deletions(-) diff --git a/js/test_cases/helpers_math/README.md b/js/test_cases/helpers_math/README.md index 9984083..9ae12f0 100644 --- a/js/test_cases/helpers_math/README.md +++ b/js/test_cases/helpers_math/README.md @@ -2,3 +2,8 @@ Early termination ----------------- If the math helper has a body then it stops rendering conditionals after the first conditional that returns true. Rendering an else block does not cause early termination, but else blocks are rendered. + +Non-number values +----------------- + +If the math operation involves non-numbers, NaN is returned. diff --git a/js/test_cases/helpers_math/input1.json b/js/test_cases/helpers_math/input1.json index 77959bc..3b5727f 100644 --- a/js/test_cases/helpers_math/input1.json +++ b/js/test_cases/helpers_math/input1.json @@ -1,5 +1,7 @@ { "number_7": 7, "string_7": "7", - "string_cat": "cat" + "string_cat": "cat", + "add_operation": "add", + "a_operation": "a" } diff --git a/js/test_cases/helpers_math/main.dust b/js/test_cases/helpers_math/main.dust index 5108995..5559821 100644 --- a/js/test_cases/helpers_math/main.dust +++ b/js/test_cases/helpers_math/main.dust @@ -4,6 +4,7 @@ Bodiless math{~n} {@math key=number_7 method="add" operand="4" /}{~n} {@math key=string_7 method="add" operand="4" /}{~n} {@math key=string_cat method="add" operand="4" /}{~n} +{@math key=string_cat method="add" operand="foo" /}{~n} Math with body: dot reference{~n} ============================={~n} @@ -44,15 +45,67 @@ Math with body: standalone else block{~n} Math with body: early termination{~n} ============================={~n} {@math key="7" method="add" operand="4"} - {@eq value=11}math result is 11{:else}math result is not 11{/eq}{~n} - {@eq value=11}math result is 11{:else}math result is not 11{/eq}{~n} + {@eq value=11}math result is 11{~n}{:else}math result is not 11{~n}{/eq} + {@eq value=11}math result is 11{~n}{:else}math result is not 11{~n}{/eq} {/math} Math with body: early termination else block{~n} ============================================{~n} {@math key="7" method="add" operand="4"} - {@eq value=12}math result is 12{:else}math result is not 12{/eq}{~n} - {@eq value=11}math result is 11{:else}math result is not 11{/eq}{~n} - {@eq value=12}math result is 12{:else}math result is not 12{/eq}{~n} + {@eq value=12}math result is 12{~n}{:else}math result is not 12{~n}{/eq} + {@eq value=11}math result is 11{~n}{:else}math result is not 11{~n}{/eq} + {@eq value=12}math result is 12{~n}{:else}math result is not 12{~n}{/eq} {/math} + +Math with body: any{~n} +==================={~n} +{@math key="7" method="add" operand="4"} + {@eq value=10}math result is 10{~n}{:else}math result is not 10{~n}{/eq} + {@eq value=11}math result is 11{~n}{:else}math result is not 11{~n}{/eq} + {@eq value=12}math result is 12{~n}{:else}math result is not 12{~n}{/eq} + {@any}We found the value{~n}{/any} + {@none}We did not find the value{~n}{/none} +{/math} + +Math with body: none{~n} +===================={~n} +{@math key="7" method="add" operand="4"} + {@eq value=10}math result is 10{~n}{:else}math result is not 10{~n}{/eq} + {@eq value=12}math result is 12{~n}{:else}math result is not 12{~n}{/eq} + {@any}We found the value{~n}{/any} + {@none}We did not find the value{~n}{/none} +{/math} + +Math where method is a reference{~n} +================================{~n} +{@math key="7" method=add_operation operand="4" /}{~n} + +Math where method is a template{~n} +==============================={~n} +{@math key="7" method="{a_operation}dd" operand="4" /}{~n} + +Math where key is a template{~n} +============================{~n} +{@math key="{string_7}1" method="add" operand="4" /}{~n} + +Math where method is absent{~n} +============================{~n} +{@math key="7" operand="4" /}{~n} + +Math where method is missing{~n} +============================{~n} +{@math key="7" method=foobar operand="4" /}{~n} + +Math with unknown method{~n} +========================{~n} +{@math key="7" method="twirl" operand="4" /}{~n} + +Math with body where method is absent{~n} +===================================={~n} +{@math key="7" operand="4"} + {@eq value=12}math result is 12{~n}{:else}math result is not 12{~n}{/eq} + {@any}We found the value{~n}{/any} + {@none}We did not find the value{~n}{/none} + plain text not inside a tag{~n} +{/math} diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index b8104a7..1ad71c5 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -644,7 +644,32 @@ impl<'a> DustRenderer<'a> { } _ => return Ok("".to_owned()), }, - DustTag::DTHelperMath(parameterized_block) => {} + DustTag::DTHelperMath(parameterized_block) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + ¶meterized_block.explicit_context, + ); + let new_breadcrumbs_ref = new_breadcrumbs.as_ref().unwrap_or(breadcrumbs); + let param_map = + ParametersContext::new(self, breadcrumbs, ¶meterized_block.params, None); + match ¶meterized_block.contents { + None => { + // TODO: math without body, calculate the value and render it + // Calculate the value + // Render + } + Some(_) => { + // TODO: math with body, calculate the value and use it like a select block + // Calculate the value + // Generate a ParametersContext with the result of the math operation as key + // calculate are_any_checks_true + // Generate a SelectContext + // render_maybe_body + } + } + } } Ok("".to_owned()) @@ -902,6 +927,52 @@ impl<'a> DustRenderer<'a> { _ => panic!("perform_comparison_check only implemented for comparison helpers (eq, ne, gt, gte, lt, lte)") } } + + /// Performs a math operation (add, subtract, multiply, divide, + /// mod, abs, floor, ceil) and returns the result or None if + /// nothing should be rendered. + fn performance_math_operation( + &'a self, + breadcrumbs: &'a Vec>, + math_parameters: &'a ParametersContext<'a>, + ) -> Option> { + let method = match self.tap(breadcrumbs, math_parameters, "method") { + None | Some(Err(_)) => return None, + Some(Ok(ice_result)) => ice_result, + }; + let method_rendered = match method.get_context_element_reference().render(&Vec::new()) { + Ok(text) => text, + Err(_) => return None, + }; + + let left_side = self.tap(breadcrumbs, math_parameters, "key"); + let right_side = self.tap(breadcrumbs, math_parameters, "operand"); + + let left_side_ce = left_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }); + let right_side_ce = right_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }); + + match method_rendered.as_str() { + "add" => todo!(), + "subtract" => todo!(), + "multiply" => todo!(), + "divide" => todo!(), + "mod" => todo!(), + "abs" => todo!(), + "floor" => todo!(), + "ceil" => todo!(), + _ => return None, + } + + todo!() + } } struct BlockContext<'a> { From 978bbe6eb3f78ef6b36dc67eabbfd13915ab1c5d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 13:37:26 -0400 Subject: [PATCH 04/33] Implemented float literals for OwnedLiterals, not yet for serde_json. --- js/test_cases/helpers_eq/main.dust | 4 ++++ src/parser/parser.rs | 1 + src/renderer/parameters_context.rs | 32 ++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/js/test_cases/helpers_eq/main.dust b/js/test_cases/helpers_eq/main.dust index aad3f75..777fa89 100644 --- a/js/test_cases/helpers_eq/main.dust +++ b/js/test_cases/helpers_eq/main.dust @@ -27,3 +27,7 @@ Do objects with different paths referencing the same variable match?{~n} {#int renamed=some_obj} {@eq key=some_obj value=renamed}some_obj equals renamed{:else}some_obj does not equal renamed{/eq}{~n} {/int} + +Floating point equality{~n} +======================={~n} +{@eq key=int value=7.0}int is equal to 7.0{:else}int is not equal to 7.0{/eq} diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 015a070..d1ddf96 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -119,6 +119,7 @@ pub struct Partial<'a> { pub enum OwnedLiteral { LString(String), LPositiveInteger(u64), + LFloat(f64), } #[derive(Debug, PartialEq)] diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index e7d13aa..130f84a 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -137,6 +137,7 @@ impl Truthiness for OwnedLiteral { match self { OwnedLiteral::LString(text) => !text.is_empty(), OwnedLiteral::LPositiveInteger(_num) => true, + OwnedLiteral::LFloat(_num) => true, } } } @@ -146,6 +147,7 @@ impl Renderable for OwnedLiteral { match self { OwnedLiteral::LString(text) => Ok(text.clone()), OwnedLiteral::LPositiveInteger(num) => Ok(num.to_string()), + OwnedLiteral::LFloat(num) => Ok(num.to_string()), } } } @@ -180,10 +182,25 @@ impl CompareContextElement for OwnedLiteral { (OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => { self_text == &other_num.to_string() } + (OwnedLiteral::LFloat(self_num), OwnedLiteral::LString(other_text)) => { + &self_num.to_string() == other_text + } + (OwnedLiteral::LString(self_text), OwnedLiteral::LFloat(other_num)) => { + self_text == &other_num.to_string() + } ( OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => self_num == other_num, + (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 + } }, } } @@ -212,10 +229,25 @@ impl CompareContextElement for OwnedLiteral { (OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(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::LPositiveInteger(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::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)) + } }, } } From a378d6e6f3996285d240ec9808563c9716f9e308 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 13:47:43 -0400 Subject: [PATCH 05/33] Add comparisons for json floats but not yet between OwnedLiterals and json. --- src/bin.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 143f4cd..d2a9f35 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -503,28 +503,43 @@ where { let self_number: JsonNumber = self_input.into(); let other_number: JsonNumber = other_input.into(); - // TODO: Figure out how javascript compares floats and ints match (self_number, other_number) { (JsonNumber::Failure, _) => return None, (_, JsonNumber::Failure) => return None, (JsonNumber::UnsignedInteger(self_num), JsonNumber::UnsignedInteger(other_num)) => { return self_num.partial_cmp(&other_num) } - (JsonNumber::UnsignedInteger(_self_num), JsonNumber::SignedInteger(_other_num)) => { - return Some(Ordering::Greater) + (JsonNumber::UnsignedInteger(self_num), JsonNumber::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); + } + } + (JsonNumber::UnsignedInteger(self_num), JsonNumber::Decimal(other_num)) => { + return (self_num as f64).partial_cmp(&other_num) } - (JsonNumber::UnsignedInteger(_self_num), JsonNumber::Decimal(_other_num)) => return None, - (JsonNumber::SignedInteger(_self_num), JsonNumber::UnsignedInteger(_other_num)) => { - return Some(Ordering::Less) + (JsonNumber::SignedInteger(self_num), JsonNumber::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); + } } (JsonNumber::SignedInteger(self_num), JsonNumber::SignedInteger(other_num)) => { return self_num.partial_cmp(&other_num) } - (JsonNumber::SignedInteger(_self_num), JsonNumber::Decimal(_other_num)) => return None, + (JsonNumber::SignedInteger(self_num), JsonNumber::Decimal(other_num)) => { + return (self_num as f64).partial_cmp(&other_num) + } - (JsonNumber::Decimal(_self_num), JsonNumber::UnsignedInteger(_other_num)) => return None, - (JsonNumber::Decimal(_self_num), JsonNumber::SignedInteger(_other_num)) => return None, + (JsonNumber::Decimal(self_num), JsonNumber::UnsignedInteger(other_num)) => { + return self_num.partial_cmp(&(other_num as f64)) + } + (JsonNumber::Decimal(self_num), JsonNumber::SignedInteger(other_num)) => { + return self_num.partial_cmp(&(other_num as f64)) + } (JsonNumber::Decimal(self_num), JsonNumber::Decimal(other_num)) => { return self_num.partial_cmp(&other_num) } From 5b656a44ad9f35b3b790d4d898802e32c37cd56c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 14:19:03 -0400 Subject: [PATCH 06/33] For partial_cmp, convert OwnedLiterals into serde_json values. --- src/bin.rs | 62 +++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index d2a9f35..66bb1cf 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -332,7 +332,18 @@ impl CompareContextElement for serde_json::Value { return self.as_str().map_or(false, |s| s == other_string) } Some(OwnedLiteral::LPositiveInteger(other_num)) => { - return self.as_u64().map_or(false, |n| n == *other_num) + let other_json_num: serde_json::Number = std::convert::From::from(*other_num); + return self + .equals(&serde_json::Value::Number(other_json_num) as &dyn ContextElement); + } + Some(OwnedLiteral::LFloat(other_num)) => { + let other_json_num = serde_json::Number::from_f64(*other_num); + match other_json_num { + None => return false, + Some(ojn) => { + return self.equals(&serde_json::Value::Number(ojn) as &dyn ContextElement) + } + } } } false @@ -401,39 +412,24 @@ impl CompareContextElement for serde_json::Value { // Handle literals match other.to_any().downcast_ref::() { None => (), - Some(other_literal) => match (self, other_literal) { - (serde_json::Value::String(self_string), OwnedLiteral::LString(other_string)) => { - return self_string.partial_cmp(other_string) - } - ( - serde_json::Value::String(self_string), - OwnedLiteral::LPositiveInteger(_other_num), - ) => return compare_json_numbers(self_string, other_literal), - (serde_json::Value::Number(self_num), OwnedLiteral::LString(other_string)) => { - return compare_json_numbers(self_num, other_string) - } - ( - serde_json::Value::Number(self_num), - OwnedLiteral::LPositiveInteger(_other_num), - ) => return compare_json_numbers(self_num, other_literal), - (serde_json::Value::Array(_), _) => { - // TODO - todo!() - } - (serde_json::Value::Object(_), _) => { - // TODO - todo!() - } - (serde_json::Value::Bool(_), _) => { - // TODO - todo!() - } - (serde_json::Value::Null, _) => { - // TODO - todo!() + Some(OwnedLiteral::LString(text)) => { + return self.to_string().partial_cmp(text); + } + Some(OwnedLiteral::LPositiveInteger(other_num)) => { + let other_json_num: serde_json::Number = std::convert::From::from(*other_num); + return self.partial_compare( + &serde_json::Value::Number(other_json_num) as &dyn ContextElement + ); + } + Some(OwnedLiteral::LFloat(other_num)) => { + let other_json_num = serde_json::Number::from_f64(*other_num); + match other_json_num { + None => return None, + Some(ojn) => return self + .partial_compare(&serde_json::Value::Number(ojn) as &dyn ContextElement), } - }, - } + } + }; None } } From 4d28120732ffeb29ded0613af3e38e28722ea6e9 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 14:55:27 -0400 Subject: [PATCH 07/33] I have broken the comparison function. --- src/bin.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 66bb1cf..c5b6a2f 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -410,18 +410,21 @@ impl CompareContextElement for serde_json::Value { } } // Handle literals - match other.to_any().downcast_ref::() { - None => (), - Some(OwnedLiteral::LString(text)) => { + match (self, other.to_any().downcast_ref::()) { + (_, None) => (), + (serde_json::Value::String(self_text), Some(OwnedLiteral::LString(other_text))) => { + return self_text.partial_cmp(other_text); + } + (_, Some(OwnedLiteral::LString(text))) => { return self.to_string().partial_cmp(text); } - Some(OwnedLiteral::LPositiveInteger(other_num)) => { + (_, Some(OwnedLiteral::LPositiveInteger(other_num))) => { let other_json_num: serde_json::Number = std::convert::From::from(*other_num); return self.partial_compare( &serde_json::Value::Number(other_json_num) as &dyn ContextElement ); } - Some(OwnedLiteral::LFloat(other_num)) => { + (_, Some(OwnedLiteral::LFloat(other_num))) => { let other_json_num = serde_json::Number::from_f64(*other_num); match other_json_num { None => return None, @@ -483,6 +486,13 @@ impl From<&OwnedLiteral> for JsonNumber { match original { OwnedLiteral::LPositiveInteger(num) => JsonNumber::UnsignedInteger(*num), OwnedLiteral::LString(text) => text.into(), + OwnedLiteral::LFloat(num) => { + if num.is_nan() { + JsonNumber::Failure + } else { + JsonNumber::Decimal(*num) + } + } } } } From 03ff75b2de759e159c69034e75f9ad08a811b17c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 15:31:52 -0400 Subject: [PATCH 08/33] Fix number comparison logic. --- src/bin.rs | 113 ++++++++++++++++++++++------------------------------- 1 file changed, 46 insertions(+), 67 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index c5b6a2f..87035cf 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -366,74 +366,27 @@ impl CompareContextElement for serde_json::Value { .partial_compare(other); } - // Handle other serde_json::Value - match other.to_any().downcast_ref::() { - None => (), - Some(other_json_value) => { - return match (self, other_json_value) { - ( - serde_json::Value::Bool(self_boolean), - serde_json::Value::Bool(other_boolean), - ) => self_boolean.partial_cmp(other_boolean), - ( - serde_json::Value::Number(self_number), - serde_json::Value::Number(other_number), - ) => return compare_json_numbers(self_number, other_number), - ( - serde_json::Value::String(self_string), - serde_json::Value::Number(other_number), - ) => return compare_json_numbers(self_string, other_number), - ( - serde_json::Value::Number(self_number), - serde_json::Value::String(other_string), - ) => return compare_json_numbers(self_number, other_string), - ( - serde_json::Value::String(self_string), - serde_json::Value::String(other_string), - ) => self_string.partial_cmp(other_string), - ( - serde_json::Value::Array(_self_array), - serde_json::Value::Array(_other_array), - ) => { - // TODO: is this reachable given the early convert to string before this block? - return self - .render(&Vec::new()) - .unwrap_or("".to_owned()) - .partial_cmp( - &other_json_value - .render(&Vec::new()) - .unwrap_or("".to_owned()), - ); - } - _ => None, - }; - } + let maybe_json_other = other.to_any().downcast_ref::(); + let maybe_literal_other = other.to_any().downcast_ref::(); + + // If they're both strings, compare them directly + match (self, maybe_json_other, maybe_literal_other) { + // If they're both strings, compare them directly + ( + serde_json::Value::String(self_string), + Some(serde_json::Value::String(other_string)), + _, + ) => return self_string.partial_cmp(&other_string), + ( + serde_json::Value::String(self_string), + _, + Some(OwnedLiteral::LString(other_string)), + ) => return self_string.partial_cmp(&other_string), + // Otherwise, convert to numbers are 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."), } - // Handle literals - match (self, other.to_any().downcast_ref::()) { - (_, None) => (), - (serde_json::Value::String(self_text), Some(OwnedLiteral::LString(other_text))) => { - return self_text.partial_cmp(other_text); - } - (_, Some(OwnedLiteral::LString(text))) => { - return self.to_string().partial_cmp(text); - } - (_, Some(OwnedLiteral::LPositiveInteger(other_num))) => { - let other_json_num: serde_json::Number = std::convert::From::from(*other_num); - return self.partial_compare( - &serde_json::Value::Number(other_json_num) as &dyn ContextElement - ); - } - (_, Some(OwnedLiteral::LFloat(other_num))) => { - let other_json_num = serde_json::Number::from_f64(*other_num); - match other_json_num { - None => return None, - Some(ojn) => return self - .partial_compare(&serde_json::Value::Number(ojn) as &dyn ContextElement), - } - } - }; - None } } @@ -445,6 +398,32 @@ enum JsonNumber { Failure, } +impl From<&serde_json::Value> for JsonNumber { + /// Convert from a JSON value to a number for comparison based on + /// the logic described at + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than + fn from(original: &serde_json::Value) -> Self { + match original { + serde_json::Value::Null => JsonNumber::UnsignedInteger(0), + serde_json::Value::Bool(boolean) => { + if *boolean { + JsonNumber::UnsignedInteger(1) + } else { + JsonNumber::UnsignedInteger(0) + } + } + serde_json::Value::Number(num) => num.into(), + serde_json::Value::String(text) => text.into(), + serde_json::Value::Array(_) => { + panic!("Only primitives should be cast to numbers for comparisons") + } + serde_json::Value::Object(_) => { + panic!("Only primitives should be cast to numbers for comparisons") + } + } + } +} + impl From<&String> for JsonNumber { fn from(original: &String) -> Self { match original.parse::() { From 02bcefb75c3d471a730ca32a6837564d711c82cd Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 15:33:21 -0400 Subject: [PATCH 09/33] Remove outdated TODO. --- src/bin.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin.rs b/src/bin.rs index 87035cf..dbbce8a 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -274,7 +274,6 @@ impl Renderable for serde_json::Value { serde_json::Value::Number(num) => Ok(num.to_string()), serde_json::Value::String(string) => Ok(string.to_string()), serde_json::Value::Array(arr) => { - // TODO: Handle the filters instead of passing a Vec::new() let rendered: Result, RenderError> = arr.iter().map(|val| val.render(&Vec::new())).collect(); let rendered_slice: &[String] = &rendered?; From d99aa44d8ef3605486df23036dc39a452883b165 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 16:06:31 -0400 Subject: [PATCH 10/33] Add support for negative integers. --- src/bin.rs | 6 ++ src/parser/parser.rs | 35 ++++++++++ src/renderer/parameters_context.rs | 102 +++++++++++++++++++++++++---- 3 files changed, 129 insertions(+), 14 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index dbbce8a..55254f0 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -335,6 +335,11 @@ impl CompareContextElement for serde_json::Value { return self .equals(&serde_json::Value::Number(other_json_num) as &dyn ContextElement); } + Some(OwnedLiteral::LNegativeInteger(other_num)) => { + let other_json_num: serde_json::Number = std::convert::From::from(*other_num); + return self + .equals(&serde_json::Value::Number(other_json_num) as &dyn ContextElement); + } Some(OwnedLiteral::LFloat(other_num)) => { let other_json_num = serde_json::Number::from_f64(*other_num); match other_json_num { @@ -463,6 +468,7 @@ impl From<&OwnedLiteral> for JsonNumber { fn from(original: &OwnedLiteral) -> Self { match original { OwnedLiteral::LPositiveInteger(num) => JsonNumber::UnsignedInteger(*num), + OwnedLiteral::LNegativeInteger(num) => JsonNumber::SignedInteger(*num), OwnedLiteral::LString(text) => text.into(), OwnedLiteral::LFloat(num) => { if num.is_nan() { diff --git a/src/parser/parser.rs b/src/parser/parser.rs index d1ddf96..daa9431 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -119,6 +119,7 @@ pub struct Partial<'a> { pub enum OwnedLiteral { LString(String), LPositiveInteger(u64), + LNegativeInteger(i64), LFloat(f64), } @@ -354,6 +355,34 @@ fn postitive_integer_literal(i: &str) -> IResult<&str, u64> { )(i) } +/// No decimals, just the sign and digits +fn negative_integer_literal(i: &str) -> IResult<&str, i64> { + map( + verify( + map( + recognize(tuple((tag("-"), digit1))), + |number_string: &str| number_string.parse::(), + ), + |parse_result| parse_result.is_ok(), + ), + |parsed_number| parsed_number.unwrap(), + )(i) +} + +/// A non-scientific notation float (sign, digits, decimal, and more digits) +fn float_literal(i: &str) -> IResult<&str, f64> { + map( + verify( + map( + recognize(tuple((opt(one_of("+-")), digit1, tag("."), digit1))), + |number_string: &str| number_string.parse::(), + ), + |parse_result| parse_result.is_ok(), + ), + |parsed_number| parsed_number.unwrap(), + )(i) +} + fn template_string_rvalue(i: &str) -> IResult<&str, Vec> { let (i, template_string) = verify(quoted_string, |s: &String| { partial_quoted_tag(s.as_str()).is_ok() @@ -376,6 +405,12 @@ fn rvalue(i: &str) -> IResult<&str, RValue> { map(postitive_integer_literal, |num| { RValue::RVLiteral(OwnedLiteral::LPositiveInteger(num)) }), + map(negative_integer_literal, |num| { + RValue::RVLiteral(OwnedLiteral::LNegativeInteger(num)) + }), + map(float_literal, |num| { + RValue::RVLiteral(OwnedLiteral::LFloat(num)) + }), ))(i) } diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 130f84a..0008ac5 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -137,6 +137,7 @@ impl Truthiness for OwnedLiteral { match self { OwnedLiteral::LString(text) => !text.is_empty(), OwnedLiteral::LPositiveInteger(_num) => true, + OwnedLiteral::LNegativeInteger(_num) => true, OwnedLiteral::LFloat(_num) => true, } } @@ -147,6 +148,7 @@ impl Renderable for OwnedLiteral { match self { 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()), } } @@ -176,31 +178,67 @@ impl CompareContextElement for OwnedLiteral { (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::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => { + self_num == other_num + } (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::LNegativeInteger(self_num), OwnedLiteral::LString(other_text)) => { + &self_num.to_string() == other_text + } + (OwnedLiteral::LString(self_text), OwnedLiteral::LNegativeInteger(other_num)) => { + self_text == &other_num.to_string() + } (OwnedLiteral::LFloat(self_num), OwnedLiteral::LString(other_text)) => { &self_num.to_string() == other_text } (OwnedLiteral::LString(self_text), OwnedLiteral::LFloat(other_num)) => { self_text == &other_num.to_string() } - ( - OwnedLiteral::LPositiveInteger(self_num), - OwnedLiteral::LPositiveInteger(other_num), - ) => self_num == other_num, - (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 + } + } }, } } @@ -223,31 +261,67 @@ impl CompareContextElement for OwnedLiteral { (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::LPositiveInteger(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::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) + } + } }, } } From 2a89fd826e1b2723897d27a63bce8167d8a7113b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 16:18:48 -0400 Subject: [PATCH 11/33] Update tests to use templates instead of literal strings for string parameters. --- src/parser/parser.rs | 94 ++++++++++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 26 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index daa9431..d9cb88f 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -1204,7 +1204,9 @@ mod tests { }, KVPair { key: "animal", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ] }) @@ -1230,7 +1232,9 @@ mod tests { }, KVPair { key: "animal", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ] }) @@ -1265,7 +1269,9 @@ mod tests { }, KVPair { key: "animal", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ] }) @@ -1293,7 +1299,9 @@ mod tests { }, KVPair { key: "animal", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ] }) @@ -1323,7 +1331,9 @@ mod tests { }, KVPair { key: "animal", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ] }) @@ -1334,7 +1344,7 @@ mod tests { #[test] fn test_literals() { assert_eq!( - dust_tag(r#"{>foo a="foo" b=179/}"#), + dust_tag(r#"{>foo a="foo" b=179 c=17.1 d=-12 e=-17.4/}"#), Ok(( "", DustTag::DTPartial(Partial { @@ -1345,11 +1355,25 @@ mod tests { params: vec![ KVPair { key: "a", - value: RValue::RVLiteral(OwnedLiteral::LString("foo".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "foo".to_owned() + }]) }, KVPair { key: "b", value: RValue::RVLiteral(OwnedLiteral::LPositiveInteger(179)) + }, + KVPair { + key: "c", + value: RValue::RVLiteral(OwnedLiteral::LFloat(17.1)) + }, + KVPair { + key: "d", + value: RValue::RVLiteral(OwnedLiteral::LNegativeInteger(-12)) + }, + KVPair { + key: "e", + value: RValue::RVLiteral(OwnedLiteral::LFloat(-17.4)) } ] }) @@ -1373,7 +1397,9 @@ mod tests { }, KVPair { key: "value", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ], contents: Some(Body { @@ -1410,7 +1436,9 @@ mod tests { }, KVPair { key: "value", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ], contents: None, @@ -1438,7 +1466,9 @@ mod tests { }, KVPair { key: "value", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ], contents: Some(Body { @@ -1477,7 +1507,9 @@ mod tests { }, KVPair { key: "value", - value: RValue::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + value: RValue::RVTemplate(vec![PartialNameElement::PNSpan { + contents: "cat".to_owned() + }]) } ], contents: None, @@ -1593,33 +1625,43 @@ mod tests { params: vec![ KVPair { key: "v1", - value: RValue::RVLiteral( - OwnedLiteral::LString("b".to_owned()) - ) + value: RValue::RVTemplate(vec![ + PartialNameElement::PNSpan { + contents: "b".to_owned() + } + ]) }, KVPair { key: "v2", - value: RValue::RVLiteral( - OwnedLiteral::LString("b".to_owned()) - ) + value: RValue::RVTemplate(vec![ + PartialNameElement::PNSpan { + contents: "b".to_owned() + } + ]) }, KVPair { key: "v3", - value: RValue::RVLiteral( - OwnedLiteral::LString("b".to_owned()) - ) + value: RValue::RVTemplate(vec![ + PartialNameElement::PNSpan { + contents: "b".to_owned() + } + ]) }, KVPair { key: "v4", - value: RValue::RVLiteral( - OwnedLiteral::LString("b".to_owned()) - ) + value: RValue::RVTemplate(vec![ + PartialNameElement::PNSpan { + contents: "b".to_owned() + } + ]) }, KVPair { key: "v5", - value: RValue::RVLiteral( - OwnedLiteral::LString("b".to_owned()) - ) + value: RValue::RVTemplate(vec![ + PartialNameElement::PNSpan { + contents: "b".to_owned() + } + ]) } ] } From de5932b4c69769fa03ac13dc1b2237b40677cd67 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 16:23:46 -0400 Subject: [PATCH 12/33] Fix bug in parsing order for literals. --- src/parser/parser.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index d9cb88f..88e09f2 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -402,14 +402,14 @@ fn rvalue(i: &str) -> IResult<&str, RValue> { alt(( map(path, RValue::RVPath), map(template_string_rvalue, RValue::RVTemplate), - map(postitive_integer_literal, |num| { - RValue::RVLiteral(OwnedLiteral::LPositiveInteger(num)) + map(float_literal, |num| { + RValue::RVLiteral(OwnedLiteral::LFloat(num)) }), map(negative_integer_literal, |num| { RValue::RVLiteral(OwnedLiteral::LNegativeInteger(num)) }), - map(float_literal, |num| { - RValue::RVLiteral(OwnedLiteral::LFloat(num)) + map(postitive_integer_literal, |num| { + RValue::RVLiteral(OwnedLiteral::LPositiveInteger(num)) }), ))(i) } @@ -739,6 +739,13 @@ mod tests { use nom::error::ErrorKind; use nom::Err::Error; + #[test] + fn test_direct_literal() { + assert_eq!(super::float_literal("-17.4"), Ok(("", -17.4))); + assert_eq!(super::float_literal("17.1"), Ok(("", 17.1))); + assert_eq!(super::negative_integer_literal("-12"), Ok(("", -12))); + } + #[test] fn test_reference() { assert_eq!( From 8ef3949a655f334db69fb4f8cd8479201fff8f3c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 16:32:57 -0400 Subject: [PATCH 13/33] Fix float json to literal equality comparison by casting them both to float. --- src/bin.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 55254f0..df8a4b8 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -340,15 +340,10 @@ impl CompareContextElement for serde_json::Value { return self .equals(&serde_json::Value::Number(other_json_num) as &dyn ContextElement); } - Some(OwnedLiteral::LFloat(other_num)) => { - let other_json_num = serde_json::Number::from_f64(*other_num); - match other_json_num { - None => return false, - Some(ojn) => { - return self.equals(&serde_json::Value::Number(ojn) as &dyn ContextElement) - } - } - } + Some(OwnedLiteral::LFloat(other_num)) => match self.as_f64() { + None => return false, + Some(self_float) => return self_float == *other_num, + }, } false } From db11677b227635a7a39ee2690fe7bb2b1412753a Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 17:50:22 -0400 Subject: [PATCH 14/33] Implement addition for OwnedLiterals. --- src/bin.rs | 5 ++ src/renderer/context_element.rs | 13 ++++- src/renderer/mod.rs | 1 + src/renderer/parameters_context.rs | 90 ++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/bin.rs b/src/bin.rs index df8a4b8..21579b6 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -8,6 +8,7 @@ use renderer::compile_template; use renderer::CompileError; use renderer::ContextElement; use renderer::DustRenderer; +use renderer::IceResult; use renderer::IntoContextElement; use renderer::Loopable; use renderer::RenderError; @@ -387,6 +388,10 @@ impl CompareContextElement for serde_json::Value { _ => panic!("Unimplemented comparison type."), } } + + fn math_add<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } } #[derive(Debug)] diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index a83ca52..5ef6e3c 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -4,6 +4,7 @@ use crate::renderer::errors::RenderError; use crate::renderer::errors::WalkError; use crate::renderer::DustRenderer; use std::any::Any; +use std::ops::Add; use std::rc::Rc; use std::{cmp::Ordering, fmt::Debug}; @@ -61,6 +62,8 @@ pub trait CompareContextElement: CastToAny { fn equals(&self, other: &dyn ContextElement) -> bool; fn partial_compare(&self, other: &dyn ContextElement) -> Option; + + fn math_add<'a>(&self, other: &dyn ContextElement) -> Option>; } impl CastToAny for C { @@ -81,6 +84,14 @@ impl<'a, 'b> PartialOrd<&'b dyn ContextElement> for &'a dyn ContextElement { } } +impl<'a> Add<&'a dyn ContextElement> for &'a dyn ContextElement { + type Output = Option>; + + fn add(self, other: &'a dyn ContextElement) -> Self::Output { + self.math_add(other) + } +} + pub trait FromContextElement { fn from_context_element(&self) -> &dyn IntoContextElement; } @@ -91,7 +102,7 @@ impl FromContextElement for C { } } -pub trait IntoContextElement: Debug + Walkable /* + CloneIntoBoxedContextElement*/ { +pub trait IntoContextElement: Debug + Walkable { fn into_context_element<'a>( &'a self, renderer: &DustRenderer, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 288b2ab..434dedd 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -12,6 +12,7 @@ mod walking; pub use context_element::CompareContextElement; pub use context_element::ContextElement; +pub use context_element::IceResult; pub use context_element::IntoContextElement; pub use context_element::Loopable; pub use context_element::Renderable; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 0008ac5..5a6f80b 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -18,6 +18,7 @@ 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> { @@ -325,4 +326,93 @@ impl CompareContextElement for OwnedLiteral { }, } } + + 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, + ( + OwnedLiteral::LPositiveInteger(self_num), + OwnedLiteral::LPositiveInteger(other_num), + ) => math_ints(*self_num, *other_num, std::ops::Add::add) + .map(IceResult::from_owned), + ( + OwnedLiteral::LNegativeInteger(self_num), + OwnedLiteral::LNegativeInteger(other_num), + ) => math_ints(*self_num, *other_num, std::ops::Add::add) + .map(IceResult::from_owned), + ( + OwnedLiteral::LPositiveInteger(self_num), + OwnedLiteral::LNegativeInteger(other_num), + ) => math_ints(*self_num, *other_num, std::ops::Add::add) + .map(IceResult::from_owned), + ( + OwnedLiteral::LNegativeInteger(self_num), + OwnedLiteral::LPositiveInteger(other_num), + ) => math_ints(*self_num, *other_num, std::ops::Add::add) + .map(IceResult::from_owned), + (OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => Some( + IceResult::from_owned(OwnedLiteral::LFloat(self_num + other_num)), + ), + (OwnedLiteral::LFloat(self_num), OwnedLiteral::LPositiveInteger(other_num)) => { + Some(IceResult::from_owned(OwnedLiteral::LFloat( + self_num + (*other_num as f64), + ))) + } + (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LFloat(other_num)) => { + Some(IceResult::from_owned(OwnedLiteral::LFloat( + (*self_num as f64) + other_num, + ))) + } + (OwnedLiteral::LFloat(self_num), OwnedLiteral::LNegativeInteger(other_num)) => { + Some(IceResult::from_owned(OwnedLiteral::LFloat( + self_num + (*other_num as f64), + ))) + } + (OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LFloat(other_num)) => { + Some(IceResult::from_owned(OwnedLiteral::LFloat( + (*self_num as f64) + other_num, + ))) + } + } + } + } + } } + +/// For math operations that take in integers and return integers +/// (add, subtract, multiply) +fn math_ints(left: L, right: R, operation: F) -> Option +where + L: Into, + R: Into, + F: Fn(i128, i128) -> i128, +{ + let result = operation(left.into(), right.into()); + std::convert::TryInto::::try_into(result) + .map(OwnedLiteral::LPositiveInteger) + .ok() + .or(std::convert::TryInto::::try_into(result) + .map(OwnedLiteral::LNegativeInteger) + .ok()) +} + +// /// For math operations that take in integers and return integers +// /// (add, subtract, multiply) +// fn math_floats(left: L, right: R, operation: F) -> Option +// where +// L: Into, +// R: Into, +// F: Fn(f64, f64) -> f64, +// { +// let result = operation(left.into(), right.into()); +// std::convert::TryInto::::try_into(result) +// .map(OwnedLiteral::LFloat) +// .ok() +// } From df0ae0564875e033111cf5334d91c392d0d23c4b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 18:34:13 -0400 Subject: [PATCH 15/33] Start of implementation for serde_json but I'm going to change my approach. The permutations are pretty intense, so I think I'm going to do the same design I did for comparison where I have a JsonNumber (but I'll call this one MathNumber and rename JsonNumber to ComparisonNumber), convert the types to that, and then do the math. --- src/bin.rs | 59 +++++++++++++++++++++++++++++- src/renderer/mod.rs | 1 + src/renderer/parameters_context.rs | 2 +- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 21579b6..fdb7905 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -5,6 +5,7 @@ use parser::Filter; use parser::OwnedLiteral; use parser::Template; use renderer::compile_template; +use renderer::math_ints; use renderer::CompileError; use renderer::ContextElement; use renderer::DustRenderer; @@ -390,7 +391,63 @@ impl CompareContextElement for serde_json::Value { } fn math_add<'a>(&self, other: &dyn ContextElement) -> Option> { - todo!() + match ( + self, + other.to_any().downcast_ref::(), + other.to_any().downcast_ref::(), + ) { + // If its neither of those types, then it is unimplemented + (_, None, None) => panic!("Math operation on unimplemented type"), + // Since this is specifically for the math helper, non-primitives are not supported + (serde_json::Value::Array(_), _, _) + | (serde_json::Value::Object(_), _, _) + | (_, Some(serde_json::Value::Array(_)), _) + | (_, Some(serde_json::Value::Object(_)), _) => None, + // Strings also are ignored because this is specifically a math function + (serde_json::Value::String(_), _, _) + | (_, Some(serde_json::Value::String(_)), _) + | (_, _, Some(OwnedLiteral::LString(_))) => None, + // Handle other serde_json::Value + ( + serde_json::Value::Number(self_num), + Some(serde_json::Value::Number(other_num)), + _, + ) => { + match ( + self_num.as_u64(), + other_num.as_u64(), + self_num.as_i64(), + other_num.as_i64(), + self_num.as_f64(), + other_num.as_f64(), + ) { + // If both numbers are integers, we can pass them into math_ints + (Some(self_num), Some(other_num), _, _, _, _) => { + math_ints(self_num, other_num, std::ops::Add::add) + .map(IceResult::from_owned) + } + (_, _, Some(self_num), Some(other_num), _, _) => { + math_ints(self_num, other_num, std::ops::Add::add) + .map(IceResult::from_owned) + } + (Some(self_num), _, _, Some(other_num), _, _) => { + math_ints(self_num, other_num, std::ops::Add::add) + .map(IceResult::from_owned) + } + (_, Some(other_num), Some(self_num), _, _, _) => { + math_ints(self_num, other_num, std::ops::Add::add) + .map(IceResult::from_owned) + } + // Otherwise they'll be floats and we can just do the math directly + (_, _, _, _, Some(self_num), Some(other_num)) => Some(IceResult::from_owned( + OwnedLiteral::LFloat(self_num + other_num), + )), + _ => panic!("Unhandled operation, some integer must not have cast to a float."), + } + } + // Handle literals + _ => todo!(), + } } } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 434dedd..f773381 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -21,6 +21,7 @@ pub use context_element::Walkable; pub use errors::CompileError; pub use errors::RenderError; pub use errors::WalkError; +pub use parameters_context::math_ints; pub use renderer::compile_template; pub use renderer::DustRenderer; pub use select_context::SelectContext; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 5a6f80b..85a422d 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -388,7 +388,7 @@ impl CompareContextElement for OwnedLiteral { /// For math operations that take in integers and return integers /// (add, subtract, multiply) -fn math_ints(left: L, right: R, operation: F) -> Option +pub fn math_ints(left: L, right: R, operation: F) -> Option where L: Into, R: Into, From d9ce0111137ae6f2b28ac22e733ea8f2db5a865c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 19:02:56 -0400 Subject: [PATCH 16/33] Add implementation based on MathNumber. --- src/bin.rs | 139 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 43 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index fdb7905..e282473 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -18,9 +18,11 @@ use renderer::Truthiness; use renderer::WalkError; use renderer::Walkable; use std::cmp::Ordering; +use std::convert::TryInto; use std::env; use std::fs; use std::io::{self, Read}; +use std::ops::Add; use std::path::Path; mod parser; @@ -391,11 +393,9 @@ impl CompareContextElement for serde_json::Value { } fn math_add<'a>(&self, other: &dyn ContextElement) -> Option> { - match ( - self, - other.to_any().downcast_ref::(), - other.to_any().downcast_ref::(), - ) { + let other_json = other.to_any().downcast_ref::(); + let other_literal = other.to_any().downcast_ref::(); + match (self, other_json, other_literal) { // If its neither of those types, then it is unimplemented (_, None, None) => panic!("Math operation on unimplemented type"), // Since this is specifically for the math helper, non-primitives are not supported @@ -408,45 +408,13 @@ impl CompareContextElement for serde_json::Value { | (_, Some(serde_json::Value::String(_)), _) | (_, _, Some(OwnedLiteral::LString(_))) => None, // Handle other serde_json::Value - ( - serde_json::Value::Number(self_num), - Some(serde_json::Value::Number(other_num)), - _, - ) => { - match ( - self_num.as_u64(), - other_num.as_u64(), - self_num.as_i64(), - other_num.as_i64(), - self_num.as_f64(), - other_num.as_f64(), - ) { - // If both numbers are integers, we can pass them into math_ints - (Some(self_num), Some(other_num), _, _, _, _) => { - math_ints(self_num, other_num, std::ops::Add::add) - .map(IceResult::from_owned) - } - (_, _, Some(self_num), Some(other_num), _, _) => { - math_ints(self_num, other_num, std::ops::Add::add) - .map(IceResult::from_owned) - } - (Some(self_num), _, _, Some(other_num), _, _) => { - math_ints(self_num, other_num, std::ops::Add::add) - .map(IceResult::from_owned) - } - (_, Some(other_num), Some(self_num), _, _, _) => { - math_ints(self_num, other_num, std::ops::Add::add) - .map(IceResult::from_owned) - } - // Otherwise they'll be floats and we can just do the math directly - (_, _, _, _, Some(self_num), Some(other_num)) => Some(IceResult::from_owned( - OwnedLiteral::LFloat(self_num + other_num), - )), - _ => panic!("Unhandled operation, some integer must not have cast to a float."), - } - } + (_, Some(other_json_value), _) => (std::convert::Into::::into(self) + + std::convert::Into::::into(other_json_value)) + .map(IceResult::from_owned), // Handle literals - _ => todo!(), + (_, _, Some(other_literal)) => (std::convert::Into::::into(self) + + std::convert::Into::::into(other_literal)) + .map(IceResult::from_owned), } } } @@ -593,6 +561,91 @@ where } } +#[derive(Debug)] +enum MathNumber { + Integer(i128), + Decimal(f64), + Failure, +} + +impl From<&serde_json::Value> for MathNumber { + fn from(original: &serde_json::Value) -> Self { + match original { + serde_json::Value::Null => MathNumber::Integer(0), + serde_json::Value::Bool(boolean) => { + if *boolean { + MathNumber::Integer(1) + } else { + MathNumber::Integer(0) + } + } + serde_json::Value::Number(num) => num.into(), + serde_json::Value::String(text) => { + panic!("Strings should not be cast to numbers for math") + } + serde_json::Value::Array(_) => { + panic!("Only primitives should be cast to numbers for comparisons") + } + serde_json::Value::Object(_) => { + panic!("Only primitives should be cast to numbers for comparisons") + } + } + } +} + +impl From<&serde_json::Number> for MathNumber { + fn from(original: &serde_json::Number) -> Self { + match original.as_u64() { + Some(num) => return MathNumber::Integer(num.try_into().unwrap()), + None => (), + }; + match original.as_i64() { + Some(num) => return MathNumber::Integer(num.into()), + None => (), + }; + match original.as_f64() { + Some(num) => return MathNumber::Decimal(num), + None => (), + }; + MathNumber::Failure + } +} + +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::LPositiveInteger(num) => { + return MathNumber::Integer((*num).try_into().unwrap()) + } + OwnedLiteral::LNegativeInteger(num) => return MathNumber::Integer((*num).into()), + OwnedLiteral::LFloat(num) => return MathNumber::Decimal(*num), + } + } +} + +impl Add for MathNumber { + type Output = Option; + + fn add(self, other: MathNumber) -> Self::Output { + match (self, other) { + (MathNumber::Failure, _) | (_, MathNumber::Failure) => None, + (MathNumber::Integer(self_num), MathNumber::Integer(other_num)) => { + math_ints(self_num, other_num, std::ops::Add::add) + } + (MathNumber::Decimal(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat(self_num + other_num)) + } + (MathNumber::Integer(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat((self_num as f64) + other_num)) + } + (MathNumber::Decimal(self_num), MathNumber::Integer(other_num)) => { + Some(OwnedLiteral::LFloat(self_num + (other_num as f64))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; From d6ad7c28f370e60a3fb07e975db46ca820ae8bd3 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 19:13:02 -0400 Subject: [PATCH 17/33] Move MathNumber into the library. While this will be used for the OwnedLiterals since they must always exhibit the original DustJS behavior, I am going to keep exposing separate math functions for ContextElement instead of simply requiring them to implement Into since people might want to implement math in unusual ways with unusual types. --- src/bin.rs | 45 +--------------- src/renderer/math.rs | 76 ++++++++++++++++++++++++++ src/renderer/mod.rs | 3 +- src/renderer/parameters_context.rs | 87 +++--------------------------- 4 files changed, 86 insertions(+), 125 deletions(-) create mode 100644 src/renderer/math.rs diff --git a/src/bin.rs b/src/bin.rs index e282473..d0695ce 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -5,13 +5,13 @@ use parser::Filter; use parser::OwnedLiteral; use parser::Template; use renderer::compile_template; -use renderer::math_ints; use renderer::CompileError; use renderer::ContextElement; use renderer::DustRenderer; use renderer::IceResult; use renderer::IntoContextElement; use renderer::Loopable; +use renderer::MathNumber; use renderer::RenderError; use renderer::Renderable; use renderer::Truthiness; @@ -22,7 +22,6 @@ use std::convert::TryInto; use std::env; use std::fs; use std::io::{self, Read}; -use std::ops::Add; use std::path::Path; mod parser; @@ -561,13 +560,6 @@ where } } -#[derive(Debug)] -enum MathNumber { - Integer(i128), - Decimal(f64), - Failure, -} - impl From<&serde_json::Value> for MathNumber { fn from(original: &serde_json::Value) -> Self { match original { @@ -611,41 +603,6 @@ impl From<&serde_json::Number> for MathNumber { } } -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::LPositiveInteger(num) => { - return MathNumber::Integer((*num).try_into().unwrap()) - } - OwnedLiteral::LNegativeInteger(num) => return MathNumber::Integer((*num).into()), - OwnedLiteral::LFloat(num) => return MathNumber::Decimal(*num), - } - } -} - -impl Add for MathNumber { - type Output = Option; - - fn add(self, other: MathNumber) -> Self::Output { - match (self, other) { - (MathNumber::Failure, _) | (_, MathNumber::Failure) => None, - (MathNumber::Integer(self_num), MathNumber::Integer(other_num)) => { - math_ints(self_num, other_num, std::ops::Add::add) - } - (MathNumber::Decimal(self_num), MathNumber::Decimal(other_num)) => { - Some(OwnedLiteral::LFloat(self_num + other_num)) - } - (MathNumber::Integer(self_num), MathNumber::Decimal(other_num)) => { - Some(OwnedLiteral::LFloat((self_num as f64) + other_num)) - } - (MathNumber::Decimal(self_num), MathNumber::Integer(other_num)) => { - Some(OwnedLiteral::LFloat(self_num + (other_num as f64))) - } - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/renderer/math.rs b/src/renderer/math.rs new file mode 100644 index 0000000..02b305e --- /dev/null +++ b/src/renderer/math.rs @@ -0,0 +1,76 @@ +use crate::parser::OwnedLiteral; +use std::convert::TryInto; +use std::ops::Add; + +#[derive(Debug)] +pub enum MathNumber { + Integer(i128), + Decimal(f64), + Failure, +} + +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::LPositiveInteger(num) => { + return MathNumber::Integer((*num).try_into().unwrap()) + } + OwnedLiteral::LNegativeInteger(num) => return MathNumber::Integer((*num).into()), + OwnedLiteral::LFloat(num) => return MathNumber::Decimal(*num), + } + } +} + +impl Add for MathNumber { + type Output = Option; + + fn add(self, other: MathNumber) -> Self::Output { + match (self, other) { + (MathNumber::Failure, _) | (_, MathNumber::Failure) => None, + (MathNumber::Integer(self_num), MathNumber::Integer(other_num)) => { + math_ints(self_num, other_num, std::ops::Add::add) + } + (MathNumber::Decimal(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat(self_num + other_num)) + } + (MathNumber::Integer(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat((self_num as f64) + other_num)) + } + (MathNumber::Decimal(self_num), MathNumber::Integer(other_num)) => { + Some(OwnedLiteral::LFloat(self_num + (other_num as f64))) + } + } + } +} + +/// For math operations that take in integers and return integers +/// (add, subtract, multiply) +pub fn math_ints(left: L, right: R, operation: F) -> Option +where + L: Into, + R: Into, + F: Fn(i128, i128) -> i128, +{ + let result = operation(left.into(), right.into()); + std::convert::TryInto::::try_into(result) + .map(OwnedLiteral::LPositiveInteger) + .ok() + .or(std::convert::TryInto::::try_into(result) + .map(OwnedLiteral::LNegativeInteger) + .ok()) +} + +// /// For math operations that take in integers and return integers +// /// (add, subtract, multiply) +// fn math_floats(left: L, right: R, operation: F) -> Option +// where +// L: Into, +// R: Into, +// F: Fn(f64, f64) -> f64, +// { +// let result = operation(left.into(), right.into()); +// std::convert::TryInto::::try_into(result) +// .map(OwnedLiteral::LFloat) +// .ok() +// } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index f773381..cf5ec08 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -5,6 +5,7 @@ mod context_element; mod errors; mod inline_partial_tree; mod iteration_context; +mod math; mod parameters_context; mod renderer; mod select_context; @@ -21,7 +22,7 @@ pub use context_element::Walkable; pub use errors::CompileError; pub use errors::RenderError; pub use errors::WalkError; -pub use parameters_context::math_ints; +pub use math::MathNumber; pub use renderer::compile_template; pub use renderer::DustRenderer; pub use select_context::SelectContext; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 85a422d..23b720d 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -7,6 +7,7 @@ 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::DustRenderer; use crate::renderer::Loopable; @@ -18,7 +19,6 @@ 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> { @@ -334,85 +334,12 @@ impl CompareContextElement for OwnedLiteral { // type. match other.to_any().downcast_ref::() { None => other.math_add(self), - Some(other_literal) => { - match (self, other_literal) { - (OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None, - ( - OwnedLiteral::LPositiveInteger(self_num), - OwnedLiteral::LPositiveInteger(other_num), - ) => math_ints(*self_num, *other_num, std::ops::Add::add) - .map(IceResult::from_owned), - ( - OwnedLiteral::LNegativeInteger(self_num), - OwnedLiteral::LNegativeInteger(other_num), - ) => math_ints(*self_num, *other_num, std::ops::Add::add) - .map(IceResult::from_owned), - ( - OwnedLiteral::LPositiveInteger(self_num), - OwnedLiteral::LNegativeInteger(other_num), - ) => math_ints(*self_num, *other_num, std::ops::Add::add) - .map(IceResult::from_owned), - ( - OwnedLiteral::LNegativeInteger(self_num), - OwnedLiteral::LPositiveInteger(other_num), - ) => math_ints(*self_num, *other_num, std::ops::Add::add) - .map(IceResult::from_owned), - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => Some( - IceResult::from_owned(OwnedLiteral::LFloat(self_num + other_num)), - ), - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LPositiveInteger(other_num)) => { - Some(IceResult::from_owned(OwnedLiteral::LFloat( - self_num + (*other_num as f64), - ))) - } - (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LFloat(other_num)) => { - Some(IceResult::from_owned(OwnedLiteral::LFloat( - (*self_num as f64) + other_num, - ))) - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LNegativeInteger(other_num)) => { - Some(IceResult::from_owned(OwnedLiteral::LFloat( - self_num + (*other_num as f64), - ))) - } - (OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LFloat(other_num)) => { - Some(IceResult::from_owned(OwnedLiteral::LFloat( - (*self_num as f64) + other_num, - ))) - } - } - } + 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), + }, } } } - -/// For math operations that take in integers and return integers -/// (add, subtract, multiply) -pub fn math_ints(left: L, right: R, operation: F) -> Option -where - L: Into, - R: Into, - F: Fn(i128, i128) -> i128, -{ - let result = operation(left.into(), right.into()); - std::convert::TryInto::::try_into(result) - .map(OwnedLiteral::LPositiveInteger) - .ok() - .or(std::convert::TryInto::::try_into(result) - .map(OwnedLiteral::LNegativeInteger) - .ok()) -} - -// /// For math operations that take in integers and return integers -// /// (add, subtract, multiply) -// fn math_floats(left: L, right: R, operation: F) -> Option -// where -// L: Into, -// R: Into, -// F: Fn(f64, f64) -> f64, -// { -// let result = operation(left.into(), right.into()); -// std::convert::TryInto::::try_into(result) -// .map(OwnedLiteral::LFloat) -// .ok() -// } From 241f6c04e40b65b430088b0802d414765e10f929 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 19:15:51 -0400 Subject: [PATCH 18/33] Rename JsonNumber to ComparisonNumber. --- src/bin.rs | 81 +++++++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index d0695ce..8401cbd 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -419,25 +419,25 @@ impl CompareContextElement for serde_json::Value { } #[derive(Debug)] -enum JsonNumber { +enum ComparisonNumber { UnsignedInteger(u64), SignedInteger(i64), Decimal(f64), Failure, } -impl From<&serde_json::Value> for JsonNumber { +impl From<&serde_json::Value> for ComparisonNumber { /// Convert from a JSON value to a number for comparison based on /// the logic described at /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than fn from(original: &serde_json::Value) -> Self { match original { - serde_json::Value::Null => JsonNumber::UnsignedInteger(0), + serde_json::Value::Null => ComparisonNumber::UnsignedInteger(0), serde_json::Value::Bool(boolean) => { if *boolean { - JsonNumber::UnsignedInteger(1) + ComparisonNumber::UnsignedInteger(1) } else { - JsonNumber::UnsignedInteger(0) + ComparisonNumber::UnsignedInteger(0) } } serde_json::Value::Number(num) => num.into(), @@ -452,53 +452,53 @@ impl From<&serde_json::Value> for JsonNumber { } } -impl From<&String> for JsonNumber { +impl From<&String> for ComparisonNumber { fn from(original: &String) -> Self { match original.parse::() { - Ok(num) => return JsonNumber::UnsignedInteger(num), + Ok(num) => return ComparisonNumber::UnsignedInteger(num), Err(_) => (), }; match original.parse::() { - Ok(num) => return JsonNumber::SignedInteger(num), + Ok(num) => return ComparisonNumber::SignedInteger(num), Err(_) => (), }; match original.parse::() { - Ok(num) => return JsonNumber::Decimal(num), + Ok(num) => return ComparisonNumber::Decimal(num), Err(_) => (), }; - JsonNumber::Failure + ComparisonNumber::Failure } } -impl From<&serde_json::Number> for JsonNumber { +impl From<&serde_json::Number> for ComparisonNumber { fn from(original: &serde_json::Number) -> Self { match original.as_u64() { - Some(num) => return JsonNumber::UnsignedInteger(num), + Some(num) => return ComparisonNumber::UnsignedInteger(num), None => (), }; match original.as_i64() { - Some(num) => return JsonNumber::SignedInteger(num), + Some(num) => return ComparisonNumber::SignedInteger(num), None => (), }; match original.as_f64() { - Some(num) => return JsonNumber::Decimal(num), + Some(num) => return ComparisonNumber::Decimal(num), None => (), }; - JsonNumber::Failure + ComparisonNumber::Failure } } -impl From<&OwnedLiteral> for JsonNumber { +impl From<&OwnedLiteral> for ComparisonNumber { fn from(original: &OwnedLiteral) -> Self { match original { - OwnedLiteral::LPositiveInteger(num) => JsonNumber::UnsignedInteger(*num), - OwnedLiteral::LNegativeInteger(num) => JsonNumber::SignedInteger(*num), + OwnedLiteral::LPositiveInteger(num) => ComparisonNumber::UnsignedInteger(*num), + OwnedLiteral::LNegativeInteger(num) => ComparisonNumber::SignedInteger(*num), OwnedLiteral::LString(text) => text.into(), OwnedLiteral::LFloat(num) => { if num.is_nan() { - JsonNumber::Failure + ComparisonNumber::Failure } else { - JsonNumber::Decimal(*num) + ComparisonNumber::Decimal(*num) } } } @@ -512,49 +512,56 @@ impl From<&OwnedLiteral> for JsonNumber { /// strings fn compare_json_numbers(self_input: S, other_input: O) -> Option where - S: Into, - O: Into, + S: Into, + O: Into, { - let self_number: JsonNumber = self_input.into(); - let other_number: JsonNumber = other_input.into(); + let self_number: ComparisonNumber = self_input.into(); + let other_number: ComparisonNumber = other_input.into(); match (self_number, other_number) { - (JsonNumber::Failure, _) => return None, - (_, JsonNumber::Failure) => return None, - (JsonNumber::UnsignedInteger(self_num), JsonNumber::UnsignedInteger(other_num)) => { - return self_num.partial_cmp(&other_num) - } - (JsonNumber::UnsignedInteger(self_num), JsonNumber::SignedInteger(other_num)) => { + (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); } } - (JsonNumber::UnsignedInteger(self_num), JsonNumber::Decimal(other_num)) => { + (ComparisonNumber::UnsignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { return (self_num as f64).partial_cmp(&other_num) } - (JsonNumber::SignedInteger(self_num), JsonNumber::UnsignedInteger(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); } } - (JsonNumber::SignedInteger(self_num), JsonNumber::SignedInteger(other_num)) => { + (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::SignedInteger(other_num)) => { return self_num.partial_cmp(&other_num) } - (JsonNumber::SignedInteger(self_num), JsonNumber::Decimal(other_num)) => { + (ComparisonNumber::SignedInteger(self_num), ComparisonNumber::Decimal(other_num)) => { return (self_num as f64).partial_cmp(&other_num) } - (JsonNumber::Decimal(self_num), JsonNumber::UnsignedInteger(other_num)) => { + (ComparisonNumber::Decimal(self_num), ComparisonNumber::UnsignedInteger(other_num)) => { return self_num.partial_cmp(&(other_num as f64)) } - (JsonNumber::Decimal(self_num), JsonNumber::SignedInteger(other_num)) => { + (ComparisonNumber::Decimal(self_num), ComparisonNumber::SignedInteger(other_num)) => { return self_num.partial_cmp(&(other_num as f64)) } - (JsonNumber::Decimal(self_num), JsonNumber::Decimal(other_num)) => { + (ComparisonNumber::Decimal(self_num), ComparisonNumber::Decimal(other_num)) => { return self_num.partial_cmp(&other_num) } } From 11096d7ecec540249efb105e5ca10cb99b96dda6 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 19:28:45 -0400 Subject: [PATCH 19/33] Bodiless math implementation. --- src/renderer/renderer.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 1ad71c5..aae1fd0 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -656,9 +656,14 @@ impl<'a> DustRenderer<'a> { ParametersContext::new(self, breadcrumbs, ¶meterized_block.params, None); match ¶meterized_block.contents { None => { - // TODO: math without body, calculate the value and render it - // Calculate the value - // Render + return self + .perform_math_operation(breadcrumbs, ¶m_map) + .map(|final_val| { + final_val + .get_context_element_reference() + .render(&Vec::new()) + }) + .unwrap_or(Ok("".to_owned())); } Some(_) => { // TODO: math with body, calculate the value and use it like a select block @@ -931,7 +936,7 @@ impl<'a> DustRenderer<'a> { /// Performs a math operation (add, subtract, multiply, divide, /// mod, abs, floor, ceil) and returns the result or None if /// nothing should be rendered. - fn performance_math_operation( + fn perform_math_operation( &'a self, breadcrumbs: &'a Vec>, math_parameters: &'a ParametersContext<'a>, @@ -959,8 +964,11 @@ impl<'a> DustRenderer<'a> { .map(|ice| ice.get_context_element_reference()) }); - match method_rendered.as_str() { - "add" => todo!(), + return match method_rendered.as_str() { + "add" => match (left_side_ce, right_side_ce) { + (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, + (Some(Ok(l)), Some(Ok(r))) => l.math_add(r), + }, "subtract" => todo!(), "multiply" => todo!(), "divide" => todo!(), @@ -968,10 +976,8 @@ impl<'a> DustRenderer<'a> { "abs" => todo!(), "floor" => todo!(), "ceil" => todo!(), - _ => return None, - } - - todo!() + _ => None, + }; } } From 9eb70f436cae2013f7acfa4f6a0ad767da35d284 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 19:39:31 -0400 Subject: [PATCH 20/33] Starting a type casting trait --- src/bin.rs | 7 +++++++ src/renderer/context_element.rs | 5 +++++ src/renderer/mod.rs | 1 + src/renderer/parameters_context.rs | 7 +++++++ src/renderer/renderer.rs | 7 +++++++ 5 files changed, 27 insertions(+) diff --git a/src/bin.rs b/src/bin.rs index 8401cbd..5c36944 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -5,6 +5,7 @@ use parser::Filter; use parser::OwnedLiteral; use parser::Template; use renderer::compile_template; +use renderer::Castable; use renderer::CompileError; use renderer::ContextElement; use renderer::DustRenderer; @@ -312,6 +313,12 @@ impl Loopable for serde_json::Value { } } +impl Castable for serde_json::Value { + fn cast_to_type<'a>(&'a self, target: &str) -> Option> { + todo!() + } +} + impl CompareContextElement for serde_json::Value { fn equals(&self, other: &dyn ContextElement) -> bool { // Handle other serde_json::Value diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index 5ef6e3c..7edd71d 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -17,6 +17,7 @@ pub trait ContextElement: + CompareContextElement + FromContextElement + IntoRcIce + + Castable { } @@ -54,6 +55,10 @@ pub trait Loopable { fn get_loop_elements(&self) -> Vec<&dyn ContextElement>; } +pub trait Castable { + fn cast_to_type<'a>(&'a self, target: &str) -> Option>; +} + pub trait CastToAny { fn to_any(&self) -> &dyn Any; } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index cf5ec08..571a02b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -11,6 +11,7 @@ mod renderer; mod select_context; mod walking; +pub use context_element::Castable; pub use context_element::CompareContextElement; pub use context_element::ContextElement; pub use context_element::IceResult; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 23b720d..026cf68 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -9,6 +9,7 @@ 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; @@ -167,6 +168,12 @@ impl Walkable for OwnedLiteral { } } +impl Castable for OwnedLiteral { + fn cast_to_type<'a>(&'a self, target: &str) -> Option> { + todo!() + } +} + impl CompareContextElement for OwnedLiteral { fn equals(&self, other: &dyn ContextElement) -> bool { // If its an OwnedLiteral then compare them directly, diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index aae1fd0..e282791 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -964,6 +964,13 @@ impl<'a> DustRenderer<'a> { .map(|ice| ice.get_context_element_reference()) }); + // TODO convert string to number or render NaN + + println!( + "Doing {:?} {:?} {:?}", + left_side, method_rendered, right_side + ); + return match method_rendered.as_str() { "add" => match (left_side_ce, right_side_ce) { (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, From dcbf8e83f609bd0b947126af15bd520a2a83c15e Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 19:55:40 -0400 Subject: [PATCH 21/33] Implement number casting. --- src/bin.rs | 17 ++++++++++++++++- src/renderer/parameters_context.rs | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 5c36944..fdde0a1 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -315,7 +315,22 @@ impl Loopable for serde_json::Value { impl Castable for serde_json::Value { fn cast_to_type<'a>(&'a self, target: &str) -> Option> { - todo!() + match (self, target) { + (serde_json::Value::String(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(), + (serde_json::Value::Number(_), "number") => Some(IceResult::from_borrowed(self)), + (_, _) => panic!("Unimplemented cast"), + } } } diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 026cf68..c1240b5 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -170,7 +170,24 @@ impl Walkable for OwnedLiteral { impl Castable for OwnedLiteral { fn cast_to_type<'a>(&'a self, target: &str) -> Option> { - todo!() + 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::LPositiveInteger(_), "number") => Some(IceResult::from_borrowed(self)), + (OwnedLiteral::LNegativeInteger(_), "number") => Some(IceResult::from_borrowed(self)), + (OwnedLiteral::LFloat(_), "number") => Some(IceResult::from_borrowed(self)), + (_, _) => panic!("Unimplemented cast"), + } } } From e6c17fb603c4255c1cfba86330d3237ab58beab2 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 20:02:48 -0400 Subject: [PATCH 22/33] Bodiless math is now working in the simple cases. --- src/renderer/renderer.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index e282791..50f592c 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -957,24 +957,29 @@ impl<'a> DustRenderer<'a> { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) + .map(|ce| ce.cast_to_type("number")) }); let right_side_ce = right_side.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) + .map(|ce| ce.cast_to_type("number")) }); - // TODO convert string to number or render NaN - - println!( - "Doing {:?} {:?} {:?}", - left_side, method_rendered, right_side - ); + // println!( + // "Doing {:?} {:?} {:?}", + // left_side, method_rendered, right_side + // ); return match method_rendered.as_str() { "add" => match (left_side_ce, right_side_ce) { (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, - (Some(Ok(l)), Some(Ok(r))) => l.math_add(r), + (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( + OwnedLiteral::LString("NaN".to_owned()), + )), + (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l + .get_context_element_reference() + .math_add(r.get_context_element_reference()), }, "subtract" => todo!(), "multiply" => todo!(), From 28a4fea96ff59e1d5c4126c9496eeb437b8e3ddb Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 21:13:16 -0400 Subject: [PATCH 23/33] Remove RValue from ParametersContext so I can re-use it for calculated values from math. --- src/renderer/parameters_context.rs | 43 +++++++++++++++--------------- src/renderer/renderer.rs | 2 ++ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index c1240b5..2f73b29 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; #[derive(Debug)] pub struct ParametersContext<'a> { parent: Option<&'a ParametersContext<'a>>, - params: HashMap<&'a str, (&'a RValue<'a>, Option>)>, + params: HashMap<&'a str, Option>>, } impl<'a> ParametersContext<'a> { @@ -42,26 +42,25 @@ impl<'a> ParametersContext<'a> { // 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(); + let rendered_params: HashMap<&'a str, 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, v) + }) + .collect(); ParametersContext { parent: parent, @@ -90,7 +89,7 @@ impl<'a> IntoContextElement for ParametersContext<'a> { impl<'a> Walkable for ParametersContext<'a> { fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { - match self.params.get(segment).map(|(_rvalue, bte)| bte) { + match self.params.get(segment) { Some(Some(bte)) => Ok(bte.borrow()), Some(None) => Err(WalkError::CantWalk), None => self diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 50f592c..1a41586 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -668,7 +668,9 @@ impl<'a> DustRenderer<'a> { Some(_) => { // TODO: math with body, calculate the value and use it like a select block // Calculate the value + let calculated_value = self.perform_math_operation(breadcrumbs, ¶m_map); // Generate a ParametersContext with the result of the math operation as key + // calculate are_any_checks_true // Generate a SelectContext // render_maybe_body From d9f10290f51146172ffb9c6afb8175651c3faaf6 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 21:28:21 -0400 Subject: [PATCH 24/33] Math helper nearly done, test almost passes. --- src/renderer/parameters_context.rs | 10 +++++++ src/renderer/renderer.rs | 47 +++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 2f73b29..4dabd9a 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -68,6 +68,16 @@ impl<'a> ParametersContext<'a> { } } + pub fn from_values( + parent: Option<&'a ParametersContext<'a>>, + params: HashMap<&'a str, Option>>, + ) -> Self { + ParametersContext { + parent: parent, + params: params, + } + } + pub fn contains_key(&self, segment: &str) -> bool { self.params.contains_key(segment) || self diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 1a41586..002e78e 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -665,15 +665,54 @@ impl<'a> DustRenderer<'a> { }) .unwrap_or(Ok("".to_owned())); } - Some(_) => { - // TODO: math with body, calculate the value and use it like a select block + Some(body) => { // Calculate the value - let calculated_value = self.perform_math_operation(breadcrumbs, ¶m_map); - // Generate a ParametersContext with the result of the math operation as key + let calculated_value = + match self.perform_math_operation(breadcrumbs, ¶m_map) { + None => return Ok("".to_owned()), + Some(val) => val, + }; + // Generate a ParametersContext with the result of the math operation as key + let converted_value: BreadcrumbTreeElement<'_> = calculated_value.into(); + let calculated_param_map: HashMap<&str, Option>> = + vec![("key", Some(converted_value))].into_iter().collect(); + let calculated_context = + ParametersContext::from_values(None, calculated_param_map); // calculate are_any_checks_true + let are_any_checks_true = body + .elements + .iter() + .filter_map(|te| match te { + TemplateElement::TETag(dt) => match dt { + DustTag::DTHelperEquals(_) + | DustTag::DTHelperNotEquals(_) + | DustTag::DTHelperGreaterThan(_) + | DustTag::DTHelperLessThan(_) + | DustTag::DTHelperGreaterThanOrEquals(_) + | DustTag::DTHelperLessThanOrEquals(_) => Some(dt), + _ => None, + }, + _ => None, + }) + .map(|dt| { + self.perform_comparison_check( + dt, + new_breadcrumbs_ref, + Some(&calculated_context), + ) + }) + .any(|check_result| check_result.unwrap_or(false)); // Generate a SelectContext + let select_context = + SelectContext::new(&calculated_context, are_any_checks_true); // render_maybe_body + return self.render_maybe_body( + ¶meterized_block.contents, + new_breadcrumbs_ref, + blocks, + &mut Some(select_context), + ); } } } From b897656cef0648676905f0509e17bd1b112cd18a Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 21:37:11 -0400 Subject: [PATCH 25/33] Fix all of the math test except for block template-based methods. --- src/bin.rs | 1 + src/renderer/parameters_context.rs | 22 ++++------------------ 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index fdde0a1..9669d2e 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -336,6 +336,7 @@ impl Castable for serde_json::Value { impl CompareContextElement for serde_json::Value { fn equals(&self, other: &dyn ContextElement) -> bool { + // println!("Json equality check {:?} == {:?}", self, other); // Handle other serde_json::Value match other.to_any().downcast_ref::() { None => (), diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 4dabd9a..461b732 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -202,6 +202,7 @@ impl Castable for OwnedLiteral { 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 @@ -220,27 +221,12 @@ 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::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => { self_num == other_num } - (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::LNegativeInteger(self_num), OwnedLiteral::LString(other_text)) => { - &self_num.to_string() == other_text - } - (OwnedLiteral::LString(self_text), OwnedLiteral::LNegativeInteger(other_num)) => { - self_text == &other_num.to_string() - } - (OwnedLiteral::LFloat(self_num), OwnedLiteral::LString(other_text)) => { - &self_num.to_string() == other_text - } - (OwnedLiteral::LString(self_text), OwnedLiteral::LFloat(other_num)) => { - self_text == &other_num.to_string() - } (OwnedLiteral::LFloat(self_num), OwnedLiteral::LPositiveInteger(other_num)) => { *self_num == (*other_num as f64) } From 2c5c2d239c81d4119ad3bf69ceca3e8d2f21f143 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 22:03:48 -0400 Subject: [PATCH 26/33] Add a special case to not render anything if the method parameter to the math helper is a template to match the official DustJS implementation. --- src/renderer/parameters_context.rs | 56 ++++++++++++++++++------------ src/renderer/renderer.rs | 25 +++++++++++-- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 461b732..25695e0 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; #[derive(Debug)] pub struct ParametersContext<'a> { parent: Option<&'a ParametersContext<'a>>, - params: HashMap<&'a str, Option>>, + params: HashMap<&'a str, (&'a RValue<'a>, Option>)>, } impl<'a> ParametersContext<'a> { @@ -42,25 +42,26 @@ impl<'a> ParametersContext<'a> { // 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, 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, v) - }) - .collect(); + 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, @@ -70,7 +71,7 @@ impl<'a> ParametersContext<'a> { pub fn from_values( parent: Option<&'a ParametersContext<'a>>, - params: HashMap<&'a str, Option>>, + params: HashMap<&'a str, (&'a RValue<'a>, Option>)>, ) -> Self { ParametersContext { parent: parent, @@ -85,6 +86,17 @@ impl<'a> ParametersContext<'a> { .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> { @@ -99,7 +111,7 @@ impl<'a> IntoContextElement for ParametersContext<'a> { impl<'a> Walkable for ParametersContext<'a> { fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { - match self.params.get(segment) { + match self.params.get(segment).map(|(_rvalue, bte)| bte) { Some(Some(bte)) => Ok(bte.borrow()), Some(None) => Err(WalkError::CantWalk), None => self diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 002e78e..8801ed7 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -5,6 +5,7 @@ use crate::parser::Filter; use crate::parser::OwnedLiteral; use crate::parser::PartialNameElement; use crate::parser::Path; +use crate::parser::RValue; use crate::parser::Special; use crate::parser::Template; use crate::parser::TemplateElement; @@ -675,8 +676,13 @@ impl<'a> DustRenderer<'a> { // Generate a ParametersContext with the result of the math operation as key let converted_value: BreadcrumbTreeElement<'_> = calculated_value.into(); - let calculated_param_map: HashMap<&str, Option>> = - vec![("key", Some(converted_value))].into_iter().collect(); + let dummy_rvalue = RValue::RVLiteral(OwnedLiteral::LPositiveInteger(0)); + let calculated_param_map: HashMap< + &str, + (&RValue<'_>, Option>), + > = vec![("key", (&dummy_rvalue, Some(converted_value)))] + .into_iter() + .collect(); let calculated_context = ParametersContext::from_values(None, calculated_param_map); // calculate are_any_checks_true @@ -982,6 +988,21 @@ impl<'a> DustRenderer<'a> { breadcrumbs: &'a Vec>, math_parameters: &'a ParametersContext<'a>, ) -> Option> { + // Special case: if method is a template then do not render + // anything. This is to match the behavior of dustjs even + // though it works fine. + match math_parameters.get_original_rvalue("method") { + Some(RValue::RVTemplate(template)) => { + if template.iter().any(|pne| match pne { + PartialNameElement::PNReference { .. } => true, + PartialNameElement::PNSpan { .. } => false, + }) { + return None; + } + } + _ => (), + } + let method = match self.tap(breadcrumbs, math_parameters, "method") { None | Some(Err(_)) => return None, Some(Ok(ice_result)) => ice_result, From d8b8c223f05ebb67d5b93382891413102f04f718 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 22:12:00 -0400 Subject: [PATCH 27/33] Add stubs for the math functions. --- src/bin.rs | 28 ++++++++++++++++++++++++++++ src/renderer/context_element.rs | 7 +++++++ src/renderer/parameters_context.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/bin.rs b/src/bin.rs index 9669d2e..f611a97 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -439,6 +439,34 @@ impl CompareContextElement for serde_json::Value { .map(IceResult::from_owned), } } + + fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_abs<'a>(&self) -> Option> { + todo!() + } + + fn math_floor<'a>(&self) -> Option> { + todo!() + } + + fn math_ceil<'a>(&self) -> Option> { + todo!() + } } #[derive(Debug)] diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index 7edd71d..fe78473 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -69,6 +69,13 @@ pub trait CompareContextElement: CastToAny { fn partial_compare(&self, other: &dyn ContextElement) -> Option; fn math_add<'a>(&self, other: &dyn ContextElement) -> Option>; + fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option>; + fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option>; + fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option>; + fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option>; + fn math_abs<'a>(&self) -> Option>; + fn math_floor<'a>(&self) -> Option>; + fn math_ceil<'a>(&self) -> Option>; } impl CastToAny for C { diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 25695e0..fa747f9 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -373,4 +373,32 @@ impl CompareContextElement for OwnedLiteral { }, } } + + fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option> { + todo!() + } + + fn math_abs<'a>(&self) -> Option> { + todo!() + } + + fn math_floor<'a>(&self) -> Option> { + todo!() + } + + fn math_ceil<'a>(&self) -> Option> { + todo!() + } } From 0a2e1b007c1f2fe89caa352fd3796d94bcab9081 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 22:23:40 -0400 Subject: [PATCH 28/33] Add stubs for MathNumber. --- src/renderer/context_element.rs | 36 +++++++++++++++++++++++++ src/renderer/math.rs | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index fe78473..56d194c 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -5,6 +5,10 @@ use crate::renderer::errors::WalkError; use crate::renderer::DustRenderer; use std::any::Any; use std::ops::Add; +use std::ops::Div; +use std::ops::Mul; +use std::ops::Rem; +use std::ops::Sub; use std::rc::Rc; use std::{cmp::Ordering, fmt::Debug}; @@ -104,6 +108,38 @@ impl<'a> Add<&'a dyn ContextElement> for &'a dyn ContextElement { } } +impl<'a> Sub<&'a dyn ContextElement> for &'a dyn ContextElement { + type Output = Option>; + + fn sub(self, other: &'a dyn ContextElement) -> Self::Output { + self.math_subtract(other) + } +} + +impl<'a> Mul<&'a dyn ContextElement> for &'a dyn ContextElement { + type Output = Option>; + + fn mul(self, other: &'a dyn ContextElement) -> Self::Output { + self.math_multiply(other) + } +} + +impl<'a> Div<&'a dyn ContextElement> for &'a dyn ContextElement { + type Output = Option>; + + fn div(self, other: &'a dyn ContextElement) -> Self::Output { + self.math_divide(other) + } +} + +impl<'a> Rem<&'a dyn ContextElement> for &'a dyn ContextElement { + type Output = Option>; + + fn rem(self, other: &'a dyn ContextElement) -> Self::Output { + self.math_modulus(other) + } +} + pub trait FromContextElement { fn from_context_element(&self) -> &dyn IntoContextElement; } diff --git a/src/renderer/math.rs b/src/renderer/math.rs index 02b305e..0c4456d 100644 --- a/src/renderer/math.rs +++ b/src/renderer/math.rs @@ -1,6 +1,10 @@ use crate::parser::OwnedLiteral; use std::convert::TryInto; use std::ops::Add; +use std::ops::Div; +use std::ops::Mul; +use std::ops::Rem; +use std::ops::Sub; #[derive(Debug)] pub enum MathNumber { @@ -44,6 +48,50 @@ impl Add for MathNumber { } } +impl Sub for MathNumber { + type Output = Option; + + fn sub(self, other: MathNumber) -> Self::Output { + todo!() + } +} + +impl Mul for MathNumber { + type Output = Option; + + fn mul(self, other: MathNumber) -> Self::Output { + todo!() + } +} + +impl Div for MathNumber { + type Output = Option; + + fn div(self, other: MathNumber) -> Self::Output { + todo!() + } +} + +impl Rem for MathNumber { + type Output = Option; + + fn rem(self, other: MathNumber) -> Self::Output { + todo!() + } +} + +impl MathNumber { + fn math_abs(&self) -> Option { + todo!() + } + fn math_floor(&self) -> Option { + todo!() + } + fn math_ceil(&self) -> Option { + todo!() + } +} + /// For math operations that take in integers and return integers /// (add, subtract, multiply) pub fn math_ints(left: L, right: R, operation: F) -> Option From f783f45d2467d0333b8eec169bd2f8fd8aceec97 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 22:32:30 -0400 Subject: [PATCH 29/33] Implement the 2-number math functions for MathNumber. --- src/renderer/math.rs | 64 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/src/renderer/math.rs b/src/renderer/math.rs index 0c4456d..cc35f7f 100644 --- a/src/renderer/math.rs +++ b/src/renderer/math.rs @@ -52,7 +52,21 @@ impl Sub for MathNumber { type Output = Option; fn sub(self, other: MathNumber) -> Self::Output { - todo!() + match (self, other) { + (MathNumber::Failure, _) | (_, MathNumber::Failure) => None, + (MathNumber::Integer(self_num), MathNumber::Integer(other_num)) => { + math_ints(self_num, other_num, std::ops::Sub::sub) + } + (MathNumber::Decimal(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat(self_num - other_num)) + } + (MathNumber::Integer(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat((self_num as f64) - other_num)) + } + (MathNumber::Decimal(self_num), MathNumber::Integer(other_num)) => { + Some(OwnedLiteral::LFloat(self_num - (other_num as f64))) + } + } } } @@ -60,7 +74,21 @@ impl Mul for MathNumber { type Output = Option; fn mul(self, other: MathNumber) -> Self::Output { - todo!() + match (self, other) { + (MathNumber::Failure, _) | (_, MathNumber::Failure) => None, + (MathNumber::Integer(self_num), MathNumber::Integer(other_num)) => { + math_ints(self_num, other_num, std::ops::Mul::mul) + } + (MathNumber::Decimal(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat(self_num * other_num)) + } + (MathNumber::Integer(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat((self_num as f64) * other_num)) + } + (MathNumber::Decimal(self_num), MathNumber::Integer(other_num)) => { + Some(OwnedLiteral::LFloat(self_num * (other_num as f64))) + } + } } } @@ -68,7 +96,21 @@ impl Div for MathNumber { type Output = Option; fn div(self, other: MathNumber) -> Self::Output { - todo!() + match (self, other) { + (MathNumber::Failure, _) | (_, MathNumber::Failure) => None, + (MathNumber::Integer(self_num), MathNumber::Integer(other_num)) => { + Some(OwnedLiteral::LFloat((self_num as f64) / (other_num as f64))) + } + (MathNumber::Decimal(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat(self_num / other_num)) + } + (MathNumber::Integer(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat((self_num as f64) / other_num)) + } + (MathNumber::Decimal(self_num), MathNumber::Integer(other_num)) => { + Some(OwnedLiteral::LFloat(self_num / (other_num as f64))) + } + } } } @@ -76,7 +118,21 @@ impl Rem for MathNumber { type Output = Option; fn rem(self, other: MathNumber) -> Self::Output { - todo!() + match (self, other) { + (MathNumber::Failure, _) | (_, MathNumber::Failure) => None, + (MathNumber::Integer(self_num), MathNumber::Integer(other_num)) => { + math_ints(self_num, other_num, std::ops::Rem::rem) + } + (MathNumber::Decimal(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat(self_num % other_num)) + } + (MathNumber::Integer(self_num), MathNumber::Decimal(other_num)) => { + Some(OwnedLiteral::LFloat((self_num as f64) % other_num)) + } + (MathNumber::Decimal(self_num), MathNumber::Integer(other_num)) => { + Some(OwnedLiteral::LFloat(self_num % (other_num as f64))) + } + } } } From fdd467298a2176ec3bebaf1ae4e05c6c6478f77b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 22:46:03 -0400 Subject: [PATCH 30/33] Finish the MathNumber implementations. --- src/renderer/math.rs | 53 +++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/renderer/math.rs b/src/renderer/math.rs index cc35f7f..9a74f24 100644 --- a/src/renderer/math.rs +++ b/src/renderer/math.rs @@ -1,4 +1,5 @@ use crate::parser::OwnedLiteral; +use std::convert::TryFrom; use std::convert::TryInto; use std::ops::Add; use std::ops::Div; @@ -138,13 +139,25 @@ impl Rem for MathNumber { impl MathNumber { fn math_abs(&self) -> Option { - todo!() + match self { + MathNumber::Failure => None, + MathNumber::Integer(num) => num.abs().try_into().ok(), + MathNumber::Decimal(num) => Some(OwnedLiteral::LFloat(num.abs())), + } } fn math_floor(&self) -> Option { - todo!() + match self { + MathNumber::Failure => None, + MathNumber::Integer(num) => (*num).try_into().ok(), + MathNumber::Decimal(num) => Some(OwnedLiteral::LFloat(num.floor())), + } } fn math_ceil(&self) -> Option { - todo!() + match self { + MathNumber::Failure => None, + MathNumber::Integer(num) => (*num).try_into().ok(), + MathNumber::Decimal(num) => Some(OwnedLiteral::LFloat(num.ceil())), + } } } @@ -156,25 +169,19 @@ where R: Into, F: Fn(i128, i128) -> i128, { - let result = operation(left.into(), right.into()); - std::convert::TryInto::::try_into(result) - .map(OwnedLiteral::LPositiveInteger) - .ok() - .or(std::convert::TryInto::::try_into(result) - .map(OwnedLiteral::LNegativeInteger) - .ok()) + operation(left.into(), right.into()).try_into().ok() } -// /// For math operations that take in integers and return integers -// /// (add, subtract, multiply) -// fn math_floats(left: L, right: R, operation: F) -> Option -// where -// L: Into, -// R: Into, -// F: Fn(f64, f64) -> f64, -// { -// let result = operation(left.into(), right.into()); -// std::convert::TryInto::::try_into(result) -// .map(OwnedLiteral::LFloat) -// .ok() -// } +impl TryFrom for OwnedLiteral { + type Error = &'static str; + + fn try_from(original: i128) -> Result { + std::convert::TryInto::::try_into(original) + .map(OwnedLiteral::LPositiveInteger) + .ok() + .or(std::convert::TryInto::::try_into(original) + .map(OwnedLiteral::LNegativeInteger) + .ok()) + .ok_or("Value does not fit into either u64 or i64") + } +} From 431b34dac7e59f1251cdb151e44ee6e604afe2d0 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 22:52:07 -0400 Subject: [PATCH 31/33] Wired up the OwnedLiterals to the math functions. --- src/renderer/math.rs | 6 +-- src/renderer/parameters_context.rs | 68 +++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/renderer/math.rs b/src/renderer/math.rs index 9a74f24..8161019 100644 --- a/src/renderer/math.rs +++ b/src/renderer/math.rs @@ -138,21 +138,21 @@ impl Rem for MathNumber { } impl MathNumber { - fn math_abs(&self) -> Option { + pub fn math_abs(&self) -> Option { match self { MathNumber::Failure => None, MathNumber::Integer(num) => num.abs().try_into().ok(), MathNumber::Decimal(num) => Some(OwnedLiteral::LFloat(num.abs())), } } - fn math_floor(&self) -> Option { + pub fn math_floor(&self) -> Option { match self { MathNumber::Failure => None, MathNumber::Integer(num) => (*num).try_into().ok(), MathNumber::Decimal(num) => Some(OwnedLiteral::LFloat(num.floor())), } } - fn math_ceil(&self) -> Option { + pub fn math_ceil(&self) -> Option { match self { MathNumber::Failure => None, MathNumber::Integer(num) => (*num).try_into().ok(), diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index fa747f9..897f404 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -375,30 +375,84 @@ impl CompareContextElement for OwnedLiteral { } fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option> { - todo!() + // 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> { - todo!() + // 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> { - todo!() + // 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> { - todo!() + // 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> { - todo!() + std::convert::Into::::into(self) + .math_abs() + .map(IceResult::from_owned) } fn math_floor<'a>(&self) -> Option> { - todo!() + std::convert::Into::::into(self) + .math_floor() + .map(IceResult::from_owned) } fn math_ceil<'a>(&self) -> Option> { - todo!() + std::convert::Into::::into(self) + .math_ceil() + .map(IceResult::from_owned) } } From cdd10576e8fa7b1b114b3a53948a194841b15232 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 22:55:38 -0400 Subject: [PATCH 32/33] Wire up serde_json for the math functions. --- src/bin.rs | 110 ++++++++++++++++++++++++++--- src/renderer/parameters_context.rs | 2 +- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index f611a97..3c4c8c2 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -441,31 +441,125 @@ impl CompareContextElement for serde_json::Value { } fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option> { - todo!() + let other_json = other.to_any().downcast_ref::(); + let other_literal = other.to_any().downcast_ref::(); + match (self, other_json, other_literal) { + // If its neither of those types, then it is unimplemented + (_, None, None) => panic!("Math operation on unimplemented type"), + // Since this is specifically for the math helper, non-primitives are not supported + (serde_json::Value::Array(_), _, _) + | (serde_json::Value::Object(_), _, _) + | (_, Some(serde_json::Value::Array(_)), _) + | (_, Some(serde_json::Value::Object(_)), _) => None, + // Strings also are ignored because this is specifically a math function + (serde_json::Value::String(_), _, _) + | (_, Some(serde_json::Value::String(_)), _) + | (_, _, Some(OwnedLiteral::LString(_))) => None, + // Handle other serde_json::Value + (_, Some(other_json_value), _) => (std::convert::Into::::into(self) + - std::convert::Into::::into(other_json_value)) + .map(IceResult::from_owned), + // Handle literals + (_, _, Some(other_literal)) => (std::convert::Into::::into(self) + - std::convert::Into::::into(other_literal)) + .map(IceResult::from_owned), + } } fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option> { - todo!() + let other_json = other.to_any().downcast_ref::(); + let other_literal = other.to_any().downcast_ref::(); + match (self, other_json, other_literal) { + // If its neither of those types, then it is unimplemented + (_, None, None) => panic!("Math operation on unimplemented type"), + // Since this is specifically for the math helper, non-primitives are not supported + (serde_json::Value::Array(_), _, _) + | (serde_json::Value::Object(_), _, _) + | (_, Some(serde_json::Value::Array(_)), _) + | (_, Some(serde_json::Value::Object(_)), _) => None, + // Strings also are ignored because this is specifically a math function + (serde_json::Value::String(_), _, _) + | (_, Some(serde_json::Value::String(_)), _) + | (_, _, Some(OwnedLiteral::LString(_))) => None, + // Handle other serde_json::Value + (_, Some(other_json_value), _) => (std::convert::Into::::into(self) + * std::convert::Into::::into(other_json_value)) + .map(IceResult::from_owned), + // Handle literals + (_, _, Some(other_literal)) => (std::convert::Into::::into(self) + * std::convert::Into::::into(other_literal)) + .map(IceResult::from_owned), + } } fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option> { - todo!() + let other_json = other.to_any().downcast_ref::(); + let other_literal = other.to_any().downcast_ref::(); + match (self, other_json, other_literal) { + // If its neither of those types, then it is unimplemented + (_, None, None) => panic!("Math operation on unimplemented type"), + // Since this is specifically for the math helper, non-primitives are not supported + (serde_json::Value::Array(_), _, _) + | (serde_json::Value::Object(_), _, _) + | (_, Some(serde_json::Value::Array(_)), _) + | (_, Some(serde_json::Value::Object(_)), _) => None, + // Strings also are ignored because this is specifically a math function + (serde_json::Value::String(_), _, _) + | (_, Some(serde_json::Value::String(_)), _) + | (_, _, Some(OwnedLiteral::LString(_))) => None, + // Handle other serde_json::Value + (_, Some(other_json_value), _) => (std::convert::Into::::into(self) + / std::convert::Into::::into(other_json_value)) + .map(IceResult::from_owned), + // Handle literals + (_, _, Some(other_literal)) => (std::convert::Into::::into(self) + / std::convert::Into::::into(other_literal)) + .map(IceResult::from_owned), + } } fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option> { - todo!() + let other_json = other.to_any().downcast_ref::(); + let other_literal = other.to_any().downcast_ref::(); + match (self, other_json, other_literal) { + // If its neither of those types, then it is unimplemented + (_, None, None) => panic!("Math operation on unimplemented type"), + // Since this is specifically for the math helper, non-primitives are not supported + (serde_json::Value::Array(_), _, _) + | (serde_json::Value::Object(_), _, _) + | (_, Some(serde_json::Value::Array(_)), _) + | (_, Some(serde_json::Value::Object(_)), _) => None, + // Strings also are ignored because this is specifically a math function + (serde_json::Value::String(_), _, _) + | (_, Some(serde_json::Value::String(_)), _) + | (_, _, Some(OwnedLiteral::LString(_))) => None, + // Handle other serde_json::Value + (_, Some(other_json_value), _) => (std::convert::Into::::into(self) + % std::convert::Into::::into(other_json_value)) + .map(IceResult::from_owned), + // Handle literals + (_, _, Some(other_literal)) => (std::convert::Into::::into(self) + % std::convert::Into::::into(other_literal)) + .map(IceResult::from_owned), + } } fn math_abs<'a>(&self) -> Option> { - todo!() + std::convert::Into::::into(self) + .math_abs() + .map(IceResult::from_owned) } fn math_floor<'a>(&self) -> Option> { - todo!() + std::convert::Into::::into(self) + .math_floor() + .map(IceResult::from_owned) } fn math_ceil<'a>(&self) -> Option> { - todo!() + std::convert::Into::::into(self) + .math_ceil() + .map(IceResult::from_owned) } } @@ -630,7 +724,7 @@ impl From<&serde_json::Value> for MathNumber { } } serde_json::Value::Number(num) => num.into(), - serde_json::Value::String(text) => { + serde_json::Value::String(_) => { panic!("Strings should not be cast to numbers for math") } serde_json::Value::Array(_) => { diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 897f404..a886586 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -233,7 +233,7 @@ impl CompareContextElement for OwnedLiteral { OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LNegativeInteger(other_num), ) => self_num == other_num, - (OwnedLiteral::LString(self_text), _) | (_, OwnedLiteral::LString(self_text)) => { + (OwnedLiteral::LString(_self_text), _) | (_, OwnedLiteral::LString(_self_text)) => { false } (OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => { From c5a0ad6786999c99d1393422f786ed324202fb43 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 13 Jun 2020 23:01:29 -0400 Subject: [PATCH 33/33] Wire up the math functions to the renderer. --- src/renderer/renderer.rs | 64 +++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 8801ed7..cf48524 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -1043,13 +1043,63 @@ impl<'a> DustRenderer<'a> { .get_context_element_reference() .math_add(r.get_context_element_reference()), }, - "subtract" => todo!(), - "multiply" => todo!(), - "divide" => todo!(), - "mod" => todo!(), - "abs" => todo!(), - "floor" => todo!(), - "ceil" => todo!(), + "subtract" => match (left_side_ce, right_side_ce) { + (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, + (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( + OwnedLiteral::LString("NaN".to_owned()), + )), + (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l + .get_context_element_reference() + .math_subtract(r.get_context_element_reference()), + }, + "multiply" => match (left_side_ce, right_side_ce) { + (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, + (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( + OwnedLiteral::LString("NaN".to_owned()), + )), + (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l + .get_context_element_reference() + .math_multiply(r.get_context_element_reference()), + }, + "divide" => match (left_side_ce, right_side_ce) { + (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, + (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( + OwnedLiteral::LString("NaN".to_owned()), + )), + (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l + .get_context_element_reference() + .math_divide(r.get_context_element_reference()), + }, + "mod" => match (left_side_ce, right_side_ce) { + (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, + (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( + OwnedLiteral::LString("NaN".to_owned()), + )), + (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l + .get_context_element_reference() + .math_modulus(r.get_context_element_reference()), + }, + "abs" => match left_side_ce { + None | Some(Err(_)) => None, + Some(Ok(None)) => Some(IceResult::from_owned(OwnedLiteral::LString( + "NaN".to_owned(), + ))), + Some(Ok(Some(num))) => num.get_context_element_reference().math_abs(), + }, + "floor" => match left_side_ce { + None | Some(Err(_)) => None, + Some(Ok(None)) => Some(IceResult::from_owned(OwnedLiteral::LString( + "NaN".to_owned(), + ))), + Some(Ok(Some(num))) => num.get_context_element_reference().math_floor(), + }, + "ceil" => match left_side_ce { + None | Some(Err(_)) => None, + Some(Ok(None)) => Some(IceResult::from_owned(OwnedLiteral::LString( + "NaN".to_owned(), + ))), + Some(Ok(Some(num))) => num.get_context_element_reference().math_ceil(), + }, _ => None, }; }