You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

510 lines
20 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::comparison_number::compare_json_numbers;
use crate::renderer::comparison_number::ComparisonNumber;
use crate::renderer::context_element::CompareContextElement;
use crate::renderer::context_element::ContextElement;
use crate::renderer::context_element::IceResult;
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;
use crate::renderer::Renderable;
use crate::renderer::Sizable;
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 from_values(
parent: Option<&'a ParametersContext<'a>>,
params: HashMap<&'a str, (&'a RValue<'a>, Option<BreadcrumbTreeElement<'a>>)>,
) -> Self {
ParametersContext {
parent: parent,
params: 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)
}
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> {
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::LBoolean(boolean) => *boolean,
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::LBoolean(boolean) => Ok(boolean.to_string()),
OwnedLiteral::LString(text) => Ok(text.clone()),
OwnedLiteral::LPositiveInteger(num) => Ok(num.to_string()),
OwnedLiteral::LNegativeInteger(num) => Ok(num.to_string()),
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 Sizable for OwnedLiteral {
fn is_castable(&self) -> bool {
match self {
OwnedLiteral::LBoolean(_) => false,
OwnedLiteral::LFloat(_) => true,
OwnedLiteral::LPositiveInteger(_) => true,
OwnedLiteral::LNegativeInteger(_) => true,
OwnedLiteral::LString(_) => true,
}
}
fn get_size<'a>(&'a self) -> Option<IceResult<'a>> {
match self {
OwnedLiteral::LBoolean(_) => {
Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0)))
}
OwnedLiteral::LFloat(_) => Some(IceResult::from_borrowed(self)),
OwnedLiteral::LPositiveInteger(_) => Some(IceResult::from_borrowed(self)),
OwnedLiteral::LNegativeInteger(_) => Some(IceResult::from_borrowed(self)),
OwnedLiteral::LString(text) => Some(IceResult::from_owned(
OwnedLiteral::LPositiveInteger(text.len().try_into().unwrap()),
)),
}
}
}
impl Castable for OwnedLiteral {
fn cast_to_type<'a>(&'a self, target: &str) -> Option<IceResult<'a>> {
match (self, target) {
(OwnedLiteral::LString(text), "number") => text
.parse::<u64>()
.map(|num| IceResult::from_owned(OwnedLiteral::LPositiveInteger(num)))
.or_else(|_| {
text.parse::<i64>()
.map(|num| IceResult::from_owned(OwnedLiteral::LNegativeInteger(num)))
})
.or_else(|_| {
text.parse::<f64>()
.map(|num| IceResult::from_owned(OwnedLiteral::LFloat(num)))
})
.ok(),
(OwnedLiteral::LBoolean(boolean), "number") => {
if *boolean {
Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(1)))
} else {
Some(IceResult::from_owned(OwnedLiteral::LPositiveInteger(0)))
}
}
(OwnedLiteral::LPositiveInteger(_), "number") => Some(IceResult::from_borrowed(self)),
(OwnedLiteral::LNegativeInteger(_), "number") => Some(IceResult::from_borrowed(self)),
(OwnedLiteral::LFloat(_), "number") => Some(IceResult::from_borrowed(self)),
(OwnedLiteral::LString(_), "string") => Some(IceResult::from_borrowed(self)),
(OwnedLiteral::LBoolean(boolean), "string") => Some(IceResult::from_owned(
OwnedLiteral::LString(boolean.to_string()),
)),
(OwnedLiteral::LPositiveInteger(num), "string") => Some(IceResult::from_owned(
OwnedLiteral::LString(num.to_string()),
)),
(OwnedLiteral::LNegativeInteger(num), "string") => Some(IceResult::from_owned(
OwnedLiteral::LString(num.to_string()),
)),
(OwnedLiteral::LFloat(num), "string") => Some(IceResult::from_owned(
OwnedLiteral::LString(num.to_string()),
)),
(OwnedLiteral::LString(text), "boolean") => {
if text.is_empty() {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(false)))
} else {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(true)))
}
}
(OwnedLiteral::LBoolean(_), "boolean") => Some(IceResult::from_borrowed(self)),
(OwnedLiteral::LPositiveInteger(num), "boolean") => {
if *num == 0 {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(false)))
} else {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(true)))
}
}
(OwnedLiteral::LNegativeInteger(num), "boolean") => {
if *num == 0 {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(false)))
} else {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(true)))
}
}
(OwnedLiteral::LFloat(num), "boolean") => {
if *num == 0.0 || num.is_nan() {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(false)))
} else {
Some(IceResult::from_owned(OwnedLiteral::LBoolean(true)))
}
}
(_, _) => panic!("Unimplemented cast"),
}
}
}
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
// 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::LBoolean(self_bool), OwnedLiteral::LBoolean(other_bool)) => {
self_bool == other_bool
}
(OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => false,
(OwnedLiteral::LBoolean(_), _) | (_, OwnedLiteral::LBoolean(_)) => false,
(OwnedLiteral::LFloat(self_num), OwnedLiteral::LFloat(other_num)) => {
self_num == other_num
}
(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) {
// If they're both strings, compare them directly
(OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => {
self_text.partial_cmp(other_text)
}
// Otherwise, convert to numbers and compare them that way
(_, _) => return compare_json_numbers(self, other_literal),
},
}
}
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,
(_, _) => (std::convert::Into::<MathNumber>::into(self)
+ std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
},
}
}
fn math_subtract<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
// 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::<Self>() {
None => other.math_subtract(self),
Some(other_literal) => match (self, other_literal) {
(OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None,
(_, _) => (std::convert::Into::<MathNumber>::into(self)
- std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
},
}
}
fn math_multiply<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
// 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::<Self>() {
None => other.math_multiply(self),
Some(other_literal) => match (self, other_literal) {
(OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None,
(_, _) => (std::convert::Into::<MathNumber>::into(self)
* std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
},
}
}
fn math_divide<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
// 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::<Self>() {
None => other.math_divide(self),
Some(other_literal) => match (self, other_literal) {
(OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None,
(_, _) => (std::convert::Into::<MathNumber>::into(self)
/ std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
},
}
}
fn math_modulus<'a>(&self, other: &dyn ContextElement) -> Option<IceResult<'a>> {
// 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::<Self>() {
None => other.math_modulus(self),
Some(other_literal) => match (self, other_literal) {
(OwnedLiteral::LString(_), _) | (_, OwnedLiteral::LString(_)) => None,
(_, _) => (std::convert::Into::<MathNumber>::into(self)
% std::convert::Into::<MathNumber>::into(other_literal))
.map(IceResult::from_owned),
},
}
}
fn math_abs<'a>(&self) -> Option<IceResult<'a>> {
std::convert::Into::<MathNumber>::into(self)
.math_abs()
.map(IceResult::from_owned)
}
fn math_floor<'a>(&self) -> Option<IceResult<'a>> {
std::convert::Into::<MathNumber>::into(self)
.math_floor()
.map(IceResult::from_owned)
}
fn math_ceil<'a>(&self) -> Option<IceResult<'a>> {
std::convert::Into::<MathNumber>::into(self)
.math_ceil()
.map(IceResult::from_owned)
}
}
impl From<&OwnedLiteral> for ComparisonNumber {
fn from(original: &OwnedLiteral) -> Self {
match original {
OwnedLiteral::LBoolean(boolean) => {
if *boolean {
ComparisonNumber::UnsignedInteger(1)
} else {
ComparisonNumber::UnsignedInteger(0)
}
}
OwnedLiteral::LPositiveInteger(num) => ComparisonNumber::UnsignedInteger(*num),
OwnedLiteral::LNegativeInteger(num) => ComparisonNumber::SignedInteger(*num),
OwnedLiteral::LString(text) => text.into(),
OwnedLiteral::LFloat(num) => {
if num.is_nan() {
ComparisonNumber::Failure
} else {
ComparisonNumber::Decimal(*num)
}
}
}
}
}