/*
 * Copyright (c) 2015 Andrew Kelley
 *
 * This file is part of zig, which is MIT licensed.
 * See http://opensource.org/licenses/MIT
 */

#include "parser.hpp"
#include "errmsg.hpp"
#include "analyze.hpp"

#include <stdarg.h>
#include <stdio.h>
#include <limits.h>
#include <errno.h>

struct ParseContext {
    Buf *buf;
    size_t current_token;
    ZigList<Token> *tokens;
    ZigType *owner;
    ErrColor err_color;
};

struct PtrPayload {
    Token *asterisk;
    Token *payload;
};

struct PtrIndexPayload {
    Token *asterisk;
    Token *payload;
    Token *index;
};

static AstNode *ast_parse_root(ParseContext *pc);
static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc);
static AstNode *ast_parse_test_decl(ParseContext *pc);
static AstNode *ast_parse_top_level_comptime(ParseContext *pc);
static AstNode *ast_parse_top_level_decl(ParseContext *pc, VisibMod visib_mod);
static AstNode *ast_parse_fn_proto(ParseContext *pc);
static AstNode *ast_parse_var_decl(ParseContext *pc);
static AstNode *ast_parse_container_field(ParseContext *pc);
static AstNode *ast_parse_statement(ParseContext *pc);
static AstNode *ast_parse_if_statement(ParseContext *pc);
static AstNode *ast_parse_labeled_statement(ParseContext *pc);
static AstNode *ast_parse_loop_statement(ParseContext *pc);
static AstNode *ast_parse_for_statement(ParseContext *pc);
static AstNode *ast_parse_while_statement(ParseContext *pc);
static AstNode *ast_parse_block_expr_statement(ParseContext *pc);
static AstNode *ast_parse_block_expr(ParseContext *pc);
static AstNode *ast_parse_assign_expr(ParseContext *pc);
static AstNode *ast_parse_expr(ParseContext *pc);
static AstNode *ast_parse_bool_or_expr(ParseContext *pc);
static AstNode *ast_parse_bool_and_expr(ParseContext *pc);
static AstNode *ast_parse_compare_expr(ParseContext *pc);
static AstNode *ast_parse_bitwise_expr(ParseContext *pc);
static AstNode *ast_parse_bit_shit_expr(ParseContext *pc);
static AstNode *ast_parse_addition_expr(ParseContext *pc);
static AstNode *ast_parse_multiply_expr(ParseContext *pc);
static AstNode *ast_parse_prefix_expr(ParseContext *pc);
static AstNode *ast_parse_primary_expr(ParseContext *pc);
static AstNode *ast_parse_if_expr(ParseContext *pc);
static AstNode *ast_parse_block(ParseContext *pc);
static AstNode *ast_parse_loop_expr(ParseContext *pc);
static AstNode *ast_parse_for_expr(ParseContext *pc);
static AstNode *ast_parse_while_expr(ParseContext *pc);
static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc);
static AstNode *ast_parse_init_list(ParseContext *pc);
static AstNode *ast_parse_type_expr(ParseContext *pc);
static AstNode *ast_parse_error_union_expr(ParseContext *pc);
static AstNode *ast_parse_suffix_expr(ParseContext *pc);
static AstNode *ast_parse_primary_type_expr(ParseContext *pc);
static AstNode *ast_parse_container_decl(ParseContext *pc);
static AstNode *ast_parse_error_set_decl(ParseContext *pc);
static AstNode *ast_parse_grouped_expr(ParseContext *pc);
static AstNode *ast_parse_if_type_expr(ParseContext *pc);
static AstNode *ast_parse_labeled_type_expr(ParseContext *pc);
static AstNode *ast_parse_loop_type_expr(ParseContext *pc);
static AstNode *ast_parse_for_type_expr(ParseContext *pc);
static AstNode *ast_parse_while_type_expr(ParseContext *pc);
static AstNode *ast_parse_switch_expr(ParseContext *pc);
static AstNode *ast_parse_asm_expr(ParseContext *pc);
static AstNode *ast_parse_enum_lit(ParseContext *pc);
static AstNode *ast_parse_asm_output(ParseContext *pc);
static AsmOutput *ast_parse_asm_output_item(ParseContext *pc);
static AstNode *ast_parse_asm_input(ParseContext *pc);
static AsmInput *ast_parse_asm_input_item(ParseContext *pc);
static AstNode *ast_parse_asm_clobbers(ParseContext *pc);
static Token *ast_parse_break_label(ParseContext *pc);
static Token *ast_parse_block_label(ParseContext *pc);
static AstNode *ast_parse_field_init(ParseContext *pc);
static AstNode *ast_parse_while_continue_expr(ParseContext *pc);
static AstNode *ast_parse_link_section(ParseContext *pc);
static Optional<AstNodeFnProto> ast_parse_fn_cc(ParseContext *pc);
static AstNode *ast_parse_param_decl(ParseContext *pc);
static AstNode *ast_parse_param_type(ParseContext *pc);
static AstNode *ast_parse_if_prefix(ParseContext *pc);
static AstNode *ast_parse_while_prefix(ParseContext *pc);
static AstNode *ast_parse_for_prefix(ParseContext *pc);
static Token *ast_parse_payload(ParseContext *pc);
static Optional<PtrPayload> ast_parse_ptr_payload(ParseContext *pc);
static Optional<PtrIndexPayload> ast_parse_ptr_index_payload(ParseContext *pc);
static AstNode *ast_parse_switch_prong(ParseContext *pc);
static AstNode *ast_parse_switch_case(ParseContext *pc);
static AstNode *ast_parse_switch_item(ParseContext *pc);
static AstNode *ast_parse_assign_op(ParseContext *pc);
static AstNode *ast_parse_compare_op(ParseContext *pc);
static AstNode *ast_parse_bitwise_op(ParseContext *pc);
static AstNode *ast_parse_bit_shift_op(ParseContext *pc);
static AstNode *ast_parse_addition_op(ParseContext *pc);
static AstNode *ast_parse_multiply_op(ParseContext *pc);
static AstNode *ast_parse_prefix_op(ParseContext *pc);
static AstNode *ast_parse_prefix_type_op(ParseContext *pc);
static AstNode *ast_parse_suffix_op(ParseContext *pc);
static AstNode *ast_parse_async_prefix(ParseContext *pc);
static AstNode *ast_parse_fn_call_argumnets(ParseContext *pc);
static AstNode *ast_parse_array_type_start(ParseContext *pc);
static AstNode *ast_parse_ptr_type_start(ParseContext *pc);
static AstNode *ast_parse_container_decl_auto(ParseContext *pc);
static AstNode *ast_parse_container_decl_type(ParseContext *pc);
static AstNode *ast_parse_byte_align(ParseContext *pc);

ATTRIBUTE_PRINTF(3, 4)
ATTRIBUTE_NORETURN
static void ast_error(ParseContext *pc, Token *token, const char *format, ...) {
    va_list ap;
    va_start(ap, format);
    Buf *msg = buf_vprintf(format, ap);
    va_end(ap);


    ErrorMsg *err = err_msg_create_with_line(pc->owner->data.structure.root_struct->path,
            token->start_line, token->start_column,
            pc->owner->data.structure.root_struct->source_code,
            pc->owner->data.structure.root_struct->line_offsets, msg);
    err->line_start = token->start_line;
    err->column_start = token->start_column;

    print_err_msg(err, pc->err_color);
    exit(EXIT_FAILURE);
}

static Buf ast_token_str(Buf *input, Token *token) {
    Buf str = BUF_INIT;
    buf_init_from_mem(&str, buf_ptr(input) + token->start_pos, token->end_pos - token->start_pos);
    return str;
}

ATTRIBUTE_NORETURN
static void ast_invalid_token_error(ParseContext *pc, Token *token) {
    Buf token_value = ast_token_str(pc->buf, token);
    ast_error(pc, token, "invalid token: '%s'", buf_ptr(&token_value));
}

static AstNode *ast_create_node_no_line_info(ParseContext *pc, NodeType type) {
    AstNode *node = allocate<AstNode>(1);
    node->type = type;
    node->owner = pc->owner;
    return node;
}

static AstNode *ast_create_node(ParseContext *pc, NodeType type, Token *first_token) {
    assert(first_token);
    AstNode *node = ast_create_node_no_line_info(pc, type);
    node->line = first_token->start_line;
    node->column = first_token->start_column;
    return node;
}

static AstNode *ast_create_node_copy_line_info(ParseContext *pc, NodeType type, AstNode *from) {
    assert(from);
    AstNode *node = ast_create_node_no_line_info(pc, type);
    node->line = from->line;
    node->column = from->column;
    return node;
}

static Token *peek_token_i(ParseContext *pc, size_t i) {
    return &pc->tokens->at(pc->current_token + i);
}

static Token *peek_token(ParseContext *pc) {
    return peek_token_i(pc, 0);
}

static Token *eat_token(ParseContext *pc) {
    Token *res = peek_token(pc);
    pc->current_token += 1;
    return res;
}

static Token *eat_token_if(ParseContext *pc, TokenId id) {
    Token *res = peek_token(pc);
    if (res->id == id)
        return eat_token(pc);

    return nullptr;
}

static Token *expect_token(ParseContext *pc, TokenId id) {
    Token *res = eat_token(pc);
    if (res->id != id)
        ast_error(pc, res, "expected token '%s', found '%s'", token_name(id), token_name(res->id));

    return res;
}

static void put_back_token(ParseContext *pc) {
    pc->current_token -= 1;
}

static Buf *token_buf(Token *token) {
    if (token == nullptr)
        return nullptr;
    assert(token->id == TokenIdStringLiteral || token->id == TokenIdSymbol);
    return &token->data.str_lit.str;
}

static BigInt *token_bigint(Token *token) {
    assert(token->id == TokenIdIntLiteral);
    return &token->data.int_lit.bigint;
}

static AstNode *token_symbol(ParseContext *pc, Token *token) {
    assert(token->id == TokenIdSymbol);
    AstNode *res = ast_create_node(pc, NodeTypeSymbol, token);
    res->data.symbol_expr.symbol = token_buf(token);
    return res;
}

// (Rule SEP)* Rule?
template<typename T>
static ZigList<T *> ast_parse_list(ParseContext *pc, TokenId sep, T *(*parser)(ParseContext*)) {
    ZigList<T *> res = {};
    while (true) {
        T *curr = parser(pc);
        if (curr == nullptr)
            break;

        res.append(curr);
        if (eat_token_if(pc, sep) == nullptr)
            break;
    }

    return res;
}

static AstNode *ast_expect(ParseContext *pc, AstNode *(*parser)(ParseContext*)) {
    AstNode *res = parser(pc);
    if (res == nullptr)
        ast_invalid_token_error(pc, peek_token(pc));
    return res;
}

enum BinOpChain {
    BinOpChainOnce,
    BinOpChainInf,
};

// Op* Child
static AstNode *ast_parse_prefix_op_expr(
    ParseContext *pc,
    AstNode *(*op_parser)(ParseContext *),
    AstNode *(*child_parser)(ParseContext *)
) {
    AstNode *res = nullptr;
    AstNode **right = &res;
    while (true) {
        AstNode *prefix = op_parser(pc);
        if (prefix == nullptr)
            break;

        *right = prefix;
        switch (prefix->type) {
            case NodeTypePrefixOpExpr:
                right = &prefix->data.prefix_op_expr.primary_expr;
                break;
            case NodeTypeReturnExpr:
                right = &prefix->data.return_expr.expr;
                break;
            case NodeTypeAwaitExpr:
                right = &prefix->data.await_expr.expr;
                break;
            case NodeTypePromiseType:
                right = &prefix->data.promise_type.payload_type;
                break;
            case NodeTypeArrayType:
                right = &prefix->data.array_type.child_type;
                break;
            case NodeTypePointerType: {
                // We might get two pointers from *_ptr_type_start
                AstNode *child = prefix->data.pointer_type.op_expr;
                if (child == nullptr)
                    child = prefix;
                right = &child->data.pointer_type.op_expr;
                break;
            }
            default:
                zig_unreachable();
        }
    }

    // If we have already consumed a token, and determined that
    // this node is a prefix op, then we expect that the node has
    // a child.
    if (res != nullptr) {
        *right = ast_expect(pc, child_parser);
    } else {
        // Otherwise, if we didn't consume a token, then we can return
        // null, if the child expr did.
        *right = child_parser(pc);
        if (*right == nullptr)
            return nullptr;
    }

    return res;
}

// Child (Op Child)(*/?)
static AstNode *ast_parse_bin_op_expr(
    ParseContext *pc,
    BinOpChain chain,
    AstNode *(*op_parse)(ParseContext*),
    AstNode *(*child_parse)(ParseContext*)
) {
    AstNode *res = child_parse(pc);
    if (res == nullptr)
        return nullptr;

    do {
        AstNode *op = op_parse(pc);
        if (op == nullptr)
            break;

        AstNode *left = res;
        AstNode *right = ast_expect(pc, child_parse);
        res = op;
        switch (op->type) {
            case NodeTypeBinOpExpr:
                op->data.bin_op_expr.op1 = left;
                op->data.bin_op_expr.op2 = right;
                break;
            case NodeTypeUnwrapErrorExpr:
                op->data.unwrap_err_expr.op1 = left;
                op->data.unwrap_err_expr.op2 = right;
                break;
            default:
                zig_unreachable();
        }
    } while (chain == BinOpChainInf);

    return res;
}

