Skip to main content

gracile_core/
parser.rs

1//! Hand-written recursive descent parser that builds the AST from tokens.
2
3use crate::ast::*;
4use crate::error::{Error, Result, Span};
5use crate::lexer::{Token, TokenKind};
6
7/// Hand-written recursive descent parser state.
8pub struct Parser {
9    tokens: Vec<Token>,
10    pos: usize,
11}
12
13impl Parser {
14    pub fn new(tokens: Vec<Token>) -> Self {
15        Parser { tokens, pos: 0 }
16    }
17
18    pub fn parse(mut self) -> Result<Template> {
19        let nodes = self.parse_nodes()?;
20        self.expect_eof()?;
21        Ok(Template { nodes })
22    }
23
24    fn peek(&self) -> &Token {
25        &self.tokens[self.pos]
26    }
27
28    fn peek_kind(&self) -> &TokenKind {
29        &self.peek().kind
30    }
31
32    fn peek_span(&self) -> Span {
33        self.peek().span.clone()
34    }
35
36    fn advance(&mut self) -> &Token {
37        let tok = &self.tokens[self.pos];
38        if self.pos + 1 < self.tokens.len() {
39            self.pos += 1;
40        }
41        tok
42    }
43
44    fn expect_close(&mut self) -> Result<()> {
45        match self.peek_kind() {
46            TokenKind::Close => {
47                self.advance();
48                Ok(())
49            }
50            other => Err(Error::ParseError {
51                message: format!("Expected '}}', got {}", other),
52                span: self.peek_span(),
53            }),
54        }
55    }
56
57    fn expect_ident(&mut self) -> Result<String> {
58        match self.peek_kind().clone() {
59            TokenKind::Ident(name) => {
60                self.advance();
61                Ok(name)
62            }
63            other => Err(Error::ParseError {
64                message: format!("Expected identifier, got {}", other),
65                span: self.peek_span(),
66            }),
67        }
68    }
69
70    fn expect_string(&mut self) -> Result<String> {
71        match self.peek_kind().clone() {
72            TokenKind::StringLit(s) => {
73                self.advance();
74                Ok(s)
75            }
76            other => Err(Error::ParseError {
77                message: format!("Expected string literal, got {}", other),
78                span: self.peek_span(),
79            }),
80        }
81    }
82
83    fn expect_keyword(&mut self, expected: TokenKind) -> Result<()> {
84        if *self.peek_kind() == expected {
85            self.advance();
86            Ok(())
87        } else {
88            Err(Error::ParseError {
89                message: format!("Expected {}, got {}", expected, self.peek_kind()),
90                span: self.peek_span(),
91            })
92        }
93    }
94
95    fn expect_eof(&mut self) -> Result<()> {
96        match self.peek_kind() {
97            TokenKind::Eof => Ok(()),
98            other => Err(Error::ParseError {
99                message: format!("Expected end of template, got {}", other),
100                span: self.peek_span(),
101            }),
102        }
103    }
104
105    /// Parse nodes until a stop token (`ContinueOpen`, `BlockClose`, or `Eof`),
106    /// then apply standalone-tag stripping.
107    fn parse_nodes(&mut self) -> Result<Vec<Node>> {
108        let mut nodes: Vec<Node> = Vec::new();
109
110        loop {
111            match self.peek_kind() {
112                TokenKind::Eof | TokenKind::ContinueOpen | TokenKind::BlockClose => break,
113
114                TokenKind::RawText(_) => {
115                    let TokenKind::RawText(text) = self.advance().kind.clone() else {
116                        unreachable!()
117                    };
118                    nodes.push(Node::RawText(text));
119                }
120
121                TokenKind::CommentOpen => {
122                    self.advance(); // CommentOpen
123                    let body = match self.peek_kind().clone() {
124                        TokenKind::CommentBody(b) => {
125                            self.advance();
126                            b
127                        }
128                        _ => String::new(),
129                    };
130                    match self.peek_kind() {
131                        TokenKind::CommentClose => {
132                            self.advance();
133                        }
134                        _ => {
135                            return Err(Error::ParseError {
136                                message: "Expected comment close '!}'".to_string(),
137                                span: self.peek_span(),
138                            });
139                        }
140                    }
141                    nodes.push(Node::Comment(body));
142                }
143
144                TokenKind::ExprOpen => {
145                    self.advance(); // ExprOpen
146                    let expr = self.parse_expr()?;
147                    self.expect_close()?;
148                    nodes.push(Node::ExprTag(ExprTag { expr, raw: false }));
149                }
150
151                TokenKind::ExprOpenRaw => {
152                    self.advance(); // ExprOpenRaw
153                    let expr = self.parse_expr()?;
154                    self.expect_close()?;
155                    nodes.push(Node::ExprTag(ExprTag { expr, raw: true }));
156                }
157
158                TokenKind::BlockOpen => {
159                    let node = self.parse_block()?;
160                    nodes.push(node);
161                }
162
163                TokenKind::SpecialOpen => {
164                    let node = self.parse_special()?;
165                    nodes.push(node);
166                }
167
168                other => {
169                    return Err(Error::ParseError {
170                        message: format!("Unexpected token {}", other),
171                        span: self.peek_span(),
172                    });
173                }
174            }
175        }
176
177        strip_standalone(&mut nodes);
178        Ok(nodes)
179    }
180
181    fn parse_block(&mut self) -> Result<Node> {
182        self.advance(); // BlockOpen
183
184        match self.peek_kind().clone() {
185            TokenKind::KwIf => {
186                self.advance(); // `if`
187                let cond = self.parse_expr()?;
188                self.expect_close()?;
189                self.parse_if_block(cond)
190            }
191            TokenKind::KwEach => {
192                self.advance(); // `each`
193                let iterable = self.parse_expr()?;
194                self.expect_keyword(TokenKind::KwAs)?;
195                let pattern = self.parse_pattern()?;
196                let index_binding = if self.peek_kind() == &TokenKind::Comma {
197                    self.advance(); // `,`
198                    Some(self.expect_ident()?)
199                } else {
200                    None
201                };
202                let loop_binding = if self.peek_kind() == &TokenKind::Comma {
203                    self.advance(); // `,`
204                    Some(self.expect_ident()?)
205                } else {
206                    None
207                };
208                self.expect_close()?;
209                self.parse_each_block(iterable, pattern, index_binding, loop_binding)
210            }
211            TokenKind::KwSnippet => {
212                self.advance(); // `snippet`
213                let name = self.expect_ident()?;
214                self.expect(&TokenKind::LParen)?;
215                let params = self.parse_param_list()?;
216                self.expect(&TokenKind::RParen)?;
217                self.expect_close()?;
218                self.parse_snippet_block(name, params)
219            }
220            TokenKind::KwRaw => {
221                self.advance(); // `raw`
222                self.expect_close()?;
223                let body = match self.peek_kind().clone() {
224                    TokenKind::RawBody(b) => {
225                        self.advance();
226                        b
227                    }
228                    _ => String::new(),
229                };
230                self.expect(&TokenKind::BlockClose)?;
231                self.expect_keyword(TokenKind::KwRaw)?;
232                self.expect_close()?;
233                Ok(Node::RawBlock(body))
234            }
235            other => Err(Error::ParseError {
236                message: format!("Unknown block keyword {}", other),
237                span: self.peek_span(),
238            }),
239        }
240    }
241
242    fn parse_if_block(&mut self, first_cond: Expr) -> Result<Node> {
243        let first_body = self.parse_nodes()?;
244        let mut branches = vec![IfBranch {
245            condition: first_cond,
246            body: first_body,
247        }];
248        let mut else_body: Option<Vec<Node>> = None;
249
250        loop {
251            match self.peek_kind() {
252                TokenKind::ContinueOpen => {
253                    self.advance(); // ContinueOpen
254                    self.expect_keyword(TokenKind::KwElse)?;
255                    if self.peek_kind() == &TokenKind::KwIf {
256                        self.advance(); // `if`
257                        let cond = self.parse_expr()?;
258                        self.expect_close()?;
259                        let body = self.parse_nodes()?;
260                        branches.push(IfBranch {
261                            condition: cond,
262                            body,
263                        });
264                    } else {
265                        self.expect_close()?;
266                        else_body = Some(self.parse_nodes()?);
267                    }
268                }
269                TokenKind::BlockClose => {
270                    self.advance(); // BlockClose
271                    self.expect_keyword(TokenKind::KwIf)?;
272                    self.expect_close()?;
273                    break;
274                }
275                other => {
276                    return Err(Error::ParseError {
277                        message: format!(
278                            "Expected {{:else}}, {{:else if}}, or {{/if}}, got {}",
279                            other
280                        ),
281                        span: self.peek_span(),
282                    });
283                }
284            }
285        }
286
287        Ok(Node::IfBlock(IfBlock {
288            branches,
289            else_body,
290        }))
291    }
292
293    fn parse_each_block(
294        &mut self,
295        iterable: Expr,
296        pattern: Pattern,
297        index_binding: Option<String>,
298        loop_binding: Option<String>,
299    ) -> Result<Node> {
300        let body = self.parse_nodes()?;
301        let mut else_body: Option<Vec<Node>> = None;
302
303        loop {
304            match self.peek_kind() {
305                TokenKind::ContinueOpen => {
306                    self.advance(); // ContinueOpen
307                    self.expect_keyword(TokenKind::KwElse)?;
308                    self.expect_close()?;
309                    else_body = Some(self.parse_nodes()?);
310                }
311                TokenKind::BlockClose => {
312                    self.advance(); // BlockClose
313                    self.expect_keyword(TokenKind::KwEach)?;
314                    self.expect_close()?;
315                    break;
316                }
317                other => {
318                    return Err(Error::ParseError {
319                        message: format!("Expected {{:else}} or {{/each}}, got {}", other),
320                        span: self.peek_span(),
321                    });
322                }
323            }
324        }
325
326        Ok(Node::EachBlock(EachBlock {
327            iterable,
328            pattern,
329            index_binding,
330            loop_binding,
331            body,
332            else_body,
333        }))
334    }
335
336    fn parse_snippet_block(&mut self, name: String, params: Vec<String>) -> Result<Node> {
337        let body = self.parse_nodes()?;
338        match self.peek_kind() {
339            TokenKind::BlockClose => {
340                self.advance(); // BlockClose
341                self.expect_keyword(TokenKind::KwSnippet)?;
342                self.expect_close()?;
343            }
344            other => {
345                return Err(Error::ParseError {
346                    message: format!("Expected {{/snippet}}, got {}", other),
347                    span: self.peek_span(),
348                });
349            }
350        }
351        Ok(Node::SnippetBlock(SnippetBlock { name, params, body }))
352    }
353
354    fn parse_special(&mut self) -> Result<Node> {
355        self.advance(); // SpecialOpen
356
357        match self.peek_kind().clone() {
358            TokenKind::KwRender => {
359                self.advance(); // `render`
360                let name = self.expect_ident()?;
361                self.expect(&TokenKind::LParen)?;
362                let args = self.parse_arg_list()?;
363                self.expect(&TokenKind::RParen)?;
364                self.expect_close()?;
365                Ok(Node::RenderTag(RenderTag { name, args }))
366            }
367            TokenKind::KwConst => {
368                self.advance(); // `const`
369                let name = self.expect_ident()?;
370                self.expect(&TokenKind::Assign)?;
371                let expr = self.parse_expr()?;
372                self.expect_close()?;
373                Ok(Node::ConstTag(ConstTag { name, expr }))
374            }
375            TokenKind::KwInclude => {
376                self.advance(); // `include`
377                let path = self.expect_string()?;
378                self.expect_close()?;
379                Ok(Node::IncludeTag(IncludeTag { path }))
380            }
381            TokenKind::KwDebug => {
382                self.advance(); // `debug`
383                let expr = if self.peek_kind() != &TokenKind::Close {
384                    Some(self.parse_expr()?)
385                } else {
386                    None
387                };
388                self.expect_close()?;
389                Ok(Node::DebugTag(DebugTag { expr }))
390            }
391            other => Err(Error::ParseError {
392                message: format!("Unknown special tag keyword {}", other),
393                span: self.peek_span(),
394            }),
395        }
396    }
397
398    fn parse_pattern(&mut self) -> Result<Pattern> {
399        if self.peek_kind() == &TokenKind::LBraceD {
400            self.advance(); // `{`
401            let mut names = Vec::new();
402            loop {
403                if self.peek_kind() == &TokenKind::RBraceD {
404                    self.advance(); // `}`
405                    break;
406                }
407                names.push(self.expect_ident()?);
408                if self.peek_kind() == &TokenKind::Comma {
409                    self.advance();
410                    if self.peek_kind() == &TokenKind::RBraceD {
411                        self.advance();
412                        break;
413                    }
414                }
415            }
416            Ok(Pattern::Destructure(names))
417        } else {
418            Ok(Pattern::Ident(self.expect_ident()?))
419        }
420    }
421
422    fn parse_param_list(&mut self) -> Result<Vec<String>> {
423        let mut params = Vec::new();
424        if self.peek_kind() == &TokenKind::RParen {
425            return Ok(params);
426        }
427        params.push(self.expect_ident()?);
428        while self.peek_kind() == &TokenKind::Comma {
429            self.advance();
430            if self.peek_kind() == &TokenKind::RParen {
431                break;
432            }
433            params.push(self.expect_ident()?);
434        }
435        Ok(params)
436    }
437
438    fn parse_arg_list(&mut self) -> Result<Vec<Expr>> {
439        let mut args = Vec::new();
440        if self.peek_kind() == &TokenKind::RParen {
441            return Ok(args);
442        }
443        args.push(self.parse_expr()?);
444        while self.peek_kind() == &TokenKind::Comma {
445            self.advance();
446            if self.peek_kind() == &TokenKind::RParen {
447                break;
448            }
449            args.push(self.parse_expr()?);
450        }
451        Ok(args)
452    }
453
454    fn parse_expr(&mut self) -> Result<Expr> {
455        self.parse_filter_expr()
456    }
457
458    fn parse_filter_expr(&mut self) -> Result<Expr> {
459        let expr = self.parse_ternary()?;
460        let mut filters = Vec::new();
461        while self.peek_kind() == &TokenKind::Pipe {
462            self.advance();
463            let name = self.expect_ident()?;
464            let args = if self.peek_kind() == &TokenKind::LParen {
465                self.advance();
466                let a = self.parse_arg_list()?;
467                self.expect(&TokenKind::RParen)?;
468                a
469            } else {
470                Vec::new()
471            };
472            filters.push(FilterApplication { name, args });
473        }
474        if filters.is_empty() {
475            Ok(expr)
476        } else {
477            Ok(Expr::Filter {
478                expr: Box::new(expr),
479                filters,
480            })
481        }
482    }
483
484    fn parse_ternary(&mut self) -> Result<Expr> {
485        let cond = self.parse_nullish()?;
486        if self.peek_kind() == &TokenKind::Question {
487            self.advance();
488            let consequent = self.parse_expr()?;
489            self.expect(&TokenKind::Colon)?;
490            let alternate = self.parse_expr()?;
491            Ok(Expr::Ternary {
492                condition: Box::new(cond),
493                consequent: Box::new(consequent),
494                alternate: Box::new(alternate),
495            })
496        } else {
497            Ok(cond)
498        }
499    }
500
501    fn parse_nullish(&mut self) -> Result<Expr> {
502        let mut left = self.parse_or()?;
503        while self.peek_kind() == &TokenKind::NullCoalesce {
504            self.advance();
505            let right = self.parse_or()?;
506            left = Expr::Binary {
507                op: BinaryOp::NullCoalesce,
508                left: Box::new(left),
509                right: Box::new(right),
510            };
511        }
512        Ok(left)
513    }
514
515    fn parse_or(&mut self) -> Result<Expr> {
516        let mut left = self.parse_and()?;
517        while self.peek_kind() == &TokenKind::Or {
518            self.advance();
519            let right = self.parse_and()?;
520            left = Expr::Binary {
521                op: BinaryOp::Or,
522                left: Box::new(left),
523                right: Box::new(right),
524            };
525        }
526        Ok(left)
527    }
528
529    fn parse_and(&mut self) -> Result<Expr> {
530        let mut left = self.parse_equality()?;
531        while self.peek_kind() == &TokenKind::And {
532            self.advance();
533            let right = self.parse_equality()?;
534            left = Expr::Binary {
535                op: BinaryOp::And,
536                left: Box::new(left),
537                right: Box::new(right),
538            };
539        }
540        Ok(left)
541    }
542
543    fn parse_equality(&mut self) -> Result<Expr> {
544        let mut left = self.parse_comparison()?;
545        loop {
546            let op = match self.peek_kind() {
547                TokenKind::Eq => BinaryOp::Eq,
548                TokenKind::Neq => BinaryOp::Neq,
549                _ => break,
550            };
551            self.advance();
552            let right = self.parse_comparison()?;
553            left = Expr::Binary {
554                op,
555                left: Box::new(left),
556                right: Box::new(right),
557            };
558        }
559        Ok(left)
560    }
561
562    fn parse_comparison(&mut self) -> Result<Expr> {
563        let mut left = self.parse_test()?;
564        loop {
565            let op = match self.peek_kind() {
566                TokenKind::Lt => BinaryOp::Lt,
567                TokenKind::Gt => BinaryOp::Gt,
568                TokenKind::Lte => BinaryOp::Lte,
569                TokenKind::Gte => BinaryOp::Gte,
570                _ => break,
571            };
572            self.advance();
573            let right = self.parse_test()?;
574            left = Expr::Binary {
575                op,
576                left: Box::new(left),
577                right: Box::new(right),
578            };
579        }
580        Ok(left)
581    }
582
583    fn parse_test(&mut self) -> Result<Expr> {
584        let expr = self.parse_membership()?;
585        if self.peek_kind() == &TokenKind::KwIs {
586            self.advance();
587            let negated = if self.peek_kind() == &TokenKind::KwNot {
588                self.advance();
589                true
590            } else {
591                false
592            };
593            let test_name = self.expect_ident()?;
594            Ok(Expr::Test {
595                expr: Box::new(expr),
596                negated,
597                test_name,
598            })
599        } else {
600            Ok(expr)
601        }
602    }
603
604    fn parse_membership(&mut self) -> Result<Expr> {
605        let expr = self.parse_additive()?;
606        match self.peek_kind() {
607            TokenKind::KwIn => {
608                self.advance();
609                let collection = self.parse_additive()?;
610                Ok(Expr::Membership {
611                    expr: Box::new(expr),
612                    negated: false,
613                    collection: Box::new(collection),
614                })
615            }
616            TokenKind::KwNot => {
617                self.advance();
618                self.expect_keyword(TokenKind::KwIn)?;
619                let collection = self.parse_additive()?;
620                Ok(Expr::Membership {
621                    expr: Box::new(expr),
622                    negated: true,
623                    collection: Box::new(collection),
624                })
625            }
626            _ => Ok(expr),
627        }
628    }
629
630    fn parse_additive(&mut self) -> Result<Expr> {
631        let mut left = self.parse_multiplicative()?;
632        loop {
633            let op = match self.peek_kind() {
634                TokenKind::Add => BinaryOp::Add,
635                TokenKind::Sub => BinaryOp::Sub,
636                _ => break,
637            };
638            self.advance();
639            let right = self.parse_multiplicative()?;
640            left = Expr::Binary {
641                op,
642                left: Box::new(left),
643                right: Box::new(right),
644            };
645        }
646        Ok(left)
647    }
648
649    fn parse_multiplicative(&mut self) -> Result<Expr> {
650        let mut left = self.parse_unary()?;
651        loop {
652            let op = match self.peek_kind() {
653                TokenKind::Mul => BinaryOp::Mul,
654                TokenKind::Div => BinaryOp::Div,
655                TokenKind::Mod => BinaryOp::Mod,
656                _ => break,
657            };
658            self.advance();
659            let right = self.parse_unary()?;
660            left = Expr::Binary {
661                op,
662                left: Box::new(left),
663                right: Box::new(right),
664            };
665        }
666        Ok(left)
667    }
668
669    fn parse_unary(&mut self) -> Result<Expr> {
670        match self.peek_kind() {
671            TokenKind::Bang => {
672                self.advance();
673                Ok(Expr::Unary {
674                    op: UnaryOp::Not,
675                    operand: Box::new(self.parse_unary()?),
676                })
677            }
678            TokenKind::Sub => {
679                self.advance();
680                Ok(Expr::Unary {
681                    op: UnaryOp::Neg,
682                    operand: Box::new(self.parse_unary()?),
683                })
684            }
685            _ => self.parse_postfix(),
686        }
687    }
688
689    fn parse_postfix(&mut self) -> Result<Expr> {
690        let mut expr = self.parse_primary()?;
691        loop {
692            match self.peek_kind() {
693                TokenKind::Dot => {
694                    self.advance();
695                    let prop = self.expect_ident()?;
696                    expr = Expr::MemberAccess {
697                        object: Box::new(expr),
698                        property: prop,
699                    };
700                }
701                TokenKind::LBracket => {
702                    self.advance();
703                    let index = self.parse_expr()?;
704                    self.expect(&TokenKind::RBracket)?;
705                    expr = Expr::IndexAccess {
706                        object: Box::new(expr),
707                        index: Box::new(index),
708                    };
709                }
710                _ => break,
711            }
712        }
713        Ok(expr)
714    }
715
716    fn parse_primary(&mut self) -> Result<Expr> {
717        match self.peek_kind().clone() {
718            TokenKind::Null => {
719                self.advance();
720                Ok(Expr::Null)
721            }
722            TokenKind::True => {
723                self.advance();
724                Ok(Expr::Bool(true))
725            }
726            TokenKind::False => {
727                self.advance();
728                Ok(Expr::Bool(false))
729            }
730            TokenKind::IntLit(i) => {
731                self.advance();
732                Ok(Expr::Int(i))
733            }
734            TokenKind::FloatLit(f) => {
735                self.advance();
736                Ok(Expr::Float(f))
737            }
738            TokenKind::StringLit(s) => {
739                self.advance();
740                Ok(Expr::String(s))
741            }
742            TokenKind::Ident(name) => {
743                self.advance();
744                Ok(Expr::Ident(name))
745            }
746            TokenKind::LParen => {
747                self.advance();
748                let expr = self.parse_expr()?;
749                self.expect(&TokenKind::RParen)?;
750                Ok(expr)
751            }
752            TokenKind::LBracket => {
753                self.advance();
754                let mut elements = Vec::new();
755                if self.peek_kind() != &TokenKind::RBracket {
756                    elements.push(self.parse_expr()?);
757                    while self.peek_kind() == &TokenKind::Comma {
758                        self.advance();
759                        if self.peek_kind() == &TokenKind::RBracket {
760                            break;
761                        }
762                        elements.push(self.parse_expr()?);
763                    }
764                }
765                self.expect(&TokenKind::RBracket)?;
766                Ok(Expr::Array(elements))
767            }
768            other => Err(Error::ParseError {
769                message: format!("Expected expression, got {}", other),
770                span: self.peek_span(),
771            }),
772        }
773    }
774
775    fn expect(&mut self, kind: &TokenKind) -> Result<&Token> {
776        if std::mem::discriminant(self.peek_kind()) == std::mem::discriminant(kind) {
777            Ok(self.advance())
778        } else {
779            Err(Error::ParseError {
780                message: format!("Expected {}, got {}", kind, self.peek_kind()),
781                span: self.peek_span(),
782            })
783        }
784    }
785}
786
787/// Strip standalone block tags from their surrounding lines.
788///
789/// A block-level tag is "standalone" when:
790/// - the text before it on its line is entirely whitespace (spaces/tabs), and
791/// - the text after it on its line is entirely whitespace (spaces/tabs) + newline.
792///
793/// When standalone, the tag's entire line is removed from the output:
794/// - the whitespace prefix is stripped from the preceding raw-text node,
795/// - the trailing newline is stripped from the following raw-text node,
796/// - the opening newline is stripped from the block's first body raw-text node,
797/// - the closing whitespace prefix is stripped from the block's last body raw-text node.
798fn strip_standalone(nodes: &mut Vec<Node>) {
799    let len = nodes.len();
800    for i in 0..len {
801        if !is_standalone_eligible(&nodes[i]) {
802            continue;
803        }
804
805        // For block nodes that have Vec<Node> bodies (if/each/snippet): require that
806        // the first body node is RawText starting with '\n'.  This confirms the opener
807        // tag was immediately followed by a newline and therefore IS on its own line.
808        // Without this check, inline blocks like `{#if cond}text{/if}` would wrongly
809        // be treated as standalone and their bodies would be stripped.
810        if has_vec_body(&nodes[i]) {
811            match block_first_body_text(&nodes[i]) {
812                Some(s) if s.starts_with('\n') => {} // opener was on its own line — OK
813                _ => continue,                       // inline or empty body — skip
814            }
815        }
816
817        // Check: is text after the last newline in the preceding node blank?
818        let prefix_blank = match i.checked_sub(1) {
819            None => true, // start of body — treat as after a newline
820            Some(prev) => match &nodes[prev] {
821                Node::RawText(s) => after_last_nl(s).chars().all(|c| c == ' ' || c == '\t'),
822                _ => false,
823            },
824        };
825        if !prefix_blank {
826            continue;
827        }
828
829        // Check: is text before the first newline in the following node blank?
830        let suffix_blank = match nodes.get(i + 1) {
831            None => true, // end of body — treat as before a newline
832            Some(Node::RawText(s)) => before_first_nl(s).chars().all(|c| c == ' ' || c == '\t'),
833            Some(_) => false,
834        };
835        if !suffix_blank {
836            continue;
837        }
838
839        // Strip the blank prefix from the preceding raw-text node.
840        if i > 0
841            && let Node::RawText(s) = &mut nodes[i - 1]
842        {
843            trim_line_tail(s);
844        }
845
846        // Strip the rest of the tag's line (blank spaces + newline) from the
847        // start of the following raw-text node.
848        if i + 1 < len
849            && let Node::RawText(s) = &mut nodes[i + 1]
850        {
851            strip_through_first_nl(s);
852        }
853
854        // Strip the opening/closing newlines inside block bodies.
855        strip_block_body_edges(&mut nodes[i]);
856    }
857
858    // Remove raw-text nodes that became empty after stripping.
859    nodes.retain(|n| !matches!(n, Node::RawText(s) if s.is_empty()));
860}
861
862/// Whether a node triggers standalone-line stripping.
863fn is_standalone_eligible(node: &Node) -> bool {
864    matches!(
865        node,
866        Node::IfBlock(_)
867            | Node::EachBlock(_)
868            | Node::SnippetBlock(_)
869            | Node::RawBlock(_)
870            | Node::RenderTag(_)
871            | Node::ConstTag(_)
872            | Node::IncludeTag(_)
873            | Node::DebugTag(_)
874            | Node::Comment(_)
875    )
876}
877
878/// Whether a node has a `Vec<Node>` body (as opposed to no body or a String body).
879fn has_vec_body(node: &Node) -> bool {
880    matches!(
881        node,
882        Node::IfBlock(_) | Node::EachBlock(_) | Node::SnippetBlock(_)
883    )
884}
885
886/// Return the text of the first body node if it is `RawText`, otherwise `None`.
887fn block_first_body_text(node: &Node) -> Option<&str> {
888    let body = match node {
889        Node::IfBlock(b) => &b.branches[0].body,
890        Node::EachBlock(b) => &b.body,
891        Node::SnippetBlock(b) => &b.body,
892        _ => return None,
893    };
894    match body.first() {
895        Some(Node::RawText(s)) => Some(s.as_str()),
896        _ => None,
897    }
898}
899
900/// Strip the opening newline (from the opener tag's line) from the first body
901/// node, and the closing whitespace prefix (from the closer tag's line) from
902/// the last body node.
903fn strip_block_body_edges(node: &mut Node) {
904    match node {
905        Node::IfBlock(b) => {
906            // Every branch body is bounded by standalone tags on both sides
907            // ({#if}, {:else if}, {:else}, {/if}).  Strip the head and tail of
908            // each branch so all those lines disappear from the output.
909            for branch in &mut b.branches {
910                strip_body_head(&mut branch.body);
911                strip_body_tail(&mut branch.body);
912            }
913            if let Some(eb) = &mut b.else_body {
914                strip_body_head(eb);
915                strip_body_tail(eb);
916            }
917        }
918        Node::EachBlock(b) => {
919            strip_body_head(&mut b.body);
920            if let Some(eb) = &mut b.else_body {
921                strip_body_tail(&mut b.body); // blank line before {:else}
922                strip_body_head(eb); // newline after {:else}
923                strip_body_tail(eb); // blank line before {/each}
924            } else {
925                strip_body_tail(&mut b.body);
926            }
927        }
928        Node::SnippetBlock(b) => {
929            strip_body_head(&mut b.body);
930            strip_body_tail(&mut b.body);
931        }
932        _ => {}
933    }
934}
935
936fn strip_body_head(body: &mut Vec<Node>) {
937    if let Some(Node::RawText(s)) = body.first_mut() {
938        // Only strip when the first "line" of the body (before the first \n) is
939        // entirely blank — meaning the opener tag occupied its own line.
940        let should_strip = match s.find('\n') {
941            Some(pos) => s[..pos].chars().all(|c| c == ' ' || c == '\t'),
942            None => false, // body is on the same line as the opener — don't touch
943        };
944        if should_strip {
945            strip_through_first_nl(s);
946            if s.is_empty() {
947                body.remove(0);
948            }
949        }
950    }
951}
952
953fn strip_body_tail(body: &mut Vec<Node>) {
954    if let Some(Node::RawText(s)) = body.last_mut() {
955        // Only strip when the last "line" of the body (after the last \n) is
956        // entirely blank — meaning the closer tag occupied its own line.
957        let blank_tail = match s.rfind('\n') {
958            Some(pos) => s[pos + 1..].chars().all(|c| c == ' ' || c == '\t'),
959            None => false, // body is on the same line as the closer — don't touch
960        };
961        if blank_tail {
962            trim_line_tail(s);
963            if s.is_empty() {
964                body.pop();
965            }
966        }
967    }
968}
969
970/// Returns the substring after the last `\n` (or the whole string if none).
971fn after_last_nl(s: &str) -> &str {
972    match s.rfind('\n') {
973        Some(pos) => &s[pos + 1..],
974        None => s,
975    }
976}
977
978/// Returns the substring before the first `\n` (or the whole string if none).
979fn before_first_nl(s: &str) -> &str {
980    match s.find('\n') {
981        Some(pos) => &s[..pos],
982        None => s,
983    }
984}
985
986/// Strip everything after the last `\n`, keeping the `\n` itself.
987/// If there is no `\n`, clear the whole string (it was a blank opening prefix).
988fn trim_line_tail(s: &mut String) {
989    match s.rfind('\n') {
990        Some(pos) => s.truncate(pos + 1),
991        None => s.clear(),
992    }
993}
994
995/// Strip everything up to and including the first `\n` (handling `\r\n` too).
996/// If there is no `\n`, clear the whole string (everything is on the tag's line).
997fn strip_through_first_nl(s: &mut String) {
998    if let Some(pos) = s.find('\n') {
999        *s = s[pos + 1..].to_string();
1000    } else {
1001        s.clear();
1002    }
1003}
1004
1005/// Parse a token stream into a [`Template`] AST.
1006pub fn parse(tokens: Vec<Token>) -> Result<Template> {
1007    Parser::new(tokens).parse()
1008}