Skip to main content

oak_liquid/parser/
mod.rs

1/// Liquid Parser module
2///
3/// This module defines the parser for Liquid templates, responsible for parsing the tokens into an AST.
4use oak_core::{
5    parser::{ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer},
6    source::{Source, TextEdit},
7};
8
9/// Element type definitions for Liquid parser.
10pub mod element_type;
11use crate::{
12    language::LiquidLanguage,
13    lexer::{LiquidLexer, token_type::LiquidTokenType},
14};
15use element_type::LiquidElementType;
16
17pub(crate) type State<'a, S> = ParserState<'a, LiquidLanguage, S>;
18
19/// Parser for Liquid templates
20#[derive(Debug, Clone)]
21pub struct LiquidParser<'config> {
22    /// Language configuration
23    config: &'config LiquidLanguage,
24}
25
26impl<'config> LiquidParser<'config> {
27    /// Create a new Liquid parser
28    pub fn new(config: &'config LiquidLanguage) -> Self {
29        Self { config }
30    }
31
32    fn parse_node<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
33        match state.peek_kind() {
34            Some(LiquidTokenType::DoubleLeftBrace) => self.parse_variable(state),
35            Some(LiquidTokenType::LeftBracePercent) => self.parse_tag_statement(state),
36            Some(LiquidTokenType::Comment) => {
37                let cp = state.checkpoint();
38                state.bump();
39                state.finish_at(cp, LiquidElementType::Comment);
40                Ok(())
41            }
42            _ => {
43                let cp = state.checkpoint();
44                state.advance();
45                state.finish_at(cp, LiquidElementType::Text);
46                Ok(())
47            }
48        }
49    }
50
51    fn parse_variable<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
52        let cp = state.checkpoint();
53        state.expect(LiquidTokenType::DoubleLeftBrace)?;
54        self.parse_expression(state)?;
55        state.expect(LiquidTokenType::DoubleRightBrace)?;
56        state.finish_at(cp, LiquidElementType::Variable);
57        Ok(())
58    }
59
60    fn parse_tag_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
61        let cp = state.checkpoint();
62        state.expect(LiquidTokenType::LeftBracePercent)?;
63
64        let kind = state.peek_kind();
65        match kind {
66            Some(LiquidTokenType::Identifier) => {
67                let text = state.peek_text().unwrap_or_default();
68                match text.as_ref() {
69                    "if" => self.parse_if_statement(state, cp),
70                    "for" => self.parse_for_statement(state, cp),
71                    "block" => self.parse_block_statement(state, cp),
72                    "macro" => self.parse_macro_definition(state, cp),
73                    _ => {
74                        while state.not_at_end() && !state.at(LiquidTokenType::PercentRightBrace) {
75                            state.advance();
76                        }
77                        state.expect(LiquidTokenType::PercentRightBrace)?;
78                        state.finish_at(cp, LiquidElementType::Tag);
79                        Ok(())
80                    }
81                }
82            }
83            _ => {
84                while state.not_at_end() && !state.at(LiquidTokenType::PercentRightBrace) {
85                    state.advance();
86                }
87                state.expect(LiquidTokenType::PercentRightBrace)?;
88                state.finish_at(cp, LiquidElementType::Tag);
89                Ok(())
90            }
91        }
92    }
93
94    fn parse_if_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
95        state.expect(LiquidTokenType::Identifier)?; // if
96        self.parse_expression(state)?;
97        state.expect(LiquidTokenType::PercentRightBrace)?;
98
99        while state.not_at_end() {
100            if state.at(LiquidTokenType::LeftBracePercent) {
101                if let Some(LiquidTokenType::Identifier) = state.peek_kind_at(1) {
102                    let text = state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).unwrap_or_default();
103                    if text == "endif" || text == "elif" || text == "else" {
104                        break;
105                    }
106                }
107            }
108            self.parse_node(state)?;
109        }
110
111        if state.at(LiquidTokenType::LeftBracePercent) {
112            let text = state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).unwrap_or_default();
113            if text == "elif" {
114                state.expect(LiquidTokenType::LeftBracePercent)?;
115                self.parse_if_statement(state, state.checkpoint())?;
116            }
117            else if text == "else" {
118                state.expect(LiquidTokenType::LeftBracePercent)?;
119                state.expect(LiquidTokenType::Identifier)?; // else
120                state.expect(LiquidTokenType::PercentRightBrace)?;
121                while state.not_at_end() && !(state.at(LiquidTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endif").unwrap_or(false)) {
122                    self.parse_node(state)?;
123                }
124            }
125        }
126
127        if state.at(LiquidTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endif").unwrap_or(false) {
128            state.expect(LiquidTokenType::LeftBracePercent)?;
129            state.expect(LiquidTokenType::Identifier)?; // endif
130            state.expect(LiquidTokenType::PercentRightBrace)?;
131        }
132
133        state.finish_at(cp, LiquidElementType::IfStatement);
134        Ok(())
135    }
136
137    fn parse_for_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
138        state.expect(LiquidTokenType::Identifier)?; // for
139        self.parse_expression(state)?; // x
140        // Liquid for syntax: for x in y
141        if state.peek_text().map(|t| t == "in").unwrap_or(false) {
142            state.advance();
143            self.parse_expression(state)?; // y
144        }
145        state.expect(LiquidTokenType::PercentRightBrace)?;
146
147        while state.not_at_end() && !(state.at(LiquidTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endfor").unwrap_or(false)) {
148            self.parse_node(state)?;
149        }
150
151        state.expect(LiquidTokenType::LeftBracePercent)?;
152        state.expect(LiquidTokenType::Identifier)?; // endfor
153        state.expect(LiquidTokenType::PercentRightBrace)?;
154
155        state.finish_at(cp, LiquidElementType::ForStatement);
156        Ok(())
157    }
158
159    fn parse_block_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
160        state.expect(LiquidTokenType::Identifier)?; // block
161        state.expect(LiquidTokenType::Identifier)?; // name
162        state.expect(LiquidTokenType::PercentRightBrace)?;
163
164        while state.not_at_end() && !(state.at(LiquidTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endblock").unwrap_or(false)) {
165            self.parse_node(state)?;
166        }
167
168        state.expect(LiquidTokenType::LeftBracePercent)?;
169        state.expect(LiquidTokenType::Identifier)?; // endblock
170        if state.at(LiquidTokenType::Identifier) {
171            state.advance();
172        }
173        state.expect(LiquidTokenType::PercentRightBrace)?;
174
175        state.finish_at(cp, LiquidElementType::Block);
176        Ok(())
177    }
178
179    fn parse_macro_definition<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
180        state.expect(LiquidTokenType::Identifier)?; // macro
181        state.expect(LiquidTokenType::Identifier)?; // name
182
183        if state.at(LiquidTokenType::LeftParen) {
184            state.advance();
185            while state.not_at_end() && !state.at(LiquidTokenType::RightParen) {
186                if state.at(LiquidTokenType::Identifier) {
187                    state.advance();
188                }
189                if state.at(LiquidTokenType::Comma) {
190                    state.advance();
191                }
192            }
193            state.expect(LiquidTokenType::RightParen)?;
194        }
195
196        state.expect(LiquidTokenType::PercentRightBrace)?;
197
198        while state.not_at_end() && !(state.at(LiquidTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endmacro").unwrap_or(false)) {
199            self.parse_node(state)?;
200        }
201
202        state.expect(LiquidTokenType::LeftBracePercent)?;
203        state.expect(LiquidTokenType::Identifier)?; // endmacro
204        state.expect(LiquidTokenType::PercentRightBrace)?;
205
206        state.finish_at(cp, LiquidElementType::MacroDefinition);
207        Ok(())
208    }
209
210    fn parse_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
211        self.parse_binary_expression(state, 0)
212    }
213
214    fn parse_binary_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, min_precedence: i32) -> Result<(), oak_core::OakError> {
215        let cp = state.checkpoint();
216        self.parse_primary_expression(state)?;
217
218        while let Some(kind) = state.peek_kind() {
219            let precedence = self.get_precedence(kind);
220            if precedence < min_precedence {
221                break;
222            }
223
224            state.advance();
225            self.parse_binary_expression(state, precedence + 1)?;
226            state.finish_at(cp, if kind == LiquidTokenType::Pipe { LiquidElementType::Filter } else { LiquidElementType::Expression });
227        }
228
229        Ok(())
230    }
231
232    fn get_precedence(&self, kind: LiquidTokenType) -> i32 {
233        match kind {
234            LiquidTokenType::Pipe => 1,
235            LiquidTokenType::Plus | LiquidTokenType::Minus => 2,
236            LiquidTokenType::Star | LiquidTokenType::Slash => 3,
237            _ => -1,
238        }
239    }
240
241    fn parse_primary_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
242        let cp = state.checkpoint();
243
244        match state.peek_kind() {
245            Some(LiquidTokenType::Identifier) => {
246                state.advance();
247                if state.at(LiquidTokenType::LeftParen) {
248                    state.advance();
249                    while state.not_at_end() && !state.at(LiquidTokenType::RightParen) {
250                        self.parse_expression(state)?;
251                        if state.at(LiquidTokenType::Comma) {
252                            state.advance();
253                        }
254                    }
255                    state.expect(LiquidTokenType::RightParen)?;
256                    state.finish_at(cp, LiquidElementType::Function);
257                }
258                else {
259                    state.finish_at(cp, LiquidElementType::Identifier);
260                }
261            }
262            Some(LiquidTokenType::String) | Some(LiquidTokenType::Number) | Some(LiquidTokenType::Boolean) => {
263                state.advance();
264                state.finish_at(cp, LiquidElementType::Literal);
265            }
266            Some(LiquidTokenType::LeftParen) => {
267                state.advance();
268                self.parse_expression(state)?;
269                state.expect(LiquidTokenType::RightParen)?;
270            }
271            _ => {
272                while state.not_at_end() && !state.at(LiquidTokenType::PercentRightBrace) && !state.at(LiquidTokenType::DoubleRightBrace) && !state.at(LiquidTokenType::RightParen) && !state.at(LiquidTokenType::Comma) {
273                    state.advance();
274                }
275            }
276        }
277        Ok(())
278    }
279}
280
281impl<'config> Parser<LiquidLanguage> for LiquidParser<'config> {
282    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<LiquidLanguage>) -> ParseOutput<'a, LiquidLanguage> {
283        let lexer = LiquidLexer::new(&self.config);
284        parse_with_lexer(&lexer, text, edits, cache, |state| {
285            let checkpoint = state.checkpoint();
286            while state.not_at_end() {
287                self.parse_node(state)?;
288            }
289            Ok(state.finish_at(checkpoint, LiquidElementType::Root))
290        })
291    }
292}