// IfPrefix Body (KEYWORD_else Payload? Body)?
static AstNode *ast_parse_if_expr_helper(ParseContext *pc, AstNode *(*body_parser)(ParseContext*)) {
    AstNode *res = ast_parse_if_prefix(pc);
    if (res == nullptr)
        return nullptr;

    AstNode *body = ast_expect(pc, body_parser);
    Token *err_payload = nullptr;
    AstNode *else_body = nullptr;
    if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) {
        err_payload = ast_parse_payload(pc);
        else_body = ast_expect(pc, body_parser);
    }

    assert(res->type == NodeTypeIfOptional);
    if (err_payload != nullptr) {
        AstNodeTestExpr old = res->data.test_expr;
        res->type = NodeTypeIfErrorExpr;
        res->data.if_err_expr.target_node = old.target_node;
        res->data.if_err_expr.var_is_ptr = old.var_is_ptr;
        res->data.if_err_expr.var_symbol = old.var_symbol;
        res->data.if_err_expr.then_node = body;
        res->data.if_err_expr.err_symbol = token_buf(err_payload);
        res->data.if_err_expr.else_node = else_body;
        return res;
    }

    if (res->data.test_expr.var_symbol != nullptr) {
        res->data.test_expr.then_node = body;
        res->data.test_expr.else_node = else_body;
        return res;
    }

    AstNodeTestExpr old = res->data.test_expr;
    res->type = NodeTypeIfBoolExpr;
    res->data.if_bool_expr.condition = old.target_node;
    res->data.if_bool_expr.then_block = body;
    res->data.if_bool_expr.else_node = else_body;
    return res;
}

// KEYWORD_inline? (ForLoop / WhileLoop)
static AstNode *ast_parse_loop_expr_helper(
    ParseContext *pc,
    AstNode *(*for_parser)(ParseContext *),
    AstNode *(*while_parser)(ParseContext *)
) {
    Token *inline_token = eat_token_if(pc, TokenIdKeywordInline);
    AstNode *for_expr = for_parser(pc);
    if (for_expr != nullptr) {
        assert(for_expr->type == NodeTypeForExpr);
        for_expr->data.for_expr.is_inline = inline_token != nullptr;
        return for_expr;
    }

    AstNode *while_expr = while_parser(pc);
    if (while_expr != nullptr) {
        assert(while_expr->type == NodeTypeWhileExpr);
        while_expr->data.while_expr.is_inline = inline_token != nullptr;
        return while_expr;
    }

    if (inline_token != nullptr)
        ast_invalid_token_error(pc, peek_token(pc));
    return nullptr;
}

// ForPrefix Body (KEYWORD_else Body)?
static AstNode *ast_parse_for_expr_helper(ParseContext *pc, AstNode *(*body_parser)(ParseContext*)) {
    AstNode *res = ast_parse_for_prefix(pc);
    if (res == nullptr)
        return nullptr;

    AstNode *body = ast_expect(pc, body_parser);
    AstNode *else_body = nullptr;
    if (eat_token_if(pc, TokenIdKeywordElse) != nullptr)
        else_body = ast_expect(pc, body_parser);

    assert(res->type == NodeTypeForExpr);
    res->data.for_expr.body = body;
    res->data.for_expr.else_node = else_body;
    return res;
}

// WhilePrefix Body (KEYWORD_else Payload? Body)?
static AstNode *ast_parse_while_expr_helper(ParseContext *pc, AstNode *(*body_parser)(ParseContext*)) {
    AstNode *res = ast_parse_while_prefix(pc);
    if (res == nullptr)
        return nullptr;

    AstNode *body = ast_expect(pc, body_parser);
    Token *err_payload = nullptr;
    AstNode *else_body = nullptr;
    if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) {
        err_payload = ast_parse_payload(pc);
        else_body = ast_expect(pc, body_parser);
    }

    assert(res->type == NodeTypeWhileExpr);
    res->data.while_expr.body = body;
    res->data.while_expr.err_symbol = token_buf(err_payload);
    res->data.while_expr.else_node = else_body;
    return res;
}

template<TokenId id, BinOpType op>
AstNode *ast_parse_bin_op_simple(ParseContext *pc) {
    Token *op_token = eat_token_if(pc, id);
    if (op_token == nullptr)
        return nullptr;

    AstNode *res = ast_create_node(pc, NodeTypeBinOpExpr, op_token);
    res->data.bin_op_expr.bin_op = op;
    return res;
}

AstNode *ast_parse(Buf *buf, ZigList<Token> *tokens, ZigType *owner, ErrColor err_color) {
    ParseContext pc = {};
    pc.err_color = err_color;
    pc.owner = owner;
    pc.buf = buf;
    pc.tokens = tokens;
    return ast_parse_root(&pc);
}

// Root <- skip ContainerMembers eof
static AstNode *ast_parse_root(ParseContext *pc) {
    Token *first = peek_token(pc);
    AstNodeContainerDecl members = ast_parse_container_members(pc);
    if (pc->current_token != pc->tokens->length - 1)
        ast_invalid_token_error(pc, peek_token(pc));

    AstNode *node = ast_create_node(pc, NodeTypeContainerDecl, first);
    node->data.container_decl.fields = members.fields;
    node->data.container_decl.decls = members.decls;
    node->data.container_decl.layout = ContainerLayoutAuto;
    node->data.container_decl.kind = ContainerKindStruct;
    node->data.container_decl.is_root = true;

    return node;
}

// ContainerMembers
//     <- TestDecl ContainerMembers
//      / TopLevelComptime ContainerMembers
//      / KEYWORD_pub? TopLevelDecl ContainerMembers
//      / KEYWORD_pub? ContainerField COMMA ContainerMembers
//      / KEYWORD_pub? ContainerField
//      /
static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc) {
    AstNodeContainerDecl res = {};
    for (;;) {
        AstNode *test_decl = ast_parse_test_decl(pc);
        if (test_decl != nullptr) {
            res.decls.append(test_decl);
            continue;
        }

        AstNode *top_level_comptime = ast_parse_top_level_comptime(pc);
        if (top_level_comptime != nullptr) {
            res.decls.append(top_level_comptime);
            continue;
        }

        Token *visib_token = eat_token_if(pc, TokenIdKeywordPub);
        VisibMod visib_mod = visib_token != nullptr ? VisibModPub : VisibModPrivate;

        AstNode *top_level_decl = ast_parse_top_level_decl(pc, visib_mod);
        if (top_level_decl != nullptr) {
            res.decls.append(top_level_decl);
            continue;
        }

        AstNode *container_field = ast_parse_container_field(pc);
        if (container_field != nullptr) {
            assert(container_field->type == NodeTypeStructField);
            container_field->data.struct_field.visib_mod = visib_mod;
            res.fields.append(container_field);
            if (eat_token_if(pc, TokenIdComma) != nullptr) {
                continue;
            } else {
                break;
            }
        }

        // We visib_token wasn't eaten, then we haven't consumed the first token in this rule yet.
        // It is therefore safe to return and let the caller continue parsing.
        if (visib_token == nullptr)
            break;

        ast_invalid_token_error(pc, peek_token(pc));
    }

    return res;
}

// TestDecl <- KEYWORD_test STRINGLITERAL Block
static AstNode *ast_parse_test_decl(ParseContext *pc) {
    Token *test = eat_token_if(pc, TokenIdKeywordTest);
    if (test == nullptr)
        return nullptr;

    Token *name = expect_token(pc, TokenIdStringLiteral);
    AstNode *block = ast_expect(pc, ast_parse_block);
    AstNode *res = ast_create_node(pc, NodeTypeTestDecl, test);
    res->data.test_decl.name = token_buf(name);
    res->data.test_decl.body = block;
    return res;
}

// TopLevelComptime <- KEYWORD_comptime BlockExpr
static AstNode *ast_parse_top_level_comptime(ParseContext *pc) {
    Token *comptime = eat_token_if(pc, TokenIdKeywordCompTime);
    if (comptime == nullptr)
        return nullptr;

    AstNode *block = ast_expect(pc, ast_parse_block_expr);
    AstNode *res = ast_create_node(pc, NodeTypeCompTime, comptime);
    res->data.comptime_expr.expr = block;
    return res;
}

// TopLevelDecl
//     <- (KEYWORD_export / KEYWORD_extern STRINGLITERAL? / KEYWORD_inline)? FnProto (SEMICOLON / Block)
//      / (KEYWORD_export / KEYWORD_extern STRINGLITERAL?)? VarDecl
//      / KEYWORD_use Expr SEMICOLON
static AstNode *ast_parse_top_level_decl(ParseContext *pc, VisibMod visib_mod) {
    Token *first = eat_token_if(pc, TokenIdKeywordExport);
    if (first == nullptr)
        first = eat_token_if(pc, TokenIdKeywordExtern);
    if (first == nullptr)
        first = eat_token_if(pc, TokenIdKeywordInline);
    if (first != nullptr) {
        Token *lib_name = nullptr;
        if (first->id == TokenIdKeywordExtern)
            lib_name = eat_token_if(pc, TokenIdStringLiteral);

        if (first->id != TokenIdKeywordInline) {
            AstNode *var_decl = ast_parse_var_decl(pc);
            if (var_decl != nullptr) {
                assert(var_decl->type == NodeTypeVariableDeclaration);
                var_decl->line = first->start_line;
                var_decl->column = first->start_column;
                var_decl->data.variable_declaration.visib_mod = visib_mod;
                var_decl->data.variable_declaration.is_extern = first->id == TokenIdKeywordExtern;
                var_decl->data.variable_declaration.is_export = first->id == TokenIdKeywordExport;
                var_decl->data.variable_declaration.lib_name = token_buf(lib_name);
                return var_decl;
            }
        }

        AstNode *fn_proto = ast_parse_fn_proto(pc);
        if (fn_proto != nullptr) {
            AstNode *body = ast_parse_block(pc);
            if (body == nullptr)
                expect_token(pc, TokenIdSemicolon);

            assert(fn_proto->type == NodeTypeFnProto);
            fn_proto->line = first->start_line;
            fn_proto->column = first->start_column;
            fn_proto->data.fn_proto.visib_mod = visib_mod;
            fn_proto->data.fn_proto.is_extern = first->id == TokenIdKeywordExtern;
            fn_proto->data.fn_proto.is_export = first->id == TokenIdKeywordExport;
            fn_proto->data.fn_proto.is_inline = first->id == TokenIdKeywordInline;
            fn_proto->data.fn_proto.lib_name = token_buf(lib_name);
            AstNode *res = fn_proto;
            if (body != nullptr) {
                res = ast_create_node_copy_line_info(pc, NodeTypeFnDef, fn_proto);
                res->data.fn_def.fn_proto = fn_proto;
                res->data.fn_def.body = body;
                fn_proto->data.fn_proto.fn_def_node = res;
            }

            return res;
        }

        ast_invalid_token_error(pc, peek_token(pc));
    }

    AstNode *var_decl = ast_parse_var_decl(pc);
    if (var_decl != nullptr) {
        assert(var_decl->type == NodeTypeVariableDeclaration);
        var_decl->data.variable_declaration.visib_mod = visib_mod;
        return var_decl;
    }

    AstNode *fn_proto = ast_parse_fn_proto(pc);
    if (fn_proto != nullptr) {
        AstNode *body = ast_parse_block(pc);
        if (body == nullptr)
            expect_token(pc, TokenIdSemicolon);

        assert(fn_proto->type == NodeTypeFnProto);
        fn_proto->data.fn_proto.visib_mod = visib_mod;
        AstNode *res = fn_proto;
        if (body != nullptr) {
            res = ast_create_node_copy_line_info(pc, NodeTypeFnDef, fn_proto);
            res->data.fn_def.fn_proto = fn_proto;
            res->data.fn_def.body = body;
            fn_proto->data.fn_proto.fn_def_node = res;
        }

        return res;
    }

    Token *use = eat_token_if(pc, TokenIdKeywordUse);
    if (use != nullptr) {
        AstNode *expr = ast_expect(pc, ast_parse_expr);
        expect_token(pc, TokenIdSemicolon);

        AstNode *res = ast_create_node(pc, NodeTypeUse, use);
        res->data.use.visib_mod = visib_mod;
        res->data.use.expr = expr;
        return res;
    }

    return nullptr;
}

// FnProto <- FnCC? KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr)
static AstNode *ast_parse_fn_proto(ParseContext *pc) {
    Token *first = peek_token(pc);
    AstNodeFnProto fn_cc;
    Token *fn;
    if (ast_parse_fn_cc(pc).unwrap(&fn_cc)) {
        // The extern keyword for fn CC is also used for container decls.
        // We therefore put it back, as allow container decl to consume it
        // later.
        if (fn_cc.cc == CallingConventionC) {
            fn = eat_token_if(pc, TokenIdKeywordFn);
            if (fn == nullptr) {
                put_back_token(pc);
                return nullptr;
            }
        } else {
            fn = expect_token(pc, TokenIdKeywordFn);
        }
    } else {
        fn_cc = {};
        fn = eat_token_if(pc, TokenIdKeywordFn);
        if (fn == nullptr)
            return nullptr;
    }

    Token *identifier = eat_token_if(pc, TokenIdSymbol);
    expect_token(pc, TokenIdLParen);
    ZigList<AstNode *> params = ast_parse_list(pc, TokenIdComma, ast_parse_param_decl);
    expect_token(pc, TokenIdRParen);

    AstNode *align_expr = ast_parse_byte_align(pc);
    AstNode *section_expr = ast_parse_link_section(pc);
    Token *var = eat_token_if(pc, TokenIdKeywordVar);
    Token *exmark = nullptr;
    AstNode *return_type = nullptr;
    if (var == nullptr) {
        exmark = eat_token_if(pc, TokenIdBang);
        return_type = ast_expect(pc, ast_parse_type_expr);
    }

    AstNode *res = ast_create_node(pc, NodeTypeFnProto, first);
    res->data.fn_proto = fn_cc;
    res->data.fn_proto.name = token_buf(identifier);
    res->data.fn_proto.params = params;
    res->data.fn_proto.align_expr = align_expr;
    res->data.fn_proto.section_expr = section_expr;
    res->data.fn_proto.return_var_token = var;
    res->data.fn_proto.auto_err_set = exmark != nullptr;
    res->data.fn_proto.return_type = return_type;

    // It seems that the Zig compiler expects varargs to be the
    // last parameter in the decl list. This is not encoded in
    // the grammar, which allows varargs anywhere in the decl.
    // Since varargs is gonna be removed at some point, I'm not
    // gonna encode this "varargs is always last" rule in the
    // grammar, and just enforce it here, until varargs is removed.
    for (size_t i = 0; i < params.length; i++) {
        AstNode *param_decl = params.at(i);
        assert(param_decl->type == NodeTypeParamDecl);
        if (param_decl->data.param_decl.is_var_args)
            res->data.fn_proto.is_var_args = true;
        if (i != params.length - 1 && res->data.fn_proto.is_var_args)
            ast_error(pc, first, "Function prototype have varargs as a none last paramter.");
    }
    return res;
}

// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON
static AstNode *ast_parse_var_decl(ParseContext *pc) {
    Token *thread_local_kw = eat_token_if(pc, TokenIdKeywordThreadLocal);
    Token *mut_kw = eat_token_if(pc, TokenIdKeywordConst);
    if (mut_kw == nullptr)
        mut_kw = eat_token_if(pc, TokenIdKeywordVar);
    if (mut_kw == nullptr) {
        if (thread_local_kw == nullptr) {
            return nullptr;
        } else {
            ast_invalid_token_error(pc, peek_token(pc));
        }
    }
    Token *identifier = expect_token(pc, TokenIdSymbol);
    AstNode *type_expr = nullptr;
    if (eat_token_if(pc, TokenIdColon) != nullptr)
        type_expr = ast_expect(pc, ast_parse_type_expr);

    AstNode *align_expr = ast_parse_byte_align(pc);
    AstNode *section_expr = ast_parse_link_section(pc);
    AstNode *expr = nullptr;
    if (eat_token_if(pc, TokenIdEq) != nullptr)
        expr = ast_expect(pc, ast_parse_expr);

    expect_token(pc, TokenIdSemicolon);

    AstNode *res = ast_create_node(pc, NodeTypeVariableDeclaration, mut_kw);
    res->data.variable_declaration.threadlocal_tok = thread_local_kw;
    res->data.variable_declaration.is_const = mut_kw->id == TokenIdKeywordConst;
    res->data.variable_declaration.symbol = token_buf(identifier);
    res->data.variable_declaration.type = type_expr;
    res->data.variable_declaration.align_expr = align_expr;
    res->data.variable_declaration.section_expr = section_expr;
    res->data.variable_declaration.expr = expr;
    return res;
}

// ContainerField <- IDENTIFIER (COLON TypeExpr)? (EQUAL Expr)?
static AstNode *ast_parse_container_field(ParseContext *pc) {
    Token *identifier = eat_token_if(pc, TokenIdSymbol);
    if (identifier == nullptr)
        return nullptr;

    AstNode *type_expr = nullptr;
    if (eat_token_if(pc, TokenIdColon) != nullptr)
        type_expr = ast_expect(pc, ast_parse_type_expr);
    AstNode *expr = nullptr;
    if (eat_token_if(pc, TokenIdEq) != nullptr)
        expr = ast_expect(pc, ast_parse_expr);


    AstNode *res = ast_create_node(pc, NodeTypeStructField, identifier);
    res->data.struct_field.name = token_buf(identifier);
    res->data.struct_field.type = type_expr;
    res->data.struct_field.value = expr;
    return res;
}

// Statement
//     <- KEYWORD_comptime? VarDecl
//      / KEYWORD_comptime BlockExprStatement
//      / KEYWORD_suspend (SEMICOLON / BlockExprStatement)
//      / KEYWORD_defer BlockExprStatement
//      / KEYWORD_errdefer BlockExprStatement
//      / IfStatement
//      / LabeledStatement
//      / SwitchExpr
//      / AssignExpr SEMICOLON
static AstNode *ast_parse_statement(ParseContext *pc) {
    Token *comptime = eat_token_if(pc, TokenIdKeywordCompTime);
    AstNode *var_decl = ast_parse_var_decl(pc);
    if (var_decl != nullptr) {
        assert(var_decl->type == NodeTypeVariableDeclaration);
        var_decl->data.variable_declaration.is_comptime = comptime != nullptr;
        return var_decl;
    }

    if (comptime != nullptr) {
        AstNode *statement = ast_expect(pc, ast_parse_block_expr_statement);
        AstNode *res = ast_create_node(pc, NodeTypeCompTime, comptime);
        res->data.comptime_expr.expr = statement;
        return res;
    }

    Token *suspend = eat_token_if(pc, TokenIdKeywordSuspend);
    if (suspend != nullptr) {
        AstNode *statement = nullptr;
        if (eat_token_if(pc, TokenIdSemicolon) == nullptr)
            statement = ast_expect(pc, ast_parse_block_expr_statement);

        AstNode *res = ast_create_node(pc, NodeTypeSuspend, suspend);
        res->data.suspend.block = statement;
        return res;
    }

    Token *defer = eat_token_if(pc, TokenIdKeywordDefer);
    if (defer == nullptr)
        defer = eat_token_if(pc, TokenIdKeywordErrdefer);
    if (defer != nullptr) {
        AstNode *statement = ast_expect(pc, ast_parse_block_expr_statement);
        AstNode *res = ast_create_node(pc, NodeTypeDefer, defer);
        res->data.defer.kind = ReturnKindUnconditional;
        res->data.defer.expr = statement;
        if (defer->id == TokenIdKeywordErrdefer)
            res->data.defer.kind = ReturnKindError;
        return res;
    }

    AstNode *if_statement = ast_parse_if_statement(pc);
    if (if_statement != nullptr)
        return if_statement;

    AstNode *labeled_statement = ast_parse_labeled_statement(pc);
    if (labeled_statement != nullptr)
        return labeled_statement;

    AstNode *switch_expr = ast_parse_switch_expr(pc);
    if (switch_expr != nullptr)
        return switch_expr;

    AstNode *assign = ast_parse_assign_expr(pc);
    if (assign != nullptr) {
        expect_token(pc, TokenIdSemicolon);
        return assign;
    }

    return nullptr;
}

// IfStatement
//     <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
//      / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
static AstNode *ast_parse_if_statement(ParseContext *pc) {
    AstNode *res = ast_parse_if_prefix(pc);
    if (res == nullptr)
        return nullptr;

    AstNode *body = ast_parse_block_expr(pc);
    bool requires_semi = false;
    if (body == nullptr) {
        requires_semi = true;
        body = ast_parse_assign_expr(pc);
    }

    Token *err_payload = nullptr;
    AstNode *else_body = nullptr;
    if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) {
        err_payload = ast_parse_payload(pc);
        else_body = ast_expect(pc, ast_parse_statement);
    }

    if (requires_semi && else_body == nullptr)
        expect_token(pc, TokenIdSemicolon);

    assert(res->type == NodeTypeIfOptional);
    if (err_payload != nullptr) {
        AstNodeTestExpr old = res->data.test_expr;
        res->type = NodeTypeIfErrorExpr;
        res->data.if_err_expr.target_node = old.target_node;
        res->data.if_err_expr.var_is_ptr = old.var_is_ptr;
        res->data.if_err_expr.var_symbol = old.var_symbol;
        res->data.if_err_expr.then_node = body;
        res->data.if_err_expr.err_symbol = token_buf(err_payload);
        res->data.if_err_expr.else_node = else_body;
        return res;
    }

    if (res->data.test_expr.var_symbol != nullptr) {
        res->data.test_expr.then_node = body;
        res->data.test_expr.else_node = else_body;
        return res;
    }

    AstNodeTestExpr old = res->data.test_expr;
    res->type = NodeTypeIfBoolExpr;
    res->data.if_bool_expr.condition = old.target_node;
    res->data.if_bool_expr.then_block = body;
    res->data.if_bool_expr.else_node = else_body;
    return res;
}

// LabeledStatement <- BlockLabel? (Block / LoopStatement)
static AstNode *ast_parse_labeled_statement(ParseContext *pc) {
    Token *label = ast_parse_block_label(pc);
    AstNode *block = ast_parse_block(pc);
    if (block != nullptr) {
        assert(block->type == NodeTypeBlock);
        block->data.block.name = token_buf(label);
        return block;
    }

    AstNode *loop = ast_parse_loop_statement(pc);
    if (loop != nullptr) {
        switch (loop->type) {
            case NodeTypeForExpr:
                loop->data.for_expr.name = token_buf(label);
                break;
            case NodeTypeWhileExpr:
                loop->data.while_expr.name = token_buf(label);
                break;
            default:
                zig_unreachable();
        }
        return loop;
    }

    if (label != nullptr)
        ast_invalid_token_error(pc, peek_token(pc));
    return nullptr;
}

// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
static AstNode *ast_parse_loop_statement(ParseContext *pc) {
    Token *label = ast_parse_block_label(pc);
    Token *first = label;

    Token *inline_token = eat_token_if(pc, TokenIdKeywordInline);
    if (first == nullptr)
        first = inline_token;

    AstNode *for_statement = ast_parse_for_statement(pc);
    if (for_statement != nullptr) {
        assert(for_statement->type == NodeTypeForExpr);
        for_statement->data.for_expr.name = token_buf(label);
        for_statement->data.for_expr.is_inline = inline_token != nullptr;
        return for_statement;
    }

    AstNode *while_statement = ast_parse_while_statement(pc);
    if (while_statement != nullptr) {
        assert(while_statement->type == NodeTypeWhileExpr);
        while_statement->data.while_expr.name = token_buf(label);
        while_statement->data.while_expr.is_inline = inline_token != nullptr;
        return while_statement;
    }

    if (first != nullptr)
        ast_invalid_token_error(pc, peek_token(pc));
    return nullptr;
}

// ForStatement
//     <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
//      / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
static AstNode *ast_parse_for_statement(ParseContext *pc) {
    AstNode *res = ast_parse_for_prefix(pc);
    if (res == nullptr)
        return nullptr;

    AstNode *body = ast_parse_block_expr(pc);
    bool requires_semi = false;
    if (body == nullptr) {
        requires_semi = true;
        body = ast_parse_assign_expr(pc);
    }

    AstNode *else_body = nullptr;
    if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) {
        else_body = ast_expect(pc, ast_parse_statement);
    }

    if (requires_semi && else_body == nullptr)
        expect_token(pc, TokenIdSemicolon);

    assert(res->type == NodeTypeForExpr);
    res->data.for_expr.body = body;
    res->data.for_expr.else_node = else_body;
    return res;
}

// WhileStatement
//     <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
//      / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
static AstNode *ast_parse_while_statement(ParseContext *pc) {
    AstNode *res = ast_parse_while_prefix(pc);
    if (res == nullptr)
        return nullptr;

    AstNode *body = ast_parse_block_expr(pc);
    bool requires_semi = false;
    if (body == nullptr) {
        requires_semi = true;
        body = ast_parse_assign_expr(pc);
    }

    Token *err_payload = nullptr;
    AstNode *else_body = nullptr;
    if (eat_token_if(pc, TokenIdKeywordElse) != nullptr) {
        err_payload = ast_parse_payload(pc);
        else_body = ast_expect(pc, ast_parse_statement);
    }

    if (requires_semi && else_body == nullptr)
        expect_token(pc, TokenIdSemicolon);

    assert(res->type == NodeTypeWhileExpr);
    res->data.while_expr.body = body;
    res->data.while_expr.err_symbol = token_buf(err_payload);
    res->data.while_expr.else_node = else_body;
    return res;
}


// BlockExprStatement
//     <- BlockExpr
//      / AssignExpr SEMICOLON
static AstNode *ast_parse_block_expr_statement(ParseContext *pc) {
    AstNode *block = ast_parse_block_expr(pc);
    if (block != nullptr)
        return block;

    AstNode *assign_expr = ast_parse_assign_expr(pc);
    if (assign_expr != nullptr) {
        expect_token(pc, TokenIdSemicolon);
        return assign_expr;
    }

    return nullptr;
}

// BlockExpr <- BlockLabel? Block
static AstNode *ast_parse_block_expr(ParseContext *pc) {
    Token *label = ast_parse_block_label(pc);
    if (label != nullptr) {
        AstNode *res = ast_expect(pc, ast_parse_block);
        assert(res->type == NodeTypeBlock);
        res->data.block.name = token_buf(label);
        return res;
    }

    return ast_parse_block(pc);
}

// AssignExpr <- Expr (AssignOp Expr)?
static AstNode *ast_parse_assign_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(pc, BinOpChainOnce, ast_parse_assign_op, ast_parse_expr);
}

// Expr <- KEYWORD_try* BoolOrExpr
static AstNode *ast_parse_expr(ParseContext *pc) {
    return ast_parse_prefix_op_expr(
        pc,
        [](ParseContext *context) {
            Token *try_token = eat_token_if(context, TokenIdKeywordTry);
            if (try_token != nullptr) {
                AstNode *res = ast_create_node(context, NodeTypeReturnExpr, try_token);
                res->data.return_expr.kind = ReturnKindError;
                return res;
            }

            return (AstNode*)nullptr;
        },
        ast_parse_bool_or_expr
    );
}

// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*
static AstNode *ast_parse_bool_or_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(
        pc,
        BinOpChainInf,
        ast_parse_bin_op_simple<TokenIdKeywordOr, BinOpTypeBoolOr>,
        ast_parse_bool_and_expr
    );
}

// BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*
static AstNode *ast_parse_bool_and_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(
        pc,
        BinOpChainInf,
        ast_parse_bin_op_simple<TokenIdKeywordAnd, BinOpTypeBoolAnd>,
        ast_parse_compare_expr
    );
}

// CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
static AstNode *ast_parse_compare_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(pc, BinOpChainInf, ast_parse_compare_op, ast_parse_bitwise_expr);
}

// BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)*
static AstNode *ast_parse_bitwise_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(pc, BinOpChainInf, ast_parse_bitwise_op, ast_parse_bit_shit_expr);
}

// BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)*
static AstNode *ast_parse_bit_shit_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(pc, BinOpChainInf, ast_parse_bit_shift_op, ast_parse_addition_expr);
}

// AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)*
static AstNode *ast_parse_addition_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(pc, BinOpChainInf, ast_parse_addition_op, ast_parse_multiply_expr);
}

// MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)*
static AstNode *ast_parse_multiply_expr(ParseContext *pc) {
    return ast_parse_bin_op_expr(pc, BinOpChainInf, ast_parse_multiply_op, ast_parse_prefix_expr);
}

// PrefixExpr <- PrefixOp* PrimaryExpr
static AstNode *ast_parse_prefix_expr(ParseContext *pc) {
    return ast_parse_prefix_op_expr(
        pc,
        ast_parse_prefix_op,
        ast_parse_primary_expr
    );
}

// PrimaryExpr
//     <- AsmExpr
//      / IfExpr
//      / KEYWORD_break BreakLabel? Expr?
//      / KEYWORD_cancel Expr
//      / KEYWORD_comptime Expr
//      / KEYWORD_continue BreakLabel?
//      / KEYWORD_resume Expr
//      / KEYWORD_return Expr?
//      / BlockLabel? LoopExpr
//      / Block
//      / CurlySuffixExpr
static AstNode *ast_parse_primary_expr(ParseContext *pc) {
    AstNode *enum_lit = ast_parse_enum_lit(pc);
    if (enum_lit != nullptr)
        return enum_lit;

    AstNode *asm_expr = ast_parse_asm_expr(pc);
    if (asm_expr != nullptr)
        return asm_expr;

    AstNode *if_expr = ast_parse_if_expr(pc);
    if (if_expr != nullptr)
        return if_expr;

    Token *break_token = eat_token_if(pc, TokenIdKeywordBreak);
    if (break_token != nullptr) {
        Token *label = ast_parse_break_label(pc);
        AstNode *expr = ast_parse_expr(pc);

        AstNode *res = ast_create_node(pc, NodeTypeBreak, break_token);
        res->data.break_expr.name = token_buf(label);
        res->data.break_expr.expr = expr;
        return res;
    }

    Token *cancel = eat_token_if(pc, TokenIdKeywordCancel);
    if (cancel != nullptr) {
        AstNode *expr = ast_expect(pc, ast_parse_expr);
        AstNode *res = ast_create_node(pc, NodeTypeCancel, cancel);
        res->data.cancel_expr.expr = expr;
        return res;
    }

    Token *comptime = eat_token_if(pc, TokenIdKeywordCompTime);
    if (comptime != nullptr) {
        AstNode *expr = ast_expect(pc, ast_parse_expr);
        AstNode *res = ast_create_node(pc, NodeTypeCompTime, comptime);
        res->data.comptime_expr.expr = expr;
        return res;
    }

    Token *continue_token = eat_token_if(pc, TokenIdKeywordContinue);
    if (continue_token != nullptr) {
        Token *label = ast_parse_break_label(pc);
        AstNode *res = ast_create_node(pc, NodeTypeContinue, continue_token);
        res->data.continue_expr.name = token_buf(label);
        return res;
    }

    Token *resume = eat_token_if(pc, TokenIdKeywordResume);
    if (resume != nullptr) {
        AstNode *expr = ast_expect(pc, ast_parse_expr);
        AstNode *res = ast_create_node(pc, NodeTypeResume, resume);
        res->data.resume_expr.expr = expr;
        return res;
    }

    Token *return_token = eat_token_if(pc, TokenIdKeywordReturn);
    if (return_token != nullptr) {
        AstNode *expr = ast_parse_expr(pc);
        AstNode *res = ast_create_node(pc, NodeTypeReturnExpr, return_token);
        res->data.return_expr.expr = expr;
        return res;
    }

    Token *label = ast_parse_block_label(pc);
    AstNode *loop = ast_parse_loop_expr(pc);
    if (loop != nullptr) {
        switch (loop->type) {
            case NodeTypeForExpr:
                loop->data.for_expr.name = token_buf(label);
                break;
            case NodeTypeWhileExpr:
                loop->data.while_expr.name = token_buf(label);
                break;
            default:
                zig_unreachable();
        }
        return loop;
    } else if (label != nullptr) {
        // Restore the tokens that we eaten by ast_parse_block_label.
        put_back_token(pc);
        put_back_token(pc);
    }

    AstNode *block = ast_parse_block(pc);
    if (block != nullptr) {
        assert(block->type == NodeTypeBlock);
        block->data.block.name = token_buf(label);
        return block;
    }

    AstNode *curly_suffix = ast_parse_curly_suffix_expr(pc);
    if (curly_suffix != nullptr)
        return curly_suffix;

    return nullptr;
}

// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
static AstNode *ast_parse_if_expr(ParseContext *pc) {
    return ast_parse_if_expr_helper(pc, ast_parse_expr);
}

// Block <- LBRACE Statement* RBRACE
static AstNode *ast_parse_block(ParseContext *pc) {
    Token *lbrace = eat_token_if(pc, TokenIdLBrace);
    if (lbrace == nullptr)
        return nullptr;

    ZigList<AstNode *> statements = {};
    AstNode *statement;
    while ((statement = ast_parse_statement(pc)) != nullptr)
        statements.append(statement);

    expect_token(pc, TokenIdRBrace);

    AstNode *res = ast_create_node(pc, NodeTypeBlock, lbrace);
    res->data.block.statements = statements;
    return res;
}

// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
static AstNode *ast_parse_loop_expr(ParseContext *pc) {
    return ast_parse_loop_expr_helper(
        pc,
        ast_parse_for_expr,
        ast_parse_while_expr
    );
}

// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
static AstNode *ast_parse_for_expr(ParseContext *pc) {
    return ast_parse_for_expr_helper(pc, ast_parse_expr);
}

// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
static AstNode *ast_parse_while_expr(ParseContext *pc) {
    return ast_parse_while_expr_helper(pc, ast_parse_expr);
}

// CurlySuffixExpr <- TypeExpr InitList?
static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc) {
    AstNode *type_expr = ast_parse_type_expr(pc);
    if (type_expr == nullptr)
        return nullptr;

    AstNode *res = ast_parse_init_list(pc);
    if (res == nullptr)
        return type_expr;

    assert(res->type == NodeTypeContainerInitExpr);
    res->data.container_init_expr.type = type_expr;
    return res;
}

// InitList
//     <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
//      / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
//      / LBRACE RBRACE
static AstNode *ast_parse_init_list(ParseContext *pc) {
    Token *lbrace = eat_token_if(pc, TokenIdLBrace);
    if (lbrace == nullptr)
        return nullptr;

    AstNode *first = ast_parse_field_init(pc);
    if (first != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeContainerInitExpr, lbrace);
        res->data.container_init_expr.kind = ContainerInitKindStruct;
        res->data.container_init_expr.entries.append(first);

        while (eat_token_if(pc, TokenIdComma) != nullptr) {
            AstNode *field_init = ast_parse_field_init(pc);
            if (field_init == nullptr)
                break;
            res->data.container_init_expr.entries.append(field_init);
        }

        expect_token(pc, TokenIdRBrace);
        return res;
    }

    AstNode *res = ast_create_node(pc, NodeTypeContainerInitExpr, lbrace);
    res->data.container_init_expr.kind = ContainerInitKindArray;

    first = ast_parse_expr(pc);
    if (first != nullptr) {
        res->data.container_init_expr.entries.append(first);

        while (eat_token_if(pc, TokenIdComma) != nullptr) {
            AstNode *expr = ast_parse_expr(pc);
            if (expr == nullptr)
                break;
            res->data.container_init_expr.entries.append(expr);
        }

        expect_token(pc, TokenIdRBrace);
        return res;
    }

    expect_token(pc, TokenIdRBrace);
    return res;
}

// TypeExpr <- PrefixTypeOp* ErrorUnionExpr
static AstNode *ast_parse_type_expr(ParseContext *pc) {
    return ast_parse_prefix_op_expr(
        pc,
        ast_parse_prefix_type_op,
        ast_parse_error_union_expr
    );
}

// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
static AstNode *ast_parse_error_union_expr(ParseContext *pc) {
    AstNode *res = ast_parse_suffix_expr(pc);
    if (res == nullptr)
        return nullptr;

    AstNode *op = ast_parse_bin_op_simple<TokenIdBang, BinOpTypeErrorUnion>(pc);
    if (op == nullptr)
        return res;

    AstNode *right = ast_expect(pc, ast_parse_type_expr);
    assert(op->type == NodeTypeBinOpExpr);
    op->data.bin_op_expr.op1 = res;
    op->data.bin_op_expr.op2 = right;
    return op;
}

// SuffixExpr
//     <- AsyncPrefix PrimaryTypeExpr SuffixOp* FnCallArguments
//      / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
static AstNode *ast_parse_suffix_expr(ParseContext *pc) {
    AstNode *async_call = ast_parse_async_prefix(pc);
    if (async_call != nullptr) {
        if (eat_token_if(pc, TokenIdKeywordFn) != nullptr) {
            // HACK: If we see the keyword `fn`, then we assume that
            //       we are parsing an async fn proto, and not a call.
            //       We therefore put back all tokens consumed by the async
            //       prefix...
            // HACK: This loop is not actually enough to put back all the
            //       tokens. Let's hope this is fine for most code right now
            //       and wait till we get the async rework for a syntax update.
            do {
                put_back_token(pc);
            } while (peek_token(pc)->id != TokenIdKeywordAsync);

            return ast_parse_primary_type_expr(pc);
        }

        AstNode *child = ast_expect(pc, ast_parse_primary_type_expr);
        while (true) {
            AstNode *suffix = ast_parse_suffix_op(pc);
            if (suffix == nullptr)
                break;

            switch (suffix->type) {
                case NodeTypeSliceExpr:
                    suffix->data.slice_expr.array_ref_expr = child;
                    break;
                case NodeTypeArrayAccessExpr:
                    suffix->data.array_access_expr.array_ref_expr = child;
                    break;
                case NodeTypeFieldAccessExpr:
                    suffix->data.field_access_expr.struct_expr = child;
                    break;
                case NodeTypeUnwrapOptional:
                    suffix->data.unwrap_optional.expr = child;
                    break;
                case NodeTypePtrDeref:
                    suffix->data.ptr_deref_expr.target = child;
                    break;
                default:
                    zig_unreachable();
            }
            child = suffix;
        }

        // TODO: Both *_async_prefix and *_fn_call_argumnets returns an
        //       AstNode *. All we really want here is the arguments of
        //       the call we parse. We therefor "leak" the node for now.
        //       Wait till we get async rework to fix this.
        AstNode *args = ast_parse_fn_call_argumnets(pc);
        if (args == nullptr)
            ast_invalid_token_error(pc, peek_token(pc));

        assert(args->type == NodeTypeFnCallExpr);
        async_call->data.fn_call_expr.fn_ref_expr = child;
        async_call->data.fn_call_expr.params = args->data.fn_call_expr.params;
        async_call->data.fn_call_expr.is_builtin = false;
        return async_call;
    }

    AstNode *res = ast_parse_primary_type_expr(pc);
    if (res == nullptr)
        return nullptr;

    while (true) {
        AstNode *suffix = ast_parse_suffix_op(pc);
        if (suffix != nullptr) {
            switch (suffix->type) {
                case NodeTypeSliceExpr:
                    suffix->data.slice_expr.array_ref_expr = res;
                    break;
                case NodeTypeArrayAccessExpr:
                    suffix->data.array_access_expr.array_ref_expr = res;
                    break;
                case NodeTypeFieldAccessExpr:
                    suffix->data.field_access_expr.struct_expr = res;
                    break;
                case NodeTypeUnwrapOptional:
                    suffix->data.unwrap_optional.expr = res;
                    break;
                case NodeTypePtrDeref:
                    suffix->data.ptr_deref_expr.target = res;
                    break;
                default:
                    zig_unreachable();
            }
            res = suffix;
            continue;
        }

        AstNode * call = ast_parse_fn_call_argumnets(pc);
        if (call != nullptr) {
            assert(call->type == NodeTypeFnCallExpr);
            call->data.fn_call_expr.fn_ref_expr = res;
            res = call;
            continue;
        }

        break;
    }

    return res;

}

