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.
419 lines
17 KiB
Rust
419 lines
17 KiB
Rust
use crate::parser::Filter;
|
|
use crate::parser::KVPair;
|
|
use crate::parser::OwnedLiteral;
|
|
use crate::parser::RValue;
|
|
use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement;
|
|
use crate::renderer::context_element::CompareContextElement;
|
|
use crate::renderer::context_element::ContextElement;
|
|
use crate::renderer::context_element::IceResult;
|
|
use crate::renderer::context_element::IntoContextElement;
|
|
use crate::renderer::walking::walk_path;
|
|
use crate::renderer::DustRenderer;
|
|
use crate::renderer::Loopable;
|
|
use crate::renderer::RenderError;
|
|
use crate::renderer::Renderable;
|
|
use crate::renderer::Truthiness;
|
|
use crate::renderer::WalkError;
|
|
use crate::renderer::Walkable;
|
|
use std::borrow::Borrow;
|
|
use std::cmp::Ordering;
|
|
use std::collections::HashMap;
|
|
use std::convert::TryInto;
|
|
|
|
#[derive(Debug)]
|
|
pub struct ParametersContext<'a> {
|
|
parent: Option<&'a ParametersContext<'a>>,
|
|
params: HashMap<&'a str, (&'a RValue<'a>, Option<BreadcrumbTreeElement<'a>>)>,
|
|
}
|
|
|
|
impl<'a> ParametersContext<'a> {
|
|
pub fn new(
|
|
renderer: &DustRenderer,
|
|
breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
|
params: &'a Vec<KVPair>,
|
|
parent: Option<&'a ParametersContext<'a>>,
|
|
) -> Self {
|
|
// If the parameter is a Path, then we resolve it immediately
|
|
// to a context element because those are resolved using the
|
|
// breadcrumbs at the time of assignment.
|
|
//
|
|
// If the parameter is a template (for example `foo="{bar}"`)
|
|
// then those are resolved at the time of access rather than
|
|
// the time of assignment, so we leave them into their
|
|
// original IntoContextElement state.
|
|
let rendered_params: HashMap<&'a str, (&'a RValue<'a>, Option<BreadcrumbTreeElement<'a>>)> =
|
|
params
|
|
.iter()
|
|
.map(|kvpair| {
|
|
let k = kvpair.key;
|
|
let v: Option<BreadcrumbTreeElement<'a>> = match &kvpair.value {
|
|
RValue::RVLiteral(_owned_literal) => {
|
|
Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value))
|
|
}
|
|
RValue::RVPath(_path) => kvpair
|
|
.value
|
|
.into_context_element(renderer, breadcrumbs)
|
|
.map(std::convert::From::from),
|
|
RValue::RVTemplate(_template) => {
|
|
Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value))
|
|
}
|
|
};
|
|
(k, (&kvpair.value, v))
|
|
})
|
|
.collect();
|
|
|
|
ParametersContext {
|
|
parent: parent,
|
|
params: rendered_params,
|
|
}
|
|
}
|
|
|
|
pub fn contains_key(&self, segment: &str) -> bool {
|
|
self.params.contains_key(segment)
|
|
|| self
|
|
.parent
|
|
.map(|p| p.contains_key(segment))
|
|
.unwrap_or(false)
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoContextElement for ParametersContext<'a> {
|
|
fn into_context_element<'b>(
|
|
&'b self,
|
|
_renderer: &DustRenderer,
|
|
_breadcrumbs: &'b Vec<BreadcrumbTreeElement<'b>>,
|
|
) -> Option<IceResult<'b>> {
|
|
panic!("into_context_element cannot be called on pseudo elements");
|
|
}
|
|
}
|
|
|
|
impl<'a> Walkable for ParametersContext<'a> {
|
|
fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> {
|
|
match self.params.get(segment).map(|(_rvalue, bte)| bte) {
|
|
Some(Some(bte)) => Ok(bte.borrow()),
|
|
Some(None) => Err(WalkError::CantWalk),
|
|
None => self
|
|
.parent
|
|
.map(|p| p.walk(segment))
|
|
.unwrap_or(Err(WalkError::CantWalk)),
|
|
}
|
|
}
|
|
|
|
fn is_pseudo_element(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoContextElement for RValue<'a> {
|
|
fn into_context_element<'b>(
|
|
&'b self,
|
|
renderer: &DustRenderer,
|
|
breadcrumbs: &'b Vec<BreadcrumbTreeElement<'b>>,
|
|
) -> Option<IceResult<'b>> {
|
|
match self {
|
|
RValue::RVLiteral(owned_literal) => Some(IceResult::from_borrowed(owned_literal)),
|
|
RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys)
|
|
.map(|ice| ice.into_context_element(renderer, breadcrumbs))
|
|
.ok()
|
|
.flatten(),
|
|
RValue::RVTemplate(template) => renderer
|
|
.render_partial_name(template, breadcrumbs)
|
|
.map(|rendered| OwnedLiteral::LString(rendered))
|
|
.ok()
|
|
.map(|owned_literal| IceResult::from_owned(owned_literal)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Walkable for RValue<'a> {
|
|
fn walk(&self, _segment: &str) -> Result<&dyn IntoContextElement, WalkError> {
|
|
Err(WalkError::CantWalk)
|
|
}
|
|
}
|
|
|
|
impl ContextElement for OwnedLiteral {}
|
|
|
|
impl Truthiness for OwnedLiteral {
|
|
fn is_truthy(&self) -> bool {
|
|
match self {
|
|
OwnedLiteral::LString(text) => !text.is_empty(),
|
|
OwnedLiteral::LPositiveInteger(_num) => true,
|
|
OwnedLiteral::LNegativeInteger(_num) => true,
|
|
OwnedLiteral::LFloat(_num) => true,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Renderable for OwnedLiteral {
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
|
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()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Loopable for OwnedLiteral {
|
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
impl Walkable for OwnedLiteral {
|
|
fn walk(&self, _segment: &str) -> Result<&dyn IntoContextElement, WalkError> {
|
|
Err(WalkError::CantWalk)
|
|
}
|
|
}
|
|
|
|
impl CompareContextElement for OwnedLiteral {
|
|
fn equals(&self, other: &dyn ContextElement) -> bool {
|
|
// If its an OwnedLiteral then compare them directly,
|
|
// otherwise defer to the other type's implementation of
|
|
// CompareContextElement since the end user could add any
|
|
// type.
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
None => other.equals(self),
|
|
Some(other_literal) => match (self, other_literal) {
|
|
(OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => {
|
|
self_text == other_text
|
|
}
|
|
(
|
|
OwnedLiteral::LPositiveInteger(self_num),
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
|
) => self_num == other_num,
|
|
(
|
|
OwnedLiteral::LNegativeInteger(self_num),
|
|
OwnedLiteral::LNegativeInteger(other_num),
|
|
) => self_num == other_num,
|
|
(OwnedLiteral::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)
|
|
}
|
|
(OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LFloat(other_num)) => {
|
|
(*self_num as f64) == *other_num
|
|
}
|
|
(OwnedLiteral::LFloat(self_num), OwnedLiteral::LNegativeInteger(other_num)) => {
|
|
*self_num == (*other_num as f64)
|
|
}
|
|
(OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LFloat(other_num)) => {
|
|
(*self_num as f64) == *other_num
|
|
}
|
|
(
|
|
OwnedLiteral::LPositiveInteger(self_num),
|
|
OwnedLiteral::LNegativeInteger(other_num),
|
|
) => {
|
|
if *self_num < std::i64::MAX as u64 {
|
|
(*self_num as i64) == *other_num
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
(
|
|
OwnedLiteral::LNegativeInteger(self_num),
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
|
) => {
|
|
if *other_num < std::i64::MAX as u64 {
|
|
*self_num == (*other_num as i64)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
|
// If its an OwnedLiteral then compare them directly,
|
|
// otherwise defer to the other type's implementation of
|
|
// CompareContextElement since the end user could add any
|
|
// type.
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
None => match other.partial_compare(self) {
|
|
None => None,
|
|
Some(ord) => match ord {
|
|
Ordering::Equal => Some(Ordering::Equal),
|
|
Ordering::Greater => Some(Ordering::Less),
|
|
Ordering::Less => Some(Ordering::Greater),
|
|
},
|
|
},
|
|
Some(other_literal) => match (self, other_literal) {
|
|
(OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => {
|
|
self_text.partial_cmp(other_text)
|
|
}
|
|
(
|
|
OwnedLiteral::LPositiveInteger(self_num),
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
|
) => self_num.partial_cmp(other_num),
|
|
(
|
|
OwnedLiteral::LNegativeInteger(self_num),
|
|
OwnedLiteral::LNegativeInteger(other_num),
|
|
) => self_num.partial_cmp(other_num),
|
|
(OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => {
|
|
self_num.partial_cmp(other_num)
|
|
}
|
|
(OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LString(other_text)) => {
|
|
self_num.to_string().partial_cmp(other_text)
|
|
}
|
|
(OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => {
|
|
self_text.partial_cmp(&other_num.to_string())
|
|
}
|
|
(OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LString(other_text)) => {
|
|
self_num.to_string().partial_cmp(other_text)
|
|
}
|
|
(OwnedLiteral::LString(self_text), OwnedLiteral::LNegativeInteger(other_num)) => {
|
|
self_text.partial_cmp(&other_num.to_string())
|
|
}
|
|
(OwnedLiteral::LFloat(self_num), OwnedLiteral::LString(other_text)) => {
|
|
self_num.to_string().partial_cmp(other_text)
|
|
}
|
|
(OwnedLiteral::LString(self_text), OwnedLiteral::LFloat(other_num)) => {
|
|
self_text.partial_cmp(&other_num.to_string())
|
|
}
|
|
(OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LFloat(other_num)) => {
|
|
(*self_num as f64).partial_cmp(other_num)
|
|
}
|
|
(OwnedLiteral::LFloat(self_num), OwnedLiteral::LPositiveInteger(other_num)) => {
|
|
self_num.partial_cmp(&(*other_num as f64))
|
|
}
|
|
(OwnedLiteral::LNegativeInteger(self_num), OwnedLiteral::LFloat(other_num)) => {
|
|
(*self_num as f64).partial_cmp(other_num)
|
|
}
|
|
(OwnedLiteral::LFloat(self_num), OwnedLiteral::LNegativeInteger(other_num)) => {
|
|
self_num.partial_cmp(&(*other_num as f64))
|
|
}
|
|
(
|
|
OwnedLiteral::LPositiveInteger(self_num),
|
|
OwnedLiteral::LNegativeInteger(other_num),
|
|
) => {
|
|
if *self_num < std::i64::MAX as u64 {
|
|
(*self_num as i64).partial_cmp(other_num)
|
|
} else {
|
|
Some(Ordering::Greater)
|
|
}
|
|
}
|
|
(
|
|
OwnedLiteral::LNegativeInteger(self_num),
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
|
) => {
|
|
if *other_num < std::i64::MAX as u64 {
|
|
self_num.partial_cmp(&(*other_num as i64))
|
|
} else {
|
|
Some(Ordering::Less)
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn math_add<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
|
|
// 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::<Self>() {
|
|
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)
|
|
pub fn math_ints<L, R, F>(left: L, right: R, operation: F) -> Option<OwnedLiteral>
|
|
where
|
|
L: Into<i128>,
|
|
R: Into<i128>,
|
|
F: Fn(i128, i128) -> i128,
|
|
{
|
|
let result = operation(left.into(), right.into());
|
|
std::convert::TryInto::<u64>::try_into(result)
|
|
.map(OwnedLiteral::LPositiveInteger)
|
|
.ok()
|
|
.or(std::convert::TryInto::<i64>::try_into(result)
|
|
.map(OwnedLiteral::LNegativeInteger)
|
|
.ok())
|
|
}
|
|
|
|
// /// For math operations that take in integers and return integers
|
|
// /// (add, subtract, multiply)
|
|
// fn math_floats<L, R, F>(left: L, right: R, operation: F) -> Option<OwnedLiteral>
|
|
// where
|
|
// L: Into<f64>,
|
|
// R: Into<f64>,
|
|
// F: Fn(f64, f64) -> f64,
|
|
// {
|
|
// let result = operation(left.into(), right.into());
|
|
// std::convert::TryInto::<f64>::try_into(result)
|
|
// .map(OwnedLiteral::LFloat)
|
|
// .ok()
|
|
// }
|