Merge branch 'into_context_element' into render
commit
4ab311c178
@ -0,0 +1 @@
|
||||
Reference parameters are evaluated at the time of render, as opposed to direct parameters which are evaluated at the time of assignment.
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "Bob",
|
||||
"people": [
|
||||
{
|
||||
"name": "Alice",
|
||||
"petname": "rover"
|
||||
}
|
||||
],
|
||||
"truthy": "some truthy value",
|
||||
"other_petname": [
|
||||
{
|
||||
"petname": "spot"
|
||||
}
|
||||
],
|
||||
"array_petname": [
|
||||
{
|
||||
"petname": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
],
|
||||
"some_object": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"some_same_object": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"some_different_object": {
|
||||
"foo": "baz"
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{#people}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/people}
|
||||
{#people name="chris" pet="cat"}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/people}
|
||||
|
||||
Direct Parameters{~n}
|
||||
================={~n}
|
||||
{#people name="chris" pet=petname petname="whiskers"}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/people}
|
||||
{#people}
|
||||
{#truthy name="chris" pet=petname petname="whiskers"}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/truthy}
|
||||
{/people}
|
||||
{#people name="chris" pet=petname petname="whiskers"}
|
||||
{#other_petname}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/other_petname}
|
||||
{/people}
|
||||
|
||||
Reference Parameters{~n}
|
||||
===================={~n}
|
||||
{#people name="chris" pet="{petname}" petname="whiskers"}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/people}
|
||||
{#people}
|
||||
{#truthy name="chris" pet="{petname}" petname="whiskers"}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/truthy}
|
||||
{/people}
|
||||
{#people name="chris" pet="{petname}" petname="whiskers"}
|
||||
{#other_petname}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/other_petname}
|
||||
{/people}
|
||||
{! Can you have additional text in reference parameters, or just the reference !}
|
||||
{#people name="chris" pet="{petname}!" petname="whiskers"}
|
||||
{#other_petname}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/other_petname}
|
||||
{/people}
|
||||
{! Can you have filters !}
|
||||
{#people name="chris" pet="{petname|js}" petname="whiskers"}
|
||||
{#other_petname}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/other_petname}
|
||||
{/people}
|
||||
{! Can you go through multiple levels of references !}
|
||||
{#truthy name="chris" pet="{petname}" petname="{deeperpetname}" deeperpetname="fluffy"}
|
||||
Hello {name}, nice {pet}{~n}
|
||||
{/truthy}
|
||||
|
||||
Equality{~n}
|
||||
========{~n}
|
||||
{@eq key=some_object value=some_object}some_object equals some_object{:else}some_object does not equal some_object{/eq}{~n}
|
||||
{@eq key=some_object value=some_same_object}some_object equals some_same_object{:else}some_object does not equal some_same_object{/eq}{~n}
|
||||
{@eq key=some_object value="{some_object}"}some_object equals reference(some_object){:else}some_object does not equal reference(some_object){/eq}{~n}
|
||||
{@eq key="{some_object}" value="{some_object}"}reference(some_object) equals reference(some_object){:else}reference(some_object) does not equal reference(some_object){/eq}{~n}
|
||||
{@eq key="{some_object}" value="{some_same_object}"}reference(some_object) equals reference(some_same_object){:else}reference(some_object) does not equal reference(some_same_object){/eq}{~n}
|
||||
{@eq key="{some_object}" value="{some_different_object}"}reference(some_object) equals reference(some_different_object){:else}reference(some_object) does not equal reference(some_different_object){/eq}{~n}
|
@ -0,0 +1,59 @@
|
||||
use crate::renderer::context_element::ContextElement;
|
||||
use crate::renderer::context_element::IceResult;
|
||||
use crate::renderer::context_element::IntoContextElement;
|
||||
use crate::renderer::context_element::IntoRcIce;
|
||||
use std::borrow::Borrow;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BreadcrumbTreeElement<'a> {
|
||||
// Using Rc so that when we need to create BreadcrumbTrees with
|
||||
// the same BreadcrumbTreeElement but a different parent (for
|
||||
// example, when inserting behind the tail), we don't need to the
|
||||
// copy the already owned/malloc'd data.
|
||||
Owned(Rc<dyn IntoContextElement + 'a>),
|
||||
Borrowed(&'a dyn IntoContextElement),
|
||||
}
|
||||
|
||||
impl<'a> BreadcrumbTreeElement<'a> {
|
||||
pub fn from_owned<I: 'a + IntoContextElement>(val: I) -> BreadcrumbTreeElement<'a> {
|
||||
BreadcrumbTreeElement::Owned(Rc::new(val))
|
||||
}
|
||||
|
||||
pub fn from_borrowed(val: &'a dyn IntoContextElement) -> BreadcrumbTreeElement<'a> {
|
||||
BreadcrumbTreeElement::Borrowed(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a IceResult<'a>> for BreadcrumbTreeElement<'a> {
|
||||
fn from(inp: &'a IceResult<'a>) -> Self {
|
||||
match inp {
|
||||
IceResult::Owned(rc_ce) => {
|
||||
BreadcrumbTreeElement::from_borrowed(rc_ce.from_context_element())
|
||||
}
|
||||
IceResult::Borrowed(ce) => {
|
||||
BreadcrumbTreeElement::from_borrowed(ce.from_context_element())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<IceResult<'a>> for BreadcrumbTreeElement<'a> {
|
||||
fn from(inp: IceResult<'a>) -> Self {
|
||||
match inp {
|
||||
IceResult::Owned(rc_ce) => BreadcrumbTreeElement::Owned(rc_ce.into_rc_ice()),
|
||||
IceResult::Borrowed(ce) => {
|
||||
BreadcrumbTreeElement::from_borrowed(ce.from_context_element())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Borrow<dyn IntoContextElement + 'a> for BreadcrumbTreeElement<'a> {
|
||||
fn borrow(&self) -> &(dyn IntoContextElement + 'a) {
|
||||
match self {
|
||||
BreadcrumbTreeElement::Owned(ice) => ice.as_ref(),
|
||||
BreadcrumbTreeElement::Borrowed(ice) => *ice,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
use crate::parser::KVPair;
|
||||
use crate::parser::{Filter, OwnedLiteral, PartialNameElement, RValue};
|
||||
use crate::renderer::context_element::CompareContextElement;
|
||||
use crate::renderer::context_element::ContextElement;
|
||||
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::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
/// Copy the data from an RValue to an Owned struct
|
||||
///
|
||||
/// In order to get comparisons to work for our `ContextElement` trait
|
||||
/// objects, we need to be able to use `std::any::Any`. Unfortunately,
|
||||
/// `Any` requires that the structs do not have a lifetime (so they
|
||||
/// will have a `'static` lifetime. This means that we cannot have a
|
||||
/// `<'a>` appended to the struct type, so the struct cannot contain
|
||||
/// any borrows. Rather than impose the copy cost in the parser, we
|
||||
/// are imposing the cost of copying the data in the renderer because
|
||||
/// the parser has no reason to not be able to reference data from the
|
||||
/// input string.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OwnedRValue {
|
||||
RVPath(OwnedPath),
|
||||
RVTemplate(Vec<PartialNameElement>),
|
||||
RVLiteral(OwnedLiteral),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct OwnedPath {
|
||||
pub keys: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<&RValue<'_>> for OwnedRValue {
|
||||
fn from(original: &RValue<'_>) -> Self {
|
||||
match original {
|
||||
RValue::RVLiteral(literal) => OwnedRValue::RVLiteral(literal.clone()),
|
||||
RValue::RVTemplate(template) => OwnedRValue::RVTemplate(template.clone()),
|
||||
RValue::RVPath(path) => OwnedRValue::RVPath(OwnedPath {
|
||||
keys: path.keys.iter().map(|k| k.to_string()).collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParametersContext {
|
||||
params: HashMap<String, OwnedRValue>,
|
||||
breadcrumbs: Vec<Box<dyn IntoContextElement>>,
|
||||
}
|
||||
|
||||
impl ParametersContext {
|
||||
pub fn new(
|
||||
breadcrumbs: &Vec<&dyn IntoContextElement>,
|
||||
params: &Vec<KVPair>,
|
||||
) -> ParametersContext {
|
||||
let owned_params: HashMap<String, OwnedRValue> = params
|
||||
.iter()
|
||||
.map(|kvpair| (kvpair.key.to_string(), OwnedRValue::from(&kvpair.value)))
|
||||
.collect();
|
||||
let owned_breadcrumbs: Vec<Box<dyn IntoContextElement>> =
|
||||
breadcrumbs.iter().map(|ce| ce.clone_to_box()).collect();
|
||||
|
||||
ParametersContext {
|
||||
params: owned_params,
|
||||
breadcrumbs: owned_breadcrumbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextElement for ParametersContext {}
|
||||
|
||||
impl Truthiness for ParametersContext {
|
||||
fn is_truthy(&self) -> bool {
|
||||
// TODO: Would this even ever be called? Won't matter, but I'd
|
||||
// like to know. Since it is injected 1 above the current
|
||||
// context, we wouldn't be able to access it with `{.}`.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for ParametersContext {
|
||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||
// TODO: Would this even ever be called? Won't matter, but I'd
|
||||
// like to know. Since it is injected 1 above the current
|
||||
// context, we wouldn't be able to access it with `{.}`.
|
||||
Ok("[object Object]".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl Loopable for ParametersContext {
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
// TODO: Would this even ever be called? Won't matter, but I'd
|
||||
// like to know. Since it is injected 1 above the current
|
||||
// context, we wouldn't be able to access it with `{.}`.
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Walkable for ParametersContext {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> {
|
||||
let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?;
|
||||
match rval {
|
||||
OwnedRValue::RVPath(path) => walk_path(&self.breadcrumbs, &path.keys),
|
||||
OwnedRValue::RVTemplate(template) => Ok(template),
|
||||
OwnedRValue::RVLiteral(literal) => Ok(literal),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pseudo_element(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ParametersContext {
|
||||
fn clone(&self) -> Self {
|
||||
let new_params: HashMap<String, OwnedRValue> = self
|
||||
.params
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
let new_breadcrumbs: Vec<Box<dyn IntoContextElement>> = self
|
||||
.breadcrumbs
|
||||
.iter()
|
||||
.map(|bread| bread.clone_to_box())
|
||||
.collect();
|
||||
ParametersContext {
|
||||
params: new_params,
|
||||
breadcrumbs: new_breadcrumbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompareContextElement for ParametersContext {
|
||||
fn equals(&self, other: &dyn ContextElement) -> bool {
|
||||
// TODO: Does this ever happen? perhaps I should have a panic here.
|
||||
false
|
||||
}
|
||||
|
||||
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
||||
// TODO: Does this ever happen? perhaps I should have a panic here.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// println!("equals literal {:?} | {:?}", 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::LString(other_text)) => {
|
||||
&self_num.to_string() == other_text
|
||||
}
|
||||
(OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => {
|
||||
self_text == &other_num.to_string()
|
||||
}
|
||||
(
|
||||
OwnedLiteral::LPositiveInteger(self_num),
|
||||
OwnedLiteral::LPositiveInteger(other_num),
|
||||
) => self_num == other_num,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
||||
// println!("partial_compare literal {:?} | {:?}", 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 => 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::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::LPositiveInteger(self_num),
|
||||
OwnedLiteral::LPositiveInteger(other_num),
|
||||
) => self_num.partial_cmp(other_num),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContextElement for Vec<PartialNameElement> {
|
||||
fn into_context_element(
|
||||
&self,
|
||||
renderer: &DustRenderer,
|
||||
breadcrumbs: &Vec<&dyn IntoContextElement>,
|
||||
) -> &dyn ContextElement {
|
||||
// OwnedLiteral::LString(
|
||||
// renderer
|
||||
// .render_partial_name(self, breadcrumbs)
|
||||
// .expect("TODO: Make into_context_element return a RenderError"),
|
||||
// )
|
||||
// TODO
|
||||
&OwnedLiteral::LPositiveInteger(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Walkable for Vec<PartialNameElement> {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> {
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,94 @@
|
||||
use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement;
|
||||
use crate::renderer::context_element::IntoContextElement;
|
||||
use crate::renderer::WalkError;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
enum WalkResult<'a> {
|
||||
NoWalk,
|
||||
PartialWalk,
|
||||
FullyWalked(&'a dyn IntoContextElement),
|
||||
}
|
||||
|
||||
fn walk_path_from_single_level<'a, P>(
|
||||
context: &'a dyn IntoContextElement,
|
||||
path: &[P],
|
||||
) -> WalkResult<'a>
|
||||
where
|
||||
P: Borrow<str>,
|
||||
{
|
||||
if path.is_empty() {
|
||||
return WalkResult::FullyWalked(context);
|
||||
}
|
||||
|
||||
let mut walk_failure = WalkResult::NoWalk;
|
||||
let mut output = context;
|
||||
for elem in path.iter() {
|
||||
match output.walk(elem.borrow()) {
|
||||
Err(WalkError::CantWalk { .. }) => {
|
||||
return walk_failure;
|
||||
}
|
||||
Ok(new_val) => {
|
||||
walk_failure = WalkResult::PartialWalk;
|
||||
output = new_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WalkResult::FullyWalked(output)
|
||||
}
|
||||
|
||||
fn get_first_non_pseudo_element<'a>(
|
||||
breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
||||
) -> Option<&'a BreadcrumbTreeElement<'a>> {
|
||||
breadcrumbs
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|b| {
|
||||
!std::borrow::Borrow::<dyn IntoContextElement + 'a>::borrow(*b).is_pseudo_element()
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
||||
pub fn walk_path<'a, P>(
|
||||
breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
||||
path: &Vec<P>,
|
||||
) -> Result<&'a dyn IntoContextElement, WalkError>
|
||||
where
|
||||
P: Borrow<str>,
|
||||
{
|
||||
match (breadcrumbs.last(), path.first()) {
|
||||
(None, _) => return Err(WalkError::CantWalk),
|
||||
(Some(last_elem), None) => return Ok(last_elem.borrow()),
|
||||
(Some(_), Some(path_first)) if path_first.borrow() == "." => {
|
||||
let first_non_pseudo_element = get_first_non_pseudo_element(breadcrumbs);
|
||||
return match first_non_pseudo_element {
|
||||
None => Err(WalkError::CantWalk),
|
||||
Some(current_context) => {
|
||||
match walk_path_from_single_level(current_context.borrow(), &path[1..]) {
|
||||
// If no walking was done at all or we partially walked
|
||||
// then stop trying to find anything because '.' restricts
|
||||
// us to the current scope
|
||||
WalkResult::NoWalk | WalkResult::PartialWalk => Err(WalkError::CantWalk),
|
||||
WalkResult::FullyWalked(new_context) => Ok(new_context),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
(Some(_), Some(path_first)) => {
|
||||
for context in breadcrumbs.iter().rev() {
|
||||
match walk_path_from_single_level(context.borrow(), path) {
|
||||
// If no walking was done at all, keep looping
|
||||
WalkResult::NoWalk => {}
|
||||
// If we partially walked then stop trying to find
|
||||
// anything
|
||||
WalkResult::PartialWalk => {
|
||||
return Err(WalkError::CantWalk);
|
||||
}
|
||||
WalkResult::FullyWalked(new_context) => return Ok(new_context),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
Loading…
Reference in New Issue