Skip to content

Commit

Permalink
fix: Support more expressive indexing
Browse files Browse the repository at this point in the history
Interpreter has been updated to support nested expression evaluation
(e.g. `var1[var2]`).

BREAKING CHANGES:
- `Argument` -> `Expression`
- `Expression`, `Variable`, and `Path` APIs have changed.
- More remifications of this spreading out.
  • Loading branch information
epage committed Oct 18, 2018
1 parent b8c7482 commit e579dd3
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 269 deletions.
6 changes: 2 additions & 4 deletions liquid-compiler/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,10 @@ pub fn granularize(block: &str) -> Result<Vec<Token>> {
x if SINGLE_STRING_LITERAL.is_match(x) || DOUBLE_STRING_LITERAL.is_match(x) => {
Token::StringLiteral(x[1..x.len() - 1].to_owned())
}
x if NUMBER_LITERAL.is_match(x) => x
.parse::<i32>()
x if NUMBER_LITERAL.is_match(x) => x.parse::<i32>()
.map(Token::IntegerLiteral)
.unwrap_or_else(|_e| {
let x = x
.parse::<f64>()
let x = x.parse::<f64>()
.expect("matches to NUMBER_LITERAL are parseable as floats");
Token::FloatLiteral(x)
}),
Expand Down
37 changes: 19 additions & 18 deletions liquid-compiler/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ use std::collections::HashSet;
use std::iter::FromIterator;
use std::slice::Iter;

use liquid_interpreter::Argument;
use liquid_interpreter::Expression;
use liquid_interpreter::Renderable;
use liquid_interpreter::Text;
use liquid_interpreter::Variable;
use liquid_interpreter::{FilterCall, FilterChain};
use liquid_value::Index;

use super::error::{Error, Result};
use super::Element;
Expand Down Expand Up @@ -62,7 +61,7 @@ fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Ren
if tokens.len() > 1 && (tokens[1] == Token::Dot || tokens[1] == Token::OpenSquare) =>
{
let indexes = parse_indexes(&tokens[1..])?;
let mut result = Variable::with_index(x.clone());
let mut result = Variable::with_literal(x.clone());
result.extend(indexes);
Ok(Box::new(result))
}
Expand All @@ -77,16 +76,18 @@ fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Ren
}
}

pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Index>> {
let mut indexes: Vec<Index> = Vec::new();
pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Expression>> {
let mut indexes: Vec<Expression> = Vec::new();

let mut rest = 0;
while tokens.len() > rest {
tokens = &tokens[rest..];
rest = match tokens[0] {
Token::Dot if tokens.len() > 1 => {
match tokens[1] {
Token::Identifier(ref x) => indexes.push(Index::with_key(x.as_ref())),
Token::Identifier(ref x) => {
indexes.push(Expression::with_literal(x.to_owned()))
}
_ => {
return Err(unexpected_token_error("identifier", Some(&tokens[0])));
}
Expand All @@ -95,8 +96,8 @@ pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Index>> {
}
Token::OpenSquare if tokens.len() > 2 => {
let index = match tokens[1] {
Token::StringLiteral(ref x) => Index::with_key(x.as_ref()),
Token::IntegerLiteral(ref x) => Index::with_index(*x as isize),
Token::StringLiteral(ref x) => Expression::with_literal(x.to_owned()),
Token::IntegerLiteral(ref x) => Expression::with_literal(*x),
_ => {
return Err(unexpected_token_error(
"string | whole number",
Expand Down Expand Up @@ -130,7 +131,7 @@ pub fn parse_output(tokens: &[Token]) -> Result<FilterChain> {
.unwrap_or_else(|| tokens.len());

let mut entry = tokens[0].to_arg()?;
if let Argument::Var(ref mut entry) = &mut entry {
if let Expression::Variable(ref mut entry) = &mut entry {
let indexes = parse_indexes(&tokens[1..first_pipe])?;
entry.extend(indexes);
}
Expand Down Expand Up @@ -338,7 +339,7 @@ pub fn split_block<'a>(
mod test_parse_output {
use super::*;

use liquid_interpreter::Argument;
use liquid_interpreter::Expression;
use liquid_value::Value;

use super::super::lexer::granularize;
Expand All @@ -351,14 +352,14 @@ mod test_parse_output {
assert_eq!(
result.unwrap(),
FilterChain::new(
Argument::Var(Variable::with_index("abc")),
Expression::Variable(Variable::with_literal("abc")),
vec![
FilterCall::new(
"def",
vec![
Argument::Val(Value::scalar("1")),
Argument::Val(Value::scalar(2.0)),
Argument::Val(Value::scalar("3")),
Expression::Literal(Value::scalar("1")),
Expression::Literal(Value::scalar(2.0)),
Expression::Literal(Value::scalar("3")),
],
),
FilterCall::new("blabla", vec![]),
Expand All @@ -375,14 +376,14 @@ mod test_parse_output {
assert_eq!(
result.unwrap(),
FilterChain::new(
Argument::Var(Variable::with_index("abc").push_index(0)),
Expression::Variable(Variable::with_literal("abc").push_literal(0)),
vec![
FilterCall::new(
"def",
vec![
Argument::Val(Value::scalar("1")),
Argument::Val(Value::scalar(2.0)),
Argument::Val(Value::scalar("3")),
Expression::Literal(Value::scalar("1")),
Expression::Literal(Value::scalar(2.0)),
Expression::Literal(Value::scalar("3")),
],
),
FilterCall::new("blabla", vec![]),
Expand Down
21 changes: 12 additions & 9 deletions liquid-compiler/src/token.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::fmt;

use liquid_interpreter::Argument;
use liquid_interpreter::Expression;
use liquid_interpreter::Variable;
use liquid_value::{Index, Value};
use liquid_value::Value;

use super::error::Result;
use super::parser::unexpected_token_error;
Expand Down Expand Up @@ -71,16 +71,19 @@ impl Token {

/// Translates a Token to a Value, looking it up in the context if
/// necessary
pub fn to_arg(&self) -> Result<Argument> {
pub fn to_arg(&self) -> Result<Expression> {
match *self {
Token::IntegerLiteral(f) => Ok(Argument::Val(Value::scalar(f))),
Token::FloatLiteral(f) => Ok(Argument::Val(Value::scalar(f))),
Token::StringLiteral(ref s) => Ok(Argument::Val(Value::scalar(s.to_owned()))),
Token::BooleanLiteral(b) => Ok(Argument::Val(Value::scalar(b))),
Token::IntegerLiteral(f) => Ok(Expression::with_literal(f)),
Token::FloatLiteral(f) => Ok(Expression::with_literal(f)),
Token::StringLiteral(ref s) => Ok(Expression::with_literal(s.to_owned())),
Token::BooleanLiteral(b) => Ok(Expression::with_literal(b)),
Token::Identifier(ref id) => {
let mut var = Variable::default();
var.extend(id.split('.').map(Index::with_key));
Ok(Argument::Var(var))
var.extend(
id.split('.')
.map(|s| Expression::with_literal(s.to_owned())),
);
Ok(Expression::Variable(var))
}
ref x => Err(unexpected_token_error(
"string | number | boolean | identifier",
Expand Down
36 changes: 0 additions & 36 deletions liquid-interpreter/src/argument.rs

This file was deleted.

19 changes: 9 additions & 10 deletions liquid-interpreter/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::sync;
use error::{Error, Result};
use value::{Object, Path, Value};

use super::Argument;
use super::Expression;
use super::Globals;
use super::{BoxedValueFilter, FilterValue};

Expand Down Expand Up @@ -83,7 +83,7 @@ where

impl<'a, 'g> CycleState<'a, 'g> {
/// See `cycle` tag.
pub fn cycle_element(&mut self, name: &str, values: &[Argument]) -> Result<Value> {
pub fn cycle_element(&mut self, name: &str, values: &[Expression]) -> Result<Value> {
let index = self.context.cycles.cycle_index(name, values.len());
if index >= values.len() {
return Err(Error::with_msg(
Expand Down Expand Up @@ -170,16 +170,15 @@ impl<'g> Stack<'g> {
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_root(key)?;
let key = key.to_str();
let value = self.get_root(key.as_ref())?;

indexes.fold(Ok(value), |value, index| {
let value = value?;
let child = value.get(index);
let child = child
.ok_or_else(|| Error::with_msg("Invalid index").context("index", key.to_owned()))?;
let child = child.ok_or_else(|| {
Error::with_msg("Invalid index").context("index", key.as_ref().to_owned())
})?;
Ok(child)
})
}
Expand Down Expand Up @@ -387,7 +386,7 @@ impl<'g> Context<'g> {
mod test {
use super::*;

use value::Index;
use value::Scalar;

#[test]
fn stack_get_root() {
Expand All @@ -414,7 +413,7 @@ mod test {
let mut post = Object::new();
post.insert("number".into(), Value::scalar(42f64));
ctx.stack_mut().set_global("post", Value::Object(post));
let indexes = vec![Index::with_key("post"), Index::with_key("number")]
let indexes = vec![Scalar::new("post"), Scalar::new("number")]
.into_iter()
.collect();
assert_eq!(ctx.stack().get(&indexes).unwrap(), &Value::scalar(42f64));
Expand Down
45 changes: 45 additions & 0 deletions liquid-interpreter/src/expression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::fmt;

use error::Result;
use value::Scalar;
use value::Value;

use super::Context;
use variable::Variable;

/// An un-evaluated `Value`.
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
/// Un-evaluated.
Variable(Variable),
/// Evaluated.
Literal(Value),
}

impl Expression {
/// Create an expression from a scalar literal.
pub fn with_literal<S: Into<Scalar>>(literal: S) -> Self {
Expression::Literal(Value::scalar(literal))
}

/// Convert to a `Value`.
pub fn evaluate(&self, context: &Context) -> Result<Value> {
let val = match *self {
Expression::Literal(ref x) => x.clone(),
Expression::Variable(ref x) => {
let path = x.evaluate(context)?;
context.stack().get(&path)?.clone()
}
};
Ok(val)
}
}

impl fmt::Display for Expression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Expression::Literal(ref x) => write!(f, "{}", x),
Expression::Variable(ref x) => write!(f, "{}", x),
}
}
}
10 changes: 5 additions & 5 deletions liquid-interpreter/src/filter_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ use itertools;
use error::{Error, Result, ResultLiquidChainExt, ResultLiquidExt};
use value::Value;

use super::Argument;
use super::Context;
use super::Expression;
use super::Renderable;

/// A `Value` filter.
#[derive(Clone, Debug, PartialEq)]
pub struct FilterCall {
name: String,
arguments: Vec<Argument>,
arguments: Vec<Expression>,
}

impl FilterCall {
/// Create filter expression.
pub fn new(name: &str, arguments: Vec<Argument>) -> FilterCall {
pub fn new(name: &str, arguments: Vec<Expression>) -> FilterCall {
FilterCall {
name: name.to_owned(),
arguments,
Expand All @@ -41,13 +41,13 @@ impl fmt::Display for FilterCall {
/// A `Value` expression.
#[derive(Clone, Debug, PartialEq)]
pub struct FilterChain {
entry: Argument,
entry: Expression,
filters: Vec<FilterCall>,
}

impl FilterChain {
/// Create a new expression.
pub fn new(entry: Argument, filters: Vec<FilterCall>) -> Self {
pub fn new(entry: Expression, filters: Vec<FilterCall>) -> Self {
Self { entry, filters }
}

Expand Down
4 changes: 2 additions & 2 deletions liquid-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pub mod value {
pub use liquid_value::*;
}

mod argument;
mod context;
mod expression;
mod filter;
mod filter_chain;
mod globals;
Expand All @@ -29,8 +29,8 @@ mod template;
mod text;
mod variable;

pub use self::argument::*;
pub use self::context::*;
pub use self::expression::*;
pub use self::filter::*;
pub use self::filter_chain::*;
pub use self::globals::*;
Expand Down
Loading

0 comments on commit e579dd3

Please sign in to comment.