// PrimaryTypeExpr
//     <- BUILTINIDENTIFIER FnCallArguments
//      / CHAR_LITERAL
//      / ContainerDecl
//      / ErrorSetDecl
//      / FLOAT
//      / FnProto
//      / GroupedExpr
//      / LabeledTypeExpr
//      / IDENTIFIER
//      / IfTypeExpr
//      / INTEGER
//      / KEYWORD_anyerror
//      / KEYWORD_comptime TypeExpr
//      / KEYWORD_error DOT IDENTIFIER
//      / KEYWORD_false
//      / KEYWORD_null
//      / KEYWORD_promise
//      / KEYWORD_true
//      / KEYWORD_undefined
//      / KEYWORD_unreachable
//      / STRINGLITERAL
//      / SwitchExpr
static AstNode *ast_parse_primary_type_expr(ParseContext *pc) {
    // TODO: This is not in line with the grammar.
    //       Because the prev stage 1 tokenizer does not parse
    //       @[a-zA-Z_][a-zA-Z0-9_] as one token, it has to do a
    //       hack, where it accepts '@' (IDENTIFIER / KEYWORD_export).
    //       I'd say that it's better if '@' is part of the builtin
    //       identifier token.
    Token *at_sign = eat_token_if(pc, TokenIdAtSign);
    if (at_sign != nullptr) {
        Buf *name;
        Token *token = eat_token_if(pc, TokenIdKeywordExport);
        if (token == nullptr) {
            token = expect_token(pc, TokenIdSymbol);
            name = token_buf(token);
        } else {
            name = buf_create_from_str("export");
        }

        AstNode *res = ast_expect(pc, ast_parse_fn_call_argumnets);
        AstNode *name_sym = ast_create_node(pc, NodeTypeSymbol, token);
        name_sym->data.symbol_expr.symbol = name;

        assert(res->type == NodeTypeFnCallExpr);
        res->line = at_sign->start_line;
        res->column = at_sign->start_column;
        res->data.fn_call_expr.fn_ref_expr = name_sym;
        res->data.fn_call_expr.is_builtin = true;
        return res;
    }

    Token *char_lit = eat_token_if(pc, TokenIdCharLiteral);
    if (char_lit != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeCharLiteral, char_lit);
        res->data.char_literal.value = char_lit->data.char_lit.c;
        return res;
    }

    AstNode *container_decl = ast_parse_container_decl(pc);
    if (container_decl != nullptr)
        return container_decl;

    AstNode *error_set_decl = ast_parse_error_set_decl(pc);
    if (error_set_decl != nullptr)
        return error_set_decl;

    Token *float_lit = eat_token_if(pc, TokenIdFloatLiteral);
    if (float_lit != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeFloatLiteral, float_lit);
        res->data.float_literal.bigfloat = &float_lit->data.float_lit.bigfloat;
        res->data.float_literal.overflow = float_lit->data.float_lit.overflow;
        return res;
    }

    AstNode *fn_proto = ast_parse_fn_proto(pc);
    if (fn_proto != nullptr)
        return fn_proto;

    AstNode *grouped_expr = ast_parse_grouped_expr(pc);
    if (grouped_expr != nullptr)
        return grouped_expr;

    AstNode *labeled_type_expr = ast_parse_labeled_type_expr(pc);
    if (labeled_type_expr != nullptr)
        return labeled_type_expr;

    Token *identifier = eat_token_if(pc, TokenIdSymbol);
    if (identifier != nullptr)
        return token_symbol(pc, identifier);

    AstNode *if_type_expr = ast_parse_if_type_expr(pc);
    if (if_type_expr != nullptr)
        return if_type_expr;

    Token *int_lit = eat_token_if(pc, TokenIdIntLiteral);
    if (int_lit != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeIntLiteral, int_lit);
        res->data.int_literal.bigint = &int_lit->data.int_lit.bigint;
        return res;
    }

    Token *error_type = eat_token_if(pc, TokenIdKeywordAnyerror);
    if (error_type != nullptr)
        return ast_create_node(pc, NodeTypeErrorType, error_type);

    Token *comptime = eat_token_if(pc, TokenIdKeywordCompTime);
    if (comptime != nullptr) {
        AstNode *expr = ast_expect(pc, ast_parse_type_expr);
        AstNode *res = ast_create_node(pc, NodeTypeCompTime, comptime);
        res->data.comptime_expr.expr = expr;
        return res;
    }

    Token *error = eat_token_if(pc, TokenIdKeywordError);
    if (error != nullptr) {
        Token *dot = expect_token(pc, TokenIdDot);
        Token *name = expect_token(pc, TokenIdSymbol);
        AstNode *left = ast_create_node(pc, NodeTypeErrorType, error);
        AstNode *res = ast_create_node(pc, NodeTypeFieldAccessExpr, dot);
        res->data.field_access_expr.struct_expr = left;
        res->data.field_access_expr.field_name = token_buf(name);
        return res;
    }

    Token *false_token = eat_token_if(pc, TokenIdKeywordFalse);
    if (false_token != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeBoolLiteral, false_token);
        res->data.bool_literal.value = false;
        return res;
    }

    Token *null = eat_token_if(pc, TokenIdKeywordNull);
    if (null != nullptr)
        return ast_create_node(pc, NodeTypeNullLiteral, null);

    Token *promise = eat_token_if(pc, TokenIdKeywordPromise);
    if (promise != nullptr)
        return ast_create_node(pc, NodeTypePromiseType, promise);

    Token *true_token = eat_token_if(pc, TokenIdKeywordTrue);
    if (true_token != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeBoolLiteral, true_token);
        res->data.bool_literal.value = true;
        return res;
    }

    Token *undefined = eat_token_if(pc, TokenIdKeywordUndefined);
    if (undefined != nullptr)
        return ast_create_node(pc, NodeTypeUndefinedLiteral, undefined);

    Token *unreachable = eat_token_if(pc, TokenIdKeywordUnreachable);
    if (unreachable != nullptr)
        return ast_create_node(pc, NodeTypeUnreachable, unreachable);

    Token *string_lit = eat_token_if(pc, TokenIdStringLiteral);
    if (string_lit != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeStringLiteral, string_lit);
        res->data.string_literal.buf = token_buf(string_lit);
        res->data.string_literal.c = string_lit->data.str_lit.is_c_str;
        return res;
    }

    AstNode *switch_expr = ast_parse_switch_expr(pc);
    if (switch_expr != nullptr)
        return switch_expr;

    return nullptr;
}

// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
static AstNode *ast_parse_container_decl(ParseContext *pc) {
    Token *extern_token = eat_token_if(pc, TokenIdKeywordExtern);
    if (extern_token != nullptr) {
        AstNode *res = ast_parse_container_decl_auto(pc);
        if (res == nullptr) {
            put_back_token(pc);
            return nullptr;
        }

        assert(res->type == NodeTypeContainerDecl);
        res->line = extern_token->start_line;
        res->column = extern_token->start_column;
        res->data.container_decl.layout = ContainerLayoutExtern;
        return res;
    }

    Token *packed_token = eat_token_if(pc, TokenIdKeywordPacked);
    if (packed_token != nullptr) {
        AstNode *res = ast_expect(pc, ast_parse_container_decl_auto);
        assert(res->type == NodeTypeContainerDecl);
        res->line = packed_token->start_line;
        res->column = packed_token->start_column;
        res->data.container_decl.layout = ContainerLayoutPacked;
        return res;
    }

    return ast_parse_container_decl_auto(pc);
}

// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
static AstNode *ast_parse_error_set_decl(ParseContext *pc) {
    Token *first = eat_token_if(pc, TokenIdKeywordError);
    if (first == nullptr)
        return nullptr;
    if (eat_token_if(pc, TokenIdLBrace) == nullptr) {
        put_back_token(pc);
        return nullptr;
    }

    ZigList<AstNode *> decls = ast_parse_list<AstNode>(pc, TokenIdComma, [](ParseContext *context) {
        Token *ident = eat_token_if(context, TokenIdSymbol);
        if (ident == nullptr)
            return (AstNode*)nullptr;

        return token_symbol(context, ident);
    });
    expect_token(pc, TokenIdRBrace);

    AstNode *res = ast_create_node(pc, NodeTypeErrorSetDecl, first);
    res->data.err_set_decl.decls = decls;
    return res;
}

// GroupedExpr <- LPAREN Expr RPAREN
static AstNode *ast_parse_grouped_expr(ParseContext *pc) {
    Token *lparen = eat_token_if(pc, TokenIdLParen);
    if (lparen == nullptr)
        return nullptr;

    AstNode *expr = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);

    AstNode *res = ast_create_node(pc, NodeTypeGroupedExpr, lparen);
    res->data.grouped_expr = expr;
    return res;
}

// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
static AstNode *ast_parse_if_type_expr(ParseContext *pc) {
    return ast_parse_if_expr_helper(pc, ast_parse_type_expr);
}

// LabeledTypeExpr
//     <- BlockLabel Block
//      / BlockLabel? LoopTypeExpr
static AstNode *ast_parse_labeled_type_expr(ParseContext *pc) {
    Token *label = ast_parse_block_label(pc);
    if (label != nullptr) {
        AstNode *block = ast_parse_block(pc);
        if (block != nullptr) {
            assert(block->type == NodeTypeBlock);
            block->data.block.name = token_buf(label);
            return block;
        }
    }

    AstNode *loop = ast_parse_loop_type_expr(pc);
    if (loop != nullptr) {
        switch (loop->type) {
            case NodeTypeForExpr:
                loop->data.for_expr.name = token_buf(label);
                break;
            case NodeTypeWhileExpr:
                loop->data.while_expr.name = token_buf(label);
                break;
            default:
                zig_unreachable();
        }
        return loop;
    }

    if (label != nullptr)
        ast_invalid_token_error(pc, peek_token(pc));
    return nullptr;
}

// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
static AstNode *ast_parse_loop_type_expr(ParseContext *pc) {
    return ast_parse_loop_expr_helper(
        pc,
        ast_parse_for_type_expr,
        ast_parse_while_type_expr
    );
}

// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
static AstNode *ast_parse_for_type_expr(ParseContext *pc) {
    return ast_parse_for_expr_helper(pc, ast_parse_type_expr);
}

// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
static AstNode *ast_parse_while_type_expr(ParseContext *pc) {
    return ast_parse_while_expr_helper(pc, ast_parse_type_expr);
}

// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
static AstNode *ast_parse_switch_expr(ParseContext *pc) {
    Token *switch_token = eat_token_if(pc, TokenIdKeywordSwitch);
    if (switch_token == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLParen);
    AstNode *expr = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);
    expect_token(pc, TokenIdLBrace);
    ZigList<AstNode *> prongs = ast_parse_list(pc, TokenIdComma, ast_parse_switch_prong);
    expect_token(pc, TokenIdRBrace);

    AstNode *res = ast_create_node(pc, NodeTypeSwitchExpr, switch_token);
    res->data.switch_expr.expr = expr;
    res->data.switch_expr.prongs = prongs;
    return res;
}

// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN STRINGLITERAL AsmOutput? RPAREN
static AstNode *ast_parse_asm_expr(ParseContext *pc) {
    Token *asm_token = eat_token_if(pc, TokenIdKeywordAsm);
    if (asm_token == nullptr)
        return nullptr;

    Token *volatile_token = eat_token_if(pc, TokenIdKeywordVolatile);
    expect_token(pc, TokenIdLParen);
    Token *asm_template = expect_token(pc, TokenIdStringLiteral);
    AstNode *res = ast_parse_asm_output(pc);
    if (res == nullptr)
        res = ast_create_node_no_line_info(pc, NodeTypeAsmExpr);
    expect_token(pc, TokenIdRParen);

    res->line = asm_token->start_line;
    res->column = asm_token->start_column;
    res->data.asm_expr.volatile_token = volatile_token;
    res->data.asm_expr.asm_template = asm_template;
    return res;
}

static AstNode *ast_parse_enum_lit(ParseContext *pc) {
    Token *period = eat_token_if(pc, TokenIdDot);
    if (period == nullptr)
        return nullptr;

    Token *identifier = expect_token(pc, TokenIdSymbol);
    AstNode *res = ast_create_node(pc, NodeTypeEnumLiteral, period);
    res->data.enum_literal.period = period;
    res->data.enum_literal.identifier = identifier;
    return res;
}

// AsmOutput <- COLON AsmOutputList AsmInput?
static AstNode *ast_parse_asm_output(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdColon) == nullptr)
        return nullptr;

    ZigList<AsmOutput *> output_list = ast_parse_list(pc, TokenIdComma, ast_parse_asm_output_item);
    AstNode *res = ast_parse_asm_input(pc);
    if (res == nullptr)
        res = ast_create_node_no_line_info(pc, NodeTypeAsmExpr);

    res->data.asm_expr.output_list = output_list;
    return res;
}

// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
static AsmOutput *ast_parse_asm_output_item(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdLBracket) == nullptr)
        return nullptr;

    Token *sym_name = expect_token(pc, TokenIdSymbol);
    expect_token(pc, TokenIdRBracket);

    Token *str = expect_token(pc, TokenIdStringLiteral);
    expect_token(pc, TokenIdLParen);

    Token *var_name = eat_token_if(pc, TokenIdSymbol);
    AstNode *return_type = nullptr;
    if (var_name == nullptr) {
        expect_token(pc, TokenIdArrow);
        return_type = ast_expect(pc, ast_parse_type_expr);
    }

    expect_token(pc, TokenIdRParen);

    AsmOutput *res = allocate<AsmOutput>(1);
    res->asm_symbolic_name = token_buf(sym_name);
    res->constraint = token_buf(str);
    res->variable_name = token_buf(var_name);
    res->return_type = return_type;
    return res;
}

// AsmInput <- COLON AsmInputList AsmClobbers?
static AstNode *ast_parse_asm_input(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdColon) == nullptr)
        return nullptr;

    ZigList<AsmInput *> input_list = ast_parse_list(pc, TokenIdComma, ast_parse_asm_input_item);
    AstNode *res = ast_parse_asm_clobbers(pc);
    if (res == nullptr)
        res = ast_create_node_no_line_info(pc, NodeTypeAsmExpr);

    res->data.asm_expr.input_list = input_list;
    return res;
}

// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
static AsmInput *ast_parse_asm_input_item(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdLBracket) == nullptr)
        return nullptr;

    Token *sym_name = expect_token(pc, TokenIdSymbol);
    expect_token(pc, TokenIdRBracket);
    Token *constraint = expect_token(pc, TokenIdStringLiteral);
    expect_token(pc, TokenIdLParen);
    AstNode *expr = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);

    AsmInput *res = allocate<AsmInput>(1);
    res->asm_symbolic_name = token_buf(sym_name);
    res->constraint = token_buf(constraint);
    res->expr = expr;
    return res;
}

// AsmClobbers <- COLON StringList
static AstNode *ast_parse_asm_clobbers(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdColon) == nullptr)
        return nullptr;

    ZigList<Buf *> clobber_list = ast_parse_list<Buf>(pc, TokenIdComma, [](ParseContext *context) {
        Token *str = eat_token_if(context, TokenIdStringLiteral);
        if (str != nullptr)
            return token_buf(str);
        return (Buf*)nullptr;
    });

    AstNode *res = ast_create_node_no_line_info(pc, NodeTypeAsmExpr);
    res->data.asm_expr.clobber_list = clobber_list;
    return res;
}

// BreakLabel <- COLON IDENTIFIER
static Token *ast_parse_break_label(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdColon) == nullptr)
        return nullptr;

    return expect_token(pc, TokenIdSymbol);
}

// BlockLabel <- IDENTIFIER COLON
static Token *ast_parse_block_label(ParseContext *pc) {
    Token *ident = eat_token_if(pc, TokenIdSymbol);
    if (ident == nullptr)
        return nullptr;

    // We do 2 token lookahead here, as we don't want to error when
    // parsing identifiers.
    if (eat_token_if(pc, TokenIdColon) == nullptr) {
        put_back_token(pc);
        return nullptr;
    }

    return ident;
}

// FieldInit <- DOT IDENTIFIER EQUAL Expr
static AstNode *ast_parse_field_init(ParseContext *pc) {
    Token *first = eat_token_if(pc, TokenIdDot);
    if (first == nullptr)
        return nullptr;

    Token *name = expect_token(pc, TokenIdSymbol);
    expect_token(pc, TokenIdEq);
    AstNode *expr = ast_expect(pc, ast_parse_expr);

    AstNode *res = ast_create_node(pc, NodeTypeStructValueField, first);
    res->data.struct_val_field.name = token_buf(name);
    res->data.struct_val_field.expr = expr;
    return res;
}

// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
static AstNode *ast_parse_while_continue_expr(ParseContext *pc) {
    Token *first = eat_token_if(pc, TokenIdColon);
    if (first == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLParen);
    AstNode *expr = ast_expect(pc, ast_parse_assign_expr);
    expect_token(pc, TokenIdRParen);
    return expr;
}

// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
static AstNode *ast_parse_link_section(ParseContext *pc) {
    Token *first = eat_token_if(pc, TokenIdKeywordLinkSection);
    if (first == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLParen);
    AstNode *res = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);
    return res;
}

// FnCC
//     <- KEYWORD_nakedcc
//      / KEYWORD_stdcallcc
//      / KEYWORD_extern
//      / KEYWORD_async (LARROW TypeExpr RARROW)?
static Optional<AstNodeFnProto> ast_parse_fn_cc(ParseContext *pc) {
    AstNodeFnProto res = {};
    if (eat_token_if(pc, TokenIdKeywordNakedCC) != nullptr) {
        res.cc = CallingConventionNaked;
        return Optional<AstNodeFnProto>::some(res);
    }
    if (eat_token_if(pc, TokenIdKeywordStdcallCC) != nullptr) {
        res.cc = CallingConventionStdcall;
        return Optional<AstNodeFnProto>::some(res);
    }
    if (eat_token_if(pc, TokenIdKeywordExtern) != nullptr) {
        res.cc = CallingConventionC;
        return Optional<AstNodeFnProto>::some(res);
    }
    if (eat_token_if(pc, TokenIdKeywordAsync) != nullptr) {
        res.cc = CallingConventionAsync;
        if (eat_token_if(pc, TokenIdCmpLessThan) == nullptr)
            return Optional<AstNodeFnProto>::some(res);

        res.async_allocator_type = ast_expect(pc, ast_parse_type_expr);
        expect_token(pc, TokenIdCmpGreaterThan);
        return Optional<AstNodeFnProto>::some(res);
    }

    return Optional<AstNodeFnProto>::none();
}

// ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
static AstNode *ast_parse_param_decl(ParseContext *pc) {
    Token *first = eat_token_if(pc, TokenIdKeywordNoAlias);
    if (first == nullptr)
        first = eat_token_if(pc, TokenIdKeywordCompTime);

    Token *name = eat_token_if(pc, TokenIdSymbol);
    if (name != nullptr) {
        if (eat_token_if(pc, TokenIdColon) != nullptr) {
            if (first == nullptr)
                first = name;
        } else {
            // We put back the ident, so it can be parsed as a ParamType
            // later.
            put_back_token(pc);
            name = nullptr;
        }
    }

    AstNode *res;
    if (first == nullptr) {
        first = peek_token(pc);
        res = ast_parse_param_type(pc);
    } else {
        res = ast_expect(pc, ast_parse_param_type);
    }

    if (res == nullptr)
        return nullptr;

    assert(res->type == NodeTypeParamDecl);
    res->line = first->start_line;
    res->column = first->start_column;
    res->data.param_decl.name = token_buf(name);
    res->data.param_decl.is_noalias = first->id == TokenIdKeywordNoAlias;
    res->data.param_decl.is_inline = first->id == TokenIdKeywordCompTime;
    return res;
}

// ParamType
//     <- KEYWORD_var
//      / DOT3
//      / TypeExpr
static AstNode *ast_parse_param_type(ParseContext *pc) {
    Token *var_token = eat_token_if(pc, TokenIdKeywordVar);
    if (var_token != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeParamDecl, var_token);
        res->data.param_decl.var_token = var_token;
        return res;
    }

    Token *dots = eat_token_if(pc, TokenIdEllipsis3);
    if (dots != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeParamDecl, dots);
        res->data.param_decl.is_var_args = true;
        return res;
    }

    AstNode *type_expr = ast_parse_type_expr(pc);
    if (type_expr != nullptr) {
        AstNode *res = ast_create_node_copy_line_info(pc, NodeTypeParamDecl, type_expr);
        res->data.param_decl.type = type_expr;
        return res;
    }

    return nullptr;
}

// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
static AstNode *ast_parse_if_prefix(ParseContext *pc) {
    Token *first = eat_token_if(pc, TokenIdKeywordIf);
    if (first == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLParen);
    AstNode *condition = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);
    Optional<PtrPayload> opt_payload = ast_parse_ptr_payload(pc);

    PtrPayload payload;
    AstNode *res = ast_create_node(pc, NodeTypeIfOptional, first);
    res->data.test_expr.target_node = condition;
    if (opt_payload.unwrap(&payload)) {
        res->data.test_expr.var_symbol = token_buf(payload.payload);
        res->data.test_expr.var_is_ptr = payload.asterisk != nullptr;
    }
    return res;
}

// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
static AstNode *ast_parse_while_prefix(ParseContext *pc) {
    Token *while_token = eat_token_if(pc, TokenIdKeywordWhile);
    if (while_token == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLParen);
    AstNode *condition = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);
    Optional<PtrPayload> opt_payload = ast_parse_ptr_payload(pc);
    AstNode *continue_expr = ast_parse_while_continue_expr(pc);

    PtrPayload payload;
    AstNode *res = ast_create_node(pc, NodeTypeWhileExpr, while_token);
    res->data.while_expr.condition = condition;
    res->data.while_expr.continue_expr = continue_expr;
    if (opt_payload.unwrap(&payload)) {
        res->data.while_expr.var_symbol = token_buf(payload.payload);
        res->data.while_expr.var_is_ptr = payload.asterisk != nullptr;
    }

    return res;
}

// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
static AstNode *ast_parse_for_prefix(ParseContext *pc) {
    Token *for_token = eat_token_if(pc, TokenIdKeywordFor);
    if (for_token == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLParen);
    AstNode *array_expr = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);
    PtrIndexPayload payload;
    if (!ast_parse_ptr_index_payload(pc).unwrap(&payload))
        ast_invalid_token_error(pc, peek_token(pc));

    AstNode *res = ast_create_node(pc, NodeTypeForExpr, for_token);
    res->data.for_expr.array_expr = array_expr;
    res->data.for_expr.elem_node = token_symbol(pc, payload.payload);
    res->data.for_expr.elem_is_ptr = payload.asterisk != nullptr;
    if (payload.index != nullptr)
        res->data.for_expr.index_node = token_symbol(pc, payload.index);

    return res;
}

// Payload <- PIPE IDENTIFIER PIPE
static Token *ast_parse_payload(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdBinOr) == nullptr)
        return nullptr;

    Token *res = expect_token(pc, TokenIdSymbol);
    expect_token(pc, TokenIdBinOr);
    return res;
}

// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
static Optional<PtrPayload> ast_parse_ptr_payload(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdBinOr) == nullptr)
        return Optional<PtrPayload>::none();

    Token *asterisk = eat_token_if(pc, TokenIdStar);
    Token *payload = expect_token(pc, TokenIdSymbol);
    expect_token(pc, TokenIdBinOr);

    PtrPayload res;
    res.asterisk = asterisk;
    res.payload = payload;
    return Optional<PtrPayload>::some(res);
}

// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
static Optional<PtrIndexPayload> ast_parse_ptr_index_payload(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdBinOr) == nullptr)
        return Optional<PtrIndexPayload>::none();

    Token *asterisk = eat_token_if(pc, TokenIdStar);
    Token *payload = expect_token(pc, TokenIdSymbol);
    Token *index = nullptr;
    if (eat_token_if(pc, TokenIdComma) != nullptr)
        index = expect_token(pc, TokenIdSymbol);
    expect_token(pc, TokenIdBinOr);

    PtrIndexPayload res;
    res.asterisk = asterisk;
    res.payload = payload;
    res.index = index;
    return Optional<PtrIndexPayload>::some(res);
}

// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr
static AstNode *ast_parse_switch_prong(ParseContext *pc) {
    AstNode *res = ast_parse_switch_case(pc);
    if (res == nullptr)
        return nullptr;

    expect_token(pc, TokenIdFatArrow);
    Optional<PtrPayload> opt_payload = ast_parse_ptr_payload(pc);
    AstNode *expr = ast_expect(pc, ast_parse_assign_expr);

    PtrPayload payload;
    assert(res->type == NodeTypeSwitchProng);
    res->data.switch_prong.expr = expr;
    if (opt_payload.unwrap(&payload)) {
        res->data.switch_prong.var_symbol = token_symbol(pc, payload.payload);
        res->data.switch_prong.var_is_ptr = payload.asterisk != nullptr;
    }

    return res;
}

// SwitchCase
//     <- SwitchItem (COMMA SwitchItem)* COMMA?
//      / KEYWORD_else
static AstNode *ast_parse_switch_case(ParseContext *pc) {
    AstNode *first = ast_parse_switch_item(pc);
    if (first != nullptr) {
        AstNode *res = ast_create_node_copy_line_info(pc, NodeTypeSwitchProng, first);
        res->data.switch_prong.items.append(first);
        res->data.switch_prong.any_items_are_range = first->type == NodeTypeSwitchRange;

        while (eat_token_if(pc, TokenIdComma) != nullptr) {
            AstNode *item = ast_parse_switch_item(pc);
            if (item == nullptr)
                break;

            res->data.switch_prong.items.append(item);
            res->data.switch_prong.any_items_are_range |= item->type == NodeTypeSwitchRange;
        }

        return res;
    }

    Token *else_token = eat_token_if(pc, TokenIdKeywordElse);
    if (else_token != nullptr)
        return ast_create_node(pc, NodeTypeSwitchProng, else_token);

    return nullptr;
}

// SwitchItem <- Expr (DOT3 Expr)?
static AstNode *ast_parse_switch_item(ParseContext *pc) {
    AstNode *expr = ast_parse_expr(pc);
    if (expr == nullptr)
        return nullptr;

    Token *dots = eat_token_if(pc, TokenIdEllipsis3);
    if (dots != nullptr) {
        AstNode *expr2 = ast_expect(pc, ast_parse_expr);
        AstNode *res = ast_create_node(pc, NodeTypeSwitchRange, dots);
        res->data.switch_range.start = expr;
        res->data.switch_range.end = expr2;
        return res;
    }

    return expr;
}

