use std::borrow::Cow; use std::collections::BTreeSet; use std::fmt; use std::mem; use crate::compiler::ast::{self, Spanned}; use crate::compiler::lexer::{Tokenizer, WhitespaceConfig}; use crate::compiler::tokens::{Span, Token}; use crate::error::{Error, ErrorKind}; use crate::syntax::SyntaxConfig; use crate::value::Value; const MAX_RECURSION: usize = 150; const RESERVED_NAMES: [&str; 8] = [ "true", "True", "false", "False", "none", "None", "loop", "self", ]; fn unexpected(unexpected: D, expected: &str) -> Error { Error::new( ErrorKind::SyntaxError, format!("unexpected {unexpected}, expected {expected}"), ) } fn unexpected_eof(expected: &str) -> Error { unexpected("end of input", expected) } fn make_const(value: Value, span: Span) -> ast::Expr<'static> { ast::Expr::Const(Spanned::new(ast::Const { value }, span)) } fn syntax_error(msg: Cow<'static, str>) -> Error { Error::new(ErrorKind::SyntaxError, msg) } macro_rules! syntax_error { ($msg:expr) => {{ return Err(syntax_error(Cow::Borrowed($msg))); }}; ($msg:expr, $($tt:tt)*) => {{ return Err(syntax_error(Cow::Owned(format!($msg, $($tt)*)))); }}; } macro_rules! expect_token { ($parser:expr, $expectation:expr) => {{ match ok!($parser.stream.next()) { Some(rv) => rv, None => return Err(unexpected_eof($expectation)), } }}; ($parser:expr, $match:pat, $expectation:expr) => {{ match ok!($parser.stream.next()) { Some((token @ $match, span)) => (token, span), Some((token, _)) => return Err(unexpected(token, $expectation)), None => return Err(unexpected_eof($expectation)), } }}; ($parser:expr, $match:pat => $target:expr, $expectation:expr) => {{ match ok!($parser.stream.next()) { Some(($match, span)) => ($target, span), Some((token, _)) => return Err(unexpected(token, $expectation)), None => return Err(unexpected_eof($expectation)), } }}; } macro_rules! matches_token { ($p:expr, $match:pat) => { match $p.stream.current() { Err(err) => return Err(err), Ok(Some(($match, _))) => true, _ => false, } }; } macro_rules! skip_token { ($p:expr, $match:pat) => { match $p.stream.current() { Err(err) => return Err(err), Ok(Some(($match, _))) => { let _ = $p.stream.next(); true } _ => false, } }; } enum SetParseResult<'a> { Set(ast::Set<'a>), SetBlock(ast::SetBlock<'a>), } struct TokenStream<'a> { tokenizer: Tokenizer<'a>, current: Result, Span)>, Error>, last_span: Span, } impl<'a> TokenStream<'a> { /// Tokenize a template pub fn new( source: &'a str, filename: &'a str, in_expr: bool, syntax_config: SyntaxConfig, whitespace_config: WhitespaceConfig, ) -> TokenStream<'a> { let mut tokenizer = Tokenizer::new(source, filename, in_expr, syntax_config, whitespace_config); let current = tokenizer.next_token(); TokenStream { tokenizer, current, last_span: Span::default(), } } /// Advance the stream. #[inline(always)] pub fn next(&mut self) -> Result, Span)>, Error> { let rv = mem::replace(&mut self.current, self.tokenizer.next_token()); match rv { Ok(Some((token, span))) => { self.last_span = span; Ok(Some((token, span))) } Ok(None) => Ok(None), Err(err) => Err(err), } } /// Look at the current token #[inline(always)] pub fn current(&mut self) -> Result, Span)>, Error> { if self.current.is_err() { return match mem::replace(&mut self.current, Ok(None)) { Err(err) => Err(err), _ => unreachable!(), }; } match self.current { Ok(Some((ref token, span))) => Ok(Some((token, span))), Ok(None) => Ok(None), Err(_) => unreachable!(), } } /// Expands the span #[inline(always)] pub fn expand_span(&self, mut span: Span) -> Span { span.end_line = self.last_span.end_line; span.end_col = self.last_span.end_col; span.end_offset = self.last_span.end_offset; span } /// Returns the current span. #[inline(always)] pub fn current_span(&self) -> Span { match self.current { Ok(Some((_, span))) => span, _ => self.last_span, } } /// Returns the last seen span. #[inline(always)] pub fn last_span(&self) -> Span { self.last_span } } struct Parser<'a> { stream: TokenStream<'a>, #[allow(unused)] in_macro: bool, #[allow(unused)] in_loop: bool, #[allow(unused)] blocks: BTreeSet<&'a str>, depth: usize, } macro_rules! binop { ($func:ident, $next:ident, { $($tok:tt)* }) => { fn $func(&mut self) -> Result, Error> { let span = self.stream.current_span(); let mut left = ok!(self.$next()); loop { let op = match ok!(self.stream.current()) { $($tok)* _ => break, }; ok!(self.stream.next()); let right = ok!(self.$next()); left = ast::Expr::BinOp(Spanned::new( ast::BinOp { op, left, right, }, self.stream.expand_span(span), )); } Ok(left) } }; } macro_rules! unaryop { ($func:ident, $next:ident, { $($tok:tt)* }) => { fn $func(&mut self) -> Result, Error> { let span = self.stream.current_span(); let op = match ok!(self.stream.current()) { $($tok)* _ => return self.$next() }; ok!(self.stream.next()); Ok(ast::Expr::UnaryOp(Spanned::new( ast::UnaryOp { op, expr: ok!(self.$func()), }, self.stream.expand_span(span), ))) } }; } macro_rules! with_recursion_guard { ($parser:expr, $expr:expr) => {{ $parser.depth += 1; if $parser.depth > MAX_RECURSION { return Err(syntax_error(Cow::Borrowed( "template exceeds maximum recursion limits", ))); } let rv = $expr; $parser.depth -= 1; rv }}; } impl<'a> Parser<'a> { /// Creates a new parser. /// /// `in_expr` is necessary to parse within an expression context. Otherwise /// the parser starts out in template context. This means that when /// [`parse`](Self::parse) is to be called, the `in_expr` argument must be /// `false` and for [`parse_standalone_expr`](Self::parse_standalone_expr) /// it must be `true`. pub fn new( source: &'a str, filename: &'a str, in_expr: bool, syntax_config: SyntaxConfig, whitespace_config: WhitespaceConfig, ) -> Parser<'a> { Parser { stream: TokenStream::new(source, filename, in_expr, syntax_config, whitespace_config), in_macro: false, in_loop: false, blocks: BTreeSet::new(), depth: 0, } } /// Parses a template. pub fn parse(&mut self) -> Result, Error> { let span = self.stream.last_span(); self.subparse(&|_| false) .map(|children| { ast::Stmt::Template(Spanned::new( ast::Template { children }, self.stream.expand_span(span), )) }) .map_err(|err| self.attach_location_to_error(err)) } /// Parses an expression and asserts that there is no more input after it. pub fn parse_standalone_expr(&mut self) -> Result, Error> { self.parse_expr() .and_then(|result| { if ok!(self.stream.next()).is_some() { syntax_error!("unexpected input after expression") } else { Ok(result) } }) .map_err(|err| self.attach_location_to_error(err)) } /// Returns the current filename. pub fn filename(&self) -> &str { self.stream.tokenizer.filename() } fn parse_ifexpr(&mut self) -> Result, Error> { let mut span = self.stream.last_span(); let mut expr = ok!(self.parse_or()); loop { if skip_token!(self, Token::Ident("if")) { let expr2 = ok!(self.parse_or()); let expr3 = if skip_token!(self, Token::Ident("else")) { Some(ok!(self.parse_ifexpr())) } else { None }; expr = ast::Expr::IfExpr(Spanned::new( ast::IfExpr { test_expr: expr2, true_expr: expr, false_expr: expr3, }, self.stream.expand_span(span), )); span = self.stream.last_span(); } else { break; } } Ok(expr) } binop!(parse_or, parse_and, { Some((Token::Ident("or"), _)) => ast::BinOpKind::ScOr, }); binop!(parse_and, parse_not, { Some((Token::Ident("and"), _)) => ast::BinOpKind::ScAnd, }); unaryop!(parse_not, parse_compare, { Some((Token::Ident("not"), _)) => ast::UnaryOpKind::Not, }); fn parse_compare(&mut self) -> Result, Error> { let mut span = self.stream.last_span(); let mut expr = ok!(self.parse_math1()); loop { let mut negated = false; let op = match ok!(self.stream.current()) { Some((Token::Eq, _)) => ast::BinOpKind::Eq, Some((Token::Ne, _)) => ast::BinOpKind::Ne, Some((Token::Lt, _)) => ast::BinOpKind::Lt, Some((Token::Lte, _)) => ast::BinOpKind::Lte, Some((Token::Gt, _)) => ast::BinOpKind::Gt, Some((Token::Gte, _)) => ast::BinOpKind::Gte, Some((Token::Ident("in"), _)) => ast::BinOpKind::In, Some((Token::Ident("not"), _)) => { ok!(self.stream.next()); expect_token!(self, Token::Ident("in"), "in"); negated = true; ast::BinOpKind::In } _ => break, }; if !negated { ok!(self.stream.next()); } expr = ast::Expr::BinOp(Spanned::new( ast::BinOp { op, left: expr, right: ok!(self.parse_math1()), }, self.stream.expand_span(span), )); if negated { expr = ast::Expr::UnaryOp(Spanned::new( ast::UnaryOp { op: ast::UnaryOpKind::Not, expr, }, self.stream.expand_span(span), )); } span = self.stream.last_span(); } Ok(expr) } binop!(parse_math1, parse_concat, { Some((Token::Plus, _)) => ast::BinOpKind::Add, Some((Token::Minus, _)) => ast::BinOpKind::Sub, }); binop!(parse_concat, parse_math2, { Some((Token::Tilde, _)) => ast::BinOpKind::Concat, }); binop!(parse_math2, parse_pow, { Some((Token::Mul, _)) => ast::BinOpKind::Mul, Some((Token::Div, _)) => ast::BinOpKind::Div, Some((Token::FloorDiv, _)) => ast::BinOpKind::FloorDiv, Some((Token::Mod, _)) => ast::BinOpKind::Rem, }); binop!(parse_pow, parse_unary, { Some((Token::Pow, _)) => ast::BinOpKind::Pow, }); unaryop!(parse_unary_only, parse_primary, { Some((Token::Minus, _)) => ast::UnaryOpKind::Neg, }); fn parse_unary(&mut self) -> Result, Error> { let span = self.stream.current_span(); let mut expr = ok!(self.parse_unary_only()); expr = ok!(self.parse_postfix(expr, span)); self.parse_filter_expr(expr) } fn parse_postfix( &mut self, expr: ast::Expr<'a>, mut span: Span, ) -> Result, Error> { let mut expr = expr; loop { let next_span = self.stream.current_span(); match ok!(self.stream.current()) { Some((Token::Dot, _)) => { ok!(self.stream.next()); match ok!(self.stream.next()) { Some((Token::Ident(name), _)) => { expr = ast::Expr::GetAttr(Spanned::new( ast::GetAttr { name, expr }, self.stream.expand_span(span), )); } Some((Token::Int(idx), idx_span)) => { expr = ast::Expr::GetItem(Spanned::new( ast::GetItem { expr, subscript_expr: make_const(Value::from(idx), idx_span), }, self.stream.expand_span(span), )); } Some((Token::Int128(idx), idx_span)) => { expr = ast::Expr::GetItem(Spanned::new( ast::GetItem { expr, subscript_expr: make_const(Value::from(*idx), idx_span), }, self.stream.expand_span(span), )); } Some((token, _)) => return Err(unexpected(token, "identifier or integer")), None => return Err(unexpected_eof("identifier or integer")), } } Some((Token::BracketOpen, _)) => { ok!(self.stream.next()); let mut start = None; let mut stop = None; let mut step = None; let mut is_slice = false; if !matches_token!(self, Token::Colon) { start = Some(ok!(self.parse_expr())); } if skip_token!(self, Token::Colon) { is_slice = true; if !matches_token!(self, Token::BracketClose | Token::Colon) { stop = Some(ok!(self.parse_expr())); } if skip_token!(self, Token::Colon) && !matches_token!(self, Token::BracketClose) { step = Some(ok!(self.parse_expr())); } } expect_token!(self, Token::BracketClose, "`]`"); if !is_slice { expr = ast::Expr::GetItem(Spanned::new( ast::GetItem { expr, subscript_expr: ok!(start.ok_or_else(|| { syntax_error(Cow::Borrowed("empty subscript")) })), }, self.stream.expand_span(span), )); } else { expr = ast::Expr::Slice(Spanned::new( ast::Slice { expr, start, stop, step, }, self.stream.expand_span(span), )); } } Some((Token::ParenOpen, _)) => { let args = ok!(self.parse_args()); expr = ast::Expr::Call(Spanned::new( ast::Call { expr, args }, self.stream.expand_span(span), )); } _ => break, } span = next_span; } Ok(expr) } fn parse_filter_test_name(&mut self) -> Result<(&'a str, Span), Error> { let (first_segment, span) = expect_token!(self, Token::Ident(name) => name, "identifier"); let start_offset = span.start_offset as usize; let mut end_offset = span.end_offset as usize; let mut is_dotted = false; while skip_token!(self, Token::Dot) { let (_, segment_span) = expect_token!(self, Token::Ident(name) => name, "identifier"); end_offset = segment_span.end_offset as usize; is_dotted = true; } if is_dotted { Ok(( &self.stream.tokenizer.source()[start_offset..end_offset], self.stream.expand_span(span), )) } else { Ok((first_segment, span)) } } fn parse_filter_expr(&mut self, expr: ast::Expr<'a>) -> Result, Error> { let mut expr = expr; loop { match ok!(self.stream.current()) { Some((Token::Pipe, _)) => { ok!(self.stream.next()); let (name, span) = ok!(self.parse_filter_test_name()); let args = if matches_token!(self, Token::ParenOpen) { ok!(self.parse_args()) } else { Vec::new() }; expr = ast::Expr::Filter(Spanned::new( ast::Filter { name, expr: Some(expr), args, }, self.stream.expand_span(span), )); } Some((Token::Ident("is"), _)) => { ok!(self.stream.next()); let negated = skip_token!(self, Token::Ident("not")); let (name, span) = ok!(self.parse_filter_test_name()); let args = if matches_token!(self, Token::ParenOpen) { ok!(self.parse_args()) } else if matches_token!( self, Token::Ident(_) | Token::Str(_) | Token::String(_) | Token::Int(_) | Token::Int128(_) | Token::Float(_) | Token::Plus | Token::Minus | Token::BracketOpen | Token::BraceOpen ) && !matches_token!( self, Token::Ident("and") | Token::Ident("or") | Token::Ident("else") | Token::Ident("is") ) { let span = self.stream.current_span(); let mut expr = ok!(self.parse_unary_only()); expr = ok!(self.parse_postfix(expr, span)); vec![ast::CallArg::Pos(expr)] } else { Vec::new() }; expr = ast::Expr::Test(Spanned::new( ast::Test { name, expr, args }, self.stream.expand_span(span), )); if negated { expr = ast::Expr::UnaryOp(Spanned::new( ast::UnaryOp { op: ast::UnaryOpKind::Not, expr, }, self.stream.expand_span(span), )); } } _ => break, } } Ok(expr) } fn parse_args(&mut self) -> Result>, Error> { let mut args = Vec::new(); let mut first_span = None; let mut has_kwargs = false; enum ArgType { Regular, Splat, KwargsSplat, } expect_token!(self, Token::ParenOpen, "`(`"); loop { if skip_token!(self, Token::ParenClose) { break; } if !args.is_empty() || has_kwargs { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::ParenClose) { break; } } let arg_type = if skip_token!(self, Token::Pow) { ArgType::KwargsSplat } else if skip_token!(self, Token::Mul) { ArgType::Splat } else { ArgType::Regular }; let expr = ok!(self.parse_expr()); match arg_type { ArgType::Regular => { // keyword argument match expr { ast::Expr::Var(ref var) if skip_token!(self, Token::Assign) => { if first_span.is_none() { first_span = Some(var.span()); } has_kwargs = true; args.push(ast::CallArg::Kwarg(var.id, ok!(self.parse_expr_noif()))); } _ if has_kwargs => { return Err(syntax_error(Cow::Borrowed( "non-keyword arg after keyword arg", ))); } _ => { args.push(ast::CallArg::Pos(expr)); } } } ArgType::Splat => { args.push(ast::CallArg::PosSplat(expr)); } ArgType::KwargsSplat => { args.push(ast::CallArg::KwargSplat(expr)); has_kwargs = true; } } // Set an arbitrary limit of max function parameters. This is done // in parts because the opcodes can only express 2**16 as argument // count. if args.len() > 2000 { syntax_error!("Too many arguments in function call") } } Ok(args) } fn parse_primary(&mut self) -> Result, Error> { with_recursion_guard!(self, self.parse_primary_impl()) } fn parse_primary_impl(&mut self) -> Result, Error> { let (token, span) = expect_token!(self, "expression"); macro_rules! const_val { ($expr:expr) => { make_const(Value::from($expr), self.stream.expand_span(span)) }; } match token { Token::Ident("true" | "True") => Ok(const_val!(true)), Token::Ident("false" | "False") => Ok(const_val!(false)), Token::Ident("none" | "None") => Ok(const_val!(())), Token::Ident(name) => Ok(ast::Expr::Var(Spanned::new(ast::Var { id: name }, span))), Token::Str(val) if !matches!( self.stream.current(), Ok(Some((Token::Str(_), _) | (Token::String(_), _))) ) => { Ok(const_val!(val)) } Token::Str(_) | Token::String(_) => { let mut buf = match token { Token::Str(s) => s.to_owned(), Token::String(s) => s.into_string(), _ => unreachable!(), }; loop { match ok!(self.stream.current()) { Some((Token::Str(s), _)) => buf.push_str(s), Some((Token::String(s), _)) => buf.push_str(s), _ => break, } ok!(self.stream.next()); } Ok(const_val!(buf)) } Token::Int(val) => Ok(const_val!(val)), Token::Int128(val) => Ok(const_val!(*val)), Token::Float(val) => Ok(const_val!(val)), Token::ParenOpen => self.parse_tuple_or_expression(span), Token::BracketOpen => self.parse_list_expr(span), Token::BraceOpen => self.parse_map_expr(span), token => syntax_error!("unexpected {}", token), } } fn parse_list_expr(&mut self, span: Span) -> Result, Error> { let mut items = Vec::with_capacity(4); loop { if skip_token!(self, Token::BracketClose) { break; } if !items.is_empty() { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::BracketClose) { break; } } items.push(ok!(self.parse_expr())); } Ok(ast::Expr::List(Spanned::new( ast::List { items }, self.stream.expand_span(span), ))) } fn parse_map_expr(&mut self, span: Span) -> Result, Error> { let mut keys = Vec::with_capacity(4); let mut values = Vec::with_capacity(4); loop { if skip_token!(self, Token::BraceClose) { break; } if !keys.is_empty() { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::BraceClose) { break; } } keys.push(ok!(self.parse_expr())); expect_token!(self, Token::Colon, "`:`"); values.push(ok!(self.parse_expr())); } Ok(ast::Expr::Map(Spanned::new( ast::Map { keys, values }, self.stream.expand_span(span), ))) } fn parse_tuple_or_expression(&mut self, span: Span) -> Result, Error> { // MiniJinja does not really have tuples, but it treats the tuple // syntax the same as lists. if skip_token!(self, Token::ParenClose) { return Ok(ast::Expr::List(Spanned::new( ast::List { items: vec![] }, self.stream.expand_span(span), ))); } let mut expr = ok!(self.parse_expr()); if matches_token!(self, Token::Comma) { let mut items = vec![expr]; loop { if skip_token!(self, Token::ParenClose) { break; } expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::ParenClose) { break; } items.push(ok!(self.parse_expr())); } expr = ast::Expr::List(Spanned::new( ast::List { items }, self.stream.expand_span(span), )); } else { expect_token!(self, Token::ParenClose, "`)`"); } Ok(expr) } fn parse_expr(&mut self) -> Result, Error> { with_recursion_guard!(self, self.parse_ifexpr()) } fn parse_expr_noif(&mut self) -> Result, Error> { self.parse_or() } fn parse_stmt(&mut self) -> Result, Error> { with_recursion_guard!(self, self.parse_stmt_unprotected()) } fn parse_stmt_unprotected(&mut self) -> Result, Error> { let (token, span) = expect_token!(self, "block keyword"); macro_rules! respan { ($expr:expr) => { Spanned::new($expr, self.stream.expand_span(span)) }; } let ident = match token { Token::Ident(ident) => ident, token => syntax_error!("unknown {}, expected statement", token), }; Ok(match ident { "for" => ast::Stmt::ForLoop(respan!(ok!(self.parse_for_stmt()))), "if" => ast::Stmt::IfCond(respan!(ok!(self.parse_if_cond()))), "with" => ast::Stmt::WithBlock(respan!(ok!(self.parse_with_block()))), "set" => match ok!(self.parse_set()) { SetParseResult::Set(rv) => ast::Stmt::Set(respan!(rv)), SetParseResult::SetBlock(rv) => ast::Stmt::SetBlock(respan!(rv)), }, "autoescape" => ast::Stmt::AutoEscape(respan!(ok!(self.parse_auto_escape()))), "filter" => ast::Stmt::FilterBlock(respan!(ok!(self.parse_filter_block()))), #[cfg(feature = "multi_template")] "block" => ast::Stmt::Block(respan!(ok!(self.parse_block()))), #[cfg(feature = "multi_template")] "extends" => ast::Stmt::Extends(respan!(ok!(self.parse_extends()))), #[cfg(feature = "multi_template")] "include" => ast::Stmt::Include(respan!(ok!(self.parse_include()))), #[cfg(feature = "multi_template")] "import" => ast::Stmt::Import(respan!(ok!(self.parse_import()))), #[cfg(feature = "multi_template")] "from" => ast::Stmt::FromImport(respan!(ok!(self.parse_from_import()))), #[cfg(feature = "macros")] "macro" => ast::Stmt::Macro(respan!(ok!(self.parse_macro()))), #[cfg(feature = "macros")] "call" => ast::Stmt::CallBlock(respan!(ok!(self.parse_call_block()))), #[cfg(feature = "loop_controls")] "continue" => { if !self.in_loop { syntax_error!("'continue' must be placed inside a loop"); } ast::Stmt::Continue(respan!(ast::Continue)) } #[cfg(feature = "loop_controls")] "break" => { if !self.in_loop { syntax_error!("'break' must be placed inside a loop"); } ast::Stmt::Break(respan!(ast::Break)) } "do" => ast::Stmt::Do(respan!(ok!(self.parse_do()))), name => syntax_error!("unknown statement {}", name), }) } fn parse_assign_name(&mut self, dotted: bool) -> Result, Error> { let (id, span) = expect_token!(self, Token::Ident(name) => name, "identifier"); if RESERVED_NAMES.contains(&id) { syntax_error!("cannot assign to reserved variable name {}", id); } let mut rv = ast::Expr::Var(ast::Spanned::new(ast::Var { id }, span)); if dotted { while skip_token!(self, Token::Dot) { let (attr, span) = expect_token!(self, Token::Ident(name) => name, "identifier"); rv = ast::Expr::GetAttr(ast::Spanned::new( ast::GetAttr { expr: rv, name: attr, }, span, )); } } Ok(rv) } fn parse_assignment(&mut self, dotted: bool) -> Result, Error> { let span = self.stream.current_span(); let mut items = Vec::with_capacity(2); let mut is_tuple = false; loop { if !items.is_empty() { expect_token!(self, Token::Comma, "`,`"); } if matches_token!( self, Token::ParenClose | Token::VariableEnd | Token::BlockEnd | Token::Ident("in") ) { break; } items.push(if skip_token!(self, Token::ParenOpen) { let rv = ok!(self.parse_assignment(dotted)); expect_token!(self, Token::ParenClose, "`)`"); rv } else { ok!(self.parse_assign_name(dotted)) }); if matches_token!(self, Token::Comma) { is_tuple = true; } else { break; } } if !is_tuple && items.len() == 1 { Ok(items.into_iter().next().unwrap()) } else { Ok(ast::Expr::List(Spanned::new( ast::List { items }, self.stream.expand_span(span), ))) } } fn parse_for_stmt(&mut self) -> Result, Error> { let old_in_loop = std::mem::replace(&mut self.in_loop, true); let target = ok!(self.parse_assignment(false)); expect_token!(self, Token::Ident("in"), "in"); let iter = ok!(self.parse_expr_noif()); let filter_expr = if skip_token!(self, Token::Ident("if")) { Some(ok!(self.parse_expr())) } else { None }; let recursive = skip_token!(self, Token::Ident("recursive")); expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endfor" | "else")))); let else_body = if skip_token!(self, Token::Ident("else")) { expect_token!(self, Token::BlockEnd, "end of block"); ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endfor")))) } else { Vec::new() }; ok!(self.stream.next()); self.in_loop = old_in_loop; Ok(ast::ForLoop { target, iter, filter_expr, recursive, body, else_body, }) } fn parse_if_cond(&mut self) -> Result, Error> { let expr = ok!(self.parse_expr_noif()); expect_token!(self, Token::BlockEnd, "end of block"); let true_body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endif" | "else" | "elif")))); let false_body = match ok!(self.stream.next()) { Some((Token::Ident("else"), _)) => { expect_token!(self, Token::BlockEnd, "end of block"); let rv = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endif")))); ok!(self.stream.next()); rv } Some((Token::Ident("elif"), span)) => vec![ast::Stmt::IfCond(Spanned::new( ok!(self.parse_if_cond()), self.stream.expand_span(span), ))], _ => Vec::new(), }; Ok(ast::IfCond { expr, true_body, false_body, }) } fn parse_with_block(&mut self) -> Result, Error> { let mut assignments = Vec::with_capacity(2); while !matches_token!(self, Token::BlockEnd) { if !assignments.is_empty() { expect_token!(self, Token::Comma, "comma"); } let target = if skip_token!(self, Token::ParenOpen) { let assign = ok!(self.parse_assignment(false)); expect_token!(self, Token::ParenClose, "`)`"); assign } else { ok!(self.parse_assign_name(false)) }; expect_token!(self, Token::Assign, "assignment operator"); let expr = ok!(self.parse_expr()); assignments.push((target, expr)); } expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endwith")))); ok!(self.stream.next()); Ok(ast::WithBlock { assignments, body }) } fn parse_set(&mut self) -> Result, Error> { let target = ok!(self.parse_assignment(true)); if matches_token!(self, Token::BlockEnd | Token::Pipe) { let filter = if skip_token!(self, Token::Pipe) { Some(ok!(self.parse_filter_chain())) } else { None }; expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endset")))); ok!(self.stream.next()); Ok(SetParseResult::SetBlock(ast::SetBlock { target, filter, body, })) } else { expect_token!(self, Token::Assign, "assignment operator"); // Parse RHS - single expression or comma-separated tuple let expr = ok!(self.parse_expr()); let expr = if skip_token!(self, Token::Comma) { let span = self.stream.current_span(); let mut items = vec![expr]; loop { if matches_token!(self, Token::BlockEnd) { break; } items.push(ok!(self.parse_expr())); if !skip_token!(self, Token::Comma) { break; } } ast::Expr::List(Spanned::new( ast::List { items }, self.stream.expand_span(span), )) } else { expr }; Ok(SetParseResult::Set(ast::Set { target, expr })) } } #[cfg(feature = "multi_template")] fn parse_block(&mut self) -> Result, Error> { if self.in_macro { syntax_error!("block tags in macros are not allowed"); } let old_in_loop = std::mem::replace(&mut self.in_loop, false); let (name, _) = expect_token!(self, Token::Ident(name) => name, "identifier"); if !self.blocks.insert(name) { syntax_error!("block '{}' defined twice", name); } expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endblock")))); ok!(self.stream.next()); if let Some((Token::Ident(trailing_name), _)) = ok!(self.stream.current()) { if *trailing_name != name { syntax_error!( "mismatching name on block. Got `{}`, expected `{}`", *trailing_name, name ); } ok!(self.stream.next()); } self.in_loop = old_in_loop; Ok(ast::Block { name, body }) } fn parse_auto_escape(&mut self) -> Result, Error> { let enabled = ok!(self.parse_expr()); expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endautoescape")))); ok!(self.stream.next()); Ok(ast::AutoEscape { enabled, body }) } fn parse_filter_chain(&mut self) -> Result, Error> { let mut filter = None; while !matches_token!(self, Token::BlockEnd) { if filter.is_some() { expect_token!(self, Token::Pipe, "`|`"); } let (name, span) = ok!(self.parse_filter_test_name()); let args = if matches_token!(self, Token::ParenOpen) { ok!(self.parse_args()) } else { Vec::new() }; filter = Some(ast::Expr::Filter(Spanned::new( ast::Filter { name, expr: filter, args, }, self.stream.expand_span(span), ))); } filter.ok_or_else(|| syntax_error(Cow::Borrowed("expected a filter"))) } fn parse_filter_block(&mut self) -> Result, Error> { let filter = ok!(self.parse_filter_chain()); expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endfilter")))); ok!(self.stream.next()); Ok(ast::FilterBlock { filter, body }) } #[cfg(feature = "multi_template")] fn parse_extends(&mut self) -> Result, Error> { let name = ok!(self.parse_expr()); Ok(ast::Extends { name }) } #[cfg(feature = "multi_template")] fn parse_include(&mut self) -> Result, Error> { let name = ok!(self.parse_expr()); let skipped_context = ok!(self.skip_context_marker()); let ignore_missing = if skip_token!(self, Token::Ident("ignore")) { expect_token!(self, Token::Ident("missing"), "missing keyword"); if !skipped_context { ok!(self.skip_context_marker()); } true } else { false }; Ok(ast::Include { name, ignore_missing, }) } #[cfg(feature = "multi_template")] fn parse_import(&mut self) -> Result, Error> { let expr = ok!(self.parse_expr()); expect_token!(self, Token::Ident("as"), "as"); let name = ok!(self.parse_expr()); ok!(self.skip_context_marker()); Ok(ast::Import { expr, name }) } #[cfg(feature = "multi_template")] fn parse_from_import(&mut self) -> Result, Error> { let expr = ok!(self.parse_expr()); let mut names = Vec::with_capacity(4); expect_token!(self, Token::Ident("import"), "import"); loop { if ok!(self.skip_context_marker()) || matches_token!(self, Token::BlockEnd) { break; } if !names.is_empty() { expect_token!(self, Token::Comma, "`,`"); } if ok!(self.skip_context_marker()) || matches_token!(self, Token::BlockEnd) { break; } let name = ok!(self.parse_assign_name(false)); let alias = if skip_token!(self, Token::Ident("as")) { Some(ok!(self.parse_assign_name(false))) } else { None }; names.push((name, alias)); } Ok(ast::FromImport { expr, names }) } #[cfg(feature = "multi_template")] fn skip_context_marker(&mut self) -> Result { // with/without context is without meaning in MiniJinja, but for syntax // copatibility it's supported. if skip_token!(self, Token::Ident("with") | Token::Ident("without")) { expect_token!(self, Token::Ident("context"), "context"); Ok(true) } else { Ok(false) } } #[cfg(feature = "macros")] fn parse_macro_args_and_defaults( &mut self, args: &mut Vec>, defaults: &mut Vec>, ) -> Result<(), Error> { loop { if skip_token!(self, Token::ParenClose) { break; } if !args.is_empty() { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::ParenClose) { break; } } args.push(ok!(self.parse_assign_name(false))); if skip_token!(self, Token::Assign) { defaults.push(ok!(self.parse_expr())); } else if !defaults.is_empty() { expect_token!(self, Token::Assign, "`=`"); } } Ok(()) } #[cfg(feature = "macros")] fn parse_macro_or_call_block_body( &mut self, args: Vec>, defaults: Vec>, name: Option<&'a str>, ) -> Result, Error> { expect_token!(self, Token::BlockEnd, "end of block"); let old_in_loop = std::mem::replace(&mut self.in_loop, false); let old_in_macro = std::mem::replace(&mut self.in_macro, true); let body = ok!(self.subparse(&|tok| match tok { Token::Ident("endmacro") if name.is_some() => true, Token::Ident("endcall") if name.is_none() => true, _ => false, })); self.in_macro = old_in_macro; self.in_loop = old_in_loop; ok!(self.stream.next()); Ok(ast::Macro { name: name.unwrap_or("caller"), args, defaults, body, }) } #[cfg(feature = "macros")] fn parse_macro(&mut self) -> Result, Error> { let (name, _) = expect_token!(self, Token::Ident(name) => name, "identifier"); expect_token!(self, Token::ParenOpen, "`(`"); let mut args = Vec::with_capacity(4); let mut defaults = Vec::with_capacity(4); ok!(self.parse_macro_args_and_defaults(&mut args, &mut defaults)); self.parse_macro_or_call_block_body(args, defaults, Some(name)) } #[cfg(feature = "macros")] fn parse_call_block(&mut self) -> Result, Error> { let span = self.stream.last_span(); let mut args = Vec::with_capacity(4); let mut defaults = Vec::with_capacity(4); if skip_token!(self, Token::ParenOpen) { ok!(self.parse_macro_args_and_defaults(&mut args, &mut defaults)); } let call = match ok!(self.parse_expr()) { ast::Expr::Call(call) => call, expr => syntax_error!( "expected call expression in call block, got {}", expr.description() ), }; let macro_decl = ok!(self.parse_macro_or_call_block_body(args, defaults, None)); Ok(ast::CallBlock { call, macro_decl: Spanned::new(macro_decl, self.stream.expand_span(span)), }) } fn parse_do(&mut self) -> Result, Error> { let call = match ok!(self.parse_expr()) { ast::Expr::Call(call) => call, expr => syntax_error!( "expected call expression in call block, got {}", expr.description() ), }; Ok(ast::Do { call }) } fn subparse( &mut self, end_check: &dyn Fn(&Token) -> bool, ) -> Result>, Error> { let mut rv = Vec::with_capacity(16); while let Some((token, span)) = ok!(self.stream.next()) { match token { Token::TemplateData(raw) => { rv.push(ast::Stmt::EmitRaw(Spanned::new(ast::EmitRaw { raw }, span))) } Token::VariableStart => { let expr = ok!(self.parse_expr()); rv.push(ast::Stmt::EmitExpr(Spanned::new( ast::EmitExpr { expr }, self.stream.expand_span(span), ))); expect_token!(self, Token::VariableEnd, "end of variable block"); } Token::BlockStart => { let (tok, _span) = match ok!(self.stream.current()) { Some(rv) => rv, None => syntax_error!("unexpected end of input, expected keyword"), }; if end_check(tok) { return Ok(rv); } rv.push(ok!(self.parse_stmt())); expect_token!(self, Token::BlockEnd, "end of block"); } _ => unreachable!("lexer produced garbage"), } } Ok(rv) } #[inline] fn attach_location_to_error(&mut self, mut err: Error) -> Error { if err.line().is_none() { err.set_filename_and_span(self.filename(), self.stream.last_span()) } err } } /// Parses a template. pub fn parse<'source>( source: &'source str, filename: &'source str, syntax_config: SyntaxConfig, whitespace_config: WhitespaceConfig, ) -> Result, Error> { Parser::new(source, filename, false, syntax_config, whitespace_config).parse() } /// Parses a standalone expression. pub fn parse_expr(source: &str) -> Result, Error> { Parser::new( source, "", true, Default::default(), Default::default(), ) .parse_standalone_expr() }