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<D: fmt::Display>(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<Option<(Token<'a>, 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<Option<(Token<'a>, 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<Option<(&Token<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Stmt<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<Vec<ast::CallArg<'a>>, 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<ast::Expr<'a>, Error> {
        with_recursion_guard!(self, self.parse_primary_impl())
    }

    fn parse_primary_impl(&mut self) -> Result<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, Error> {
        with_recursion_guard!(self, self.parse_ifexpr())
    }

    fn parse_expr_noif(&mut self) -> Result<ast::Expr<'a>, Error> {
        self.parse_or()
    }

    fn parse_stmt(&mut self) -> Result<ast::Stmt<'a>, Error> {
        with_recursion_guard!(self, self.parse_stmt_unprotected())
    }

    fn parse_stmt_unprotected(&mut self) -> Result<ast::Stmt<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::ForLoop<'a>, 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<ast::IfCond<'a>, 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<ast::WithBlock<'a>, 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<SetParseResult<'a>, 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<ast::Block<'a>, 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<ast::AutoEscape<'a>, 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<ast::Expr<'a>, 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<ast::FilterBlock<'a>, 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<ast::Extends<'a>, Error> {
        let name = ok!(self.parse_expr());
        Ok(ast::Extends { name })
    }

    #[cfg(feature = "multi_template")]
    fn parse_include(&mut self) -> Result<ast::Include<'a>, 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<ast::Import<'a>, 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<ast::FromImport<'a>, 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<bool, Error> {
        // 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<ast::Expr<'a>>,
        defaults: &mut Vec<ast::Expr<'a>>,
    ) -> 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<ast::Expr<'a>>,
        defaults: Vec<ast::Expr<'a>>,
        name: Option<&'a str>,
    ) -> Result<ast::Macro<'a>, 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<ast::Macro<'a>, 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<ast::CallBlock<'a>, 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<ast::Do<'a>, 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<Vec<ast::Stmt<'a>>, 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<ast::Stmt<'source>, Error> {
    Parser::new(source, filename, false, syntax_config, whitespace_config).parse()
}

/// Parses a standalone expression.
pub fn parse_expr(source: &str) -> Result<ast::Expr<'_>, Error> {
    Parser::new(
        source,
        "<expression>",
        true,
        Default::default(),
        Default::default(),
    )
    .parse_standalone_expr()
}
