liquid_core/parser/
parser.rs

1//! Parser
2//!
3//! This module contains functions than can be used for writing plugins
4//! but should be ignored for simple usage.
5
6use crate::error::{Error, Result, ResultLiquidExt};
7use crate::model::Value;
8use crate::runtime::Expression;
9use crate::runtime::Renderable;
10use crate::runtime::Variable;
11
12use super::Language;
13use super::Text;
14use super::{Filter, FilterArguments, FilterChain};
15
16use pest::Parser;
17
18mod inner {
19    #[derive(Parser)]
20    #[grammar = "parser/grammar.pest"]
21    pub struct LiquidParser;
22}
23
24use self::inner::*;
25
26type Pair<'a> = ::pest::iterators::Pair<'a, Rule>;
27type Pairs<'a> = ::pest::iterators::Pairs<'a, Rule>;
28
29/// Converts a `pest::Error` into a `liquid::Error`.
30fn convert_pest_error(err: ::pest::error::Error<Rule>) -> Error {
31    let err = err.renamed_rules(|&rule| match rule {
32        Rule::LesserThan => "\"<\"".to_string(),
33        Rule::GreaterThan => "\">\"".to_string(),
34        Rule::LesserThanEquals => "\"<=\"".to_string(),
35        Rule::GreaterThanEquals => "\">=\"".to_string(),
36        Rule::Equals => "\"==\"".to_string(),
37        Rule::NotEquals => "\"!=\"".to_string(),
38        Rule::LesserThanGreaterThan => "\"<>\"".to_string(),
39        Rule::Assign => "\"=\"".to_string(),
40        Rule::Comma => "\",\"".to_string(),
41        Rule::Colon => "\":\"".to_string(),
42        other => format!("{:?}", other),
43    });
44    Error::with_msg(err.to_string())
45}
46
47/// Generates a `liquid::Error` with the given message pointing to
48/// the pest
49fn error_from_pair(pair: Pair, msg: String) -> Error {
50    let pest_error = ::pest::error::Error::new_from_span(
51        ::pest::error::ErrorVariant::CustomError { message: msg },
52        pair.as_span(),
53    );
54    convert_pest_error(pest_error)
55}
56
57/// Parses the provided &str into a number of Renderable items.
58pub fn parse(text: &str, options: &Language) -> Result<Vec<Box<dyn Renderable>>> {
59    let mut liquid = LiquidParser::parse(Rule::LaxLiquidFile, text)
60        .expect("Parsing with Rule::LaxLiquidFile should not raise errors, but InvalidLiquid tokens instead.")
61        .next()
62        .expect("Unwrapping LiquidFile to access the elements.")
63        .into_inner();
64
65    let mut renderables = Vec::new();
66
67    while let Some(element) = liquid.next() {
68        if element.as_rule() == Rule::EOI {
69            break;
70        }
71
72        renderables.push(BlockElement::parse_pair(
73            element.into(),
74            &mut liquid,
75            options,
76        )?);
77    }
78    Ok(renderables)
79}
80
81/// Given a `Variable` as a string, parses it into a `Variable`.
82pub fn parse_variable(text: &str) -> Result<Variable> {
83    let variable = LiquidParser::parse(Rule::Variable, text)
84        .map_err(convert_pest_error)?
85        .next()
86        .expect("Parsing a variable failed.");
87
88    Ok(parse_variable_pair(variable))
89}
90
91/// Parses a `Scalar` from a `Pair` with a literal value.
92/// This `Pair` must be `Rule::Literal`.
93fn parse_literal(literal: Pair) -> Value {
94    if literal.as_rule() != Rule::Literal {
95        panic!("Expected literal.");
96    }
97
98    let literal = literal
99        .into_inner()
100        .next()
101        .expect("Get into the rule inside literal.");
102
103    match literal.as_rule() {
104        Rule::NilLiteral => Value::Nil,
105        Rule::EmptyLiteral => Value::State(crate::model::State::Empty),
106        Rule::BlankLiteral => Value::State(crate::model::State::Blank),
107        Rule::StringLiteral => {
108            let literal = literal.as_str();
109            let trim_quotes = &literal[1..literal.len() - 1];
110
111            Value::scalar(trim_quotes.to_owned())
112        }
113        Rule::IntegerLiteral => Value::scalar(
114            literal
115                .as_str()
116                .parse::<i64>()
117                .expect("Grammar ensures matches are parseable as integers."),
118        ),
119        Rule::FloatLiteral => Value::scalar(
120            literal
121                .as_str()
122                .parse::<f64>()
123                .expect("Grammar ensures matches are parseable as floats."),
124        ),
125        Rule::BooleanLiteral => Value::scalar(
126            literal
127                .as_str()
128                .parse::<bool>()
129                .expect("Grammar ensures matches are parseable as bools."),
130        ),
131        _ => unreachable!(),
132    }
133}
134
135/// Parses a `Variable` from a `Pair` with a variable.
136/// This `Pair` must be `Rule::Variable`.
137fn parse_variable_pair(variable: Pair) -> Variable {
138    if variable.as_rule() != Rule::Variable {
139        panic!("Expected variable.");
140    }
141
142    let mut indexes = variable.into_inner();
143
144    let first_identifier = indexes
145        .next()
146        .expect("A variable starts with an identifier.")
147        .as_str()
148        .to_owned();
149    let mut variable = Variable::with_literal(first_identifier);
150
151    let indexes = indexes.map(|index| match index.as_rule() {
152        Rule::Identifier => Expression::with_literal(index.as_str().to_owned()),
153        Rule::Value => parse_value(index),
154        _ => unreachable!(),
155    });
156
157    variable.extend(indexes);
158    variable
159}
160
161/// Parses an `Expression` from a `Pair` with a value.
162///
163/// Do not confuse this value with `liquid-value`'s `Value`.
164/// In this runtime, value refers to either a literal value or a variable.
165///
166/// This `Pair` must be `Rule::Value`.
167fn parse_value(value: Pair) -> Expression {
168    if value.as_rule() != Rule::Value {
169        panic!("Expected value.");
170    }
171
172    let value = value.into_inner().next().expect("Get inside the value.");
173
174    match value.as_rule() {
175        Rule::Literal => Expression::Literal(parse_literal(value)),
176        Rule::Variable => Expression::Variable(parse_variable_pair(value)),
177        _ => unreachable!(),
178    }
179}
180
181/// Parses a `FilterCall` from a `Pair` with a filter.
182/// This `Pair` must be `Rule::Filter`.
183fn parse_filter(filter: Pair, options: &Language) -> Result<Box<dyn Filter>> {
184    if filter.as_rule() != Rule::Filter {
185        panic!("Expected a filter.");
186    }
187
188    let filter_str = filter.as_str();
189    let mut filter = filter.into_inner();
190    let name = filter.next().expect("A filter always has a name.").as_str();
191
192    let mut keyword_args = Vec::new();
193    let mut positional_args = Vec::new();
194
195    for arg in filter {
196        match arg.as_rule() {
197            Rule::PositionalFilterArgument => {
198                let value = arg.into_inner().next().expect("Rule ensures value.");
199                let value = parse_value(value);
200                positional_args.push(value);
201            }
202            Rule::KeywordFilterArgument => {
203                let mut arg = arg.into_inner();
204                let key = arg.next().expect("Rule ensures identifier.").as_str();
205                let value = arg.next().expect("Rule ensures value.");
206                let value = parse_value(value);
207                keyword_args.push((key, value));
208            }
209            _ => unreachable!(),
210        }
211    }
212
213    let args = FilterArguments {
214        positional: Box::new(positional_args.into_iter()),
215        keyword: Box::new(keyword_args.into_iter()),
216    };
217
218    let f = options.filters.get(name).ok_or_else(|| {
219        let mut available: Vec<_> = options.filters.plugin_names().collect();
220        available.sort_unstable();
221        let available = itertools::join(available, ", ");
222        Error::with_msg("Unknown filter")
223            .context("requested filter", name.to_owned())
224            .context("available filters", available)
225    })?;
226
227    let f = f
228        .parse(args)
229        .trace("Filter parsing error")
230        .context_key("filter")
231        .value_with(|| filter_str.to_string().into())?;
232
233    Ok(f)
234}
235
236/// Parses a `FilterChain` from a `Pair` with a filter chain.
237/// This `Pair` must be `Rule::FilterChain`.
238fn parse_filter_chain(chain: Pair, options: &Language) -> Result<FilterChain> {
239    if chain.as_rule() != Rule::FilterChain {
240        panic!("Expected an expression with filters.");
241    }
242
243    let mut chain = chain.into_inner();
244    let entry = parse_value(
245        chain
246            .next()
247            .expect("A filterchain always has starts by a value."),
248    );
249    let filters: Result<Vec<_>> = chain.map(|f| parse_filter(f, options)).collect();
250    let filters = filters?;
251
252    let filters = FilterChain::new(entry, filters);
253    Ok(filters)
254}
255
256/// An interface to access elements inside a block.
257pub struct TagBlock<'a: 'b, 'b> {
258    start_tag: &'b str,
259    end_tag: &'b str,
260    iter: &'b mut dyn Iterator<Item = Pair<'a>>,
261    closed: bool,
262}
263
264impl<'a, 'b> TagBlock<'a, 'b> {
265    fn new(
266        start_tag: &'b str,
267        end_tag: &'b str,
268        next_elements: &'b mut dyn Iterator<Item = Pair<'a>>,
269    ) -> Self {
270        TagBlock {
271            start_tag,
272            end_tag,
273            iter: next_elements,
274            closed: false,
275        }
276    }
277
278    /// Returns the next element of the block, if any, similarly to an iterator.
279    ///
280    /// However, if the input text reaches its end and the block is not closed,
281    /// an error is returned instead.
282    #[allow(clippy::should_implement_trait)]
283    pub fn next(&mut self) -> Result<Option<BlockElement<'a>>> {
284        if self.closed {
285            return Ok(None);
286        }
287
288        let element = self.iter.next().expect("File shouldn't end before EOI.");
289
290        if element.as_rule() == Rule::EOI {
291            return error_from_pair(
292                element,
293                format!("Unclosed block. {{% {} %}} tag expected.", self.end_tag),
294            )
295            .into_err();
296        }
297
298        // Tags are treated separately so as to check for a possible `{% endtag %}`
299        if element.as_rule() == Rule::Tag {
300            let as_str = element.as_str();
301            let mut tag = element
302                .into_inner()
303                .next()
304                .expect("Unwrapping TagInner")
305                .into_inner();
306            let name = tag.next().expect("Tags start by their identifier.");
307            let name_str = name.as_str();
308
309            // Check if this tag is the same as the block's reflected end-tag.
310            if name_str == self.end_tag {
311                // Then this is a block ending tag and will close the block.
312
313                // no more arguments should be supplied, trying to supply them is an error
314                if let Some(token) = tag.next() {
315                    return TagToken::from(token).raise_error().into_err();
316                }
317
318                self.closed = true;
319                return Ok(None);
320            } else {
321                // Then this is a regular tag
322                let tokens = TagTokenIter::new(&name, tag);
323                return Ok(Some(BlockElement::Tag(Tag {
324                    name,
325                    tokens,
326                    as_str,
327                })));
328            }
329        }
330        Ok(Some(element.into()))
331    }
332
333    /// Retrieves all the content of this block as a String, regardless of
334    /// being valid liquid or not.
335    ///
336    /// Do not use this method in a block you already called `.next()` on.
337    ///
338    /// Set the parameter `allow_nesting` of this function to true if you
339    /// still want these tags to nest (so the number of `{% name %}` must
340    /// be equal to the number of `{% endname %}`) of false if you don't
341    /// (only the first `{% name %}` is parsed, a single `{% endname %}`
342    /// will always close the tag).
343    ///
344    /// # Panics
345    ///
346    /// Will panic if used in a closed block.
347    pub fn escape_liquid(&mut self, allow_nesting: bool) -> Result<&'a str> {
348        if self.closed {
349            panic!("`escape_liquid` must be used in an open tag.")
350        }
351
352        let mut nesting_level = 1;
353
354        // Working with pest positions allows returning a `&str` instead of a `String`
355        let mut start_pos = None;
356        let mut end_pos = None;
357
358        #[allow(clippy::while_let_on_iterator)]
359        while let Some(element) = self.iter.next() {
360            let element_as_span = element.as_span();
361            if start_pos.is_none() {
362                start_pos = Some(element_as_span.start_pos());
363            }
364
365            if element.as_rule() == Rule::EOI {
366                return error_from_pair(
367                    element,
368                    format!("Unclosed block. {{% {} %}} tag expected.", self.end_tag),
369                )
370                .into_err();
371            }
372
373            // Tags are potentially `{% endtag %}`
374            if element.as_rule() == Rule::Tag {
375                let mut tag = element
376                    .into_inner()
377                    .next()
378                    .expect("Unwrapping TagInner")
379                    .into_inner();
380                let name = tag.next().expect("Tags start by their identifier.");
381                let name_str = name.as_str();
382
383                // Check if this tag is the same as the block's reflected end-tag.
384                if name_str == self.end_tag {
385                    // No more arguments should be supplied. If they are, it is
386                    // assumed not to be a tag closer.
387                    if tag.next().is_none() {
388                        nesting_level -= 1;
389                        if nesting_level == 0 {
390                            self.closed = true;
391                            let start_pos = start_pos.expect("Will be `Some` inside this loop.");
392                            let output = match end_pos {
393                                Some(end_pos) => start_pos.span(&end_pos).as_str(),
394                                None => "",
395                            };
396
397                            return Ok(output);
398                        }
399                    }
400                } else if name_str == self.start_tag && allow_nesting {
401                    // Going deeper in the nested blocks.
402                    nesting_level += 1;
403                }
404            }
405
406            end_pos = Some(element_as_span.end_pos());
407        }
408
409        panic!("Function must eventually find either a Rule::EOI or a closing tag.")
410    }
411
412    /// A convenient method that parses every element remaining in the block.
413    pub fn parse_all(&mut self, options: &Language) -> Result<Vec<Box<dyn Renderable>>> {
414        let mut renderables = Vec::new();
415        while let Some(r) = self.parse_next(options)? {
416            renderables.push(r);
417        }
418        Ok(renderables)
419    }
420
421    /// Parses the next element in the block just as if it weren't inside any block.
422    ///
423    /// Returns none if no element is left and raises the same errors as `next()`.
424    pub fn parse_next(&mut self, options: &Language) -> Result<Option<Box<dyn Renderable>>> {
425        match self.next()? {
426            None => Ok(None),
427            Some(element) => Ok(Some(element.parse(self, options)?)),
428        }
429    }
430
431    /// Checks whether the block was fully parsed its elements.
432    ///
433    /// This must be added at the end of every block right before returning, so as
434    /// to ensure that it doesn't leave any unparsed element by accident.
435    pub fn assert_empty(self) {
436        assert!(
437            self.closed,
438            "Block {{% {} %}} doesn't exhaust its iterator of elements.",
439            self.start_tag
440        )
441    }
442}
443
444/// An element that is raw text.
445pub struct Raw<'a> {
446    text: &'a str,
447}
448impl<'a> From<Pair<'a>> for Raw<'a> {
449    fn from(element: Pair<'a>) -> Self {
450        if element.as_rule() != Rule::Raw {
451            panic!("Only rule Raw can be converted to Raw.");
452        }
453        Raw {
454            text: element.as_str(),
455        }
456    }
457}
458#[allow(clippy::from_over_into)]
459impl<'a> Into<&'a str> for Raw<'a> {
460    fn into(self) -> &'a str {
461        self.as_str()
462    }
463}
464impl<'a> Raw<'a> {
465    /// Turns the text into a Renderable.
466    pub fn into_renderable(self) -> Box<dyn Renderable> {
467        Box::new(Text::new(self.as_str()))
468    }
469
470    /// Returns the text as a str.
471    pub fn as_str(&self) -> &'a str {
472        self.text
473    }
474}
475
476/// An element that is a tag.
477pub struct Tag<'a> {
478    name: Pair<'a>,
479    tokens: TagTokenIter<'a>,
480    as_str: &'a str,
481}
482
483impl<'a> From<Pair<'a>> for Tag<'a> {
484    fn from(element: Pair<'a>) -> Self {
485        if element.as_rule() != Rule::Tag {
486            panic!("Only rule Tag can be converted to Tag.");
487        }
488        let as_str = element.as_str();
489        let mut tag = element
490            .into_inner()
491            .next()
492            .expect("Unwrapping TagInner.")
493            .into_inner();
494        let name = tag.next().expect("A tag starts with an identifier.");
495        let tokens = TagTokenIter::new(&name, tag);
496
497        Tag {
498            name,
499            tokens,
500            as_str,
501        }
502    }
503}
504
505impl<'a> Tag<'a> {
506    /// Creates a new tag from a string such as "{% tagname tagtoken1 tagtoken2 ... %}".
507    ///
508    /// This is used as a debug tool. It allows to easily build tags in unit tests.
509    pub fn new(text: &'a str) -> Result<Self> {
510        let tag = LiquidParser::parse(Rule::Tag, text)
511            .map_err(convert_pest_error)?
512            .next()
513            .ok_or_else(|| Error::with_msg("Tried to create a Tag from an invalid string."))?;
514
515        Ok(tag.into())
516    }
517
518    /// Returns the name of this tag.
519    pub fn name(&self) -> &str {
520        self.name.as_str()
521    }
522
523    /// Returns the tokens of this tag.
524    pub fn tokens(&mut self) -> &mut TagTokenIter<'a> {
525        &mut self.tokens
526    }
527
528    /// Consumes this structure to obtain ownership over its tokens.
529    pub fn into_tokens(self) -> TagTokenIter<'a> {
530        self.tokens
531    }
532
533    /// Returns the tag as a str.
534    pub fn as_str(&self) -> &str {
535        self.as_str
536    }
537
538    /// Parses the tag just as if it weren't inside any block.
539    pub fn parse(
540        self,
541        tag_block: &mut TagBlock,
542        options: &Language,
543    ) -> Result<Box<dyn Renderable>> {
544        self.parse_pair(&mut tag_block.iter, options)
545    }
546
547    /// The same as `parse`, but directly takes an iterator over `Pair`s instead of a TagBlock.
548    fn parse_pair(
549        self,
550        next_elements: &mut dyn Iterator<Item = Pair>,
551        options: &Language,
552    ) -> Result<Box<dyn Renderable>> {
553        let (name, tokens) = (self.name, self.tokens);
554        let position = name.as_span();
555        let name = name.as_str();
556
557        if let Some(plugin) = options.tags.get(name) {
558            plugin.parse(tokens, options)
559        } else if let Some(plugin) = options.blocks.get(name) {
560            let reflection = plugin.reflection();
561            let block = TagBlock::new(reflection.start_tag(), reflection.end_tag(), next_elements);
562            let renderables = plugin.parse(tokens, block, options)?;
563            Ok(renderables)
564        } else {
565            let pest_error = ::pest::error::Error::new_from_span(
566                ::pest::error::ErrorVariant::CustomError {
567                    message: "Unknown tag.".to_string(),
568                },
569                position,
570            );
571            let mut all_tags: Vec<_> = options.tags.plugin_names().collect();
572            all_tags.sort_unstable();
573            let all_tags = itertools::join(all_tags, ", ");
574            let mut all_blocks: Vec<_> = options.blocks.plugin_names().collect();
575            all_blocks.sort_unstable();
576            let all_blocks = itertools::join(all_blocks, ", ");
577            let error = convert_pest_error(pest_error)
578                .context("requested", name.to_owned())
579                .context("available tags", all_tags)
580                .context("available blocks", all_blocks);
581            Err(error)
582        }
583    }
584}
585
586/// An element that is an expression.
587pub struct Exp<'a> {
588    element: Pair<'a>,
589}
590
591impl<'a> From<Pair<'a>> for Exp<'a> {
592    fn from(element: Pair<'a>) -> Self {
593        if element.as_rule() != Rule::Expression {
594            panic!("Only rule Expression can be converted to Expression.");
595        }
596        Exp { element }
597    }
598}
599
600impl Exp<'_> {
601    /// Parses the expression just as if it weren't inside any block.
602    pub fn parse(self, options: &Language) -> Result<Box<dyn Renderable>> {
603        let filter_chain = self
604            .element
605            .into_inner()
606            .next()
607            .expect("Unwrapping ExpressionInner")
608            .into_inner()
609            .next()
610            .expect("An expression consists of one filterchain.");
611
612        let filter_chain = parse_filter_chain(filter_chain, options)?;
613        Ok(Box::new(filter_chain))
614    }
615
616    /// Returns the expression as a str.
617    pub fn as_str(&self) -> &str {
618        self.element.as_str()
619    }
620}
621
622/// This token could not be recognized as valid liquid.
623/// If parsed, will raise an error.
624pub struct InvalidLiquidToken<'a> {
625    element: Pair<'a>,
626}
627impl InvalidLiquidToken<'_> {
628    /// Returns the expression as a str.
629    // TODO consider removing this
630    pub fn as_str(&self) -> &str {
631        self.element.as_str()
632    }
633
634    /// Tries to parse this as valid liquid, which will inevitably raise an error.
635    /// This is needed in order to raise the right error message.
636    pub fn parse(self, tag_block: &mut TagBlock) -> Result<Box<dyn Renderable>> {
637        self.parse_pair(&mut tag_block.iter)
638    }
639
640    /// Tries to parse this as valid liquid, which will inevitably raise an error.
641    /// This is needed in order to raise the correct error message.
642    fn parse_pair(
643        self,
644        next_elements: &mut dyn Iterator<Item = Pair>,
645    ) -> Result<Box<dyn Renderable>> {
646        use pest::error::LineColLocation;
647
648        let invalid_token_span = self.element.as_span();
649        let invalid_token_position = invalid_token_span.start_pos();
650        let (offset_l, offset_c) = invalid_token_position.line_col();
651        let offset_l = offset_l - 1;
652        let offset_c = (0..offset_c)
653            .rev()
654            .find(|i| invalid_token_position.line_of().is_char_boundary(*i))
655            .unwrap_or(0);
656
657        let end_position = match next_elements.last() {
658            Some(element) => element.as_span().end_pos(),
659            None => invalid_token_span.end_pos(),
660        };
661
662        let mut text = String::from(&invalid_token_position.line_of()[..offset_c]);
663        text.push_str(invalid_token_position.span(&end_position).as_str());
664
665        // Reparses from the line where invalid liquid started, in order
666        // to raise the error.
667        let mut error = match LiquidParser::parse(Rule::LiquidFile, &text) {
668            Ok(_) => panic!("`LiquidParser::parse` should fail in InvalidLiquidTokens."),
669            Err(error) => error,
670        };
671
672        // Adds an offset to the line of the error, in order to show the right line
673        // TODO when liquid::error is able to handle line/col information by itself
674        // make this operation on the liquid Error type instead.
675        error.line_col = match error.line_col {
676            LineColLocation::Span((ls, cs), (le, ce)) => {
677                LineColLocation::Span((ls + offset_l, cs), (le + offset_l, ce))
678            }
679            LineColLocation::Pos((ls, cs)) => LineColLocation::Pos((ls + offset_l, cs)),
680        };
681
682        Err(convert_pest_error(error))
683    }
684}
685impl<'a> From<Pair<'a>> for InvalidLiquidToken<'a> {
686    fn from(element: Pair<'a>) -> Self {
687        if element.as_rule() != Rule::InvalidLiquid {
688            panic!("Tried to parse a valid liquid token as invalid.");
689        }
690        InvalidLiquidToken { element }
691    }
692}
693
694/// An element that can be raw text, a tag, or an expression.
695///
696/// This is the result of calling `next()` on a `TagBlock`.
697pub enum BlockElement<'a> {
698    Raw(Raw<'a>),
699    Tag(Tag<'a>),
700    Expression(Exp<'a>),
701    Invalid(InvalidLiquidToken<'a>),
702}
703impl<'a> From<Pair<'a>> for BlockElement<'a> {
704    fn from(element: Pair<'a>) -> Self {
705        match element.as_rule() {
706            Rule::Raw => BlockElement::Raw(element.into()),
707            Rule::Tag => BlockElement::Tag(element.into()),
708            Rule::Expression => BlockElement::Expression(element.into()),
709            Rule::InvalidLiquid => BlockElement::Invalid(element.into()),
710            _ => panic!(
711                "Only rules Raw | Tag | Expression can be converted to BlockElement. Found {:?}",
712                element.as_rule()
713            ),
714        }
715    }
716}
717
718impl<'a> BlockElement<'a> {
719    /// Parses the element in the block just as if it weren't inside any block.
720    pub fn parse(
721        self,
722        block: &mut TagBlock<'a, '_>,
723        options: &Language,
724    ) -> Result<Box<dyn Renderable>> {
725        match self {
726            BlockElement::Raw(raw) => Ok(raw.into_renderable()),
727            BlockElement::Tag(tag) => tag.parse(block, options),
728            BlockElement::Expression(exp) => exp.parse(options),
729            BlockElement::Invalid(invalid) => invalid.parse(block),
730        }
731    }
732
733    /// The same as `parse`, but directly takes an iterator over `Pair`s instead of a TagBlock.
734    fn parse_pair(
735        self,
736        next_elements: &mut dyn Iterator<Item = Pair>,
737        options: &Language,
738    ) -> Result<Box<dyn Renderable>> {
739        match self {
740            BlockElement::Raw(raw) => Ok(raw.into_renderable()),
741            BlockElement::Tag(tag) => tag.parse_pair(next_elements, options),
742            BlockElement::Expression(exp) => exp.parse(options),
743            BlockElement::Invalid(invalid) => invalid.parse_pair(next_elements),
744        }
745    }
746
747    /// Returns the element as a str.
748    pub fn as_str(&self) -> &str {
749        match self {
750            BlockElement::Raw(raw) => raw.as_str(),
751            BlockElement::Tag(tag) => tag.as_str(),
752            BlockElement::Expression(exp) => exp.as_str(),
753            BlockElement::Invalid(invalid) => invalid.as_str(),
754        }
755    }
756}
757
758/// An iterator over `TagToken`s that is aware of their position in the file.
759///
760/// The awareness of the position allows more precise error messages.
761pub struct TagTokenIter<'a> {
762    iter: Box<dyn Iterator<Item = TagToken<'a>> + 'a>,
763    position: ::pest::Position<'a>,
764}
765impl<'a> Iterator for TagTokenIter<'a> {
766    type Item = TagToken<'a>;
767    fn next(&mut self) -> Option<Self::Item> {
768        self.iter.next().inspect(|next| {
769            self.position = next.token.as_span().end_pos();
770        })
771    }
772}
773impl<'a> TagTokenIter<'a> {
774    fn new(name: &Pair<'a>, tokens: Pairs<'a>) -> Self {
775        TagTokenIter {
776            iter: Box::new(tokens.map(TagToken::from)),
777            position: name.as_span().end_pos(),
778        }
779    }
780
781    /// Creates an error with the given message pointing at the current
782    /// position of the iterator.
783    pub fn raise_error(&mut self, error_msg: &str) -> Error {
784        let pest_error = ::pest::error::Error::new_from_pos(
785            ::pest::error::ErrorVariant::CustomError {
786                message: error_msg.to_string(),
787            },
788            self.position,
789        );
790        convert_pest_error(pest_error)
791    }
792
793    /// Returns the next tag token or raises an error if there is none.
794    pub fn expect_next(&mut self, error_msg: &str) -> Result<TagToken<'a>> {
795        self.next().ok_or_else(|| self.raise_error(error_msg))
796    }
797
798    /// Returns `Ok` if the iterator is empty, an error otherwise
799    pub fn expect_nothing(&mut self) -> Result<()> {
800        if let Some(token) = self.next() {
801            Err(token.raise_error())
802        } else {
803            Ok(())
804        }
805    }
806}
807
808/// The result of calling `TagToken`'s `try`.
809///
810/// If the token is successfully matched, the match is returned;
811/// otherwise, the TagToken is returned back.
812pub enum TryMatchToken<'a, T> {
813    Matches(T),
814    Fails(TagToken<'a>),
815}
816
817impl<T> TryMatchToken<'_, T> {
818    pub fn into_result(self) -> Result<T> {
819        match self {
820            TryMatchToken::Matches(t) => Ok(t),
821            TryMatchToken::Fails(t) => Err(t.raise_error()),
822        }
823    }
824
825    pub fn into_result_custom_msg(self, msg: &str) -> Result<T> {
826        match self {
827            TryMatchToken::Matches(t) => Ok(t),
828            TryMatchToken::Fails(t) => Err(t.raise_custom_error(msg)),
829        }
830    }
831}
832
833/// An interface to access tokens inside a tag.
834pub struct TagToken<'a> {
835    token: Pair<'a>,
836    expected: Vec<Rule>,
837}
838
839impl<'a> From<Pair<'a>> for TagToken<'a> {
840    fn from(token: Pair<'a>) -> Self {
841        TagToken {
842            token,
843            expected: Vec::new(),
844        }
845    }
846}
847
848impl<'a> TagToken<'a> {
849    /// Raises an error from this TagToken.
850    ///
851    /// The error message will be based on the expected tokens,
852    /// which this structure tracks when using the methods starting
853    /// with 'expect'.
854    ///
855    /// For example, if one calls `expect_value` and that function fails
856    /// to give an `Ok` value, calling this would show `Expected Value`
857    /// on the error message.
858    pub fn raise_error(self) -> Error {
859        let pest_error = ::pest::error::Error::new_from_span(
860            ::pest::error::ErrorVariant::ParsingError {
861                positives: self.expected,
862                negatives: vec![self.token.as_rule()],
863            },
864            self.token.as_span(),
865        );
866        convert_pest_error(pest_error)
867    }
868
869    /// Raises an error from this TagToken.
870    ///
871    /// The error will have the given error message.
872    pub fn raise_custom_error(self, msg: &str) -> Error {
873        let pest_error = ::pest::error::Error::new_from_span(
874            ::pest::error::ErrorVariant::CustomError {
875                message: msg.to_string(),
876            },
877            self.token.as_span(),
878        );
879        convert_pest_error(pest_error)
880    }
881
882    fn unwrap_filter_chain(&mut self) -> std::result::Result<Pair<'a>, ()> {
883        let token = self.token.clone();
884
885        if token.as_rule() != Rule::FilterChain {
886            return Err(());
887        }
888
889        Ok(token)
890    }
891
892    fn unwrap_value(&mut self) -> std::result::Result<Pair<'a>, ()> {
893        let filterchain = self.unwrap_filter_chain()?;
894
895        let mut chain = filterchain.into_inner();
896        let value = chain.next().expect("Unwrapping value out of Filterchain.");
897        if chain.next().is_some() {
898            // There are filters: it can't be a value
899            return Err(());
900        }
901
902        Ok(value)
903    }
904
905    fn unwrap_variable(&mut self) -> std::result::Result<Pair<'a>, ()> {
906        let value = self.unwrap_value()?;
907
908        let variable = value
909            .into_inner()
910            .next()
911            .expect("A value is made of one token.");
912
913        if variable.as_rule() != Rule::Variable {
914            return Err(());
915        }
916
917        Ok(variable)
918    }
919
920    fn unwrap_identifier(&mut self) -> std::result::Result<Pair<'a>, ()> {
921        let variable = self.unwrap_variable()?;
922
923        let mut indexes = variable.into_inner();
924        let identifier = indexes
925            .next()
926            .expect("Unwrapping identifier out of variable.");
927        if indexes.next().is_some() {
928            // There are indexes: it can't be a value
929            return Err(());
930        }
931
932        Ok(identifier)
933    }
934
935    fn unwrap_literal(&mut self) -> std::result::Result<Pair<'a>, ()> {
936        let value = self.unwrap_value()?;
937
938        let literal = value
939            .into_inner()
940            .next()
941            .expect("A value is made of one token.");
942
943        if literal.as_rule() != Rule::Literal {
944            return Err(());
945        }
946
947        Ok(literal)
948    }
949
950    /// Tries to obtain a `FilterChain` from this token.
951    pub fn expect_filter_chain(mut self, options: &Language) -> TryMatchToken<'a, FilterChain> {
952        match self.expect_filter_chain_err(options) {
953            Ok(t) => TryMatchToken::Matches(t),
954            Err(_) => {
955                self.expected.push(Rule::FilterChain);
956                TryMatchToken::Fails(self)
957            }
958        }
959    }
960
961    fn expect_filter_chain_err(&mut self, options: &Language) -> Result<FilterChain> {
962        let t = self
963            .unwrap_filter_chain()
964            .map_err(|_| Error::with_msg("failed to parse"))?;
965        let f = parse_filter_chain(t, options)?;
966        Ok(f)
967    }
968
969    /// Tries to obtain a value from this token.
970    ///
971    /// Do not confuse this value with `liquid-value`'s `Value`.
972    /// In this runtime, value refers to either a literal value or a variable.
973    pub fn expect_value(mut self) -> TryMatchToken<'a, Expression> {
974        match self.unwrap_value() {
975            Ok(t) => TryMatchToken::Matches(parse_value(t)),
976            Err(_) => {
977                self.expected.push(Rule::Value);
978                TryMatchToken::Fails(self)
979            }
980        }
981    }
982
983    /// Tries to obtain a `Variable` from this token.
984    pub fn expect_variable(mut self) -> TryMatchToken<'a, Variable> {
985        match self.unwrap_variable() {
986            Ok(t) => TryMatchToken::Matches(parse_variable_pair(t)),
987            Err(_) => {
988                self.expected.push(Rule::Variable);
989                TryMatchToken::Fails(self)
990            }
991        }
992    }
993
994    /// Tries to obtain an identifier from this token.
995    ///
996    /// The identifier is returned as a str.
997    pub fn expect_identifier(mut self) -> TryMatchToken<'a, &'a str> {
998        match self.unwrap_identifier() {
999            Ok(t) => TryMatchToken::Matches(t.as_str()),
1000            Err(_) => {
1001                self.expected.push(Rule::Identifier);
1002                TryMatchToken::Fails(self)
1003            }
1004        }
1005    }
1006
1007    /// Tries to obtain a literal value from this token.
1008    ///
1009    /// The value is returned as a `Value`.
1010    pub fn expect_literal(mut self) -> TryMatchToken<'a, Value> {
1011        match self.unwrap_literal() {
1012            Ok(t) => TryMatchToken::Matches(parse_literal(t)),
1013            Err(_) => {
1014                self.expected.push(Rule::Literal);
1015                TryMatchToken::Fails(self)
1016            }
1017        }
1018    }
1019    /// Tries to obtain a range from this token.
1020    ///
1021    /// The range is returned as a pair `(Expression, Expression)`.
1022    pub fn expect_range(mut self) -> TryMatchToken<'a, (Expression, Expression)> {
1023        let token = self.token.clone();
1024
1025        if token.as_rule() != Rule::Range {
1026            self.expected.push(Rule::Range);
1027            return TryMatchToken::Fails(self);
1028        }
1029
1030        let mut range = token.into_inner();
1031        TryMatchToken::Matches((
1032            parse_value(range.next().expect("start")),
1033            parse_value(range.next().expect("end")),
1034        ))
1035    }
1036
1037    /// Returns `Ok` if and only if the tokens' str is equal to the given str.
1038    pub fn expect_str(self, expected: &str) -> TryMatchToken<'a, ()> {
1039        if self.as_str() == expected {
1040            TryMatchToken::Matches(())
1041        } else {
1042            // TODO change `self`'s state to be aware that `expected` was expected.
1043            TryMatchToken::Fails(self)
1044        }
1045    }
1046
1047    /// Returns token as a str.
1048    pub fn as_str(&self) -> &str {
1049        self.token.as_str().trim()
1050    }
1051}
1052
1053#[cfg(test)]
1054mod test {
1055    use super::*;
1056    use crate::runtime::{Runtime, RuntimeBuilder, Template};
1057
1058    #[test]
1059    fn test_parse_literal() {
1060        let nil = LiquidParser::parse(Rule::Literal, "nil")
1061            .unwrap()
1062            .next()
1063            .unwrap();
1064        assert_eq!(parse_literal(nil), Value::Nil);
1065        let nil = LiquidParser::parse(Rule::Literal, "null")
1066            .unwrap()
1067            .next()
1068            .unwrap();
1069        assert_eq!(parse_literal(nil), Value::Nil);
1070
1071        let blank = LiquidParser::parse(Rule::Literal, "blank")
1072            .unwrap()
1073            .next()
1074            .unwrap();
1075        assert_eq!(
1076            parse_literal(blank),
1077            Value::State(crate::model::State::Blank)
1078        );
1079
1080        let empty = LiquidParser::parse(Rule::Literal, "empty")
1081            .unwrap()
1082            .next()
1083            .unwrap();
1084        assert_eq!(
1085            parse_literal(empty),
1086            Value::State(crate::model::State::Empty)
1087        );
1088
1089        let integer = LiquidParser::parse(Rule::Literal, "42")
1090            .unwrap()
1091            .next()
1092            .unwrap();
1093        assert_eq!(parse_literal(integer), Value::scalar(42));
1094
1095        let negative_int = LiquidParser::parse(Rule::Literal, "-42")
1096            .unwrap()
1097            .next()
1098            .unwrap();
1099        assert_eq!(parse_literal(negative_int), Value::scalar(-42));
1100
1101        let float = LiquidParser::parse(Rule::Literal, "4321.032")
1102            .unwrap()
1103            .next()
1104            .unwrap();
1105        assert_eq!(parse_literal(float), Value::scalar(4321.032));
1106
1107        let negative_float = LiquidParser::parse(Rule::Literal, "-4321.032")
1108            .unwrap()
1109            .next()
1110            .unwrap();
1111        assert_eq!(parse_literal(negative_float), Value::scalar(-4321.032));
1112
1113        let boolean = LiquidParser::parse(Rule::Literal, "true")
1114            .unwrap()
1115            .next()
1116            .unwrap();
1117        assert_eq!(parse_literal(boolean), Value::scalar(true));
1118
1119        let string_double_quotes = LiquidParser::parse(Rule::Literal, "\"Hello world!\"")
1120            .unwrap()
1121            .next()
1122            .unwrap();
1123        assert_eq!(
1124            parse_literal(string_double_quotes),
1125            Value::scalar("Hello world!")
1126        );
1127
1128        let string_single_quotes = LiquidParser::parse(Rule::Literal, "'Liquid'")
1129            .unwrap()
1130            .next()
1131            .unwrap();
1132        assert_eq!(parse_literal(string_single_quotes), Value::scalar("Liquid"));
1133    }
1134
1135    #[test]
1136    fn test_parse_variable_pair() {
1137        let variable = LiquidParser::parse(Rule::Variable, "foo[0].bar.baz[foo.bar]")
1138            .unwrap()
1139            .next()
1140            .unwrap();
1141
1142        let indexes = vec![
1143            Expression::Literal(Value::scalar(0)),
1144            Expression::Literal(Value::scalar("bar")),
1145            Expression::Literal(Value::scalar("baz")),
1146            Expression::Variable(Variable::with_literal("foo").push_literal("bar")),
1147        ];
1148
1149        let mut expected = Variable::with_literal("foo");
1150        expected.extend(indexes);
1151
1152        assert_eq!(parse_variable_pair(variable), expected);
1153    }
1154
1155    #[test]
1156    fn test_whitespace_control() {
1157        let options = Language::default();
1158
1159        let runtime = RuntimeBuilder::new().build();
1160        runtime.set_global("exp".into(), Value::scalar(5));
1161
1162        let text = "    \n    {{ exp }}    \n    ";
1163        let template = parse(text, &options).map(Template::new).unwrap();
1164        let output = template.render(&runtime).unwrap();
1165
1166        assert_eq!(output, "    \n    5    \n    ");
1167
1168        let text = "    \n    {{- exp }}    \n    ";
1169        let template = parse(text, &options).map(Template::new).unwrap();
1170        let output = template.render(&runtime).unwrap();
1171
1172        assert_eq!(output, "5    \n    ");
1173
1174        let text = "    \n    {{ exp -}}    \n    ";
1175        let template = parse(text, &options).map(Template::new).unwrap();
1176        let output = template.render(&runtime).unwrap();
1177
1178        assert_eq!(output, "    \n    5");
1179
1180        let text = "    \n    {{- exp -}}    \n    ";
1181        let template = parse(text, &options).map(Template::new).unwrap();
1182        let output = template.render(&runtime).unwrap();
1183
1184        assert_eq!(output, "5");
1185    }
1186
1187    /// Macro implementation of custom block test.
1188    macro_rules! test_custom_block_tags_impl {
1189        ($start_tag:expr, $end_tag:expr) => {{
1190            use crate::error::ResultLiquidReplaceExt;
1191            use crate::{BlockReflection, ParseBlock};
1192            use std::io::Write;
1193
1194            #[derive(Debug, Default, Copy, Clone)]
1195            struct CustomBlock;
1196            #[derive(Debug)]
1197            struct Custom {
1198                inside: Template,
1199            }
1200
1201            impl BlockReflection for CustomBlock {
1202                fn start_tag(&self) -> &str {
1203                    $start_tag
1204                }
1205
1206                fn end_tag(&self) -> &str {
1207                    $end_tag
1208                }
1209
1210                fn description(&self) -> &str {
1211                    "I am a description"
1212                }
1213            }
1214
1215            impl ParseBlock for CustomBlock {
1216                fn parse(
1217                    &self,
1218                    mut arguments: TagTokenIter,
1219                    mut block: TagBlock,
1220                    options: &Language,
1221                ) -> Result<Box<dyn Renderable>> {
1222                    arguments.expect_nothing()?;
1223
1224                    let inside = block.parse_all(options).map(Template::new)?;
1225
1226                    Ok(Box::new(Custom { inside }))
1227                }
1228
1229                fn reflection(&self) -> &dyn BlockReflection {
1230                    self
1231                }
1232            }
1233
1234            impl Renderable for Custom {
1235                fn render_to(&self, writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
1236                    write!(writer, "<pre>").replace("Failed to render")?;
1237                    self.inside.render_to(writer, runtime)?;
1238                    write!(writer, "</pre>").replace("Failed to render")?;
1239
1240                    Ok(())
1241                }
1242            }
1243
1244            let mut options = Language::default();
1245            options
1246                .blocks
1247                .register(CustomBlock.start_tag().to_string(), Box::new(CustomBlock));
1248
1249            let runtime = RuntimeBuilder::new().build();
1250
1251            let text = concat!("{% ", $start_tag, " %}Hello Liquid!{% ", $end_tag, " %}");
1252            let template = parse(text, &options).map(Template::new).unwrap();
1253            let output = template.render(&runtime).unwrap();
1254
1255            assert_eq!(output, "<pre>Hello Liquid!</pre>");
1256        }};
1257    }
1258
1259    /// Test compatibility of block tags that do not end with `end<name>`.
1260    #[test]
1261    fn test_custom_block_tags() {
1262        // Test that normal `<name>`-`end<name>` tags work.
1263        test_custom_block_tags_impl!("custom", "endcustom");
1264
1265        // Test that tags not of the form `<name>`-`end<name>` also work.
1266        test_custom_block_tags_impl!("startcustom", "stopcustom");
1267    }
1268}