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