Skip to content

Commit

Permalink
feat(interpreter): Create dedicated Path for indexing into a Value
Browse files Browse the repository at this point in the history
  • Loading branch information
epage committed Oct 4, 2018
1 parent fde1397 commit a936ba5
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 87 deletions.
2 changes: 1 addition & 1 deletion liquid-compiler/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ mod test {
#[test]
fn evaluate_handles_identifiers() {
let mut ctx = Context::new();
ctx.stack_mut().set_global_val("var0", Value::scalar(42f64));
ctx.stack_mut().set_global("var0", Value::scalar(42f64));
assert_eq!(
Token::Identifier("var0".to_owned())
.to_arg()
Expand Down
2 changes: 1 addition & 1 deletion liquid-interpreter/src/argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl Argument {
Argument::Val(ref x) => x.clone(),
Argument::Var(ref x) => context
.stack()
.get_val_by_index(x.indexes().iter())?
.get(x.path())?
.clone(),
};
Ok(val)
Expand Down
53 changes: 28 additions & 25 deletions liquid-interpreter/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::sync;

use error::{Error, Result};
use value::{Index, Object, Value};
use value::{Object, Value, Path};

use super::Argument;
use super::Globals;
Expand Down Expand Up @@ -141,17 +141,18 @@ impl<'g> Stack<'g> {
}

/// Recursively index into the stack.
pub fn get_val_by_index<'i, I: Iterator<Item = &'i Index>>(
pub fn get(
&self,
mut indexes: I,
path: &Path,
) -> Result<&Value> {
let mut indexes = path.iter();
let key = indexes
.next()
.ok_or_else(|| Error::with_msg("No index provided"))?;
let key = key.as_key().ok_or_else(|| {
Error::with_msg("Root index must be an object key").context("index", format!("{}", key))
})?;
let value = self.get_val(key)?;
let value = self.get_root(key)?;

indexes.fold(Ok(value), |value, index| {
let value = value?;
Expand All @@ -162,7 +163,7 @@ impl<'g> Stack<'g> {
})
}

fn get_val<'a>(&'a self, name: &str) -> Result<&'a Value> {
fn get_root<'a>(&'a self, name: &str) -> Result<&'a Value> {
for frame in self.stack.iter().rev() {
if let Some(rval) = frame.get(name) {
return Ok(rval);
Expand All @@ -174,7 +175,7 @@ impl<'g> Stack<'g> {
}

/// Sets a value in the global context.
pub fn set_global_val<S>(&mut self, name: S, val: Value) -> Option<Value>
pub fn set_global<S>(&mut self, name: S, val: Value) -> Option<Value>
where
S: Into<borrow::Cow<'static, str>>,
{
Expand All @@ -189,7 +190,7 @@ impl<'g> Stack<'g> {
/// Panics if there is no frame on the local values stack. Context
/// instances are created with a top-level stack frame in place, so
/// this should never happen in a well-formed program.
pub fn set_val<S>(&mut self, name: S, val: Value) -> Option<Value>
pub fn set<S>(&mut self, name: S, val: Value) -> Option<Value>
where
S: Into<borrow::Cow<'static, str>>,
{
Expand Down Expand Up @@ -344,71 +345,73 @@ impl<'g> Context<'g> {
mod test {
use super::*;

use value::Index;

#[test]
fn get_val() {
fn stack_get_root() {
let mut ctx = Context::new();
ctx.stack_mut()
.set_global_val("number", Value::scalar(42f64));
.set_global("number", Value::scalar(42f64));
assert_eq!(
ctx.stack().get_val("number").unwrap(),
ctx.stack().get_root("number").unwrap(),
&Value::scalar(42f64)
);
}

#[test]
fn get_val_failure() {
fn stack_get_root_failure() {
let mut ctx = Context::new();
let mut post = Object::new();
post.insert("number".into(), Value::scalar(42f64));
ctx.stack_mut().set_global_val("post", Value::Object(post));
assert!(ctx.stack().get_val("post.number").is_err());
ctx.stack_mut().set_global("post", Value::Object(post));
assert!(ctx.stack().get_root("post.number").is_err());
}

#[test]
fn get_val_by_index() {
fn stack_get() {
let mut ctx = Context::new();
let mut post = Object::new();
post.insert("number".into(), Value::scalar(42f64));
ctx.stack_mut().set_global_val("post", Value::Object(post));
let indexes = vec![Index::with_key("post"), Index::with_key("number")];
ctx.stack_mut().set_global("post", Value::Object(post));
let indexes = vec![Index::with_key("post"), Index::with_key("number")].into_iter().collect();
assert_eq!(
ctx.stack().get_val_by_index(indexes.iter()).unwrap(),
ctx.stack().get(&indexes).unwrap(),
&Value::scalar(42f64)
);
}

#[test]
fn scoped_variables() {
let mut ctx = Context::new();
ctx.stack_mut().set_global_val("test", Value::scalar(42f64));
assert_eq!(ctx.stack().get_val("test").unwrap(), &Value::scalar(42f64));
ctx.stack_mut().set_global("test", Value::scalar(42f64));
assert_eq!(ctx.stack().get_root("test").unwrap(), &Value::scalar(42f64));

ctx.run_in_scope(|new_scope| {
// assert that values are chained to the parent scope
assert_eq!(
new_scope.stack().get_val("test").unwrap(),
new_scope.stack().get_root("test").unwrap(),
&Value::scalar(42f64)
);

// set a new local value, and assert that it overrides the previous value
new_scope
.stack_mut()
.set_val("test", Value::scalar(3.14f64));
.set("test", Value::scalar(3.14f64));
assert_eq!(
new_scope.stack().get_val("test").unwrap(),
new_scope.stack().get_root("test").unwrap(),
&Value::scalar(3.14f64)
);

// sat a new val that we will pick up outside the scope
new_scope
.stack_mut()
.set_global_val("global", Value::scalar("some value"));
.set_global("global", Value::scalar("some value"));
});

// assert that the value has reverted to the old one
assert_eq!(ctx.stack().get_val("test").unwrap(), &Value::scalar(42f64));
assert_eq!(ctx.stack().get_root("test").unwrap(), &Value::scalar(42f64));
assert_eq!(
ctx.stack().get_val("global").unwrap(),
ctx.stack().get_root("global").unwrap(),
&Value::scalar("some value")
);
}
Expand Down
21 changes: 9 additions & 12 deletions liquid-interpreter/src/variable.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,46 @@
use std::fmt;
use std::io::Write;

use itertools;

use error::{Result, ResultLiquidChainExt};
use value::Index;
use value::Path;

use super::Context;
use super::Renderable;

/// A `Value` reference.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Variable {
indexes: Vec<Index>,
path: Path,
}

impl Variable {
/// Create a `Value` reference.
pub fn new<I: Into<Index>>(value: I) -> Self {
let indexes = vec![value.into()];
Self { indexes }
let path = Path::with_index(value);
Self { path }
}

/// Access the `Value` reference.
pub fn indexes(&self) -> &[Index] {
&self.indexes
pub fn path(&self) -> &Path {
&self.path
}
}

impl Extend<Index> for Variable {
fn extend<T: IntoIterator<Item = Index>>(&mut self, iter: T) {
self.indexes.extend(iter);
self.path.extend(iter);
}
}

impl fmt::Display for Variable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let data = itertools::join(self.indexes().iter(), ".");
write!(f, "{}", data)
write!(f, "{}", self.path)
}
}

impl Renderable for Variable {
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> {
let value = context.stack().get_val_by_index(self.indexes.iter())?;
let value = context.stack().get(&self.path)?;
write!(writer, "{}", value).chain("Failed to render")?;
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions liquid-value/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ travis-ci = { repository = "cobalt-org/liquid-rust" }
appveyor = { repository = "johannhof/liquid-rust" }

[dependencies]
itertools = "0.7.0"
num-traits = "0.2"
# Exposed in API
chrono = "0.4"
Expand Down
3 changes: 3 additions & 0 deletions liquid-value/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@
#[macro_use]
extern crate serde;
extern crate chrono;
extern crate itertools;
extern crate liquid_error;
extern crate num_traits;

mod index;
mod scalar;
mod ser;
mod values;
mod path;

/// Liquid Processing Errors.
pub mod error {
pub use liquid_error::*;
}

pub use index::*;
pub use path::*;
pub use scalar::*;
pub use ser::*;
pub use values::*;
78 changes: 78 additions & 0 deletions liquid-value/src/path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::fmt;
use std::iter;
use std::slice;

use itertools;

use super::Index;

/// Path to a value in an `Object`.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Path {
indexes: Vec<Index>,
}

impl Path {
/// Create a `Path` from iterator of `Index`s
pub fn new<T: IntoIterator<Item=Index>>(indexes: T) -> Self {
let indexes = indexes.into_iter().collect();
Self { indexes }
}

/// Create a `Value` reference.
pub fn with_index<I: Into<Index>>(value: I) -> Self {
let indexes = vec![value.into()];
Self { indexes }
}

/// Access the `Value` reference.
pub fn iter(&self) -> IndexIter {
IndexIter(self.indexes.iter())
}
}

impl Extend<Index> for Path {
fn extend<T: IntoIterator<Item = Index>>(&mut self, iter: T) {
self.indexes.extend(iter);
}
}

impl iter::FromIterator<Index> for Path {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = Index>,
{
let indexes = iter.into_iter().collect();
Self { indexes }
}
}

impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let data = itertools::join(self.iter(), ".");
write!(f, "{}", data)
}
}

/// Iterate over indexes in a `Value`'s `Path`.
#[derive(Debug)]
pub struct IndexIter<'i>(slice::Iter<'i, Index>);

impl<'i> Iterator for IndexIter<'i> {
type Item = &'i Index;

#[inline]
fn next(&mut self) -> Option<&'i Index> {
self.0.next()
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}

#[inline]
fn count(self) -> usize {
self.0.count()
}
}
10 changes: 5 additions & 5 deletions src/tags/assign_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Renderable for Assign {
let value = self.src.evaluate(context).trace_with(|| self.trace())?;
context
.stack_mut()
.set_global_val(self.dst.to_owned(), value);
.set_global(self.dst.to_owned(), value);
Ok(())
}
}
Expand Down Expand Up @@ -93,7 +93,7 @@ mod test {
// test one: no matching value in `tags`
{
let mut context = Context::new();
context.stack_mut().set_global_val(
context.stack_mut().set_global(
"tags",
Value::Array(vec![
Value::scalar("alpha"),
Expand All @@ -104,7 +104,7 @@ mod test {

let output = template.render(&mut context).unwrap();
assert_eq!(
context.stack().get_val_by_index([Index::with_key("freestyle")].iter()).unwrap(),
context.stack().get(&vec![Index::with_key("freestyle")].into_iter().collect()).unwrap(),
&Value::scalar(false)
);
assert_eq!(output, "");
Expand All @@ -113,7 +113,7 @@ mod test {
// test two: matching value in `tags`
{
let mut context = Context::new();
context.stack_mut().set_global_val(
context.stack_mut().set_global(
"tags",
Value::Array(vec![
Value::scalar("alpha"),
Expand All @@ -125,7 +125,7 @@ mod test {

let output = template.render(&mut context).unwrap();
assert_eq!(
context.stack().get_val_by_index([Index::with_key("freestyle")].iter()).unwrap(),
context.stack().get(&vec![Index::with_key("freestyle")].into_iter().collect()).unwrap(),
&Value::scalar(true)
);
assert_eq!(output, "<p>Freestyle!</p>");
Expand Down

0 comments on commit a936ba5

Please sign in to comment.