use crate::ast::{self, Delimiter, Kind};
use crate::compile::{ErrorKind, Result};

use super::{Checkpoint, Parser};

use Kind::*;

trait ExprCx {
    fn recover(&self, p: &mut Parser<'_>) -> Result<()>;
}

/// Simple context which performs no recovery.
struct ErrorCx;

impl ExprCx for ErrorCx {
    fn recover(&self, p: &mut Parser<'_>) -> Result<()> {
        Err(p.expected_at(0, Kind::Expr)?)
    }
}

#[derive(Default)]
struct Modifiers {
    is_pub: bool,
    is_const: bool,
    is_async: bool,
}

#[derive(Debug, Clone, Copy)]
enum Brace {
    Yes,
    No,
}

#[derive(Debug, Clone, Copy)]
enum Binary {
    Yes,
    No,
}

#[derive(Debug, Clone, Copy)]
enum Range {
    Yes,
    No,
}

macro_rules! __ws {
    () => {
        $crate::ast::Kind::Whitespace
            | $crate::ast::Kind::Comment
            | $crate::ast::Kind::MultilineComment(..)
    };
}

macro_rules! __object_key {
    () => {
        K![ident] | K![str]
    };
}

pub(crate) use __object_key as object_key;
pub(crate) use __ws as ws;

macro_rules! lit {
    () => {
        K![true] | K![false] | K![number] | K![str] | K![bytestr] | K![char] | K![byte]
    };
}

macro_rules! path_component {
    () => {
        K![::] | K![ident] | K![super] | K![crate] | K![self] | K![Self]
    }
}

pub(super) fn root(p: &mut Parser<'_>) -> Result<()> {
    p.open(Root)?;

    while !p.is_eof()? {
        stmt(p)?;
    }

    p.flush_ws()?;
    p.close()?;
    Ok(())
}

/// Parse comma-separated expressions.
pub(super) fn exprs(p: &mut Parser<'_>, separator: Kind) -> Result<()> {
    p.open(Root)?;

    while !p.is_eof()? {
        expr(p)?;
        p.bump_while(separator)?;
    }

    p.flush_ws()?;
    p.close()?;
    Ok(())
}

/// Parse comma-separated expressions.
pub(super) fn format(p: &mut Parser<'_>) -> Result<()> {
    p.open(Root)?;
    expr(p)?;

    while !p.is_eof()? {
        p.bump()?;
    }

    p.flush_ws()?;
    p.close()?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn stmt(p: &mut Parser<'_>) -> Result<()> {
    struct StmtCx;

    #[tracing::instrument(skip(p))]
    fn is_stmt_recovering(p: &mut Parser<'_>) -> Result<bool> {
        let is = match p.peek()? {
            Eof => false,
            K![;] => false,
            K![let] => false,
            K![use] => false,
            K![struct] => false,
            K![enum] => false,
            K![fn] => false,
            K![impl] => false,
            K![mod] => false,
            K![ident] => false,
            K![pub] => false,
            K![const] => false,
            K![async] => false,
            K![#] => !matches!(p.nth(1)?, K!['['] | K![!]),
            _ => true,
        };

        Ok(is)
    }

    impl ExprCx for StmtCx {
        fn recover(&self, p: &mut Parser<'_>) -> Result<()> {
            while is_stmt_recovering(p)? {
                p.bump()?;
            }

            Ok(())
        }
    }

    inner_attributes(p)?;

    let c = p.checkpoint()?;
    attributes(p)?;

    let inner_c = p.checkpoint()?;
    let m = modifiers(p)?;

    let cx = StmtCx;

    let kind = match p.peek()? {
        K![let] => {
            local(p, &cx)?;
            Local
        }
        K![use] => {
            item_use(p)?;
            p.close_at(&inner_c, ItemUse)?;
            Item
        }
        K![struct] => {
            item_struct(p)?;
            p.close_at(&inner_c, ItemStruct)?;
            Item
        }
        K![enum] => {
            item_enum(p)?;
            p.close_at(&inner_c, ItemEnum)?;
            Item
        }
        K![fn] => {
            item_fn(p)?;
            p.close_at(&inner_c, ItemFn)?;
            Item
        }
        K![impl] => {
            item_impl(p)?;
            p.close_at(&inner_c, ItemImpl)?;
            Item
        }
        K![mod] => {
            if item_mod(p)? {
                p.close_at(&inner_c, ItemMod)?;
            } else {
                p.close_at(&inner_c, ItemFileMod)?;
            }

            Item
        }
        K![ident] if m.is_const => {
            item_const(p)?;
            p.close_at(&inner_c, ItemConst)?;
            Item
        }
        _ => {
            labels(p)?;

            if matches!(
                outer_expr_with(p, Brace::Yes, Range::Yes, Binary::Yes, &cx)?,
                Error
            ) {
                Error
            } else {
                Expr
            }
        }
    };

    p.close_at(&c, kind)?;
    p.bump_while(K![;])?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn item_const(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;
    p.bump_if(K![=])?;
    expr(p)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn inner_attributes(p: &mut Parser<'_>) -> Result<()> {
    while matches!((p.peek()?, p.glued(1)?), (K![#], K![!])) {
        let c = p.checkpoint()?;

        p.bump()?;
        p.bump_if(K![!])?;

        if p.bump_if(K!['['])? {
            token_stream(p, brackets)?;
            p.bump()?;
        }

        p.close_at(&c, InnerAttribute)?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn attributes(p: &mut Parser<'_>) -> Result<()> {
    while matches!((p.peek()?, p.glued(1)?), (K![#], K!['['])) {
        let c = p.checkpoint()?;

        p.bump()?;

        if p.bump_if(K!['['])? {
            token_stream(p, brackets)?;
            p.bump()?;
        }

        p.close_at(&c, Attribute)?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn modifiers(p: &mut Parser<'_>) -> Result<Modifiers> {
    let c = p.checkpoint()?;

    let mut mods = Modifiers::default();
    let mut has_mods = false;

    loop {
        match p.peek()? {
            K![pub] => {
                mods.is_pub = true;
                p.bump()?;

                if p.peek()? == K!['('] {
                    let c = p.checkpoint()?;
                    p.bump()?;

                    let kind = match p.peek()? {
                        K![super] => {
                            p.bump()?;
                            ModifierSuper
                        }
                        K![self] => {
                            p.bump()?;
                            ModifierSelf
                        }
                        K![crate] => {
                            p.bump()?;
                            ModifierCrate
                        }
                        K![in] => {
                            p.bump()?;
                            path(p)?;
                            ModifierIn
                        }
                        _ => Error,
                    };

                    p.bump_if(K![')'])?;
                    p.close_at(&c, kind)?;
                }
            }
            K![const] => {
                mods.is_const = true;
                p.bump()?;
            }
            K![async] => {
                mods.is_async = true;
                p.bump()?;
            }
            K![move] => {
                p.bump()?;
            }
            _ => {
                break;
            }
        }

        has_mods = true;
    }

    if has_mods {
        p.close_at(&c, Modifiers)?;
    }

    Ok(mods)
}

#[tracing::instrument(skip_all)]
fn local(p: &mut Parser<'_>, cx: &dyn ExprCx) -> Result<()> {
    p.bump()?;
    pat(p)?;
    p.bump_if(K![=])?;
    expr_with(p, Brace::Yes, Range::Yes, Binary::Yes, cx)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn item_struct(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    if matches!(p.peek()?, K![ident]) {
        p.bump()?;
    }

    match p.peek()? {
        K!['{'] => {
            struct_body(p)?;
        }
        K!['('] => {
            tuple_body(p)?;
        }
        _ => {
            empty_body(p)?;
        }
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn item_use(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    loop {
        match p.peek()? {
            path_component!() | K![*] => {
                p.bump()?;
            }
            K!['{'] => {
                let c = p.checkpoint()?;
                p.bump()?;

                while matches!(p.peek()?, path_component!() | K!['{'] | K![*]) {
                    let c = p.checkpoint()?;
                    item_use(p)?;
                    p.close_at(&c, ItemUsePath)?;
                    p.bump_while(K![,])?;
                }

                p.bump_if(K!['}'])?;
                p.close_at(&c, ItemUseGroup)?;
            }
            K![as] => {
                p.bump()?;
                p.bump_if_matches(|k| matches!(k, K![ident]))?;
            }
            _ => break,
        }
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn item_enum(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    if matches!(p.peek()?, K![ident]) {
        p.bump()?;
    }

    if p.bump_if(K!['{'])? {
        while matches!(p.peek()?, K![ident]) {
            let variant = p.checkpoint()?;

            p.bump()?;

            match p.peek()? {
                K!['{'] => {
                    struct_body(p)?;
                }
                K!['('] => {
                    tuple_body(p)?;
                }
                _ => {
                    p.empty(EmptyBody)?;
                }
            }

            p.close_at(&variant, Variant)?;
            p.bump_while(K![,])?;
        }

        p.bump_if(K!['}'])?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn empty_body(p: &mut Parser<'_>) -> Result<()> {
    let c = p.checkpoint()?;
    p.close_at(&c, EmptyBody)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn struct_body(p: &mut Parser<'_>) -> Result<()> {
    let c = p.checkpoint()?;

    p.bump()?;

    while matches!(p.peek()?, K![ident]) {
        let c = p.checkpoint()?;
        p.bump()?;
        p.close_at(&c, Field)?;
        p.bump_while(K![,])?;
    }

    p.bump_if(K!['}'])?;
    p.close_at(&c, StructBody)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn tuple_body(p: &mut Parser<'_>) -> Result<()> {
    let c = p.checkpoint()?;

    p.bump()?;

    while matches!(p.peek()?, K![ident]) {
        let c = p.checkpoint()?;
        p.bump()?;
        p.close_at(&c, Field)?;
        p.bump_while(K![,])?;
    }

    p.bump_if(K![')'])?;
    p.close_at(&c, TupleBody)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn item_fn(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    if matches!(p.peek()?, K![ident]) {
        p.bump()?;
    }

    if p.peek()? == K!['('] {
        let c = p.checkpoint()?;
        p.bump()?;

        p.bump_while(K![,])?;

        while is_pat(p)? {
            pat(p)?;
            p.bump_while(K![,])?;
        }

        p.bump_if(K![')'])?;
        p.close_at(&c, FnArgs)?;
    }

    if p.peek()? == K!['{'] {
        block(p)?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn item_impl(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    if matches!(p.peek()?, path_component!()) {
        path(p)?;
    }

    block(p)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn item_mod(p: &mut Parser<'_>) -> Result<bool> {
    p.bump()?;

    if matches!(p.peek()?, K![ident]) {
        p.bump()?;
    }

    if matches!(p.peek()?, K!['{']) {
        block(p)?;
        Ok(true)
    } else {
        Ok(false)
    }
}

#[tracing::instrument(skip_all)]
fn is_pat(p: &mut Parser<'_>) -> Result<bool> {
    Ok(match p.peek()? {
        path_component!() => true,
        lit!() => true,
        K![_] => true,
        K!['('] => true,
        K!['['] => true,
        K![-] => matches!(p.glued(1)?, K![number]),
        K![#] => matches!(p.glued(1)?, K!['{']),
        _ => false,
    })
}

#[tracing::instrument(skip_all)]
fn pat(p: &mut Parser<'_>) -> Result<()> {
    let c = p.checkpoint()?;
    attributes(p)?;

    match p.peek()? {
        lit!() => {
            let c = p.checkpoint()?;
            p.bump()?;
            p.close_at(&c, Lit)?;
        }
        K![-] => {
            let c = p.checkpoint()?;
            p.bump()?;

            if matches!(p.peek()?, K![number]) {
                p.bump()?;
            }

            p.close_at(&c, Lit)?;
        }
        K![_] => {
            let c = p.checkpoint()?;
            p.bump()?;
            p.close_at(&c, PatIgnore)?;
        }
        path_component!() => {
            let c = p.checkpoint()?;
            path(p)?;

            match p.peek()? {
                K!['{'] => {
                    pat_object(p)?;
                    p.close_at(&c, PatObject)?;
                }
                K!['('] => {
                    pat_parens(p, K![')'])?;
                    p.close_at(&c, PatTuple)?;
                }
                _ => {}
            }
        }
        K!['['] => {
            let c = p.checkpoint()?;
            pat_parens(p, K![']'])?;
            p.close_at(&c, PatArray)?;
        }
        K!['('] => {
            let c = p.checkpoint()?;
            pat_parens(p, K![')'])?;
            p.close_at(&c, PatTuple)?;
        }
        K![#] if matches!(p.glued(1)?, K!['{']) => {
            let c = p.checkpoint()?;
            p.bump()?;
            p.close_at(&c, AnonymousObjectKey)?;
            pat_object(p)?;
            p.close_at(&c, PatObject)?;
        }
        _ => {
            let c = p.checkpoint()?;
            p.close_at(&c, Error)?;
        }
    }

    p.close_at(&c, Pat)?;
    Ok(())
}

fn is_expr(p: &mut Parser<'_>) -> Result<bool> {
    is_expr_with(p, Brace::Yes, Range::Yes)
}

#[tracing::instrument(skip(p))]
fn is_expr_with(p: &mut Parser<'_>, brace: Brace, range: Range) -> Result<bool> {
    Ok(match p.peek()? {
        path_component!() => true,
        K![async] => true,
        K![break] => true,
        K![continue] => true,
        K![for] => true,
        K![if] => true,
        K![let] => true,
        K![loop] => true,
        K![match] => true,
        K![return] => true,
        K![select] => true,
        K![while] => true,
        lit!() => true,
        K![||] | K![|] => true,
        K![#] => true,
        K![-] => true,
        K![!] => true,
        K![&] => true,
        K![*] => true,
        K!['label] => matches!(p.glued(1)?, K![:]),
        Kind::Open(Delimiter::Empty) => true,
        K!['('] => true,
        K!['['] => true,
        K!['{'] => matches!(brace, Brace::Yes),
        K![..] => matches!(range, Range::Yes),
        K![..=] => matches!(range, Range::Yes),
        TemplateString => true,
        _ => false,
    })
}

#[tracing::instrument(skip_all)]
fn expr(p: &mut Parser<'_>) -> Result<()> {
    let c = p.checkpoint()?;
    attributes(p)?;
    modifiers(p)?;
    labels(p)?;
    let cx = ErrorCx;
    outer_expr_with(p, Brace::Yes, Range::Yes, Binary::Yes, &cx)?;
    p.close_at(&c, Expr)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_with(
    p: &mut Parser<'_>,
    brace: Brace,
    range: Range,
    binary: Binary,
    cx: &dyn ExprCx,
) -> Result<()> {
    let c = p.checkpoint()?;
    attributes(p)?;
    modifiers(p)?;
    labels(p)?;
    outer_expr_with(p, brace, range, binary, cx)?;
    p.close_at(&c, Expr)?;
    Ok(())
}

fn is_range(kind: Kind) -> bool {
    match kind {
        ExprRangeTo => true,
        ExprRangeToInclusive => true,
        ExprRangeFull => true,
        ExprRange => true,
        ExprRangeInclusive => true,
        ExprRangeFrom => true,
        _ => false,
    }
}

fn outer_expr_with(
    p: &mut Parser<'_>,
    brace: Brace,
    range: Range,
    binary: Binary,
    cx: &dyn ExprCx,
) -> Result<Kind> {
    let c = p.checkpoint()?;
    let mut kind = expr_primary(p, brace, range, cx)?;

    if is_range(kind) {
        return Ok(kind);
    }

    kind = expr_chain(p, &c, kind)?;

    if p.peek()? == K![=] {
        p.close_at(&c, Expr)?;
        p.bump()?;
        expr_with(p, brace, Range::Yes, Binary::Yes, cx)?;
        p.close_at(&c, ExprAssign)?;
        return Ok(ExprAssign);
    }

    if matches!(binary, Binary::Yes) {
        let slice = p.array::<2>()?;
        let lookahead = ast::BinOp::from_slice(&slice);

        kind = if expr_binary(p, lookahead, 0, brace, cx)? {
            p.close_at(&c, ExprBinary)?;
            ExprBinary
        } else {
            kind
        };
    }

    if matches!(range, Range::Yes) {
        kind = expr_range(p, &c, kind, brace)?;
    }

    Ok(kind)
}

fn labels(p: &mut Parser<'_>) -> Result<()> {
    while matches!(p.peek()?, K!['label]) {
        p.bump()?;
        p.bump_while(K![:])?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_primary(p: &mut Parser<'_>, brace: Brace, range: Range, cx: &dyn ExprCx) -> Result<Kind> {
    let c = p.checkpoint()?;

    let kind = match p.peek()? {
        lit!() => {
            p.bump()?;
            Lit
        }
        path_component!() => {
            path(p)?;

            match p.peek()? {
                K!['{'] if matches!(brace, Brace::Yes) => {
                    expr_object(p)?;
                    ExprObject
                }
                K![!] if matches!(p.glued(1)?, K!['(']) => {
                    p.bump()?;
                    p.bump()?;
                    token_stream(p, parens)?;
                    p.bump()?;
                    ExprMacroCall
                }
                K![!] if matches!(p.glued(1)?, K!['{']) => {
                    p.bump()?;
                    p.bump()?;
                    token_stream(p, braces)?;
                    p.bump()?;
                    ExprMacroCall
                }
                _ => return Ok(Path),
            }
        }
        Kind::Open(Delimiter::Empty) => empty_group(p)?,
        K!['('] => expr_tuple_or_group(p)?,
        K!['['] => {
            parenthesized(p, is_expr, expr, K![']'])?;
            ExprArray
        }
        K!['{'] => {
            block_with(p)?;
            Block
        }
        TemplateString => {
            p.bump()?;
            TemplateString
        }
        K![||] => {
            p.push(ClosureArguments)?;
            expr_with(p, brace, range, Binary::Yes, cx)?;
            ExprClosure
        }
        K![|] => {
            let args = p.checkpoint()?;
            parenthesized(p, is_pat, pat, K![|])?;
            p.close_at(&args, ClosureArguments)?;

            expr_with(p, brace, range, Binary::Yes, cx)?;
            ExprClosure
        }
        K![-] if matches!(p.glued(1)?, K![number]) => {
            p.bump()?;
            p.bump()?;
            Lit
        }
        K![!] | K![-] | K![&] | K![*] => {
            p.bump()?;
            outer_expr_with(p, brace, range, Binary::No, cx)?;
            ExprUnary
        }
        K![if] => {
            expr_if(p)?;
            ExprIf
        }
        K![while] => {
            expr_while(p)?;
            ExprWhile
        }
        K![loop] => {
            expr_loop(p)?;
            ExprLoop
        }
        K![break] => {
            p.bump()?;

            labels(p)?;

            if is_expr_with(p, brace, range)? {
                let cx = ErrorCx;
                expr_with(p, brace, range, Binary::Yes, &cx)?;
            }

            ExprBreak
        }
        K![continue] => {
            p.bump()?;
            labels(p)?;
            ExprContinue
        }
        K![return] => {
            p.bump()?;

            if is_expr_with(p, brace, range)? {
                let cx = ErrorCx;
                expr_with(p, brace, range, Binary::Yes, &cx)?;
            }

            ExprReturn
        }
        K![yield] => {
            p.bump()?;

            if is_expr_with(p, brace, range)? {
                let cx = ErrorCx;
                expr_with(p, brace, range, Binary::Yes, &cx)?;
            }

            ExprYield
        }
        K![for] => {
            expr_for(p)?;
            ExprFor
        }
        K![select] => {
            expr_select(p)?;
            ExprSelect
        }
        K![match] => {
            expr_match(p)?;
            ExprMatch
        }
        K![..] if matches!(range, Range::Yes) => {
            p.bump()?;

            if is_expr_with(p, brace, Range::No)? {
                let cx = ErrorCx;
                outer_expr_with(p, brace, Range::No, Binary::Yes, &cx)?;
                ExprRangeTo
            } else {
                ExprRangeFull
            }
        }
        K![..=] if matches!(range, Range::Yes) => {
            p.bump()?;
            let cx = ErrorCx;
            outer_expr_with(p, brace, Range::No, Binary::Yes, &cx)?;
            ExprRangeToInclusive
        }
        K![#] if matches!(p.glued(1)?, K!['{']) => {
            let key = p.checkpoint()?;
            p.bump()?;
            p.close_at(&key, AnonymousObjectKey)?;
            expr_object(p)?;
            ExprObject
        }
        _ => {
            cx.recover(p)?;
            Error
        }
    };

    p.close_at(&c, kind)?;
    Ok(kind)
}

fn kind_is_callable(kind: Kind) -> bool {
    match kind {
        ExprWhile => false,
        ExprLoop => true,
        ExprFor => false,
        ExprIf => true,
        ExprMatch => true,
        ExprSelect => true,
        _ => true,
    }
}

#[tracing::instrument(skip_all)]
fn expr_chain(p: &mut Parser<'_>, c: &Checkpoint, mut kind: Kind) -> Result<Kind> {
    let mut before = p.checkpoint()?;
    let mut has_chain = false;

    while !p.is_eof()? {
        let is_callable = kind_is_callable(kind);

        let k = match p.peek()? {
            K!['['] if is_callable => {
                p.bump()?;
                expr(p)?;
                p.bump_if(K![']'])?;
                ExprIndex
            }
            // Chained function call.
            K!['('] if is_callable => {
                parenthesized(p, is_expr, expr, K![')'])?;
                ExprCall
            }
            K![?] => {
                p.bump()?;
                ExprTry
            }
            K![.] => {
                p.bump()?;

                match p.peek()? {
                    // <expr>.await
                    K![await] => {
                        p.bump()?;
                        ExprAwait
                    }
                    // <expr>.field
                    path_component!() => {
                        path(p)?;
                        ExprField
                    }
                    // <expr>.<number>
                    K![number] => {
                        p.bump()?;
                        ExprField
                    }
                    _ => Error,
                }
            }
            _ => {
                break;
            }
        };

        p.close_at(&before, k)?;
        kind = k;
        before = p.checkpoint()?;
        has_chain = true;
    }

    if has_chain {
        p.close_at(c, ExprChain)?;
    }

    Ok(kind)
}

#[tracing::instrument(skip_all)]
fn empty_group(p: &mut Parser<'_>) -> Result<Kind> {
    p.bump()?;
    expr(p)?;
    p.bump_if(Kind::Close(Delimiter::Empty))?;
    Ok(ExprEmptyGroup)
}

#[tracing::instrument(skip_all)]
fn expr_tuple_or_group(p: &mut Parser<'_>) -> Result<Kind> {
    p.bump()?;

    let mut is_tuple = false;

    while is_expr(p)? {
        expr(p)?;

        if p.bump_while(K![,])? {
            is_tuple = true;
        }
    }

    p.bump_if(K![')'])?;
    let kind = if is_tuple { ExprTuple } else { ExprGroup };
    Ok(kind)
}

#[tracing::instrument(skip_all)]
fn expr_object(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    while matches!(p.peek()?, object_key!()) {
        p.bump()?;

        if p.bump_if(K![:])? {
            expr(p)?;
        }

        p.bump_while(K![,])?;
    }

    p.bump_if(K!['}'])?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn pat_object(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    loop {
        match p.peek()? {
            K![..] => {
                p.bump()?;
            }
            object_key!() => {
                p.bump()?;

                if p.bump_if(K![:])? {
                    pat(p)?;
                }

                p.bump_while(K![,])?;
            }
            _ => {
                break;
            }
        }
    }

    p.bump_if(K!['}'])?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_if(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;
    condition(p)?;
    block(p)?;

    while p.peek()? == K![else] {
        let c = p.checkpoint()?;
        p.bump()?;

        let else_if = if p.bump_if(K![if])? {
            condition(p)?;
            true
        } else {
            false
        };

        block(p)?;

        if else_if {
            p.close_at(&c, ExprElseIf)?;
        } else {
            p.close_at(&c, ExprElse)?;
        }
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_while(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;
    condition(p)?;
    block(p)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_loop(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;
    block(p)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_for(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;
    pat(p)?;

    if p.bump_if(K![in])? {
        let cx = ErrorCx;
        expr_with(p, Brace::No, Range::Yes, Binary::Yes, &cx)?;
    }

    block(p)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_match(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    let cx = ErrorCx;
    expr_with(p, Brace::No, Range::Yes, Binary::Yes, &cx)?;

    if matches!(p.peek()?, K!['{']) {
        p.bump()?;

        while is_pat(p)? {
            let c = p.checkpoint()?;
            pat(p)?;

            if p.bump_if(K![if])? {
                expr(p)?;
            }

            if p.bump_if(K![=>])? {
                expr(p)?;
            }

            p.close_at(&c, ExprMatchArm)?;
            p.bump_while(K![,])?;
        }

        p.bump_if(K!['}'])?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_select(p: &mut Parser<'_>) -> Result<()> {
    p.bump()?;

    if matches!(p.peek()?, K!['{']) {
        p.bump()?;

        while is_pat(p)? || p.peek()? == K![default] {
            let c = p.checkpoint()?;

            match p.peek()? {
                K![default] => {
                    p.bump()?;
                }
                _ => {
                    pat(p)?;

                    if p.bump_if(K![=])? {
                        expr(p)?;
                    }
                }
            }

            if p.bump_if(K![=>])? {
                expr(p)?;
            }

            p.close_at(&c, ExprSelectArm)?;
            p.bump_while(K![,])?;
        }

        p.bump_if(K!['}'])?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn condition(p: &mut Parser<'_>) -> Result<()> {
    if p.peek()? == K![let] {
        let c = p.checkpoint()?;
        p.bump()?;
        pat(p)?;

        if p.peek()? == K![=] {
            p.bump()?;
            let cx = ErrorCx;
            expr_with(p, Brace::No, Range::Yes, Binary::Yes, &cx)?;
        }

        p.close_at(&c, Condition)?;
    } else {
        let cx = ErrorCx;
        expr_with(p, Brace::No, Range::Yes, Binary::Yes, &cx)?;
    }

    Ok(())
}

#[tracing::instrument(skip_all)]
fn path(p: &mut Parser<'_>) -> Result<()> {
    let c = p.checkpoint()?;

    while matches!(p.peek()?, path_component!()) {
        // Parse a generic path if we are in a context supporting binary
        // expressions, or if we just parsed the prefix `::` of the turbofish
        // syntax.
        let has_generics = matches!(p.peek()?, K![::]);
        p.bump()?;

        // We can't parse generics in binary expressions, since they would be
        // ambiguous with expressions such as `self::FOO< 10`.
        while has_generics && matches!(p.peek()?, K![<]) {
            let c = p.checkpoint()?;
            p.bump()?;

            while matches!(p.peek()?, path_component!()) {
                // Inner paths are unambiguous.
                path(p)?;
                p.bump_while(K![,])?;
            }

            p.bump_if(K![>])?;
            p.close_at(&c, PathGenerics)?;
        }
    }

    p.close_at(&c, Path)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn expr_binary(
    p: &mut Parser<'_>,
    mut lookahead: Option<ast::BinOp>,
    min_precedence: usize,
    brace: Brace,
    cx: &dyn ExprCx,
) -> Result<bool> {
    let mut has_any = false;

    while let Some(op) = lookahead.take() {
        let precedence = op.precedence();

        if precedence < min_precedence {
            break;
        }

        let op_c = p.checkpoint()?;
        op.advance(p)?;
        p.close_at(&op_c, ExprOperator)?;

        let c = p.checkpoint()?;
        outer_expr_with(p, brace, Range::No, Binary::No, cx)?;

        has_any = true;

        let slice = p.array::<2>()?;
        lookahead = ast::BinOp::from_slice(&slice);

        while let Some(next) = lookahead {
            match (precedence, next.precedence()) {
                (lh, rh) if lh < rh => {
                    // Higher precedence elements require us to recurse.
                    if expr_binary(p, Some(next), lh + 1, brace, cx)? {
                        p.close_at(&c, ExprBinary)?;
                    }

                    let slice = p.array::<2>()?;
                    lookahead = ast::BinOp::from_slice(&slice);
                    continue;
                }
                (lh, rh) if lh == rh => {
                    if !next.is_assoc() {
                        return Err(p.error(c.span(), ErrorKind::PrecedenceGroupRequired)?);
                    }
                }
                _ => {}
            };

            break;
        }
    }

    Ok(has_any)
}

#[tracing::instrument(skip_all)]
fn expr_range(p: &mut Parser<'_>, c: &Checkpoint, kind: Kind, brace: Brace) -> Result<Kind> {
    let kind = match p.peek()? {
        K![..] => {
            p.bump()?;

            if is_expr_with(p, brace, Range::No)? {
                let cx = ErrorCx;
                outer_expr_with(p, brace, Range::No, Binary::Yes, &cx)?;
                ExprRange
            } else {
                ExprRangeFrom
            }
        }
        K![..=] => {
            p.bump()?;

            if is_expr_with(p, brace, Range::No)? {
                let cx = ErrorCx;
                outer_expr_with(p, brace, Range::No, Binary::Yes, &cx)?;
                ExprRangeInclusive
            } else {
                Error
            }
        }
        _ => {
            return Ok(kind);
        }
    };

    p.close_at(c, kind)?;
    Ok(kind)
}
#[tracing::instrument(skip_all)]
fn block(p: &mut Parser<'_>) -> Result<()> {
    let c = p.checkpoint()?;
    block_with(p)?;
    p.close_at(&c, Block)?;
    Ok(())
}

#[tracing::instrument(skip_all)]
fn block_with(p: &mut Parser<'_>) -> Result<()> {
    if p.bump_if(K!['{'])? {
        let c = p.checkpoint()?;

        while !matches!(p.peek()?, K!['}'] | Eof) {
            stmt(p)?;
        }

        p.close_at(&c, BlockBody)?;
    }

    p.bump_if(K!['}'])?;
    Ok(())
}

#[tracing::instrument(skip(p))]
fn pat_parens(p: &mut Parser, end: Kind) -> Result<()> {
    p.bump()?;

    while is_pat(p)? || p.peek()? == K![..] {
        match p.peek()? {
            K![..] => {
                p.bump()?;
            }
            _ => {
                pat(p)?;
            }
        }

        p.bump_while(K![,])?;
    }

    p.bump_if(end)?;
    Ok(())
}

#[tracing::instrument(skip(p, is, parser))]
fn parenthesized(
    p: &mut Parser,
    is: fn(&mut Parser<'_>) -> Result<bool>,
    parser: fn(&mut Parser<'_>) -> Result<()>,
    end: Kind,
) -> Result<()> {
    p.bump()?;

    while is(p)? {
        parser(p)?;
        p.bump_while(K![,])?;
    }

    p.bump_if(end)?;
    Ok(())
}

fn brackets(kind: Kind) -> i32 {
    match kind {
        K!['['] => 1,
        K![']'] => -1,
        _ => 0,
    }
}

fn parens(kind: Kind) -> i32 {
    match kind {
        K!['('] => 1,
        K![')'] => -1,
        _ => 0,
    }
}

fn braces(kind: Kind) -> i32 {
    match kind {
        K!['{'] => 1,
        K!['}'] => -1,
        _ => 0,
    }
}

/// Consumes a token stream out of balanced brackets.
#[tracing::instrument(skip_all)]
fn token_stream(p: &mut Parser<'_>, matcher: fn(Kind) -> i32) -> Result<()> {
    let c = p.checkpoint()?;

    let mut level = 1u32;

    while level > 0 {
        let m = matcher(p.peek()?);

        level = level.wrapping_add_signed(m);

        if level == 0 {
            break;
        }

        p.bump()?;
    }

    p.close_at(&c, TokenStream)?;
    Ok(())
}