// AssignOp
//     <- ASTERISKEQUAL
//      / SLASHEQUAL
//      / PERCENTEQUAL
//      / PLUSEQUAL
//      / MINUSEQUAL
//      / LARROW2EQUAL
//      / RARROW2EQUAL
//      / AMPERSANDEQUAL
//      / CARETEQUAL
//      / PIPEEQUAL
//      / ASTERISKPERCENTEQUAL
//      / PLUSPERCENTEQUAL
//      / MINUSPERCENTEQUAL
//      / EQUAL
static AstNode *ast_parse_assign_op(ParseContext *pc) {
    // In C, we have `T arr[N] = {[i] = T{}};` but it doesn't
    // seem to work in C++...
    BinOpType table[TokenIdCount] = {};
    table[TokenIdBarBarEq] = BinOpTypeAssignMergeErrorSets;
    table[TokenIdBitAndEq] = BinOpTypeAssignBitAnd;
    table[TokenIdBitOrEq] = BinOpTypeAssignBitOr;
    table[TokenIdBitShiftLeftEq] = BinOpTypeAssignBitShiftLeft;
    table[TokenIdBitShiftRightEq] = BinOpTypeAssignBitShiftRight;
    table[TokenIdBitXorEq] = BinOpTypeAssignBitXor;
    table[TokenIdDivEq] = BinOpTypeAssignDiv;
    table[TokenIdEq] = BinOpTypeAssign;
    table[TokenIdMinusEq] = BinOpTypeAssignMinus;
    table[TokenIdMinusPercentEq] = BinOpTypeAssignMinusWrap;
    table[TokenIdModEq] = BinOpTypeAssignMod;
    table[TokenIdPlusEq] = BinOpTypeAssignPlus;
    table[TokenIdPlusPercentEq] = BinOpTypeAssignPlusWrap;
    table[TokenIdTimesEq] = BinOpTypeAssignTimes;
    table[TokenIdTimesPercentEq] = BinOpTypeAssignTimesWrap;

    BinOpType op = table[peek_token(pc)->id];
    if (op != BinOpTypeInvalid) {
        Token *op_token = eat_token(pc);
        AstNode *res = ast_create_node(pc, NodeTypeBinOpExpr, op_token);
        res->data.bin_op_expr.bin_op = op;
        return res;
    }

    return nullptr;

}

// CompareOp
//     <- EQUALEQUAL
//      / EXCLAMATIONMARKEQUAL
//      / LARROW
//      / RARROW
//      / LARROWEQUAL
//      / RARROWEQUAL
static AstNode *ast_parse_compare_op(ParseContext *pc) {
    BinOpType table[TokenIdCount] = {};
    table[TokenIdCmpEq] = BinOpTypeCmpEq;
    table[TokenIdCmpNotEq] = BinOpTypeCmpNotEq;
    table[TokenIdCmpLessThan] = BinOpTypeCmpLessThan;
    table[TokenIdCmpGreaterThan] = BinOpTypeCmpGreaterThan;
    table[TokenIdCmpLessOrEq] = BinOpTypeCmpLessOrEq;
    table[TokenIdCmpGreaterOrEq] = BinOpTypeCmpGreaterOrEq;

    BinOpType op = table[peek_token(pc)->id];
    if (op != BinOpTypeInvalid) {
        Token *op_token = eat_token(pc);
        AstNode *res = ast_create_node(pc, NodeTypeBinOpExpr, op_token);
        res->data.bin_op_expr.bin_op = op;
        return res;
    }

    return nullptr;
}

// BitwiseOp
//     <- AMPERSAND
//      / CARET
//      / PIPE
//      / KEYWORD_orelse
//      / KEYWORD_catch Payload?
static AstNode *ast_parse_bitwise_op(ParseContext *pc) {
    BinOpType table[TokenIdCount] = {};
    table[TokenIdAmpersand] = BinOpTypeBinAnd;
    table[TokenIdBinXor] = BinOpTypeBinXor;
    table[TokenIdBinOr] = BinOpTypeBinOr;
    table[TokenIdKeywordOrElse] = BinOpTypeUnwrapOptional;

    BinOpType op = table[peek_token(pc)->id];
    if (op != BinOpTypeInvalid) {
        Token *op_token = eat_token(pc);
        AstNode *res = ast_create_node(pc, NodeTypeBinOpExpr, op_token);
        res->data.bin_op_expr.bin_op = op;
        return res;
    }

    Token *catch_token = eat_token_if(pc, TokenIdKeywordCatch);
    if (catch_token != nullptr) {
        Token *payload = ast_parse_payload(pc);
        AstNode *res = ast_create_node(pc, NodeTypeUnwrapErrorExpr, catch_token);
        if (payload != nullptr)
            res->data.unwrap_err_expr.symbol = token_symbol(pc, payload);

        return res;
    }

    return nullptr;
}

// BitShiftOp
//     <- LARROW2
//      / RARROW2
static AstNode *ast_parse_bit_shift_op(ParseContext *pc) {
    BinOpType table[TokenIdCount] = {};
    table[TokenIdBitShiftLeft] = BinOpTypeBitShiftLeft;
    table[TokenIdBitShiftRight] = BinOpTypeBitShiftRight;

    BinOpType op = table[peek_token(pc)->id];
    if (op != BinOpTypeInvalid) {
        Token *op_token = eat_token(pc);
        AstNode *res = ast_create_node(pc, NodeTypeBinOpExpr, op_token);
        res->data.bin_op_expr.bin_op = op;
        return res;
    }

    return nullptr;
}

// AdditionOp
//     <- PLUS
//      / MINUS
//      / PLUS2
//      / PLUSPERCENT
//      / MINUSPERCENT
static AstNode *ast_parse_addition_op(ParseContext *pc) {
    BinOpType table[TokenIdCount] = {};
    table[TokenIdPlus] = BinOpTypeAdd;
    table[TokenIdDash] = BinOpTypeSub;
    table[TokenIdPlusPlus] = BinOpTypeArrayCat;
    table[TokenIdPlusPercent] = BinOpTypeAddWrap;
    table[TokenIdMinusPercent] = BinOpTypeSubWrap;

    BinOpType op = table[peek_token(pc)->id];
    if (op != BinOpTypeInvalid) {
        Token *op_token = eat_token(pc);
        AstNode *res = ast_create_node(pc, NodeTypeBinOpExpr, op_token);
        res->data.bin_op_expr.bin_op = op;
        return res;
    }

    return nullptr;
}

// MultiplyOp
//     <- PIPE2
//      / ASTERISK
//      / SLASH
//      / PERCENT
//      / ASTERISK2
//      / ASTERISKPERCENT
static AstNode *ast_parse_multiply_op(ParseContext *pc) {
    BinOpType table[TokenIdCount] = {};
    table[TokenIdBarBar] = BinOpTypeMergeErrorSets;
    table[TokenIdStar] = BinOpTypeMult;
    table[TokenIdSlash] = BinOpTypeDiv;
    table[TokenIdPercent] = BinOpTypeMod;
    table[TokenIdStarStar] = BinOpTypeArrayMult;
    table[TokenIdTimesPercent] = BinOpTypeMultWrap;

    BinOpType op = table[peek_token(pc)->id];
    if (op != BinOpTypeInvalid) {
        Token *op_token = eat_token(pc);
        AstNode *res = ast_create_node(pc, NodeTypeBinOpExpr, op_token);
        res->data.bin_op_expr.bin_op = op;
        return res;
    }

    return nullptr;
}

// PrefixOp
//     <- EXCLAMATIONMARK
//      / MINUS
//      / TILDE
//      / MINUSPERCENT
//      / AMPERSAND
//      / KEYWORD_try
//      / KEYWORD_await
static AstNode *ast_parse_prefix_op(ParseContext *pc) {
    PrefixOp table[TokenIdCount] = {};
    table[TokenIdBang] = PrefixOpBoolNot;
    table[TokenIdDash] = PrefixOpNegation;
    table[TokenIdTilde] = PrefixOpBinNot;
    table[TokenIdMinusPercent] = PrefixOpNegationWrap;
    table[TokenIdAmpersand] = PrefixOpAddrOf;

    PrefixOp op = table[peek_token(pc)->id];
    if (op != PrefixOpInvalid) {
        Token *op_token = eat_token(pc);
        AstNode *res = ast_create_node(pc, NodeTypePrefixOpExpr, op_token);
        res->data.prefix_op_expr.prefix_op = op;
        return res;
    }

    Token *try_token = eat_token_if(pc, TokenIdKeywordTry);
    if (try_token != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeReturnExpr, try_token);
        res->data.return_expr.kind = ReturnKindError;
        return res;
    }

    Token *await = eat_token_if(pc, TokenIdKeywordAwait);
    if (await != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypeAwaitExpr, await);
        return res;
    }

    return nullptr;
}

// PrefixTypeOp
//     <- QUESTIONMARK
//      / KEYWORD_promise MINUSRARROW
//      / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile)*
//      / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile)*
static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
    Token *questionmark = eat_token_if(pc, TokenIdQuestion);
    if (questionmark != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypePrefixOpExpr, questionmark);
        res->data.prefix_op_expr.prefix_op = PrefixOpOptional;
        return res;
    }

    Token *promise = eat_token_if(pc, TokenIdKeywordPromise);
    if (promise != nullptr) {
        if (eat_token_if(pc, TokenIdArrow) != nullptr) {
            AstNode *res = ast_create_node(pc, NodeTypePromiseType, promise);
            return res;
        }

        put_back_token(pc);
    }

    AstNode *array = ast_parse_array_type_start(pc);
    if (array != nullptr) {
        assert(array->type == NodeTypeArrayType);
        while (true) {
            Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero);
            if (allowzero_token != nullptr) {
                array->data.array_type.allow_zero_token = allowzero_token;
                continue;
            }

            AstNode *align_expr = ast_parse_byte_align(pc);
            if (align_expr != nullptr) {
                array->data.array_type.align_expr = align_expr;
                continue;
            }

            if (eat_token_if(pc, TokenIdKeywordConst) != nullptr) {
                array->data.array_type.is_const = true;
                continue;
            }

            if (eat_token_if(pc, TokenIdKeywordVolatile) != nullptr) {
                array->data.array_type.is_volatile = true;
                continue;
            }
            break;
        }

        return array;
    }

    AstNode *ptr = ast_parse_ptr_type_start(pc);
    if (ptr != nullptr) {
        assert(ptr->type == NodeTypePointerType);
        // We might get two pointers from *_ptr_type_start
        AstNode *child = ptr->data.pointer_type.op_expr;
        if (child == nullptr)
            child = ptr;
        while (true) {
            Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero);
            if (allowzero_token != nullptr) {
                child->data.pointer_type.allow_zero_token = allowzero_token;
                continue;
            }

            if (eat_token_if(pc, TokenIdKeywordAlign) != nullptr) {
                expect_token(pc, TokenIdLParen);
                AstNode *align_expr = ast_parse_expr(pc);
                child->data.pointer_type.align_expr = align_expr;
                if (eat_token_if(pc, TokenIdColon) != nullptr) {
                    Token *bit_offset_start = expect_token(pc, TokenIdIntLiteral);
                    expect_token(pc, TokenIdColon);
                    Token *host_int_bytes = expect_token(pc, TokenIdIntLiteral);
                    child->data.pointer_type.bit_offset_start = token_bigint(bit_offset_start);
                    child->data.pointer_type.host_int_bytes = token_bigint(host_int_bytes);
                }
                expect_token(pc, TokenIdRParen);
                continue;
            }

            if (eat_token_if(pc, TokenIdKeywordConst) != nullptr) {
                child->data.pointer_type.is_const = true;
                continue;
            }

            if (eat_token_if(pc, TokenIdKeywordVolatile) != nullptr) {
                child->data.pointer_type.is_volatile = true;
                continue;
            }

            break;
        }

        return ptr;
    }

    return nullptr;
}

// SuffixOp
//     <- LBRACKET Expr (DOT2 Expr?)? RBRACKET
//      / DOT IDENTIFIER
//      / DOTASTERISK
//      / DOTQUESTIONMARK
static AstNode *ast_parse_suffix_op(ParseContext *pc) {
    Token *lbracket = eat_token_if(pc, TokenIdLBracket);
    if (lbracket != nullptr) {
        AstNode *start = ast_expect(pc, ast_parse_expr);
        AstNode *end = nullptr;
        if (eat_token_if(pc, TokenIdEllipsis2) != nullptr) {
            end = ast_parse_expr(pc);
            expect_token(pc, TokenIdRBracket);

            AstNode *res = ast_create_node(pc, NodeTypeSliceExpr, lbracket);
            res->data.slice_expr.start = start;
            res->data.slice_expr.end = end;
            return res;
        }

        expect_token(pc, TokenIdRBracket);

        AstNode *res = ast_create_node(pc, NodeTypeArrayAccessExpr, lbracket);
        res->data.array_access_expr.subscript = start;
        return res;
    }

    Token *dot = eat_token_if(pc, TokenIdDot);
    if (dot != nullptr) {
        if (eat_token_if(pc, TokenIdStar) != nullptr)
            return ast_create_node(pc, NodeTypePtrDeref, dot);
        if (eat_token_if(pc, TokenIdQuestion) != nullptr)
            return ast_create_node(pc, NodeTypeUnwrapOptional, dot);

        Token *ident = expect_token(pc, TokenIdSymbol);
        AstNode *res = ast_create_node(pc, NodeTypeFieldAccessExpr, dot);
        res->data.field_access_expr.field_name = token_buf(ident);
        return res;
    }

    return nullptr;
}

// AsyncPrefix <- KEYWORD_async (LARROW PrefixExpr RARROW)?
static AstNode *ast_parse_async_prefix(ParseContext *pc) {
    Token *async = eat_token_if(pc, TokenIdKeywordAsync);
    if (async == nullptr)
        return nullptr;

    AstNode *res = ast_create_node(pc, NodeTypeFnCallExpr, async);
    res->data.fn_call_expr.is_async = true;
    res->data.fn_call_expr.seen = false;
    if (eat_token_if(pc, TokenIdCmpLessThan) != nullptr) {
        AstNode *prefix_expr = ast_expect(pc, ast_parse_prefix_expr);
        expect_token(pc, TokenIdCmpGreaterThan);
        res->data.fn_call_expr.async_allocator = prefix_expr;
    }

    return res;
}

