diff --git a/liquid-compiler/src/filter.rs b/liquid-compiler/src/filter.rs index 48f26eb1f..50e9e8e36 100644 --- a/liquid-compiler/src/filter.rs +++ b/liquid-compiler/src/filter.rs @@ -1,101 +1,254 @@ -use std::fmt::{self, Debug}; +use std::fmt::{Debug, Display}; -use liquid_error; +use liquid_error::Result; +use liquid_interpreter::{Context, Expression}; use liquid_value::Value; -/// Expected return type of a `Filter`. -pub type FilterResult = Result; - -/// A trait for creating custom tags. This is a simple type alias for a function. +/// A structure that holds the information of a single parameter in a filter. +/// This includes its name, description and whether it is optional or required. /// -/// This function will be called whenever the parser encounters a tag and returns -/// 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 [`Language`](struct.Language.html). -pub trait FilterValue: Send + Sync + FilterValueClone + Debug { - /// Filter `input` based on `arguments`. - fn filter(&self, input: &Value, arguments: &[Value]) -> FilterResult; -} - -/// Support cloning of `Box`. -pub trait FilterValueClone { - /// Cloning of `dyn FilterValue`. - fn clone_box(&self) -> Box; +/// This is the return type in some `FilterReflection` functions. +pub struct ParameterReflection { + pub name: &'static str, + pub description: &'static str, + pub is_optional: bool, } -impl FilterValueClone for T -where - T: 'static + FilterValue + Clone, -{ - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} - -impl Clone for Box { - fn clone(&self) -> Box { - self.clone_box() - } +/// A trait that holds the information of the parameters of a filter. +/// +/// All structs that implement `FilterParameters` must implement this. +/// This is actually automatically implemented with `#[derive(FilterParameters)]`. +/// +/// This trait allows `FilterReflection` macro to extract the parameters information +/// from a given `FilterParameters` structure. +pub trait FilterParametersReflection { + fn positional_parameters() -> &'static [ParameterReflection]; + fn keyword_parameters() -> &'static [ParameterReflection]; } -/// Function signature that can act as a `FilterValue`. -pub type FnFilterValue = fn(&Value, &[Value]) -> FilterResult; - -#[derive(Clone)] -struct FnValueFilter { - filter: FnFilterValue, +/// A trait that holds the information of a filter about itself, such as +/// its name, description and parameters. +/// +/// All structs that implement `ParseFilter` must implement this. +/// +/// # Deriving +/// +/// This trait may be derived with `liquid-derive`'s `#[derive(FilterReflection)]`. However, +/// it is necessary to use the `#[filter(...)]` helper attribute. See documentation on +/// `liquid-derive` for more information. +pub trait FilterReflection { + fn name(&self) -> &'static str; + fn description(&self) -> &'static str; + + fn positional_parameters(&self) -> &'static [ParameterReflection]; + fn keyword_parameters(&self) -> &'static [ParameterReflection]; } -impl FnValueFilter { - fn new(filter: FnFilterValue) -> Self { - Self { filter } - } +/// A trait that declares and holds the parameters of a filter. +/// +/// Provides `from_args`, to construct itself from `FilterArguments` (parses the arguments) +/// and `evaluate`, to construct its evaluated counterpart (evaluates the arguments). +/// +/// # Deriving +/// +/// The whole point of this structure is to facilitate the process of deriving a filter. +/// Thus, this trait and all traits it requires may be derived with `#[derive(Debug, FilterParameters)]`. +/// +/// See documentation for `FilterParameters` macro on `liquid-derive` for more information. +pub trait FilterParameters<'a>: Sized + FilterParametersReflection + Debug + Display { + type EvaluatedFilterParameters; + fn from_args(args: FilterArguments) -> Result; + fn evaluate(&'a self, context: &'a Context) -> Result; } -impl FilterValue for FnValueFilter { - fn filter(&self, input: &Value, arguments: &[Value]) -> FilterResult { - (self.filter)(input, arguments) - } +/// Structure that holds the unparsed arguments of a filter, both positional and keyword. +pub struct FilterArguments<'a> { + pub positional: Box>, + pub keyword: Box + 'a>, } -impl Debug for FnValueFilter { - #[inline] - fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { - writeln!(formatter, "fn filter") - } +/// A trait that holds a filter, ready to evaluate. +/// +/// # Deriving +/// +/// You cannot derive `Filter`, as it would go against the very point of creating your own filter. +/// You can, however, derive some other traits that are necessary in order to implement it. +/// +/// In order to implement this trait, the struct must also implement `Debug` and `Display`, as well +/// as either `Default` or `From` (where T is the FilterParameters struct), respectively, in a +/// filter without or with parameters. +/// +/// For `Debug` and `Default`, one may use rust's `#[derive(Debug)]` and `#[derive(Default)]` macros. +/// For `Display` and `From`, one may use `liquid-derive`'s `#[derive(Display_filter)]` and +/// `#[derive(FromFilterParameters)]`. +/// +/// Note, however, that you may need helper attributes like `#[name = "..."]` and `#[parameters]` for +/// using liquid-derive`'s macros. See documentation on `liquid-derive` for more information. +/// +/// # Examples +/// +/// Filter for filter with no arguments: +/// ```ignore +/// #[derive(Debug, Default, Display_filter)] +/// #[name = "abs"] // The name of the filter, for `Display_filter`. +/// struct AbsFilter; // There are no parameters, so implements `Default`. +/// +/// impl Filter for AbsFilter { +/// fn evaluate(&self, input: &Value, _context: &Context) -> Result { +/// // Implementation of the filter here +/// } +/// } +/// ``` +/// +/// Filter for filter with arguments: +/// ```ignore +/// #[derive(Debug, FromFilterParameters, Display_filter)] +/// #[name = "at_least"] // The name of the filter for derives +/// struct AtLeastFilter { // There are parameters, so derives `FromFilterParameters`. +/// #[parameters] // Mark the FilterParameters struct for derives +/// args: AtLeastArgs, // A struct that implements `FilterParameters` +/// } +/// +/// impl Filter for AtLeastFilter { +/// fn evaluate(&self, input: &Value, context: &Context) -> Result { +/// // Evaluate the `FilterParameters` +/// let args = self.args.evaluate(context)?; +/// +/// // Implementation of the filter here +/// } +/// } +/// ``` +/// +/// Filter for a configurable filter: +/// ```ignore +/// #[derive(Debug, Display_filter)] +/// #[name = "example"] // The name of the filter for `Display` +/// // Because construction happens manually (without derive) in `FilterParser` +/// // no need to derive neither `Default` nor `FromFilterParameters`. +/// struct ExampleFilter { +/// #[parameters] // Mark the FilterParameters struct for `Display` +/// args: ExampleArgs, // A struct that implements `FilterParameters` +/// state: i32, // See `ParseFilter` example for context +/// } +/// +/// impl Filter for AtLeastFilter { +/// fn evaluate(&self, input: &Value, context: &Context) -> Result { +/// // Evaluate the `FilterParameters` +/// let args = self.args.evaluate(context)?; +/// +/// // Implementation of the filter here +/// } +/// } +/// ``` +pub trait Filter: Send + Sync + Debug + Display { + // This will evaluate the expressions and evaluate the filter. + fn evaluate(&self, input: &Value, context: &Context) -> Result; } -#[derive(Clone, Debug)] -enum EnumValueFilter { - Fun(FnValueFilter), - Heap(Box), +/// A trait to register a new filter in the `liquid::Parser`. +/// +/// To implement this trait, the structure must also implement `FilterReflection`, thus giving +/// meta information about the filter (namely it's name). +/// +/// Every time a filter with that name is encountered, it is parsed with the `ParseFilter::parse` +/// method, yielding a new `Filter`. +/// +/// # Deriving +/// +/// In order to implement this trait, the struct must also implement `FilterReflection` and +/// `Clone`. +/// +/// `Clone` may be derived with rust's `#[derive(Clone)]`. `FilterReflection` may be derived +/// with `liquid-derive`'s `#[derive(FilterReflection)]`. ParseFilter may be derived with +/// `#[derive(FilterReflection)]`. +/// +/// In order to use `liquid-derive`'s macros, however, it is necessary to use the `#[filter(...)]` +/// helper attribute. See documentation on `liquid-derive` for more information. +/// +/// # Examples +/// +/// ParseFilter for filter with no arguments: +/// ```ignore +/// #[derive(Clone, ParseFilter, FilterReflection)] +/// #[filter( +/// name = "abs", +/// description = "Returns the absolute value of a number.", +/// parsed(AbsFilter) // A struct that implements `Filter` (must implement `Default`) +/// )] +/// pub struct Abs; +/// ``` +/// +/// ParseFilter for filter with arguments: +/// ```ignore +/// #[derive(Clone, ParseFilter, FilterReflection)] +/// #[filter( +/// name = "slice", +/// description = "Takes a slice of a given string or array.", +/// parameters(SliceArgs), // A struct that implements `FilterParameters` +/// parsed(SliceFilter) // A struct that implements `Filter` (must implement `From`) +/// )] +/// pub struct Slice; +/// ``` +/// +/// ParseFilter for a configurable filter: +/// ```ignore +/// #[derive(Clone, FilterReflection)] +/// #[filter( +/// name = "example", +/// description = "This filter exists for example purposes.", +/// parameters(ExampleArgs) // A struct that implements `FilterParameters` +/// )] +/// pub struct ExampleParser { +/// // You can add as many fields as you find necessary to configure the filter +/// // before registering it. +/// pub mode: i32, +/// } +/// +/// // For configurable filters, there is no default implementation of `ParseFilter` +/// impl ParseFilter for ExampleParser { +/// fn parse(&self, arguments: FilterArguments) -> Result> { +/// // Create the `FilterParameters` struct from the given `arguments` +/// let args = ExampleArgs::from_args(arguments)?; +/// // Use the configuration state of the `ParseFilter` +/// let state = self.state; +/// +/// // Create the `Filter` struct and return it, passing the information +/// // about the arguments and the configuration of the `ParseFilter`. +/// Ok(Box::new(ExampleFilter { args, state })) +/// } +/// } +/// ``` +pub trait ParseFilter: Send + Sync + ParseFilterClone + FilterReflection { + /// Filter `input` based on `arguments`. + fn parse(&self, arguments: FilterArguments) -> Result>; } -/// Custom `Box` with a `FnFilterValue` optimization. -#[derive(Clone, Debug)] -pub struct BoxedValueFilter { - filter: EnumValueFilter, +/// Support cloning of `Box`. +pub trait ParseFilterClone { + /// Cloning of `dyn ParseFilter`. + fn clone_box(&self) -> Box; } -impl FilterValue for BoxedValueFilter { - fn filter(&self, input: &Value, arguments: &[Value]) -> FilterResult { - match self.filter { - EnumValueFilter::Fun(ref f) => f.filter(input, arguments), - EnumValueFilter::Heap(ref f) => f.filter(input, arguments), - } +impl ParseFilterClone for T +where + T: 'static + ParseFilter + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) } } -impl From FilterResult> for BoxedValueFilter { - fn from(filter: FnFilterValue) -> BoxedValueFilter { - let filter = EnumValueFilter::Fun(FnValueFilter::new(filter)); - Self { filter } +impl Clone for Box { + fn clone(&self) -> Box { + self.clone_box() } } -impl From> for BoxedValueFilter { - fn from(filter: Box) -> BoxedValueFilter { - let filter = EnumValueFilter::Heap(filter); - Self { filter } +impl From for Box +where + T: 'static + ParseFilter, +{ + fn from(filter: T) -> Self { + Box::new(filter) } } diff --git a/liquid-compiler/src/filter_chain.rs b/liquid-compiler/src/filter_chain.rs index 3853b01ad..2b92a4eac 100644 --- a/liquid-compiler/src/filter_chain.rs +++ b/liquid-compiler/src/filter_chain.rs @@ -3,73 +3,23 @@ use std::io::Write; use itertools; +use super::Filter; use liquid_error::{Result, ResultLiquidExt, ResultLiquidReplaceExt}; use liquid_interpreter::Context; use liquid_interpreter::Expression; use liquid_interpreter::Renderable; use liquid_value::Value; -use super::BoxedValueFilter; -use super::FilterValue; - -/// A `Value` filter. -#[derive(Clone, Debug)] -pub struct FilterCall { - name: String, - filter: BoxedValueFilter, - arguments: Vec, -} - -impl FilterCall { - /// Create filter expression. - pub fn new(name: &str, filter: BoxedValueFilter, arguments: Vec) -> FilterCall { - FilterCall { - name: name.to_owned(), - filter, - arguments, - } - } - - pub fn evaluate(&self, context: &Context, entry: &Value) -> Result { - let arguments: Result> = self - .arguments - .iter() - .map(|a| Ok(a.evaluate(context)?.to_owned())) - .collect(); - let arguments = arguments?; - self.filter - .filter(entry, &*arguments) - .trace("Filter error") - .context_key("filter") - .value_with(|| format!("{}", self).into()) - .context_key("input") - .value_with(|| format!("{}", entry.source()).into()) - .context_key("args") - .value_with(|| itertools::join(arguments.iter().map(Value::source), ", ").into()) - } -} - -impl fmt::Display for FilterCall { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}: {}", - self.name, - itertools::join(&self.arguments, ", ") - ) - } -} - /// A `Value` expression. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct FilterChain { entry: Expression, - filters: Vec, + filters: Vec>, } impl FilterChain { /// Create a new expression. - pub fn new(entry: Expression, filters: Vec) -> Self { + pub fn new(entry: Expression, filters: Vec>) -> Self { Self { entry, filters } } @@ -80,7 +30,13 @@ impl FilterChain { // apply all specified filters for filter in &self.filters { - entry = filter.evaluate(context, &entry)?; + entry = filter + .evaluate(&entry, context) + .trace("Filter error") + .context_key("filter") + .value_with(|| format!("{}", filter).into()) + .context_key("input") + .value_with(|| format!("{}", entry.source()).into())?; } Ok(entry) diff --git a/liquid-compiler/src/lang.rs b/liquid-compiler/src/lang.rs index a2cdca16f..fa09b24d0 100644 --- a/liquid-compiler/src/lang.rs +++ b/liquid-compiler/src/lang.rs @@ -1,13 +1,13 @@ use super::BoxedBlockParser; use super::BoxedTagParser; -use super::BoxedValueFilter; +use super::ParseFilter; use super::PluginRegistry; #[derive(Clone)] pub struct Language { pub blocks: PluginRegistry, pub tags: PluginRegistry, - pub filters: PluginRegistry, + pub filters: PluginRegistry>, non_exhaustive: (), } diff --git a/liquid-compiler/src/parser.rs b/liquid-compiler/src/parser.rs index c578311c8..9c06e1edc 100644 --- a/liquid-compiler/src/parser.rs +++ b/liquid-compiler/src/parser.rs @@ -6,7 +6,7 @@ use std; use itertools; -use liquid_error::{Error, Result}; +use liquid_error::{Error, Result, ResultLiquidExt}; use liquid_interpreter::Expression; use liquid_interpreter::Renderable; use liquid_interpreter::Variable; @@ -16,7 +16,7 @@ use super::Language; use super::ParseBlock; use super::ParseTag; use super::Text; -use super::{FilterCall, FilterChain}; +use super::{Filter, FilterArguments, FilterChain}; use pest::Parser; @@ -175,28 +175,56 @@ 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, options: &Language) -> Result { +fn parse_filter(filter: Pair, options: &Language) -> Result> { if filter.as_rule() != Rule::Filter { panic!("Expected a filter."); } + let filter_str = filter.as_str(); let mut filter = filter.into_inner(); let name = filter.next().expect("A filter always has a name.").as_str(); - let args = filter.map(parse_value).collect(); - - 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); + + let mut keyword_args = Vec::new(); + let mut positional_args = Vec::new(); + + for arg in filter { + match arg.as_rule() { + Rule::PositionalFilterArgument => { + let value = arg.into_inner().next().expect("Rule ensures value."); + let value = parse_value(value); + positional_args.push(value); + } + Rule::KeywordFilterArgument => { + let mut arg = arg.into_inner(); + let key = arg.next().expect("Rule ensures identifier.").as_str(); + let value = arg.next().expect("Rule ensures value."); + let value = parse_value(value); + keyword_args.push((key, value)); + } + _ => unreachable!(), + } + } + + let args = FilterArguments { + positional: Box::new(positional_args.into_iter()), + keyword: Box::new(keyword_args.into_iter()), + }; + + 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) + })?; + + let f = f + .parse(args) + .trace("Filter parsing error") + .context_key("filter") + .value_with(|| filter_str.to_string().into())?; + Ok(f) } diff --git a/src/parser.rs b/src/parser.rs index 9d4103483..e6650f569 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -23,7 +23,7 @@ where { blocks: compiler::PluginRegistry, tags: compiler::PluginRegistry, - filters: compiler::PluginRegistry, + filters: compiler::PluginRegistry>, partials: Option

, } @@ -193,12 +193,10 @@ where } /// Inserts a new custom filter into the parser - pub fn filter>( - mut self, - name: &'static str, - filter: F, - ) -> Self { - self.filters.register(name, filter.into()); + pub fn filter>>(mut self, filter: F) -> Self { + let filter = filter.into(); + self.filters + .register(compiler::FilterReflection::name(&*filter), filter); self } diff --git a/src/tags/assign_tag.rs b/src/tags/assign_tag.rs index dbb678573..48d0757a1 100644 --- a/src/tags/assign_tag.rs +++ b/src/tags/assign_tag.rs @@ -9,7 +9,7 @@ use compiler::TagTokenIter; use interpreter::Context; use interpreter::Renderable; -#[derive(Clone, Debug)] +#[derive(Debug)] struct Assign { dst: String, src: FilterChain,