// Copyright (c) 2024 LinZeming
// Released under the MIT License
//
// Parser — converts a stream of `Token`s into a template AST.
//
// The parser reads tokens produced by the lexer and builds an abstract
// syntax tree consisting of `Node`s and `Expr`s. It uses a recursive-
// descent strategy with a Pratt parser for expressions.
// ---------------------------------------------------------------------------
// Parser state
// ---------------------------------------------------------------------------
///|
/// Parser state — holds the token stream and current position.
///
/// All methods that modify state return a `(result, Parser)` pair so the
/// caller always has the latest state without needing mutable references.
pub(all) struct Parser {
tokens : Array[Token]
pos : Int
errors : Array[String]
} derive(Debug)
// ---------------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------------
///|
/// Create a new parser from a token array (typically produced by
/// `tokenize`).
pub fn Parser::new(tokens : Array[Token]) -> Parser {
{
tokens,
pos: 0,
errors: Array::new(),
}
}
// ---------------------------------------------------------------------------
// Token inspection (read-only)
// ---------------------------------------------------------------------------
///|
/// Return the token at the current position, or `None` if at EOF.
pub fn Parser::peek(self : Parser) -> Token? {
if self.pos < self.tokens.length() {
Some(self.tokens[self.pos])
} else {
None
}
}
///|
/// Look ahead *offset* tokens from the current position.
/// `peek_at(0)` is equivalent to `peek()`.
pub fn Parser::peek_at(self : Parser, offset : Int) -> Token? {
let idx = self.pos + offset
if idx >= 0 && idx < self.tokens.length() {
Some(self.tokens[idx])
} else {
None
}
}
///|
/// Return the current position in the token stream (0-indexed).
pub fn Parser::current_pos(self : Parser) -> Int {
self.pos
}
// ---------------------------------------------------------------------------
// Token consumption (state-modifying)
// ---------------------------------------------------------------------------
///|
/// Consume and return the current token, advancing the position.
/// Returns `None` if already at EOF.
pub fn Parser::advance(self : Parser) -> (Token?, Parser) {
if self.pos < self.tokens.length() {
let tok = self.tokens[self.pos]
(Some(tok), { ..self, pos: self.pos + 1 })
} else {
(None, self)
}
}
///|
/// Consume the current token if it satisfies *check*, otherwise record an
/// error and return `Err(message)`.
pub fn Parser::expect(
self : Parser,
check : (Token) -> Bool,
message : String
) -> Result[(Token, Parser), String] {
let tok_opt = self.peek()
match tok_opt {
None => {
let err_msg = message + " — unexpected EOF"
self.error(err_msg) |> ignore
Err(err_msg)
}
Some(tok) => {
if check(tok) {
let (consumed, next) = self.advance()
match consumed {
Some(t) => Ok((t, next))
None => {
let err_msg = message + " — unexpected EOF"
self.error(err_msg) |> ignore
Err(err_msg)
}
}
} else {
let err_msg = message + " (got unexpected token at line " +
tok.line.to_string() + ", col " + tok.col.to_string() + ")"
self.error(err_msg) |> ignore
Err(err_msg)
}
}
}
}
///|
/// Record a parse error without consuming input.
pub fn Parser::error(self : Parser, msg : String) -> Parser {
let errs = self.errors
errs.push(msg)
{ ..self, errors: errs }
}
///|
/// Return all accumulated parse errors.
pub fn Parser::get_errors(self : Parser) -> Array[String] {
self.errors
}
///|
/// Create a `TemplateError` positioned at the current token.
pub fn Parser::make_error(
self : Parser,
kind : ErrorKind,
msg : String
) -> TemplateError {
let tok_opt = self.peek()
match tok_opt {
Some(t) => TemplateError::new(kind, msg)
.with_location(loc_at(t.line, t.col))
None => TemplateError::new(kind, msg)
}
}
// ---------------------------------------------------------------------------
// Pratt expression parser — operator precedence
// ---------------------------------------------------------------------------
/// Binding power for prefix operators.
fn prefix_bp(kind : TokenKind) -> Int? {
match kind {
TokenKind::Not => Some(7)
TokenKind::Minus => Some(7)
_ => None
}
}
/// Binding power for infix / postfix operators.
/// Returns `(left_bp, right_bp)`.
fn infix_bp(kind : TokenKind) -> (Int, Int)? {
match kind {
TokenKind::Pipe => Some((1, 2))
TokenKind::Or => Some((2, 3))
TokenKind::And => Some((3, 4))
TokenKind::Eq |
TokenKind::NotEq |
TokenKind::Lt |
TokenKind::Gt |
TokenKind::LtEq |
TokenKind::GtEq => Some((4, 5))
TokenKind::Plus |
TokenKind::Minus => Some((5, 6))
TokenKind::Star |
TokenKind::Slash |
TokenKind::Percent => Some((6, 7))
TokenKind::Dot => Some((8, 9))
TokenKind::LBracket => Some((8, 9))
TokenKind::LParen => Some((8, 9))
_ => None
}
}
/// Map a `TokenKind` to the corresponding `BinOpKind`.
fn token_to_binop(kind : TokenKind) -> BinOpKind? {
match kind {
TokenKind::Plus => Some(BinOpKind::Add)
TokenKind::Minus => Some(BinOpKind::Sub)
TokenKind::Star => Some(BinOpKind::Mul)
TokenKind::Slash => Some(BinOpKind::Div)
TokenKind::Percent => Some(BinOpKind::Mod)
TokenKind::Eq => Some(BinOpKind::Eq)
TokenKind::NotEq => Some(BinOpKind::Neq)
TokenKind::Lt => Some(BinOpKind::Lt)
TokenKind::Gt => Some(BinOpKind::Gt)
TokenKind::LtEq => Some(BinOpKind::Le)
TokenKind::GtEq => Some(BinOpKind::Ge)
TokenKind::And => Some(BinOpKind::And)
TokenKind::Or => Some(BinOpKind::Or)
_ => None
}
}
/// Map a `TokenKind` to the corresponding `UnaryOpKind`.
fn token_to_unary(kind : TokenKind) -> UnaryOpKind? {
match kind {
TokenKind::Not => Some(UnaryOpKind::Not)
TokenKind::Minus => Some(UnaryOpKind::Neg)
_ => None
}
}
/// String label for a `TokenKind` (for error messages).
fn token_kind_label(kind : TokenKind) -> String {
match kind {
TokenKind::RParen => "')'"
TokenKind::Comma => "','"
_ => "delimiter"
}
}
// ---------------------------------------------------------------------------
// Expression parsing
// ---------------------------------------------------------------------------
///|
/// Parse an expression with the given minimum binding power (Pratt parser).
pub fn Parser::parse_expression(
self : Parser,
min_bp : Int
) -> Result[(Expr, Parser), String] {
// 1. Parse prefix (primary expression or unary op).
let init_result = self.parse_prefix()
let (init_expr, init_p) = match init_result {
Ok((e, p)) => (e, p)
Err(msg) => return Err(msg)
}
let mut lhs = init_expr
let mut p = init_p
// 2. Loop: handle infix / postfix operators.
// State-driven loop using for.
let result = for _state = (lhs, p) {
lhs = _state.0
p = _state.1
let tok_opt = p.peek()
let tok = match tok_opt {
Some(t) => t
None => break (lhs, p)
}
let bp_opt = infix_bp(tok.kind)
let (lbp, rbp) = match bp_opt {
Some(bp) => bp
None => break (lhs, p)
}
if lbp < min_bp {
break (lhs, p)
}
match tok.kind {
// -- Pipe (filter) ------------------------------------------------
TokenKind::Pipe => {
let (_, p2) = p.advance()
p = p2
let name_opt = p.peek()
let filter_name = match name_opt {
Some(t2) => match t2.kind {
TokenKind::Ident(n) => {
let (_, p3) = p.advance()
p = p3
n
}
_ => return Err("Expected filter name after '|'")
}
None => return Err("Expected filter name after '|'")
}
let args_result = parse_filter_args(p)
let (args, p4) = match args_result {
Ok((a, pp)) => (a, pp)
Err(msg) => return Err(msg)
}
p = p4
lhs = Filter(lhs, filter_name, args)
continue (lhs, p)
}
// -- Dot (member access) ------------------------------------------
TokenKind::Dot => {
let (_, p2) = p.advance()
p = p2
let field_opt = p.peek()
match field_opt {
Some(t2) => match t2.kind {
TokenKind::Ident(field) => {
let (_, p3) = p.advance()
p = p3
lhs = Member(lhs, field)
continue (lhs, p)
}
_ => return Err("Expected field name after '.'")
}
None => return Err("Expected field name after '.'")
}
}
// -- LBracket (index access) --------------------------------------
TokenKind::LBracket => {
let (_, p2) = p.advance()
p = p2
let idx_result = p.parse_expression(0)
let (idx_expr, p3) = match idx_result {
Ok((e, pp)) => (e, pp)
Err(msg) => return Err(msg)
}
p = p3
let expect_result = p.expect(
fn(tok_val : Token) -> Bool { tok_val.kind == TokenKind::RBracket },
"Expected ']'",
)
match expect_result {
Ok((_, p4)) => p = p4
Err(msg) => return Err(msg)
}
lhs = Index(lhs, idx_expr)
continue (lhs, p)
}
// -- LParen (function call) ---------------------------------------
TokenKind::LParen => {
let func_name = match lhs {
Variable(n) => n
_ => return Err("Expected function name before '('")
}
let (_, p2) = p.advance()
p = p2
let args_result = parse_arg_list(p, TokenKind::RParen)
let (args, p3) = match args_result {
Ok((a, pp)) => (a, pp)
Err(msg) => return Err(msg)
}
p = p3
lhs = Call(func_name, args)
continue (lhs, p)
}
// -- Binary operators ---------------------------------------------
_ => {
let binop_opt = token_to_binop(tok.kind)
let binop = match binop_opt {
Some(op) => op
None => break (lhs, p)
}
let (_, p2) = p.advance()
p = p2
let rhs_result = p.parse_expression(rbp)
let (rhs, p3) = match rhs_result {
Ok((e, pp)) => (e, pp)
Err(msg) => return Err(msg)
}
p = p3
lhs = BinOp(lhs, binop, rhs)
continue (lhs, p)
}
}
}
Ok(result)
}
///|
/// Parse a primary expression or prefix operator.
fn Parser::parse_prefix(self : Parser) -> Result[(Expr, Parser), String] {
let tok_opt = self.peek()
let tok = match tok_opt {
Some(t) => t
None => return Err("Unexpected end of input in expression")
}
match tok.kind {
TokenKind::Str(s) => {
let (_, p) = self.advance()
Ok((Literal(Str(s)), p))
}
TokenKind::Int(i) => {
let (_, p) = self.advance()
Ok((Literal(Int(i)), p))
}
TokenKind::Float(f) => {
let (_, p) = self.advance()
Ok((Literal(Float(f)), p))
}
TokenKind::Bool(b) => {
let (_, p) = self.advance()
Ok((Literal(Bool(b)), p))
}
TokenKind::Ident(name) => {
let (_, p) = self.advance()
Ok((Variable(name), p))
}
TokenKind::Not | TokenKind::Minus => {
let op = match token_to_unary(tok.kind) {
Some(o) => o
None => return Err("Unknown unary operator")
}
let (_, p) = self.advance()
let bp = match prefix_bp(tok.kind) {
Some(b) => b
None => 7
}
let operand_result = p.parse_expression(bp)
let (operand, p2) = match operand_result {
Ok((e, pp)) => (e, pp)
Err(msg) => return Err(msg)
}
Ok((UnaryOp(op, operand), p2))
}
TokenKind::LParen => {
let (_, p) = self.advance()
let expr_result = p.parse_expression(0)
let (expr, p2) = match expr_result {
Ok((e, pp)) => (e, pp)
Err(msg) => return Err(msg)
}
let expect_result = p2.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::RParen },
"Expected ')'",
)
match expect_result {
Ok((_, p3)) => Ok((expr, p3))
Err(msg) => Err(msg)
}
}
_ => Err(
"Unexpected token in expression at line " +
tok.line.to_string() +
", col " +
tok.col.to_string(),
)
}
}
// ---------------------------------------------------------------------------
// Helper parsers
// ---------------------------------------------------------------------------
/// Parse a comma-separated argument list until *close* is encountered.
fn parse_arg_list(
p : Parser,
close : TokenKind
) -> Result[(Array[Expr], Parser), String] {
let args : Array[Expr] = Array::new()
let mut pp = p
let close_label = token_kind_label(close)
// Check for empty arg list.
let first_opt = pp.peek()
match first_opt {
Some(tok) => {
if tok.kind == close {
let (_, p2) = pp.advance()
return Ok((args, p2))
}
}
None => return Err("Expected " + close_label + " but got EOF")
}
while true {
let arg_result = pp.parse_expression(0)
let (arg, p2) = match arg_result {
Ok((a, pp2)) => (a, pp2)
Err(msg) => return Err(msg)
}
pp = p2
args.push(arg)
let next_opt = pp.peek()
match next_opt {
Some(tok2) => {
if tok2.kind == close {
let (_, p3) = pp.advance()
return Ok((args, p3))
} else if tok2.kind == TokenKind::Comma {
let (_, p3) = pp.advance()
pp = p3
} else {
return Err("Expected ',' or " + close_label)
}
}
None => return Err("Expected ',' or " + close_label + " but got EOF")
}
}
// unreachable
Err("unreachable")
}
/// Parse filter arguments: optional `(arg1, arg2, ...)` or `:arg` after filter name.
fn parse_filter_args(p : Parser) -> Result[(Array[Expr], Parser), String] {
let tok_opt = p.peek()
match tok_opt {
Some(tok) => {
if tok.kind == TokenKind::LParen {
let (_, p2) = p.advance()
parse_arg_list(p2, TokenKind::RParen)
} else if tok.kind == TokenKind::Colon {
// Colon syntax: consume ':' then parse a single argument.
let (_, p2) = p.advance()
let arg_result = p2.parse_expression(0)
match arg_result {
Ok((arg, p3)) => {
let args : Array[Expr] = Array::new()
args.push(arg)
Ok((args, p3))
}
Err(msg) => Err(msg)
}
} else {
Ok((Array::new(), p))
}
}
None => Ok((Array::new(), p))
}
}
// ---------------------------------------------------------------------------
// Top-level template parsing
// ---------------------------------------------------------------------------
///|
/// Parse the complete token stream into a list of template `Node`s.
pub fn Parser::parse_template(
self : Parser
) -> Result[(Array[Node], Parser), String] {
// parse_nodes with an empty stop set (never stops except at EOF).
let stop : Array[TokenKind] = Array::new()
let result = self.parse_nodes(stop)
match result {
Ok((nodes, p)) => Ok((nodes, p))
Err(msg) => Err(msg)
}
}
// ---------------------------------------------------------------------------
// Generic body parser — parses nodes until a stop keyword
// ---------------------------------------------------------------------------
///|
/// Parse nodes until `OpenTag` followed by one of *stop_kinds* (or EOF).
///
/// When a stop keyword is encountered, the `OpenTag` and keyword are NOT
/// consumed — they remain for the caller to handle.
fn Parser::parse_nodes(
self : Parser,
stop_kinds : Array[TokenKind]
) -> Result[(Array[Node], Parser), String] {
let nodes : Array[Node] = Array::new()
let mut p = self
while true {
let tok_opt = p.peek()
let tok = match tok_opt {
Some(t) => t
None => break
}
match tok.kind {
TokenKind::Eof => break
// -- Plain text ------------------------------------------------------
TokenKind::Text(content) => {
let (_, p2) = p.advance()
p = p2
if content != "" {
nodes.push(Text(content))
}
}
// -- Variable output {{ expr }} ------------------------------------
TokenKind::OpenVar => {
let (_, p2) = p.advance()
p = p2
let expr_result = p.parse_expression(0)
let (expr, p3) = match expr_result {
Ok((e, pp)) => (e, pp)
Err(msg) => return Err(msg)
}
p = p3
let expect_result = p.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::CloseVar },
"Expected '}}'",
)
match expect_result {
Ok((_, p4)) => p = p4
Err(msg) => return Err(msg)
}
nodes.push(Output(expr))
}
// -- Block tag {% ... %} — dispatch on keyword ---------------------
TokenKind::OpenTag => {
let (_, p2) = p.advance()
p = p2
// Peek at the keyword.
let keyword_opt = p.peek()
let keyword_tok = match keyword_opt {
Some(t) => t
None => return Err("Expected keyword after '{%', got EOF")
}
// Check if this is a stop keyword — if so, back out.
let is_stop = is_stop_keyword(keyword_tok.kind, stop_kinds)
if is_stop {
// Don't advance — let the caller handle it.
// But we already consumed OpenTag. Put it back conceptually
// by returning the parser state BEFORE OpenTag was consumed.
// Simple fix: peek_at(-1) doesn't work. Instead, we return
// with the parser positioned at the keyword (OpenTag consumed).
// The caller (parse_if_block) expects to see the keyword.
break
}
// Not a stop keyword — dispatch.
let dispatch_result = dispatch_block_tag(p, keyword_tok)
match dispatch_result {
Ok((opt_node, p3)) => {
p = p3
match opt_node {
Some(node) => nodes.push(node)
None => () // skipped block
}
}
Err(msg) => return Err(msg)
}
}
// -- Comment, close tags, etc. — skip silently -----------------------
TokenKind::OpenComment | TokenKind::CloseComment |
TokenKind::CloseVar | TokenKind::CloseTag => {
let (_, p2) = p.advance()
p = p2
}
// -- Error tokens — propagate ----------------------------------------
TokenKind::Error(msg) => {
let (_, p2) = p.advance()
p = p2
return Err("Lexer error: " + msg)
}
// -- Unexpected token ------------------------------------------------
_ => {
return Err(
"Unexpected token in template at line " +
tok.line.to_string() +
", col " +
tok.col.to_string(),
)
}
}
}
Ok((nodes, p))
}
// ---------------------------------------------------------------------------
// Block tag dispatch
// ---------------------------------------------------------------------------
/// Check if *kind* is in the stop list.
fn is_stop_keyword(kind : TokenKind, stop_kinds : Array[TokenKind]) -> Bool {
for k in stop_kinds {
if kind == k {
return true
}
}
false
}
/// Dispatch a block tag based on its keyword.
/// Returns `(Option[Node], Parser)` — `None` means the block was skipped.
fn dispatch_block_tag(
p : Parser,
keyword_tok : Token
) -> Result[(Option[Node], Parser), String] {
match keyword_tok.kind {
// -- if block ---------------------------------------------------------
TokenKind::If => {
let parsed = parse_if_block(p)
match parsed {
Ok((if_node, p2)) => Ok((Some(if_node), p2))
Err(msg) => Err(msg)
}
}
// -- for block ---------------------------------------------------------
TokenKind::For => {
let parsed = parse_for_block(p)
match parsed {
Ok((for_node, p2)) => Ok((Some(for_node), p2))
Err(msg) => Err(msg)
}
}
// -- macro block -------------------------------------------------------
TokenKind::Macro => {
let parsed = parse_macro_block(p)
match parsed {
Ok((macro_node, p2)) => Ok((Some(macro_node), p2))
Err(msg) => Err(msg)
}
}
// -- include block -----------------------------------------------------
TokenKind::Include => {
let parsed = parse_include_block(p)
match parsed {
Ok((inc_node, p2)) => Ok((Some(inc_node), p2))
Err(msg) => Err(msg)
}
}
// -- block / endblock / extends — skip for now ------------------------
TokenKind::Block | TokenKind::Endblock | TokenKind::Extends => {
let p2 = skip_until_close_tag(p)
match p2 {
Ok(pp) => Ok((None, pp))
Err(msg) => Err(msg)
}
}
// -- Elif / Else / Endif appearing outside — error --------------------
TokenKind::Elif => Err("Unexpected 'elif' outside of if block")
TokenKind::Else => Err("Unexpected 'else' outside of if block")
TokenKind::Endif => Err("Unexpected 'endif' outside of if block")
TokenKind::Endfor => Err("Unexpected 'endfor' outside of for block")
TokenKind::Endblock => Err("Unexpected 'endblock' outside of block")
TokenKind::Endmacro => Err("Unexpected 'endmacro' outside of macro block")
// -- Other keywords — skip --------------------------------------------
_ => {
let p2 = skip_until_close_tag(p)
match p2 {
Ok(pp) => Ok((None, pp))
Err(msg) => Err(msg)
}
}
}
}
// ---------------------------------------------------------------------------
// If-block parser
// ---------------------------------------------------------------------------
/// Parse a full `{% if cond %} ... {% elif cond %} ... {% else %} ... {% endif %}`.
///
/// Caller has already consumed `OpenTag`. We consume `If` and everything
/// through `Endif` + `CloseTag`.
fn parse_if_block(p : Parser) -> Result[(Node, Parser), String] {
// 1. Consume 'if' keyword.
let (if_tok, p1) = p.advance()
let _ = if_tok
// 2. Parse condition expression.
let cond_result = p1.parse_expression(0)
let (condition, p2) = match cond_result {
Ok((e, pp)) => (e, pp)
Err(msg) => return Err(msg)
}
// 3. Expect CloseTag.
let expect_result = p2.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::CloseTag },
"Expected '%}' after 'if' condition",
)
let p3 = match expect_result {
Ok((_, pp)) => pp
Err(msg) => return Err(msg)
}
// 4. Parse then-body until elif / else / endif.
let stop_kinds : Array[TokenKind] = [
TokenKind::Elif,
TokenKind::Else,
TokenKind::Endif,
]
let body_result = p3.parse_nodes(stop_kinds)
let (then_body, p4) = match body_result {
Ok((ns, pp)) => (ns, pp)
Err(msg) => return Err(msg)
}
// 5. Parse elif branches and optional else.
let elifs : Array[ElifBranch] = Array::new()
let else_body : Array[Node] = Array::new()
let chain_result = parse_elif_else_chain(p4, elifs, else_body)
let (elif_branches, else_nodes, p5) = match chain_result {
Ok((eb, en, pp)) => (eb, en, pp)
Err(msg) => return Err(msg)
}
let p_final = p5
// 6. Expect Endif (the stop keyword was not consumed by parse_nodes).
let end_tag_opt = p_final.peek()
match end_tag_opt {
Some(t) => {
if t.kind == TokenKind::OpenTag {
// Consume OpenTag.
let (_, p_ot) = p_final.advance()
let endif_opt = p_ot.peek()
match endif_opt {
Some(et) => {
if et.kind == TokenKind::Endif {
let (_, p_if) = p_ot.advance()
// Expect CloseTag.
let close_result = p_if.expect(
fn(ct : Token) -> Bool { ct.kind == TokenKind::CloseTag },
"Expected '%}' after 'endif'",
)
match close_result {
Ok((_, p_ok)) => {
let if_node = Node::If(
condition, then_body, elif_branches, else_nodes,
)
return Ok((if_node, p_ok))
}
Err(msg) => return Err(msg)
}
} else {
return Err("Expected 'endif' to close if block")
}
}
None => return Err("Expected 'endif', got EOF")
}
} else {
return Err("Expected '{% endif %}' to close if block")
}
}
None => return Err("Expected '{% endif %}', got EOF")
}
}
/// Parse the elif / else chain after the then-body.
///
/// Returns `(elif_branches, else_body, parser)`.
fn parse_elif_else_chain(
p : Parser,
elifs : Array[ElifBranch],
else_nodes : Array[Node]
) -> Result[(Array[ElifBranch], Array[Node], Parser), String] {
let mut pp = p
let elif_branches = elifs
let mut else_body = else_nodes
while true {
let tok_opt = pp.peek()
let tok = match tok_opt {
Some(t) => t
None => break
}
match tok.kind {
// Must be OpenTag (since parse_nodes stopped here).
TokenKind::OpenTag => {
let (_, p2) = pp.advance()
pp = p2
// Check the keyword.
let kw_opt = pp.peek()
let kw = match kw_opt {
Some(t) => t
None => return Err("Expected keyword after '{%', got EOF")
}
match kw.kind {
// -- elif ----------------------------------------------------
TokenKind::Elif => {
let (_, p3) = pp.advance()
pp = p3
// Parse elif condition.
let cond_result = pp.parse_expression(0)
let (elif_cond, p4) = match cond_result {
Ok((e, ppp)) => (e, ppp)
Err(msg) => return Err(msg)
}
pp = p4
// Expect CloseTag.
let close_result = pp.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::CloseTag },
"Expected '%}' after 'elif' condition",
)
pp = match close_result {
Ok((_, ppp)) => ppp
Err(msg) => return Err(msg)
}
// Parse elif body until elif / else / endif.
let stop_kinds : Array[TokenKind] = [
TokenKind::Elif,
TokenKind::Else,
TokenKind::Endif,
]
let body_result = pp.parse_nodes(stop_kinds)
let (elif_body, p5) = match body_result {
Ok((ns, ppp)) => (ns, ppp)
Err(msg) => return Err(msg)
}
pp = p5
let branch = ElifBranch::{
condition: Option::Some(elif_cond),
body: elif_body,
}
elif_branches.push(branch)
// Loop: check for more elif / else / endif.
}
// -- else ----------------------------------------------------
TokenKind::Else => {
let (_, p3) = pp.advance()
pp = p3
// Expect CloseTag.
let close_result = pp.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::CloseTag },
"Expected '%}' after 'else'",
)
pp = match close_result {
Ok((_, ppp)) => ppp
Err(msg) => return Err(msg)
}
// Parse else body until endif.
let stop_kinds : Array[TokenKind] = [
TokenKind::Endif,
]
let body_result = pp.parse_nodes(stop_kinds)
let (else_nodes_parsed, p5) = match body_result {
Ok((ns, ppp)) => (ns, ppp)
Err(msg) => return Err(msg)
}
pp = p5
else_body = else_nodes_parsed
break
}
// -- endif ---------------------------------------------------
TokenKind::Endif => break
// -- Unexpected keyword --------------------------------------
_ => return Err("Expected 'elif', 'else', or 'endif'")
}
}
_ => break
}
}
Ok((elif_branches, else_body, pp))
}
// ---------------------------------------------------------------------------
// For-block parser
// ---------------------------------------------------------------------------
/// Parse a full `{% for var in expr %} ... {% endfor %}`.
///
/// Caller has already consumed `OpenTag`. We consume `For` and everything
/// through `Endfor` + `CloseTag`.
fn parse_for_block(p : Parser) -> Result[(Node, Parser), String] {
// 1. Consume 'for' keyword.
let (_, p1) = p.advance()
// 2. Read loop variable name (Ident).
let (var_tok, p2) = p1.advance()
let loop_var = match var_tok {
Some(t) => match t.kind {
TokenKind::Ident(n) => n
_ => return Err("Expected loop variable name after 'for'")
}
None => return Err("Expected loop variable name after 'for', got EOF")
}
// 3. Expect 'in' keyword.
let in_result = p2.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::In },
"Expected 'in' after loop variable",
)
let p3 = match in_result {
Ok((_, pp)) => pp
Err(msg) => return Err(msg)
}
// 4. Parse iterable expression.
let expr_result = p3.parse_expression(0)
let (iterable, p4) = match expr_result {
Ok((e, pp)) => (e, pp)
Err(msg) => return Err(msg)
}
// 5. Expect CloseTag.
let close_result = p4.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::CloseTag },
"Expected '%}' after 'for' expression",
)
let p5 = match close_result {
Ok((_, pp)) => pp
Err(msg) => return Err(msg)
}
// 6. Parse loop body until Endfor.
let stop_kinds : Array[TokenKind] = [TokenKind::Endfor]
let body_result = p5.parse_nodes(stop_kinds)
let (body, p6) = match body_result {
Ok((ns, pp)) => (ns, pp)
Err(msg) => return Err(msg)
}
// 7. Expect Endfor + CloseTag.
let end_tag_opt = p6.peek()
match end_tag_opt {
Some(t) => {
if t.kind == TokenKind::OpenTag {
let (_, p_ot) = p6.advance()
let endfor_opt = p_ot.peek()
match endfor_opt {
Some(et) => {
if et.kind == TokenKind::Endfor {
let (_, p_ef) = p_ot.advance()
let close_result2 = p_ef.expect(
fn(ct : Token) -> Bool { ct.kind == TokenKind::CloseTag },
"Expected '%}' after 'endfor'",
)
match close_result2 {
Ok((_, p_ok)) => Ok((For(loop_var, iterable, body), p_ok))
Err(msg) => Err(msg)
}
} else {
return Err("Expected 'endfor' to close for block")
}
}
None => return Err("Expected 'endfor', got EOF")
}
} else {
return Err("Expected '{% endfor %}' to close for block")
}
}
None => return Err("Expected '{% endfor %}', got EOF")
}
}
// ---------------------------------------------------------------------------
// Macro-block parser
// ---------------------------------------------------------------------------
/// Parse `{% macro name(p1, p2) %} ... {% endmacro %}`.
fn parse_macro_block(p : Parser) -> Result[(Node, Parser), String] {
// 1. Consume 'macro' keyword.
let (_, p1) = p.advance()
// 2. Read macro name.
let (name_tok, p2) = p1.advance()
let macro_name = match name_tok {
Some(t) => match t.kind {
TokenKind::Ident(n) => n
_ => return Err("Expected macro name after 'macro'")
}
None => return Err("Expected macro name, got EOF")
}
// 3. Expect '('.
let lparen_result = p2.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::LParen },
"Expected '(' after macro name",
)
let p3 = match lparen_result {
Ok((_, pp)) => pp
Err(msg) => return Err(msg)
}
// 4. Parse parameter list (comma-separated Idents).
let params_result = parse_macro_params(p3)
let (params, p4) = match params_result {
Ok((ps, pp)) => (ps, pp)
Err(msg) => return Err(msg)
}
// 5. Expect CloseTag.
let close_result = p4.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::CloseTag },
"Expected '%}' after macro params",
)
let p5 = match close_result {
Ok((_, pp)) => pp
Err(msg) => return Err(msg)
}
// 6. Parse macro body until Endmacro.
let stop_kinds : Array[TokenKind] = [TokenKind::Endmacro]
let body_result = p5.parse_nodes(stop_kinds)
let (body, p6) = match body_result {
Ok((ns, pp)) => (ns, pp)
Err(msg) => return Err(msg)
}
// 7. Expect Endmacro + CloseTag.
let end_tag_opt = p6.peek()
match end_tag_opt {
Some(t) => {
if t.kind == TokenKind::OpenTag {
let (_, p_ot) = p6.advance()
let kw_opt = p_ot.peek()
match kw_opt {
Some(et) => {
if et.kind == TokenKind::Endmacro {
let (_, p_em) = p_ot.advance()
let close_result2 = p_em.expect(
fn(ct : Token) -> Bool { ct.kind == TokenKind::CloseTag },
"Expected '%}' after 'endmacro'",
)
match close_result2 {
Ok((_, p_ok)) => Ok((Macro(macro_name, params, body), p_ok))
Err(msg) => Err(msg)
}
} else {
return Err("Expected 'endmacro' to close macro block")
}
}
None => return Err("Expected 'endmacro', got EOF")
}
} else {
return Err("Expected '{% endmacro %}' to close macro block")
}
}
None => return Err("Expected '{% endmacro %}', got EOF")
}
}
/// Parse macro parameters: `ident, ident, ... )`.
fn parse_macro_params(
p : Parser
) -> Result[(Array[String], Parser), String] {
let params : Array[String] = Array::new()
let mut pp = p
// Check for empty param list (RParen immediately).
let first_opt = pp.peek()
match first_opt {
Some(t) => {
if t.kind == TokenKind::RParen {
let (_, p2) = pp.advance()
return Ok((params, p2))
}
}
None => return Err("Expected ')' after macro params, got EOF")
}
// Parse comma-separated identifiers.
while true {
let id_opt = pp.peek()
match id_opt {
Some(t) => match t.kind {
TokenKind::Ident(name) => {
let (_, p2) = pp.advance()
pp = p2
params.push(name)
}
_ => return Err("Expected parameter name")
}
None => return Err("Expected parameter name, got EOF")
}
// Check for comma or RParen.
let sep_opt = pp.peek()
match sep_opt {
Some(t) => {
if t.kind == TokenKind::RParen {
let (_, p2) = pp.advance()
return Ok((params, p2))
} else if t.kind == TokenKind::Comma {
let (_, p2) = pp.advance()
pp = p2
} else {
return Err("Expected ',' or ')' in macro parameter list")
}
}
None => return Err("Expected ',' or ')', got EOF")
}
}
// unreachable
Ok((params, pp))
}
// ---------------------------------------------------------------------------
// Include-block parser
// ---------------------------------------------------------------------------
/// Parse `{% include "template_name" %}`.
fn parse_include_block(p : Parser) -> Result[(Node, Parser), String] {
// 1. Consume 'include' keyword.
let (_, p1) = p.advance()
// 2. Read template name (string literal or variable).
let name_opt = p1.peek()
let (tpl_name, p2) = match name_opt {
Some(t) => match t.kind {
TokenKind::Str(s) => {
let (_, pp) = p1.advance()
(s, pp)
}
_ => return Err("Expected template name string after 'include'")
}
None => return Err("Expected template name after 'include', got EOF")
}
// 3. Expect CloseTag.
let close_result = p2.expect(
fn(t : Token) -> Bool { t.kind == TokenKind::CloseTag },
"Expected '%}' after include",
)
match close_result {
Ok((_, p3)) => Ok((Include(tpl_name), p3))
Err(msg) => Err(msg)
}
}
/// Skip tokens until `CloseTag` (`%}`) is encountered.
fn skip_until_close_tag(p : Parser) -> Result[Parser, String] {
let mut pp = p
while true {
let tok_opt = pp.peek()
let tok = match tok_opt {
Some(t) => t
None => return Err("Unclosed block tag — expected '%}'")
}
match tok.kind {
TokenKind::CloseTag => {
let (_, p2) = pp.advance()
return Ok(p2)
}
TokenKind::Eof => return Err("Unclosed block tag — expected '%}' before end of file")
_ => {
let (_, p2) = pp.advance()
pp = p2
}
}
}
Err("unreachable")
}