Skip to content

Commit

Permalink
fix(compiler): Move filters to interpreter
Browse files Browse the repository at this point in the history
This is a step towards #301 and takes us one step further to
`liquid-compiler` being "stable".

BREAKING CHANGE: Things moved all over the place.
  • Loading branch information
epage committed Dec 13, 2018
1 parent 04e486a commit b9c2ff8
Show file tree
Hide file tree
Showing 21 changed files with 183 additions and 216 deletions.
15 changes: 12 additions & 3 deletions liquid-interpreter/src/filter.rs → liquid-compiler/src/filter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::{self, Debug};

use liquid_error;
use liquid_value::Value;

Expand All @@ -10,7 +12,7 @@ pub type FilterResult = Result<Value, liquid_error::Error>;
/// a new [Renderable](trait.Renderable.html) based on its parameters. The received parameters
/// specify the name of the tag, the argument [Tokens](lexer/enum.Token.html) passed to
/// the tag and the global [`LiquidOptions`](struct.LiquidOptions.html).
pub trait FilterValue: Send + Sync + FilterValueClone {
pub trait FilterValue: Send + Sync + FilterValueClone + Debug {
/// Filter `input` based on `arguments`.
fn filter(&self, input: &Value, arguments: &[Value]) -> FilterResult;
}
Expand Down Expand Up @@ -56,14 +58,21 @@ impl FilterValue for FnValueFilter {
}
}

#[derive(Clone)]
impl Debug for FnValueFilter {
#[inline]
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
writeln!(formatter, "fn filter")
}
}

#[derive(Clone, Debug)]
enum EnumValueFilter {
Fun(FnValueFilter),
Heap(Box<FilterValue>),
}

/// Custom `Box<FilterValue>` with a `FnFilterValue` optimization.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct BoxedValueFilter {
filter: EnumValueFilter,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,48 @@ use itertools;

use liquid_error::{Result, ResultLiquidChainExt, ResultLiquidExt};
use liquid_value::Value;
use liquid_interpreter::Context;
use liquid_interpreter::Expression;
use liquid_interpreter::Renderable;

use super::Context;
use super::Expression;
use super::Renderable;
use super::BoxedValueFilter;
use super::FilterValue;

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