// FnCallArguments <- LPAREN ExprList RPAREN
static AstNode *ast_parse_fn_call_argumnets(ParseContext *pc) {
    Token *paren = eat_token_if(pc, TokenIdLParen);
    if (paren == nullptr)
        return nullptr;

    ZigList<AstNode *> params = ast_parse_list(pc, TokenIdComma, ast_parse_expr);
    expect_token(pc, TokenIdRParen);

    AstNode *res = ast_create_node(pc, NodeTypeFnCallExpr, paren);
    res->data.fn_call_expr.params = params;
    res->data.fn_call_expr.seen = false;
    return res;
}

// ArrayTypeStart <- LBRACKET Expr? RBRACKET
static AstNode *ast_parse_array_type_start(ParseContext *pc) {
    Token *lbracket = eat_token_if(pc, TokenIdLBracket);
    if (lbracket == nullptr)
        return nullptr;

    AstNode *size = ast_parse_expr(pc);
    expect_token(pc, TokenIdRBracket);
    AstNode *res = ast_create_node(pc, NodeTypeArrayType, lbracket);
    res->data.array_type.size = size;
    return res;
}

// PtrTypeStart
//     <- ASTERISK
//      / ASTERISK2
//      / PTRUNKNOWN
//      / PTRC
static AstNode *ast_parse_ptr_type_start(ParseContext *pc) {
    Token *asterisk = eat_token_if(pc, TokenIdStar);
    if (asterisk != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypePointerType, asterisk);
        res->data.pointer_type.star_token = asterisk;
        return res;
    }

    Token *asterisk2 = eat_token_if(pc, TokenIdStarStar);
    if (asterisk2 != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypePointerType, asterisk2);
        AstNode *res2 = ast_create_node(pc, NodeTypePointerType, asterisk2);
        res->data.pointer_type.star_token = asterisk2;
        res2->data.pointer_type.star_token = asterisk2;
        res->data.pointer_type.op_expr = res2;
        return res;
    }

    Token *multptr = eat_token_if(pc, TokenIdBracketStarBracket);
    if (multptr != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypePointerType, multptr);
        res->data.pointer_type.star_token = multptr;
        return res;
    }

    Token *cptr = eat_token_if(pc, TokenIdBracketStarCBracket);
    if (cptr != nullptr) {
        AstNode *res = ast_create_node(pc, NodeTypePointerType, cptr);
        res->data.pointer_type.star_token = cptr;
        return res;
    }

    return nullptr;
}

// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE
static AstNode *ast_parse_container_decl_auto(ParseContext *pc) {
    AstNode *res = ast_parse_container_decl_type(pc);
    if (res == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLBrace);
    AstNodeContainerDecl members = ast_parse_container_members(pc);
    expect_token(pc, TokenIdRBrace);

    res->data.container_decl.fields = members.fields;
    res->data.container_decl.decls = members.decls;
    return res;
}

// ContainerDeclType
//     <- (KEYWORD_struct / KEYWORD_enum) (LPAREN Expr RPAREN)?
//      / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
static AstNode *ast_parse_container_decl_type(ParseContext *pc) {
    Token *first = eat_token_if(pc, TokenIdKeywordStruct);
    if (first == nullptr)
        first = eat_token_if(pc, TokenIdKeywordEnum);
    if (first != nullptr) {
        AstNode *init_arg_expr = nullptr;
        if (eat_token_if(pc, TokenIdLParen) != nullptr) {
            init_arg_expr = ast_expect(pc, ast_parse_expr);
            expect_token(pc, TokenIdRParen);
        }
        AstNode *res = ast_create_node(pc, NodeTypeContainerDecl, first);
        res->data.container_decl.init_arg_expr = init_arg_expr;
        res->data.container_decl.kind = first->id == TokenIdKeywordStruct
            ? ContainerKindStruct
            : ContainerKindEnum;
        return res;
    }

    first = eat_token_if(pc, TokenIdKeywordUnion);
    if (first != nullptr) {
        AstNode *init_arg_expr = nullptr;
        bool auto_enum = false;
        if (eat_token_if(pc, TokenIdLParen) != nullptr) {
            if (eat_token_if(pc, TokenIdKeywordEnum) != nullptr) {
                auto_enum = true;
                if (eat_token_if(pc, TokenIdLParen) != nullptr) {
                    init_arg_expr = ast_expect(pc, ast_parse_expr);
                    expect_token(pc, TokenIdRParen);
                }
            } else {
                init_arg_expr = ast_expect(pc, ast_parse_expr);
            }

            expect_token(pc, TokenIdRParen);
        }

        AstNode *res = ast_create_node(pc, NodeTypeContainerDecl, first);
        res->data.container_decl.init_arg_expr = init_arg_expr;
        res->data.container_decl.auto_enum = auto_enum;
        res->data.container_decl.kind = ContainerKindUnion;
        return res;
    }

    return nullptr;
}

// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
static AstNode *ast_parse_byte_align(ParseContext *pc) {
    if (eat_token_if(pc, TokenIdKeywordAlign) == nullptr)
        return nullptr;

    expect_token(pc, TokenIdLParen);
    AstNode *res = ast_expect(pc, ast_parse_expr);
    expect_token(pc, TokenIdRParen);
    return res;
}

static void visit_field(AstNode **node, void (*visit)(AstNode **, void *context), void *context) {
    if (*node) {
        visit(node, context);
    }
}

static void visit_node_list(ZigList<AstNode *> *list, void (*visit)(AstNode **, void *context), void *context) {
    if (list) {
        for (size_t i = 0; i < list->length; i += 1) {
            visit(&list->at(i), context);
        }
    }
}

void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *context), void *context) {
    switch (node->type) {
        case NodeTypeFnProto:
            visit_field(&node->data.fn_proto.return_type, visit, context);
            visit_node_list(&node->data.fn_proto.params, visit, context);
            visit_field(&node->data.fn_proto.align_expr, visit, context);
            visit_field(&node->data.fn_proto.section_expr, visit, context);
            visit_field(&node->data.fn_proto.async_allocator_type, visit, context);
            break;
        case NodeTypeFnDef:
            visit_field(&node->data.fn_def.fn_proto, visit, context);
            visit_field(&node->data.fn_def.body, visit, context);
            break;
        case NodeTypeParamDecl:
            visit_field(&node->data.param_decl.type, visit, context);
            break;
        case NodeTypeBlock:
            visit_node_list(&node->data.block.statements, visit, context);
            break;
        case NodeTypeGroupedExpr:
            visit_field(&node->data.grouped_expr, visit, context);
            break;
        case NodeTypeReturnExpr:
            visit_field(&node->data.return_expr.expr, visit, context);
            break;
        case NodeTypeDefer:
            visit_field(&node->data.defer.expr, visit, context);
            break;
        case NodeTypeVariableDeclaration:
            visit_field(&node->data.variable_declaration.type, visit, context);
            visit_field(&node->data.variable_declaration.expr, visit, context);
            visit_field(&node->data.variable_declaration.align_expr, visit, context);
            visit_field(&node->data.variable_declaration.section_expr, visit, context);
            break;
        case NodeTypeTestDecl:
            visit_field(&node->data.test_decl.body, visit, context);
            break;
        case NodeTypeBinOpExpr:
            visit_field(&node->data.bin_op_expr.op1, visit, context);
            visit_field(&node->data.bin_op_expr.op2, visit, context);
            break;
        case NodeTypeUnwrapErrorExpr:
            visit_field(&node->data.unwrap_err_expr.op1, visit, context);
            visit_field(&node->data.unwrap_err_expr.symbol, visit, context);
            visit_field(&node->data.unwrap_err_expr.op2, visit, context);
            break;
        case NodeTypeIntLiteral:
            // none
            break;
        case NodeTypeFloatLiteral:
            // none
            break;
        case NodeTypeStringLiteral:
            // none
            break;
        case NodeTypeCharLiteral:
            // none
            break;
        case NodeTypeSymbol:
            // none
            break;
        case NodeTypePrefixOpExpr:
            visit_field(&node->data.prefix_op_expr.primary_expr, visit, context);
            break;
        case NodeTypeFnCallExpr:
            visit_field(&node->data.fn_call_expr.fn_ref_expr, visit, context);
            visit_node_list(&node->data.fn_call_expr.params, visit, context);
            visit_field(&node->data.fn_call_expr.async_allocator, visit, context);
            break;
        case NodeTypeArrayAccessExpr:
            visit_field(&node->data.array_access_expr.array_ref_expr, visit, context);
            visit_field(&node->data.array_access_expr.subscript, visit, context);
            break;
        case NodeTypeSliceExpr:
            visit_field(&node->data.slice_expr.array_ref_expr, visit, context);
            visit_field(&node->data.slice_expr.start, visit, context);
            visit_field(&node->data.slice_expr.end, visit, context);
            break;
        case NodeTypeFieldAccessExpr:
            visit_field(&node->data.field_access_expr.struct_expr, visit, context);
            break;
        case NodeTypePtrDeref:
            visit_field(&node->data.ptr_deref_expr.target, visit, context);
            break;
        case NodeTypeUnwrapOptional:
            visit_field(&node->data.unwrap_optional.expr, visit, context);
            break;
        case NodeTypeUse:
            visit_field(&node->data.use.expr, visit, context);
            break;
        case NodeTypeBoolLiteral:
            // none
            break;
        case NodeTypeNullLiteral:
            // none
            break;
        case NodeTypeUndefinedLiteral:
            // none
            break;
        case NodeTypeIfBoolExpr:
            visit_field(&node->data.if_bool_expr.condition, visit, context);
            visit_field(&node->data.if_bool_expr.then_block, visit, context);
            visit_field(&node->data.if_bool_expr.else_node, visit, context);
            break;
        case NodeTypeIfErrorExpr:
            visit_field(&node->data.if_err_expr.target_node, visit, context);
            visit_field(&node->data.if_err_expr.then_node, visit, context);
            visit_field(&node->data.if_err_expr.else_node, visit, context);
            break;
        case NodeTypeIfOptional:
            visit_field(&node->data.test_expr.target_node, visit, context);
            visit_field(&node->data.test_expr.then_node, visit, context);
            visit_field(&node->data.test_expr.else_node, visit, context);
            break;
        case NodeTypeWhileExpr:
            visit_field(&node->data.while_expr.condition, visit, context);
            visit_field(&node->data.while_expr.body, visit, context);
            break;
        case NodeTypeForExpr:
            visit_field(&node->data.for_expr.elem_node, visit, context);
            visit_field(&node->data.for_expr.array_expr, visit, context);
            visit_field(&node->data.for_expr.index_node, visit, context);
            visit_field(&node->data.for_expr.body, visit, context);
            break;
        case NodeTypeSwitchExpr:
            visit_field(&node->data.switch_expr.expr, visit, context);
            visit_node_list(&node->data.switch_expr.prongs, visit, context);
            break;
        case NodeTypeSwitchProng:
            visit_node_list(&node->data.switch_prong.items, visit, context);
            visit_field(&node->data.switch_prong.var_symbol, visit, context);
            visit_field(&node->data.switch_prong.expr, visit, context);
            break;
        case NodeTypeSwitchRange:
            visit_field(&node->data.switch_range.start, visit, context);
            visit_field(&node->data.switch_range.end, visit, context);
            break;
        case NodeTypeCompTime:
            visit_field(&node->data.comptime_expr.expr, visit, context);
            break;
        case NodeTypeBreak:
            // none
            break;
        case NodeTypeContinue:
            // none
            break;
        case NodeTypeUnreachable:
            // none
            break;
        case NodeTypeAsmExpr:
            for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) {
                AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
                visit_field(&asm_input->expr, visit, context);
            }
            for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1) {
                AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
                visit_field(&asm_output->return_type, visit, context);
            }
            break;
        case NodeTypeContainerDecl:
            visit_node_list(&node->data.container_decl.fields, visit, context);
            visit_node_list(&node->data.container_decl.decls, visit, context);
            visit_field(&node->data.container_decl.init_arg_expr, visit, context);
            break;
        case NodeTypeStructField:
            visit_field(&node->data.struct_field.type, visit, context);
            visit_field(&node->data.struct_field.value, visit, context);
            break;
        case NodeTypeContainerInitExpr:
            visit_field(&node->data.container_init_expr.type, visit, context);
            visit_node_list(&node->data.container_init_expr.entries, visit, context);
            break;
        case NodeTypeStructValueField:
            visit_field(&node->data.struct_val_field.expr, visit, context);
            break;
        case NodeTypeArrayType:
            visit_field(&node->data.array_type.size, visit, context);
            visit_field(&node->data.array_type.child_type, visit, context);
            visit_field(&node->data.array_type.align_expr, visit, context);
            break;
        case NodeTypePromiseType:
            visit_field(&node->data.promise_type.payload_type, visit, context);
            break;
        case NodeTypeErrorType:
            // none
            break;
        case NodeTypePointerType:
            visit_field(&node->data.pointer_type.align_expr, visit, context);
            visit_field(&node->data.pointer_type.op_expr, visit, context);
            break;
        case NodeTypeErrorSetDecl:
            visit_node_list(&node->data.err_set_decl.decls, visit, context);
            break;
        case NodeTypeCancel:
            visit_field(&node->data.cancel_expr.expr, visit, context);
            break;
        case NodeTypeResume:
            visit_field(&node->data.resume_expr.expr, visit, context);
            break;
        case NodeTypeAwaitExpr:
            visit_field(&node->data.await_expr.expr, visit, context);
            break;
        case NodeTypeSuspend:
            visit_field(&node->data.suspend.block, visit, context);
            break;
        case NodeTypeEnumLiteral:
            break;
    }
}