impl FilterCall {
/// Create filter expression.
pub fn new(name: &str, arguments: Vec<Expression>) -> FilterCall {
pub fn new(name: &str, filter: BoxedValueFilter, arguments: Vec<Expression>) -> FilterCall {
FilterCall {
name: name.to_owned(),
filter,
arguments,
}
}

pub fn evaluate(&self, context: &Context, entry: &Value) -> Result<Value> {
let arguments: Result<Vec<Value>> = self
.arguments
.iter()
.map(|a| Ok(a.evaluate(context)?.to_owned()))
.collect();
let arguments = arguments?;
self.filter
.filter(entry, &*arguments)
.chain("Filter error")
.context_key("filter")
.value_with(|| format!("{}", self).into())
.context_key("input")
.value_with(|| format!("{}", &entry).into())
.context_key("args")
.value_with(|| itertools::join(&arguments, ", ").into())
}
}

impl fmt::Display for FilterCall {
Expand All @@ -39,7 +61,7 @@ impl fmt::Display for FilterCall {
}

/// A `Value` expression.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct FilterChain {
entry: Expression,
filters: Vec<FilterCall>,
Expand All @@ -58,23 +80,7 @@ impl FilterChain {

// apply all specified filters
for filter in &self.filters {
let f = context.get_filter(&filter.name)?;

let arguments: Result<Vec<Value>> = filter
.arguments
.iter()
.map(|a| Ok(a.evaluate(context)?.to_owned()))
.collect();
let arguments = arguments?;
entry = f
.filter(&entry, &*arguments)
.chain("Filter error")
.context_key("filter")
.value_with(|| format!("{}", self).into())
.context_key("input")
.value_with(|| format!("{}", &entry).into())
.context_key("args")
.value_with(|| itertools::join(&arguments, ", ").into())?;
entry = filter.evaluate(context, &entry)?;
}

Ok(entry)
Expand Down
6 changes: 6 additions & 0 deletions liquid-compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ extern crate pest_derive;

mod block;
mod include;
mod filter;
mod filter_chain;
mod registry;
mod options;
mod parser;
mod tag;
Expand All @@ -18,5 +21,8 @@ pub use include::*;
pub use options::*;
pub use parser::*;
pub use tag::*;
pub use filter::*;
pub use filter_chain::*;
pub use registry::*;

use text::Text;
6 changes: 4 additions & 2 deletions liquid-compiler/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use liquid_interpreter::PluginRegistry;

use super::PluginRegistry;
use super::BoxedBlockParser;
use super::BoxedTagParser;
use super::BoxedValueFilter;
use super::Include;
use super::NullInclude;

#[derive(Clone)]
pub struct LiquidOptions {
pub blocks: PluginRegistry<BoxedBlockParser>,
pub tags: PluginRegistry<BoxedTagParser>,
pub filters: PluginRegistry<BoxedValueFilter>,
pub include_source: Box<Include>,
}

Expand All @@ -17,6 +18,7 @@ impl Default for LiquidOptions {
LiquidOptions {
blocks: Default::default(),
tags: Default::default(),
filters: Default::default(),
include_source: Box::new(NullInclude::new()),
}
}
Expand Down
75 changes: 36 additions & 39 deletions liquid-compiler/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
use std;

use itertools;
use liquid_error::{Error, Result};
use liquid_interpreter::Expression;
use liquid_interpreter::Renderable;
use liquid_interpreter::Variable;
use liquid_interpreter::{FilterCall, FilterChain};
use liquid_value::Scalar;
use liquid_value::Value;

use super::{FilterCall, FilterChain};
use super::LiquidOptions;
use super::ParseBlock;
use super::ParseTag;
Expand Down Expand Up @@ -172,7 +173,7 @@ fn parse_value(value: Pair) -> Expression {

/// Parses a `FilterCall` from a `Pair` with a filter.
/// This `Pair` must be `Rule::Filter`.
fn parse_filter(filter: Pair) -> FilterCall {
fn parse_filter(filter: Pair, options: &LiquidOptions) -> Result<FilterCall> {
if filter.as_rule() != Rule::Filter {
panic!("Expected a filter.");
}
Expand All @@ -181,12 +182,22 @@ fn parse_filter(filter: Pair) -> FilterCall {
let name = filter.next().expect("A filter always has a name.").as_str();
let args = filter.map(parse_value).collect();

FilterCall::new(name, args)
let f = options.filters.get(name)
.ok_or_else(|| {
let mut available: Vec<_> = options.filters.plugin_names().collect();
available.sort_unstable();
let available = itertools::join(available, ", ");
Error::with_msg("Unknown filter")
.context("requested filter", name.to_owned())
.context("available filters", available)
})?.clone();
let f = FilterCall::new(name, f, args);
Ok(f)
}

/// Parses a `FilterChain` from a `Pair` with a filter chain.
/// This `Pair` must be `Rule::FilterChain`.
fn parse_filter_chain(chain: Pair) -> FilterChain {
fn parse_filter_chain(chain: Pair, options: &LiquidOptions) -> Result<FilterChain> {
if chain.as_rule() != Rule::FilterChain {
panic!("Expected an expression with filters.");
}
Expand All @@ -197,9 +208,11 @@ fn parse_filter_chain(chain: Pair) -> FilterChain {
.next()
.expect("A filterchain always has starts by a value."),
);
let filters = chain.map(parse_filter).collect();
let filters: Result<Vec<_>> = chain.map(|f| parse_filter(f, options)).collect();
let filters = filters?;

FilterChain::new(entry, filters)
let filters = FilterChain::new(entry, filters);
Ok(filters)
}

/// An interface to access elements inside a block.
Expand Down Expand Up @@ -340,6 +353,7 @@ pub struct Tag<'a> {
tokens: TagTokenIter<'a>,
as_str: &'a str,
}

impl<'a> From<Pair<'a>> for Tag<'a> {
fn from(element: Pair<'a>) -> Self {
if element.as_rule() != Rule::Tag {
Expand Down Expand Up @@ -446,6 +460,7 @@ impl<'a> Tag<'a> {
pub struct Exp<'a> {
element: Pair<'a>,
}

impl<'a> From<Pair<'a>> for Exp<'a> {
fn from(element: Pair<'a>) -> Self {
if element.as_rule() != Rule::Expression {
Expand All @@ -454,9 +469,10 @@ impl<'a> From<Pair<'a>> for Exp<'a> {
Exp { element }
}
}

impl<'a> Exp<'a> {
/// Parses the expression just as if it weren't inside any block.
pub fn parse(self) -> Result<Box<Renderable>> {
pub fn parse(self, options: &LiquidOptions) -> Result<Box<Renderable>> {
let filter_chain = self
.element
.into_inner()
Expand All @@ -466,7 +482,8 @@ impl<'a> Exp<'a> {
.next()
.expect("An expression consists of one filterchain.");

Ok(Box::new(parse_filter_chain(filter_chain)))
let filter_chain = parse_filter_chain(filter_chain, options)?;
Ok(Box::new(filter_chain))
}

/// Returns the expression as a str.
Expand Down Expand Up @@ -507,7 +524,7 @@ impl<'a> BlockElement<'a> {
match self {
BlockElement::Raw(raw) => Ok(raw.to_renderable()),
BlockElement::Tag(tag) => tag.parse(block, options),
BlockElement::Expression(exp) => exp.parse(),
BlockElement::Expression(exp) => exp.parse(options),
}
}

Expand All @@ -520,7 +537,7 @@ impl<'a> BlockElement<'a> {
match self {
BlockElement::Raw(raw) => Ok(raw.to_renderable()),
BlockElement::Tag(tag) => tag.parse_pair(next_elements, options),
BlockElement::Expression(exp) => exp.parse(),
BlockElement::Expression(exp) => exp.parse(options),
}
}

Expand Down Expand Up @@ -728,16 +745,22 @@ impl<'a> TagToken<'a> {
}

/// Tries to obtain a `FilterChain` from this token.
pub fn expect_filter_chain(mut self) -> TryMatchToken<'a, FilterChain> {
match self.unwrap_filter_chain() {
Ok(t) => TryMatchToken::Matches(parse_filter_chain(t)),
pub fn expect_filter_chain(mut self, options: &LiquidOptions) -> TryMatchToken<'a, FilterChain> {
match self.expect_filter_chain_err(options) {
Ok(t) => TryMatchToken::Matches(t),
Err(_) => {
self.expected.push(Rule::FilterChain);
TryMatchToken::Fails(self)
}
}
}

fn expect_filter_chain_err(&mut self, options: &LiquidOptions) -> Result<FilterChain> {
let t = self.unwrap_filter_chain().map_err(|_| Error::with_msg("failed to parse"))?;
let f = parse_filter_chain(t, options)?;
Ok(f)
}

/// Tries to obtain a value from this token.
///
/// Do not confuse this value with `liquid-value`'s `Value`.
Expand Down Expand Up @@ -875,32 +898,6 @@ mod test {
assert_eq!(parse_literal(string_single_quotes), Scalar::new("Liquid"));
}

#[test]
fn test_parse_filter_chain() {
let filter = LiquidParser::parse(Rule::FilterChain, "abc | def:'1',2,'3' | blabla")
.unwrap()
.next()
.unwrap();

assert_eq!(
parse_filter_chain(filter),
FilterChain::new(
Expression::Variable(Variable::with_literal("abc")),
vec![
FilterCall::new(
"def",
vec![
Expression::Literal(Value::scalar("1")),
Expression::Literal(Value::scalar(2.0)),
Expression::Literal(Value::scalar("3")),
],
),
FilterCall::new("blabla", vec![]),
]
)
);
}

#[test]
fn test_parse_variable() {
let variable = LiquidParser::parse(Rule::Variable, "foo[0].bar.baz[foo.bar]")
Expand Down
File renamed without changes.
Loading

0 comments on commit b9c2ff8

Please sign in to comment.