#ifndef LIB_COMPILER_HEADER
#define LIB_COMPILER_HEADER

#define LIB_COMPILER_MAJOR 0
#define LIB_COMPILER_MINOR 6

#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stddef.h>

#ifndef LC_THREAD_LOCAL // @override
    #if defined(__cplusplus) && __cplusplus >= 201103L
        #define LC_THREAD_LOCAL thread_local
    #elif defined(__GNUC__)
        #define LC_THREAD_LOCAL __thread
    #elif defined(_MSC_VER)
        #define LC_THREAD_LOCAL __declspec(thread)
    #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
        #define LC_THREAD_LOCAL _Thread_local
    #elif defined(__TINYC__)
        #define LC_THREAD_LOCAL _Thread_local
    #else
        #error Couldnt figure out thread local, needs to be provided manually
    #endif
#endif

#ifndef LC_FUNCTION // @override
    #define LC_FUNCTION
#endif

#ifndef LC_String // @override
typedef struct {
    char   *str;
    int64_t len;
} LC_String;
#endif

#ifndef LC_USE_CUSTOM_ARENA // @override
typedef struct LC_VMemory   LC_VMemory;
typedef struct LC_TempArena LC_TempArena;
typedef struct LC_Arena     LC_Arena;
typedef struct LC_SourceLoc LC_SourceLoc;

struct LC_VMemory {
    size_t   commit;
    size_t   reserve;
    uint8_t *data;
};

struct LC_Arena {
    LC_VMemory memory;
    int        alignment;
    size_t     len;
    size_t     base_len; // When popping to 0 this is the minimum "len" value
                         // It's so that Bootstrapped arena won't delete itself when Reseting.
};

struct LC_TempArena {
    LC_Arena *arena;
    size_t    pos;
};
#endif

typedef struct LC_MapEntry               LC_MapEntry;
typedef struct LC_Map                    LC_Map;
typedef struct LC_AST                    LC_AST;
typedef struct LC_ASTPackage             LC_ASTPackage;
typedef struct LC_ASTNoteList            LC_ASTNoteList;
typedef struct LC_ExprCompo              LC_ExprCompo;
typedef union LC_Val                     LC_Val;
typedef struct LC_ExprIdent              LC_ExprIdent;
typedef struct LC_ExprUnary              LC_ExprUnary;
typedef struct LC_ExprBinary             LC_ExprBinary;
typedef struct LC_ExprField              LC_ExprField;
typedef struct LC_ExprIndex              LC_ExprIndex;
typedef struct LC_ExprCompoItem          LC_ExprCompoItem;
typedef struct LC_ExprType               LC_ExprType;
typedef struct LC_ExprCast               LC_ExprCast;
typedef struct LC_ExprNote               LC_ExprNote;
typedef struct LC_StmtBlock              LC_StmtBlock;
typedef struct LC_StmtFor                LC_StmtFor;
typedef struct LC_StmtDefer              LC_StmtDefer;
typedef struct LC_StmtSwitch             LC_StmtSwitch;
typedef struct LC_StmtCase               LC_StmtCase;
typedef struct LC_StmtIf                 LC_StmtIf;
typedef struct LC_StmtBreak              LC_StmtBreak;
typedef struct LC_StmtAssign             LC_StmtAssign;
typedef struct LC_StmtExpr               LC_StmtExpr;
typedef struct LC_StmtVar                LC_StmtVar;
typedef struct LC_StmtConst              LC_StmtConst;
typedef struct LC_StmtReturn             LC_StmtReturn;
typedef struct LC_StmtNote               LC_StmtNote;
typedef struct LC_TypespecArray          LC_TypespecArray;
typedef struct LC_TypespecProc           LC_TypespecProc;
typedef struct LC_TypespecAggMem         LC_TypespecAggMem;
typedef struct LC_TypespecProcArg        LC_TypespecProcArg;
typedef struct LC_DeclBase               LC_DeclBase;
typedef struct LC_DeclProc               LC_DeclProc;
typedef struct LC_DeclTypedef            LC_DeclTypedef;
typedef struct LC_DeclAgg                LC_DeclAgg;
typedef struct LC_DeclVar                LC_DeclVar;
typedef struct LC_DeclConst              LC_DeclConst;
typedef struct LC_DeclNote               LC_DeclNote;
typedef union LC_ASTValue                LC_ASTValue;
typedef struct LC_TypeAndVal             LC_TypeAndVal;
typedef struct LC_TypeArray              LC_TypeArray;
typedef struct LC_TypePtr                LC_TypePtr;
typedef struct LC_TypeMemberList         LC_TypeMemberList;
typedef struct LC_TypeProc               LC_TypeProc;
typedef struct LC_TypeAgg                LC_TypeAgg;
typedef union LC_TypeValue               LC_TypeValue;
typedef struct LC_Type                   LC_Type;
typedef struct LC_TypeMember             LC_TypeMember;
typedef struct LC_Decl                   LC_Decl;
typedef struct LC_DeclStack              LC_DeclStack;
typedef struct LC_Map                    DeclScope;
typedef struct LC_ResolvedCompo          LC_ResolvedCompo;
typedef struct LC_ResolvedCompoItem      LC_ResolvedCompoItem;
typedef struct LC_ResolvedCompoArrayItem LC_ResolvedCompoArrayItem;
typedef struct LC_ResolvedArrayCompo     LC_ResolvedArrayCompo;
typedef union LC_TokenVal                LC_TokenVal;
typedef struct LC_Token                  LC_Token;
typedef struct LC_Lex                    LC_Lex;
typedef struct LC_Parser                 LC_Parser;
typedef struct LC_Resolver               LC_Resolver;
typedef struct LC_Lang                   LC_Lang;
typedef struct LC_ASTRef                 LC_ASTRef;
typedef struct LC_ASTRefList             LC_ASTRefList;
typedef struct LC_ASTFile                LC_ASTFile;
typedef LC_Val                           Expr_Atom;
typedef LC_TypespecArray                 Typespec_Pointer;
typedef uintptr_t                        LC_Intern;
typedef struct LC_GlobImport             LC_GlobImport;
typedef struct LC_UTF32Result            LC_UTF32Result;
typedef struct LC_UTF8Result             LC_UTF8Result;
typedef struct LC_UTF16Result            LC_UTF16Result;

typedef struct LC_StringNode LC_StringNode;
struct LC_StringNode {
    LC_StringNode *next;
    LC_String      string;
};

typedef struct LC_StringList LC_StringList;
struct LC_StringList {
    int64_t        node_count;
    int64_t        char_count;
    LC_StringNode *first;
    LC_StringNode *last;
};

typedef struct LC_String16 {
    wchar_t *str;
    int64_t  len;
} LC_String16;

struct LC_MapEntry {
    uint64_t key;
    uint64_t value;
};

struct LC_Map {
    LC_Arena    *arena;
    LC_MapEntry *entries;
    int          cap;
    int          len;
};

typedef struct LC_BigInt LC_BigInt;
struct LC_BigInt {
    unsigned digit_count;
    bool     is_negative;
    union {
        uint64_t  digit;
        uint64_t *digits;
    };
};

typedef enum LC_ASTKind {
    LC_ASTKind_Null,
    LC_ASTKind_Error,
    LC_ASTKind_Note,
    LC_ASTKind_NoteList,
    LC_ASTKind_File,
    LC_ASTKind_Package,
    LC_ASTKind_Ignore,
    LC_ASTKind_TypespecProcArg,
    LC_ASTKind_TypespecAggMem,
    LC_ASTKind_ExprCallItem,
    LC_ASTKind_ExprCompoundItem,
    LC_ASTKind_ExprNote, // a: int = #`sizeof(int)`;
    LC_ASTKind_StmtSwitchCase,
    LC_ASTKind_StmtSwitchDefault,
    LC_ASTKind_StmtElseIf,
    LC_ASTKind_StmtElse,
    LC_ASTKind_GlobImport,
    LC_ASTKind_DeclNote,
    LC_ASTKind_DeclProc,
    LC_ASTKind_DeclStruct,
    LC_ASTKind_DeclUnion,
    LC_ASTKind_DeclVar,
    LC_ASTKind_DeclConst,
    LC_ASTKind_DeclTypedef,
    LC_ASTKind_TypespecIdent,
    LC_ASTKind_TypespecField,
    LC_ASTKind_TypespecPointer,
    LC_ASTKind_TypespecArray,
    LC_ASTKind_TypespecProc,
    LC_ASTKind_StmtBlock,
    LC_ASTKind_StmtNote,
    LC_ASTKind_StmtReturn,
    LC_ASTKind_StmtBreak,
    LC_ASTKind_StmtContinue,
    LC_ASTKind_StmtDefer,
    LC_ASTKind_StmtFor,
    LC_ASTKind_StmtIf,
    LC_ASTKind_StmtSwitch,
    LC_ASTKind_StmtAssign,
    LC_ASTKind_StmtExpr,
    LC_ASTKind_StmtVar,
    LC_ASTKind_StmtConst,
    LC_ASTKind_ExprIdent,
    LC_ASTKind_ExprString,
    LC_ASTKind_ExprInt,
    LC_ASTKind_ExprFloat,
    LC_ASTKind_ExprBool,
    LC_ASTKind_ExprType,
    LC_ASTKind_ExprBinary,
    LC_ASTKind_ExprUnary,
    LC_ASTKind_ExprBuiltin,
    LC_ASTKind_ExprCall,
    LC_ASTKind_ExprCompound,
    LC_ASTKind_ExprCast,
    LC_ASTKind_ExprField,
    LC_ASTKind_ExprIndex,
    LC_ASTKind_ExprAddPtr,
    LC_ASTKind_ExprGetValueOfPointer,
    LC_ASTKind_ExprGetPointerOfValue,
    LC_ASTKind_Count,
    LC_ASTKind_FirstExpr     = LC_ASTKind_ExprIdent,
    LC_ASTKind_LastExpr      = LC_ASTKind_ExprGetPointerOfValue,
    LC_ASTKind_FirstStmt     = LC_ASTKind_StmtBlock,
    LC_ASTKind_LastStmt      = LC_ASTKind_StmtConst,
    LC_ASTKind_FirstTypespec = LC_ASTKind_TypespecIdent,
    LC_ASTKind_LastTypespec  = LC_ASTKind_TypespecProc,
    LC_ASTKind_FirstDecl     = LC_ASTKind_DeclProc,
    LC_ASTKind_LastDecl      = LC_ASTKind_DeclTypedef,
} LC_ASTKind;

typedef enum LC_TypeKind {
    LC_TypeKind_char,
    LC_TypeKind_uchar,
    LC_TypeKind_short,
    LC_TypeKind_ushort,
    LC_TypeKind_bool,
    LC_TypeKind_int,
    LC_TypeKind_uint,
    LC_TypeKind_long,
    LC_TypeKind_ulong,
    LC_TypeKind_llong,
    LC_TypeKind_ullong,
    LC_TypeKind_float,
    LC_TypeKind_double,
    LC_TypeKind_void,
    LC_TypeKind_Struct,
    LC_TypeKind_Union,
    LC_TypeKind_Pointer,
    LC_TypeKind_Array,
    LC_TypeKind_Proc,
    LC_TypeKind_UntypedInt,
    LC_TypeKind_UntypedFloat,
    LC_TypeKind_UntypedString,
    LC_TypeKind_Incomplete,
    LC_TypeKind_Completing,
    LC_TypeKind_Error,
    T_TotalCount,
    T_NumericCount = LC_TypeKind_void,
    T_Count        = LC_TypeKind_void + 1,
} LC_TypeKind;

typedef enum LC_DeclState {
    LC_DeclState_Unresolved,
    LC_DeclState_Resolving,
    LC_DeclState_Resolved,
    LC_DeclState_ResolvedBody, // proc
    LC_DeclState_Error,
    LC_DeclState_Count,
} LC_DeclState;

typedef enum LC_DeclKind {
    LC_DeclKind_Error,
    LC_DeclKind_Type,
    LC_DeclKind_Const,
    LC_DeclKind_Var,
    LC_DeclKind_Proc,
    LC_DeclKind_Import,
    LC_DeclKind_Count,
} LC_DeclKind;

typedef enum LC_TokenKind {
    LC_TokenKind_EOF,
    LC_TokenKind_Error,
    LC_TokenKind_Comment,
    LC_TokenKind_DocComment,
    LC_TokenKind_FileDocComment,
    LC_TokenKind_PackageDocComment,
    LC_TokenKind_Note,
    LC_TokenKind_Hash,
    LC_TokenKind_Ident,
    LC_TokenKind_Keyword,
    LC_TokenKind_String,
    LC_TokenKind_RawString,
    LC_TokenKind_Int,
    LC_TokenKind_Float,
    LC_TokenKind_Unicode,
    LC_TokenKind_OpenParen,
    LC_TokenKind_CloseParen,
    LC_TokenKind_OpenBrace,
    LC_TokenKind_CloseBrace,
    LC_TokenKind_OpenBracket,
    LC_TokenKind_CloseBracket,
    LC_TokenKind_Comma,
    LC_TokenKind_Question,
    LC_TokenKind_Semicolon,
    LC_TokenKind_Dot,
    LC_TokenKind_ThreeDots,
    LC_TokenKind_Colon,
    LC_TokenKind_Mul,
    LC_TokenKind_Div,
    LC_TokenKind_Mod,
    LC_TokenKind_LeftShift,
    LC_TokenKind_RightShift,
    LC_TokenKind_Add,
    LC_TokenKind_Sub,
    LC_TokenKind_Equals,
    LC_TokenKind_LesserThen,
    LC_TokenKind_GreaterThen,
    LC_TokenKind_LesserThenEq,
    LC_TokenKind_GreaterThenEq,
    LC_TokenKind_NotEquals,
    LC_TokenKind_BitAnd,
    LC_TokenKind_BitOr,
    LC_TokenKind_BitXor,
    LC_TokenKind_And,
    LC_TokenKind_Or,
    LC_TokenKind_AddPtr,
    LC_TokenKind_Neg,
    LC_TokenKind_Not,
    LC_TokenKind_Assign,
    LC_TokenKind_DivAssign,
    LC_TokenKind_MulAssign,
    LC_TokenKind_ModAssign,
    LC_TokenKind_SubAssign,
    LC_TokenKind_AddAssign,
    LC_TokenKind_BitAndAssign,
    LC_TokenKind_BitOrAssign,
    LC_TokenKind_BitXorAssign,
    LC_TokenKind_LeftShiftAssign,
    LC_TokenKind_RightShiftAssign,
    LC_TokenKind_Count,
} LC_TokenKind;

struct LC_ASTFile {
    LC_AST *package;
    LC_Lex *x;

    LC_AST *fimport;
    LC_AST *limport;
    LC_AST *fdecl;
    LC_AST *ldecl;

    LC_Token *doc_comment;
};

// This extension thing is only to minimize package ast size!
// I want all nodes to be equal in size, this way a lot of things are
// easier. You can loop through all the ast nodes easily and stuff like that.
typedef struct LC_ASTPackageExt LC_ASTPackageExt;
struct LC_ASTPackageExt {
    LC_StringList injected_filepaths; // to sidestep regular file finding, implement single file packages etc.
    LC_Token     *doc_comment;
    LC_DeclState  state;
    LC_String     path;

    // These are resolved later:
    // @todo: add foreign name?
    LC_Decl   *first_ordered;
    LC_Decl   *last_ordered;
    DeclScope *scope;
};

struct LC_ASTPackage {
    LC_AST           *ffile;
    LC_AST           *lfile;
    LC_Intern         name;
    LC_ASTPackageExt *ext;
};

struct LC_ASTNoteList {
    LC_AST *first;
    LC_AST *last;
};

struct LC_ResolvedCompo {
    int                   count;
    LC_ResolvedCompoItem *first;
    LC_ResolvedCompoItem *last;
};

struct LC_ResolvedCompoItem {
    LC_ResolvedCompoItem *next;
    LC_AST               *comp; // for proc this may be null because we might match default value
    LC_AST               *expr; // for proc this is very important, the result, ordered expression
    LC_TypeMember        *t;
    bool                  varg;
    bool                  defaultarg;
};

struct LC_ResolvedCompoArrayItem {
    LC_ResolvedCompoArrayItem *next;
    LC_AST                    *comp;
    int                        index;
};

struct LC_ResolvedArrayCompo {
    int                        count;
    LC_ResolvedCompoArrayItem *first;
    LC_ResolvedCompoArrayItem *last;
};

struct LC_ExprCompo {
    LC_AST *name;  // :name{thing} or name(thing)
    LC_AST *first; // {LC_ExprCompoItem, LC_ExprCompoItem}
    LC_AST *last;
    int     size;
    union {
        LC_ResolvedCompo      *resolved_items;
        LC_ResolvedArrayCompo *resolved_array_items;
    };
};

struct LC_ExprCompoItem {
    LC_Intern name;
    LC_AST   *index;
    LC_AST   *expr;
};

// clang-format off
union  LC_Val             { LC_BigInt i; double d; LC_Intern name; };
struct LC_ExprIdent       { LC_Intern name; LC_Decl *resolved_decl; };
struct LC_ExprUnary       { LC_TokenKind op; LC_AST *expr; };
struct LC_ExprBinary      { LC_TokenKind op; LC_AST *left; LC_AST *right; };
struct LC_ExprField       { LC_AST *left; LC_Intern right; LC_Decl *resolved_decl; LC_Decl *parent_decl; };
struct LC_ExprIndex       { LC_AST *base; LC_AST *index; };
struct LC_ExprType        { LC_AST *type; };
struct LC_ExprCast        { LC_AST *type; LC_AST *expr; };
struct LC_ExprNote        { LC_AST *expr; }; // v := #c(``)

typedef enum { SBLK_Norm, SBLK_Loop, SBLK_Proc, SBLK_Defer } LC_StmtBlockKind;
struct LC_StmtBlock       { LC_AST *first; LC_AST *last; LC_AST *first_defer; LC_StmtBlockKind kind; LC_Intern name; };
struct LC_StmtFor         { LC_AST *init; LC_AST *cond; LC_AST *inc; LC_AST *body; };
struct LC_StmtDefer       { LC_AST *next; LC_AST *body; };
struct LC_StmtSwitch      { LC_AST *first; LC_AST *last; LC_AST *expr; int total_switch_case_count; };
struct LC_StmtCase        { LC_AST *first; LC_AST *last; LC_AST *body; };
// 'else if' and 'else' is avaialable from 'first'.
// The else_if and else are also LC_StmtIf but
// have different kinds and don't use 'first', 'last'
struct LC_StmtIf          { LC_AST *expr; LC_AST *body; LC_AST *first; LC_AST *last; };
struct LC_StmtBreak       { LC_Intern name; };
struct LC_StmtAssign      { LC_TokenKind op; LC_AST *left; LC_AST *right; };
struct LC_StmtExpr        { LC_AST *expr; };
struct LC_StmtVar         { LC_AST *expr; LC_AST *type; LC_Intern name; LC_Decl *resolved_decl; };
struct LC_StmtConst       { LC_AST *expr; LC_Intern name; };
struct LC_StmtReturn      { LC_AST *expr; };
struct LC_StmtNote        { LC_AST *expr; }; // #c(``) in block
struct LC_TypespecArray   { LC_AST *base; LC_AST *index; };
struct LC_TypespecProc    { LC_AST *first; LC_AST *last; LC_AST *ret; bool vargs; bool vargs_any_promotion; };
struct LC_TypespecAggMem  { LC_Intern name; LC_AST *type; };
struct LC_TypespecProcArg { LC_Intern name; LC_AST *type; LC_AST *expr; LC_Decl *resolved_decl; };
struct LC_DeclBase        { LC_Intern name; LC_Token *doc_comment; LC_Decl *resolved_decl; };
struct LC_DeclProc        { LC_DeclBase base; LC_AST *body; LC_AST *type; };
struct LC_DeclTypedef     { LC_DeclBase base; LC_AST *type; };
struct LC_DeclAgg         { LC_DeclBase base; LC_AST *first; LC_AST *last; };
struct LC_DeclVar         { LC_DeclBase base; LC_AST *expr; LC_AST *type; };
struct LC_DeclConst       { LC_DeclBase base; LC_AST *expr; };
struct LC_DeclNote        { LC_DeclBase base; LC_AST *expr; bool processed; }; // #c(``) in file note list
struct LC_GlobImport      { LC_Intern name; LC_Intern path; bool resolved; LC_Decl *resolved_decl; };

struct LC_ASTRef          { LC_ASTRef *next; LC_ASTRef *prev; LC_AST *ast; };
struct LC_ASTRefList      { LC_ASTRef *first; LC_ASTRef *last; };
// clang-format on

struct LC_TypeAndVal {
    LC_Type *type;
    union {
        LC_Val v;
        union {
            LC_BigInt i;
            double    d;
            LC_Intern name;
        };
    };
};

struct LC_AST {
    LC_ASTKind kind;
    uint32_t   id;
    LC_AST    *next;
    LC_AST    *prev;
    LC_AST    *notes;
    LC_Token  *pos;

    LC_TypeAndVal const_val;
    LC_Type      *type;
    union {
        LC_ASTFile         afile;
        LC_ASTPackage      apackage;
        LC_ASTNoteList     anote_list;
        LC_ExprCompo       anote;
        LC_Val             eatom;
        LC_ExprIdent       eident;
        LC_ExprUnary       eunary;
        LC_ExprBinary      ebinary;
        LC_ExprField       efield;
        LC_ExprIndex       eindex;
        LC_ExprCompo       ecompo;
        LC_ExprCompoItem   ecompo_item;
        LC_ExprType        etype;
        LC_ExprCast        ecast;
        LC_ExprNote        enote;
        LC_StmtBlock       sblock;
        LC_StmtFor         sfor;
        LC_StmtDefer       sdefer;
        LC_StmtSwitch      sswitch;
        LC_StmtCase        scase;
        LC_StmtIf          sif;
        LC_StmtBreak       sbreak;
        LC_StmtBreak       scontinue;
        LC_StmtAssign      sassign;
        LC_StmtExpr        sexpr;
        LC_StmtVar         svar;
        LC_StmtConst       sconst;
        LC_StmtReturn      sreturn;
        LC_StmtNote        snote;
        LC_ExprIdent       tident;
        LC_TypespecArray   tarray;
        LC_TypespecArray   tpointer;
        LC_TypespecProc    tproc;
        LC_TypespecAggMem  tagg_mem;
        LC_TypespecProcArg tproc_arg;
        LC_DeclBase        dbase;
        LC_DeclProc        dproc;
        LC_DeclTypedef     dtypedef;
        LC_DeclAgg         dagg;
        LC_DeclVar         dvar;
        LC_DeclConst       dconst;
        LC_DeclNote        dnote;
        LC_GlobImport      gimport;
    };
};

struct LC_TypeArray {
    LC_Type *base;
    int      size;
};

struct LC_TypePtr {
    LC_Type *base;
};

struct LC_TypeMemberList {
    LC_TypeMember *first;
    LC_TypeMember *last;
    int            count;
};

struct LC_TypeProc {
    LC_TypeMemberList args;
    LC_Type          *ret;
    bool              vargs;
    bool              vargs_any_promotion;
};

struct LC_TypeAgg {
    LC_TypeMemberList mems;
};

struct LC_Type {
    LC_TypeKind kind;
    int         size;
    int         align;
    int         is_unsigned;
    int         id;
    int         padding;
    LC_Decl    *decl;
    union {
        LC_TypeArray tarray;
        LC_TypePtr   tptr;
        LC_TypeProc  tproc;
        LC_TypeAgg   tagg;
        LC_Type     *tbase;
        LC_Type     *tutdefault;
    };
};

struct LC_TypeMember {
    LC_TypeMember *next;
    LC_TypeMember *prev;
    LC_Intern      name;
    LC_Type       *type;
    LC_AST        *default_value_expr;
    LC_AST        *ast;
    int            offset;
};

struct LC_Decl {
    LC_DeclKind  kind;
    LC_DeclState state;
    uint8_t      is_foreign;

    LC_Decl  *next;
    LC_Decl  *prev;
    LC_Intern name;
    LC_Intern foreign_name;
    LC_AST   *ast;
    LC_AST   *package;

    LC_TypeMember *type_member;
    DeclScope     *scope;
    LC_Decl       *typedef_renamed_type_decl;
    union {
        LC_TypeAndVal val;
        struct {
            LC_Type *type;
            LC_Val   v;
        };
    };
};

typedef struct {
    LC_Arena *arena;
    LC_AST  **data;
    int       cap;
    int       len;
} LC_ASTArray;

typedef struct LC_ASTWalker LC_ASTWalker;
typedef void                LC_ASTWalkProc(LC_ASTWalker *, LC_AST *);
struct LC_ASTWalker {
    LC_ASTArray stack;

    int inside_builtin;
    int inside_note;

    uint8_t         visit_notes;
    uint8_t         depth_first;
    uint8_t         dont_recurse; // breathfirst only
    void           *user_data;
    LC_ASTWalkProc *proc;
};

struct LC_DeclStack {
    LC_Decl **stack;
    int       len;
    int       cap;
};

union LC_TokenVal {
    LC_BigInt i;
    double    f64;
    LC_Intern ident;
};

struct LC_Token {
    LC_TokenKind kind;
    int          line;
    int          column;
    int          len;
    char        *str;
    LC_Lex      *lex;
    union {
        LC_BigInt i;
        double    f64;
        LC_Intern ident;
    };
};

struct LC_Lex {
    char     *at;
    char     *begin;
    LC_Intern file;
    int       line;
    int       column;
    LC_Token *tokens;
    int       token_count;
    bool      insert_semicolon;
};

struct LC_Parser {
    LC_Token *at;
    LC_Token *begin;
    LC_Token *end;
    LC_Lex   *x;
};

struct LC_Resolver {
    LC_Type     *compo_context_type;
    int          compo_context_array_size;
    LC_Type     *expected_ret_type;
    LC_AST      *package;
    DeclScope   *active_scope;
    LC_DeclStack locals;
    LC_Map       duplicate_map; // currently used for finding duplicates in compos
    LC_ASTArray  stmt_block_stack;
};

typedef struct LC_Printer LC_Printer;
struct LC_Printer {
    LC_Arena     *arena;
    LC_StringList list;
    int           indent;
    LC_Intern     last_filename;
    int           last_line_num;
    LC_ASTArray   out_block_stack;
};

const int LC_OPF_Error    = 1;
const int LC_OPF_UTConst  = 2;
const int LC_OPF_LValue   = 4;
const int LC_OPF_Const    = 8;
const int LC_OPF_Returned = 16;

// warning: I introduced a null compare using the values in the operand
// make sure to revisit that when modifying the struct
typedef struct {
    int flags;
    union {
        LC_Decl      *decl;
        LC_TypeAndVal val;
        struct {
            LC_Type *type;
            LC_Val   v;
        };
    };
} LC_Operand;

typedef enum {
    LC_ARCH_Invalid,
    LC_ARCH_X64,
    LC_ARCH_X86,
    LC_ARCH_Count,
} LC_ARCH;

typedef enum {
    LC_GEN_Invalid,
    LC_GEN_C,
    LC_GEN_Count,
} LC_GEN;

typedef enum {
    LC_OS_Invalid,
    LC_OS_WINDOWS,
    LC_OS_LINUX,
    LC_OS_MAC,
    LC_OS_Count,
} LC_OS;

typedef struct {
    LC_String path;
    LC_String content;
    int       line;
} LoadedFile;

typedef enum {
    LC_OPResult_Error,
    LC_OPResult_Ok,
    LC_OPResult_Bool,
} LC_OPResult;

typedef struct {
    int left;
    int right;
} LC_Precedence;

typedef enum {
    LC_PrecedenceKind_Prefix,
    LC_PrecedenceKind_Infix,
    LC_PrecedenceKind_Postfix,
} LC_PrecedenceKind;

typedef enum {
    LC_CmpRes_LT,
    LC_CmpRes_GT,
    LC_CmpRes_EQ,
} LC_CmpRes;

typedef struct LC_FileIter LC_FileIter;
struct LC_FileIter {
    bool      is_valid;
    bool      is_directory;
    LC_String absolute_path;
    LC_String relative_path;
    LC_String filename;

    LC_String path;
    LC_Arena *arena;
    union {
        struct LC_Win32_FileIter *w32;
        void                     *dir;
    };
};

#define LC_LIST_KEYWORDS \
    X(for)               \
    X(import)            \
    X(if)                \
    X(else)              \
    X(return)            \
    X(defer)             \
    X(continue)          \
    X(break)             \
    X(default)           \
    X(case)              \
    X(typedef)           \
    X(switch)            \
    X(proc)              \
    X(struct)            \
    X(union)             \
    X(addptr)            \
    X(true)              \
    X(false)

#define LC_LIST_INTERNS    \
    X(foreign, true)       \
    X(api, true)           \
    X(weak, true)          \
    X(c, true)             \
    X(fallthrough, true)   \
    X(packed, true)        \
    X(not_init, true)      \
    X(unused, true)        \
    X(static_assert, true) \
    X(str, true)           \
    X(thread_local, true)  \
    X(dont_mangle, true)   \
    X(build_if, true)      \
    X(Any, false)          \
    X(main, false)         \
    X(debug_break, false)  \
    X(sizeof, false)       \
    X(alignof, false)      \
    X(typeof, false)       \
    X(lengthof, false)     \
    X(offsetof, false)

#define LC_LIST_TYPES \
    X(char, false)    \
    X(uchar, true)    \
    X(short, false)   \
    X(ushort, true)   \
    X(bool, false)    \
    X(int, false)     \
    X(uint, true)     \
    X(long, false)    \
    X(ulong, true)    \
    X(llong, false)   \
    X(ullong, true)   \
    X(float, false)   \
    X(double, false)

struct LC_Lang {
    LC_Arena *arena;

    LC_Arena *lex_arena;

    LC_Arena *ast_arena;
    int       ast_count;

    LC_Arena *decl_arena;
    int       decl_count;

    LC_Arena *type_arena;
    int       type_count;

    int errors;

    int    typeids;
    LC_Map type_map;

    LC_AST     *builtin_package;
    LC_Resolver resolver;
    LC_Parser  *parser;

    // Package registry
    LC_AST       *fpackage;
    LC_AST       *lpackage;
    LC_Intern     first_package;
    LC_ASTRefList ordered_packages;
    LC_StringList package_dirs;
    LC_ASTRefList discarded;

    LC_Map   interns;
    LC_Map   declared_notes;
    LC_Map   foreign_names;
    LC_Map   implicit_any;
    unsigned unique_counter;

    LC_Intern first_keyword;
    LC_Intern last_keyword;

#define X(x) LC_Intern k##x;
    LC_LIST_KEYWORDS
#undef X

#define X(x, declare) LC_Intern i##x;
    LC_LIST_INTERNS
#undef X

    LC_Type types[14]; // be careful when changing
#define X(TNAME, IS_UNSIGNED) LC_Type *t##TNAME;
    LC_LIST_TYPES
#undef X

    // When adding new special pointer types make sure to
    // also update LC_SetPointerSizeAndAlign so that it properly
    // updates the types after LC_LangBegin
    int      pointer_size;
    int      pointer_align;
    LC_Type *tvoid;
    LC_Type *tpvoid;
    LC_Type *tpchar;

    LC_Type  ttstring;
    LC_Type *tstring;
    LC_Type *tuntypednil;
    LC_Type *tuntypedbool;
    LC_Type *tuntypedint;
    LC_Type  ttuntypedfloat;
    LC_Type *tuntypedfloat;
    LC_Type  ttuntypedstring;
    LC_Type *tuntypedstring;
    LC_Type  ttany;
    LC_Type *tany;

    LC_Token NullToken;
    LC_AST   NullAST;
    LC_Token BuiltinToken;
    LC_Lex   NullLEX;

    LC_Printer printer;
    LC_Parser  quick_parser;

    // @configurable
    LC_ARCH arch;
    LC_GEN  gen;
    LC_OS   os;

    bool emit_line_directives;
    bool breakpoint_on_error;
    bool use_colored_terminal_output;

    void (*on_tokens_lexed)(LC_Lex *x);
    void (*on_tokens_interned)(LC_Lex *x);
    void (*on_decl_parsed)(LC_AST *n);
    void (*on_expr_parsed)(LC_AST *n);
    void (*on_stmt_parsed)(LC_AST *n);
    void (*on_typespec_parsed)(LC_AST *n);
    void (*on_decl_type_resolved)(LC_Decl *decl);
    void (*on_proc_body_resolved)(LC_Decl *decl);
    void (*on_expr_resolved)(LC_AST *expr, LC_Operand *op);
    void (*on_stmt_resolved)(LC_AST *n);
    void (*before_call_args_resolved)(LC_AST *n, LC_Type *type);
    void (*on_file_load)(LC_AST *package, LoadedFile *file);
    void (*on_message)(LC_Token *pos, char *str, int len); // pos and x can be null
    void (*on_fatal_error)(void);
    void *user_data;
};

extern LC_THREAD_LOCAL LC_Lang *L;

//
// Main @api
//

LC_FUNCTION LC_Lang *LC_LangAlloc(void);        // This allocates memory for LC_Lang which can be used to register callbacks and set configurables
LC_FUNCTION void     LC_LangBegin(LC_Lang *l);  // Prepare for compilation: init types, init builtins, set architecture variables stuff like that
LC_FUNCTION void     LC_LangEnd(LC_Lang *lang); // Deallocate language memory

LC_FUNCTION void      LC_RegisterPackageDir(char *dir);   // Add a package search directory
LC_FUNCTION void      LC_ParseAndResolve(LC_Intern name); // Fully resolve a package and all it's dependences
LC_FUNCTION LC_String LC_GenerateUnityBuild(void);        // Generate the C program and return as a string

// Smaller passes for AST modification
LC_FUNCTION void LC_ParsePackagesPass(LC_Intern name);           // These functions are equivalent to LC_ParseAndResolve,
LC_FUNCTION void LC_BuildIfPass(void);                           // you can use them to hook into the compilation process - you can modify the AST
LC_FUNCTION void LC_OrderAndResolveTopLevelPass(LC_Intern name); // before resolving or use resolved top declarations to generate some code.
LC_FUNCTION void LC_ResolveProcBodiesPass(void);                 // The Parse and Order functions can be called multiple times to accommodate this.

// Extended pass / optimization
LC_FUNCTION void LC_FindUnusedLocalsAndRemoveUnusedGlobalDeclsPass(void); // Extended pass that you can execute once you have resolved all packages

// These three functions are used to implement LC_FindUnusedLocalsAndRemoveUnusedGlobalDeclsPass
LC_FUNCTION void     LC_CountDeclRefs(LC_Arena *arena, LC_Map *map, LC_AST *ast);
LC_FUNCTION LC_Decl *LC_RemoveUnreferencedGlobalDeclsPass(LC_Map *map_of_visits);
LC_FUNCTION void     LC_ErrorOnUnreferencedLocalsPass(LC_Map *map_of_visits);

// Notes
LC_FUNCTION void    LC_DeclareNote(LC_Intern intern);
LC_FUNCTION bool    LC_IsNoteDeclared(LC_Intern intern);
LC_FUNCTION LC_AST *LC_HasNote(LC_AST *ast, LC_Intern i);

// Quick parse functions
LC_FUNCTION LC_AST *LC_ParseStmtf(const char *str, ...);
LC_FUNCTION LC_AST *LC_ParseExprf(const char *str, ...);
LC_FUNCTION LC_AST *LC_ParseDeclf(const char *str, ...);

// AST Walking and copy
LC_FUNCTION LC_AST      *LC_CopyAST(LC_Arena *arena, LC_AST *n);    // Deep copy the AST
LC_FUNCTION LC_ASTArray  LC_FlattenAST(LC_Arena *arena, LC_AST *n); // This walks the passed down tree and generates a flat array of pointers, very nice to use for traversing AST
LC_FUNCTION void         LC_WalkAST(LC_ASTWalker *ctx, LC_AST *n);
LC_FUNCTION LC_ASTWalker LC_GetDefaultWalker(LC_Arena *arena, LC_ASTWalkProc *proc);

LC_FUNCTION void    LC_ReserveAST(LC_ASTArray *arr, int size);
LC_FUNCTION void    LC_PushAST(LC_ASTArray *arr, LC_AST *ast);
LC_FUNCTION void    LC_PopAST(LC_ASTArray *arr);
LC_FUNCTION LC_AST *LC_GetLastAST(LC_ASTArray *arr);

// Interning API
LC_FUNCTION LC_Intern LC_ILit(char *str);
LC_FUNCTION void      LC_InternTokens(LC_Lex *x);

LC_FUNCTION LC_Intern LC_InternStrLen(char *str, int len);
LC_FUNCTION LC_Intern LC_GetUniqueIntern(const char *name_for_debug);
LC_FUNCTION char     *LC_GetUniqueName(const char *name_for_debug);

//
// Package functions
//
LC_FUNCTION LC_Operand    LC_ImportPackage(LC_AST *import, LC_AST *dst, LC_AST *src);
LC_FUNCTION LC_Intern     LC_MakePackageNameFromPath(LC_String path);
LC_FUNCTION bool          LC_PackageNameValid(LC_Intern name);
LC_FUNCTION bool          LC_PackageNameDuplicate(LC_Intern name);
LC_FUNCTION void          LC_AddPackageToList(LC_AST *n);
LC_FUNCTION LC_AST       *LC_RegisterPackage(LC_String path);
LC_FUNCTION void          LC_AddFileToPackage(LC_AST *pkg, LC_AST *f);
LC_FUNCTION LC_AST       *LC_FindImportInRefList(LC_ASTRefList *arr, LC_Intern path);
LC_FUNCTION void          LC_AddASTToRefList(LC_ASTRefList *refs, LC_AST *ast);
LC_FUNCTION LC_ASTRefList LC_GetPackageImports(LC_AST *package);
LC_FUNCTION LC_AST       *LC_GetPackageByName(LC_Intern name);
LC_FUNCTION LC_StringList LC_ListFilesInPackage(LC_Arena *arena, LC_String path);
LC_FUNCTION LoadedFile    LC_ReadFileHook(LC_AST *package, LC_String path);
LC_FUNCTION void          LC_ParsePackage(LC_AST *n);
LC_FUNCTION void          LC_AddOrderedPackageToRefList(LC_AST *n);
LC_FUNCTION LC_AST       *LC_OrderPackagesAndBasicResolve(LC_AST *pos, LC_Intern name);
LC_FUNCTION void          LC_AddSingleFilePackage(LC_Intern name, LC_String path);

//
// Lexing functions
//
LC_FUNCTION LC_Lex   *LC_LexStream(char *file, char *str, int line);
LC_FUNCTION LC_String LC_GetTokenLine(LC_Token *token);

LC_FUNCTION void      LC_LexingError(LC_Token *pos, const char *str, ...);
LC_FUNCTION bool      LC_IsAssign(LC_TokenKind kind);
LC_FUNCTION bool      LC_IsHexDigit(char c);
LC_FUNCTION bool      LC_IsBinDigit(char c);
LC_FUNCTION uint64_t  LC_MapCharToNumber(char c);
LC_FUNCTION uint64_t  LC_GetEscapeCode(char c);
LC_FUNCTION LC_String LC_GetEscapeString(char c);
LC_FUNCTION void      LC_LexAdvance(LC_Lex *x);
LC_FUNCTION void      LC_EatWhitespace(LC_Lex *x);
LC_FUNCTION void      LC_EatIdent(LC_Lex *x);
LC_FUNCTION void      LC_SetTokenLen(LC_Lex *x, LC_Token *t);
LC_FUNCTION void      LC_EatUntilIncluding(LC_Lex *x, char c);
LC_FUNCTION LC_BigInt LC_LexBigInt(char *string, int len, uint64_t base);
LC_FUNCTION void      LC_LexNestedComments(LC_Lex *x, LC_Token *t);
LC_FUNCTION void      LC_LexStringLiteral(LC_Lex *x, LC_Token *t, LC_TokenKind kind);
LC_FUNCTION void      LC_LexUnicodeLiteral(LC_Lex *x, LC_Token *t);
LC_FUNCTION void      LC_LexIntOrFloat(LC_Lex *x, LC_Token *t);
LC_FUNCTION void      LC_LexCase2(LC_Lex *x, LC_Token *t, LC_TokenKind tk0, char c, LC_TokenKind tk1);
LC_FUNCTION void      LC_LexCase3(LC_Lex *x, LC_Token *t, LC_TokenKind tk, char c0, LC_TokenKind tk0, char c1, LC_TokenKind tk1);
LC_FUNCTION void      LC_LexCase4(LC_Lex *x, LC_Token *t, LC_TokenKind tk, char c0, LC_TokenKind tk0, char c1, LC_TokenKind tk1, char c2, LC_TokenKind tk2);
LC_FUNCTION void      LC_LexNext(LC_Lex *x, LC_Token *t);

//
// LC_Map API
//
LC_FUNCTION void         LC_MapReserve(LC_Map *map, int size);
LC_FUNCTION int          LC_NextPow2(int v);
LC_FUNCTION LC_MapEntry *LC_GetMapEntryEx(LC_Map *map, uint64_t key);
LC_FUNCTION bool         LC_InsertWithoutReplace(LC_Map *map, void *key, void *value);
LC_FUNCTION LC_MapEntry *LC_InsertMapEntry(LC_Map *map, uint64_t key, uint64_t value);
LC_FUNCTION LC_MapEntry *LC_GetMapEntry(LC_Map *map, uint64_t key);
LC_FUNCTION void         LC_MapInsert(LC_Map *map, LC_String keystr, void *value);
LC_FUNCTION void        *LC_MapGet(LC_Map *map, LC_String keystr);
LC_FUNCTION void         LC_MapInsertU64(LC_Map *map, uint64_t key, void *value);
LC_FUNCTION void        *LC_MapGetU64(LC_Map *map, uint64_t key);
LC_FUNCTION void        *LC_MapGetP(LC_Map *map, void *key);
LC_FUNCTION void         LC_MapInsertP(LC_Map *map, void *key, void *value);
LC_FUNCTION void         LC_MapClear(LC_Map *map);

//
// LC_AST Creation
//
LC_FUNCTION LC_AST *LC_CreateAST(LC_Token *pos, LC_ASTKind kind);
LC_FUNCTION LC_AST *LC_CreateUnary(LC_Token *pos, LC_TokenKind op, LC_AST *expr);
LC_FUNCTION LC_AST *LC_CreateBinary(LC_Token *pos, LC_AST *left, LC_AST *right, LC_TokenKind op);
LC_FUNCTION LC_AST *LC_CreateIndex(LC_Token *pos, LC_AST *left, LC_AST *index);

LC_FUNCTION bool LC_ContainsCallExpr(LC_AST *ast);
LC_FUNCTION void LC_SetASTPosOnAll(LC_AST *n, LC_Token *pos);
LC_FUNCTION bool LC_ContainsCBuiltin(LC_AST *n);

//
// LC_Type functions
//
LC_FUNCTION void           LC_SetPointerSizeAndAlign(int size, int align);
LC_FUNCTION LC_Type       *LC_CreateType(LC_TypeKind kind);
LC_FUNCTION LC_Type       *LC_CreateTypedef(LC_Decl *decl, LC_Type *base);
LC_FUNCTION LC_Type       *LC_CreatePointerType(LC_Type *type);
LC_FUNCTION LC_Type       *LC_CreateArrayType(LC_Type *type, int size);
LC_FUNCTION LC_Type       *LC_CreateProcType(LC_TypeMemberList args, LC_Type *ret, bool has_vargs, bool has_vargs_any_promotion);
LC_FUNCTION LC_Type       *LC_CreateIncompleteType(LC_Decl *decl);
LC_FUNCTION LC_Type       *LC_CreateUntypedIntEx(LC_Type *base, LC_Decl *decl);
LC_FUNCTION LC_Type       *LC_CreateUntypedInt(LC_Type *base);
LC_FUNCTION LC_TypeMember *LC_AddTypeToList(LC_TypeMemberList *list, LC_Intern name, LC_Type *type, LC_AST *ast);
LC_FUNCTION int            LC_GetLevelsOfIndirection(LC_Type *type);
LC_FUNCTION bool           LC_BigIntFits(LC_BigInt i, LC_Type *type);
LC_FUNCTION LC_Type       *LC_StripPointer(LC_Type *type);

//
// Parsing functions
//
LC_FUNCTION LC_AST *LC_ParseFile(LC_AST *package, char *filename, char *content, int line);
LC_FUNCTION LC_AST *LC_ParseTokens(LC_AST *package, LC_Lex *x);

LC_FUNCTION LC_Parser     LC_MakeParser(LC_Lex *x);
LC_FUNCTION LC_Parser    *LC_MakeParserQuick(char *str);
LC_FUNCTION LC_Token     *LC_Next(void);
LC_FUNCTION LC_Token     *LC_Get(void);
LC_FUNCTION LC_Token     *LC_GetI(int i);
LC_FUNCTION LC_Token     *LC_Is(LC_TokenKind kind);
LC_FUNCTION LC_Token     *LC_IsKeyword(LC_Intern intern);
LC_FUNCTION LC_Token     *LC_Match(LC_TokenKind kind);
LC_FUNCTION LC_Token     *LC_MatchKeyword(LC_Intern intern);
LC_FUNCTION LC_Precedence LC_GetPrecedence(LC_PrecedenceKind binding, LC_TokenKind kind);
LC_FUNCTION LC_AST       *LC_ParseExprEx(int min_bp);
LC_FUNCTION LC_AST       *LC_ParseCompo(LC_Token *pos, LC_AST *left);
LC_FUNCTION LC_AST       *LC_ParseExpr(void);
LC_FUNCTION LC_AST       *LC_ParseProcType(LC_Token *pos);
LC_FUNCTION LC_AST       *LC_ParseType(void);
LC_FUNCTION LC_AST       *LC_ParseForStmt(LC_Token *pos);
LC_FUNCTION LC_AST       *LC_ParseSwitchStmt(LC_Token *pos);
LC_FUNCTION LC_AST       *LC_ParseStmt(bool check_semicolon);
LC_FUNCTION LC_AST       *LC_ParseStmtBlock(int flags);
LC_FUNCTION LC_AST       *LC_ParseProcDecl(LC_Token *name);
LC_FUNCTION LC_AST       *LC_ParseStruct(LC_ASTKind kind, LC_Token *ident);
LC_FUNCTION LC_AST       *LC_ParseTypedef(LC_Token *ident);
LC_FUNCTION LC_AST       *LC_CreateNote(LC_Token *pos, LC_Intern ident);
LC_FUNCTION LC_AST       *LC_ParseNote(void);
LC_FUNCTION LC_AST       *LC_ParseNotes(void);
LC_FUNCTION bool          LC_ResolveBuildIf(LC_AST *build_if);
LC_FUNCTION LC_AST       *LC_ParseImport(void);
LC_FUNCTION LC_AST       *LC_ParseDecl(LC_AST *file);
LC_FUNCTION bool          LC_EatUntilNextValidDecl(void);
LC_FUNCTION bool          LC_ParseHashBuildOn(LC_AST *n);
LC_FUNCTION LC_AST       *LC_ParseFileEx(LC_AST *package);

//
// Resolution functions
//
LC_FUNCTION void          LC_AddDecl(LC_DeclStack *scope, LC_Decl *decl);
LC_FUNCTION void          LC_InitDeclStack(LC_DeclStack *stack, int size);
LC_FUNCTION LC_DeclStack *LC_CreateDeclStack(int size);
LC_FUNCTION LC_Decl      *LC_FindDeclOnStack(LC_DeclStack *scp, LC_Intern name);

LC_FUNCTION DeclScope *LC_CreateScope(int size);
LC_FUNCTION LC_Decl   *LC_CreateDecl(LC_DeclKind kind, LC_Intern name, LC_AST *n);
LC_FUNCTION LC_Operand LC_AddDeclToScope(DeclScope *scp, LC_Decl *decl);
LC_FUNCTION LC_Decl   *LC_FindDeclInScope(DeclScope *scope, LC_Intern name);
LC_FUNCTION LC_Operand LC_ThereIsNoDecl(DeclScope *scp, LC_Decl *decl, bool check_locals);
LC_FUNCTION void       LC_MarkDeclError(LC_Decl *decl);
LC_FUNCTION LC_Decl   *LC_GetLocalOrGlobalDecl(LC_Intern name);
LC_FUNCTION LC_Operand LC_PutGlobalDecl(LC_Decl *decl);
LC_FUNCTION LC_Operand LC_CreateLocalDecl(LC_DeclKind kind, LC_Intern name, LC_AST *ast);
LC_FUNCTION LC_Decl   *LC_AddConstIntDecl(char *key, int64_t value);
LC_FUNCTION LC_Decl   *LC_GetBuiltin(LC_Intern name);
LC_FUNCTION void       LC_AddBuiltinConstInt(char *key, int64_t value);

LC_FUNCTION void                       LC_RegisterDeclsFromFile(LC_AST *file);
LC_FUNCTION void                       LC_ResolveDeclsFromFile(LC_AST *file);
LC_FUNCTION void                       LC_PackageDecls(LC_AST *package);
LC_FUNCTION void                       LC_ResolveProcBodies(LC_AST *package);
LC_FUNCTION void                       LC_ResolveIncompleteTypes(LC_AST *package);
LC_FUNCTION LC_Operand                 LC_ResolveNote(LC_AST *n, bool is_decl);
LC_FUNCTION LC_Operand                 LC_ResolveProcBody(LC_Decl *decl);
LC_FUNCTION LC_ResolvedCompoItem      *LC_AddResolvedCallItem(LC_ResolvedCompo *list, LC_TypeMember *t, LC_AST *comp, LC_AST *expr);
LC_FUNCTION LC_Operand                 LC_ResolveCompoCall(LC_AST *n, LC_Type *type);
LC_FUNCTION LC_Operand                 LC_ResolveCompoAggregate(LC_AST *n, LC_Type *type);
LC_FUNCTION LC_ResolvedCompoArrayItem *LC_AddResolvedCompoArrayItem(LC_ResolvedArrayCompo *arr, int index, LC_AST *comp);
LC_FUNCTION LC_Operand                 LC_ResolveCompoArray(LC_AST *n, LC_Type *type);
LC_FUNCTION LC_Operand                 LC_ResolveTypeOrExpr(LC_AST *n);
LC_FUNCTION LC_Operand                 LC_ExpectBuiltinWithOneArg(LC_AST *n);
LC_FUNCTION LC_Operand                 LC_ResolveBuiltin(LC_AST *n);
LC_FUNCTION bool                       LC_TryTyping(LC_AST *n, LC_Operand op);
LC_FUNCTION bool                       LC_TryDefaultTyping(LC_AST *n, LC_Operand *o);
LC_FUNCTION LC_Operand                 LC_ResolveNameInScope(LC_AST *n, LC_Decl *parent_decl);
LC_FUNCTION LC_Operand                 LC_ResolveExpr(LC_AST *expr);
LC_FUNCTION LC_Operand                 LC_ResolveExprAndPushCompoContext(LC_AST *expr, LC_Type *type);
LC_FUNCTION LC_Operand                 LC_ResolveExprEx(LC_AST *n);
LC_FUNCTION LC_Operand                 LC_ResolveStmtBlock(LC_AST *n);
LC_FUNCTION LC_Operand                 LC_ResolveVarDecl(LC_Decl *decl);
LC_FUNCTION LC_Operand                 LC_MakeSureNoDeferBlock(LC_AST *n, char *str);
LC_FUNCTION LC_Operand                 LC_MakeSureInsideLoopBlock(LC_AST *n, char *str);
LC_FUNCTION LC_Operand                 LC_MatchLabeledBlock(LC_AST *n);
LC_FUNCTION LC_Operand                 LC_ResolveStmt(LC_AST *n);
LC_FUNCTION LC_Operand                 LC_ResolveConstDecl(LC_Decl *decl);
LC_FUNCTION LC_Operand                 LC_ResolveName(LC_AST *pos, LC_Intern intern);
LC_FUNCTION LC_Operand                 LC_ResolveConstInt(LC_AST *n, LC_Type *int_type, uint64_t *out_size);
LC_FUNCTION LC_Operand                 LC_ResolveType(LC_AST *n);
LC_FUNCTION LC_Operand                 LC_ResolveBinaryExpr(LC_AST *n, LC_Operand l, LC_Operand r);
LC_FUNCTION LC_Operand                 LC_ResolveTypeVargs(LC_AST *pos, LC_Operand v);
LC_FUNCTION LC_Operand                 LC_ResolveTypeCast(LC_AST *pos, LC_Operand t, LC_Operand v);
LC_FUNCTION LC_Operand                 LC_ResolveTypeVarDecl(LC_AST *pos, LC_Operand t, LC_Operand v);
LC_FUNCTION LC_Operand                 LC_ResolveTypeAggregate(LC_AST *pos, LC_Type *type);

extern LC_Operand       LC_OPNull;
LC_FUNCTION LC_Operand  LC_OPError(void);
LC_FUNCTION LC_Operand  LC_OPConstType(LC_Type *type);
LC_FUNCTION LC_Operand  LC_OPDecl(LC_Decl *decl);
LC_FUNCTION LC_Operand  LC_OPType(LC_Type *type);
LC_FUNCTION LC_Operand  LC_OPLValueAndType(LC_Type *type);
LC_FUNCTION LC_Operand  LC_ConstCastFloat(LC_AST *pos, LC_Operand op);
LC_FUNCTION LC_Operand  LC_ConstCastInt(LC_AST *pos, LC_Operand op);
LC_FUNCTION LC_Operand  LC_OPInt(int64_t v);
LC_FUNCTION LC_Operand  LC_OPIntT(LC_Type *type, int64_t v);
LC_FUNCTION LC_Operand  LC_OPModDefaultUT(LC_Operand val);
LC_FUNCTION LC_Operand  LC_OPModType(LC_Operand op, LC_Type *type);
LC_FUNCTION LC_Operand  LC_OPModBool(LC_Operand op);
LC_FUNCTION LC_Operand  LC_OPModBoolV(LC_Operand op, int v);
LC_FUNCTION LC_Operand  LC_EvalBinary(LC_AST *pos, LC_Operand a, LC_TokenKind op, LC_Operand b);
LC_FUNCTION LC_Operand  LC_EvalUnary(LC_AST *pos, LC_TokenKind op, LC_Operand a);
LC_FUNCTION LC_OPResult LC_IsBinaryExprValidForType(LC_TokenKind op, LC_Type *type);
LC_FUNCTION LC_OPResult LC_IsUnaryOpValidForType(LC_TokenKind op, LC_Type *type);
LC_FUNCTION LC_OPResult LC_IsAssignValidForType(LC_TokenKind op, LC_Type *type);

//
// Error
//
LC_FUNCTION void       LC_IgnoreMessage(LC_Token *pos, char *str, int len);
LC_FUNCTION void       LC_SendErrorMessage(LC_Token *pos, LC_String s8);
LC_FUNCTION void       LC_SendErrorMessagef(LC_Lex *x, LC_Token *pos, const char *str, ...);
LC_FUNCTION LC_AST    *LC_ReportParseError(LC_Token *pos, const char *str, ...);
LC_FUNCTION LC_Operand LC_ReportASTError(LC_AST *n, const char *str, ...);
LC_FUNCTION LC_Operand LC_ReportASTErrorEx(LC_AST *n1, LC_AST *n2, const char *str, ...);
LC_FUNCTION void       LC_HandleFatalError(void);

#define LC_ASSERT(n, cond)                                                                                               \
    if (!(cond)) {                                                                                                       \
        LC_ReportASTError(n, "internal compiler error: assert condition failed: %s, inside of %s", #cond, __FUNCTION__); \
        LC_HandleFatalError();                                                                                           \
    }

//
// Code generation and printing helpers
//
LC_FUNCTION LC_StringList *LC_BeginStringGen(LC_Arena *arena);
LC_FUNCTION LC_String      LC_EndStringGen(LC_Arena *arena);

// clang-format off
#define LC_Genf(...) LC_Addf(L->printer.arena, &L->printer.list, __VA_ARGS__)
#define LC_GenLinef(...) do { LC_Genf("\n"); LC_GenIndent(); LC_Genf(__VA_ARGS__); } while (0)
// clang-format on

LC_FUNCTION void  LC_GenIndent(void);
LC_FUNCTION void  LC_GenLine(void);
LC_FUNCTION char *LC_Strf(const char *str, ...);
LC_FUNCTION char *LC_GenLCType(LC_Type *type);
LC_FUNCTION char *LC_GenLCTypeVal(LC_TypeAndVal v);
LC_FUNCTION char *LC_GenLCAggName(LC_Type *t);
LC_FUNCTION void  LC_GenLCNode(LC_AST *n);

// C code generation
LC_FUNCTION void LC_GenCHeader(LC_AST *package);
LC_FUNCTION void LC_GenCImpl(LC_AST *package);

LC_FUNCTION void LC_GenCLineDirective(LC_AST *node);
LC_FUNCTION void LC_GenLastCLineDirective(void);
LC_FUNCTION void LC_GenCLineDirectiveNum(int num);

LC_FUNCTION char     *LC_GenCTypeParen(char *str, char c);
LC_FUNCTION char     *LC_GenCType(LC_Type *type, char *str);
LC_FUNCTION LC_Intern LC_GetStringFromSingleArgNote(LC_AST *note);
LC_FUNCTION void      LC_GenCCompound(LC_AST *n);
LC_FUNCTION void      LC_GenCString(char *s, LC_Type *type);
LC_FUNCTION char     *LC_GenCVal(LC_TypeAndVal v, LC_Type *type);
LC_FUNCTION void      LC_GenCExpr(LC_AST *n);
LC_FUNCTION void      LC_GenCNote(LC_AST *note);
LC_FUNCTION void      LC_GenCVarExpr(LC_AST *n, bool is_declaration);
LC_FUNCTION void      LC_GenCDefers(LC_AST *block);
LC_FUNCTION void      LC_GenCDefersLoopBreak(LC_AST *n);
LC_FUNCTION void      LC_GenCDefersReturn(LC_AST *n);
LC_FUNCTION void      LC_GenCStmt2(LC_AST *n, int flags);
LC_FUNCTION void      LC_GenCStmt(LC_AST *n);
LC_FUNCTION void      LC_GenCExprParen(LC_AST *expr);
LC_FUNCTION void      LC_GenCStmtBlock(LC_AST *n);
LC_FUNCTION void      LC_GenCProcDecl(LC_Decl *decl);
LC_FUNCTION void      LC_GenCAggForwardDecl(LC_Decl *decl);
LC_FUNCTION void      LC_GenCTypeDecl(LC_Decl *decl);
LC_FUNCTION void      LC_GenCVarFDecl(LC_Decl *decl);

//
// Inline helpers
//
static inline bool LC_IsUTInt(LC_Type *x) { return x->kind == LC_TypeKind_UntypedInt; }
static inline bool LC_IsUTFloat(LC_Type *x) { return x->kind == LC_TypeKind_UntypedFloat; }
static inline bool LC_IsUTStr(LC_Type *x) { return x->kind == LC_TypeKind_UntypedString; }
static inline bool LC_IsUntyped(LC_Type *x) { return (x)->kind >= LC_TypeKind_UntypedInt && x->kind <= LC_TypeKind_UntypedString; }

static inline bool LC_IsNum(LC_Type *x) { return x->kind >= LC_TypeKind_char && x->kind <= LC_TypeKind_double; }
static inline bool LC_IsInt(LC_Type *x) { return (x->kind >= LC_TypeKind_char && x->kind <= LC_TypeKind_ullong) || LC_IsUTInt(x); }
static inline bool LC_IsIntLike(LC_Type *x) { return LC_IsInt(x) || x->kind == LC_TypeKind_Pointer || x->kind == LC_TypeKind_Proc; }
static inline bool LC_IsFloat(LC_Type *x) { return ((x)->kind == LC_TypeKind_float || (x->kind == LC_TypeKind_double)) || LC_IsUTFloat(x); }
static inline bool LC_IsSmallerThenInt(LC_Type *x) { return x->kind < LC_TypeKind_int; }

static inline bool LC_IsAggType(LC_Type *x) { return ((x)->kind == LC_TypeKind_Struct || (x)->kind == LC_TypeKind_Union); }
static inline bool LC_IsArray(LC_Type *x) { return (x)->kind == LC_TypeKind_Array; }
static inline bool LC_IsProc(LC_Type *x) { return (x)->kind == LC_TypeKind_Proc; }
static inline bool LC_IsVoidPtr(LC_Type *x) { return (x) == L->tpvoid; }
static inline bool LC_IsPtr(LC_Type *x) { return (x)->kind == LC_TypeKind_Pointer; }
static inline bool LC_IsPtrLike(LC_Type *x) { return (x)->kind == LC_TypeKind_Pointer || x->kind == LC_TypeKind_Proc; }
static inline bool LC_IsStr(LC_Type *x) { return x == L->tpchar || x == L->tstring; }

static inline bool LC_IsDecl(LC_AST *x) { return (x->kind >= LC_ASTKind_FirstDecl && x->kind <= LC_ASTKind_LastDecl); }
static inline bool LC_IsStmt(LC_AST *x) { return (x->kind >= LC_ASTKind_FirstStmt && x->kind <= LC_ASTKind_LastStmt); }
static inline bool LC_IsExpr(LC_AST *x) { return (x->kind >= LC_ASTKind_FirstExpr && x->kind <= LC_ASTKind_LastExpr); }
static inline bool LC_IsType(LC_AST *x) { return (x->kind >= LC_ASTKind_FirstTypespec && x->kind <= LC_ASTKind_LastTypespec); }
static inline bool LC_IsAgg(LC_AST *x) { return (x->kind == LC_ASTKind_DeclStruct || x->kind == LC_ASTKind_DeclUnion); }

// I tried removing this because I thought it's redundant
// but this reminded me that "Untyped bool" can appear from normal expressions: (a == b)
// This is required, maybe there is a way around it, not sure
static inline bool LC_IsUTConst(LC_Operand op) { return (op.flags & LC_OPF_UTConst) != 0; }
static inline bool LC_IsConst(LC_Operand op) { return (op.flags & LC_OPF_Const) != 0; }
static inline bool LC_IsLValue(LC_Operand op) { return (op.flags & LC_OPF_LValue) != 0; }
static inline bool LC_IsError(LC_Operand op) { return (op.flags & LC_OPF_Error) != 0; }

static inline LC_Type *LC_GetBase(LC_Type *x) { return (x)->tbase; }

//
// Stringifying functions
//
LC_FUNCTION const char *LC_OSToString(LC_OS os);
LC_FUNCTION const char *LC_GENToString(LC_GEN os);
LC_FUNCTION const char *LC_ARCHToString(LC_ARCH arch);
LC_FUNCTION const char *LC_ASTKindToString(LC_ASTKind kind);
LC_FUNCTION const char *LC_TypeKindToString(LC_TypeKind kind);
LC_FUNCTION const char *LC_DeclKindToString(LC_DeclKind decl_kind);
LC_FUNCTION const char *LC_TokenKindToString(LC_TokenKind token_kind);
LC_FUNCTION const char *LC_TokenKindToOperator(LC_TokenKind token_kind);

/*
The bigint code was written by Christoffer Lerno, he is the programmer
behind C3. He allowed me to use this code without any restrictions. Great guy!
You can check out C3 compiler: https://github.com/c3lang/c3c
He also writes very helpful blogs about compilers: https://c3.handmade.network/blog
*/
LC_FUNCTION LC_BigInt LC_Bigint_u64(uint64_t val);
LC_FUNCTION uint64_t *LC_Bigint_ptr(LC_BigInt *big_int);
LC_FUNCTION size_t    LC_Bigint_bits_needed(LC_BigInt *big_int);
LC_FUNCTION void      LC_Bigint_init_unsigned(LC_BigInt *big_int, uint64_t value);
LC_FUNCTION void      LC_Bigint_init_signed(LC_BigInt *dest, int64_t value);
LC_FUNCTION void      LC_Bigint_init_bigint(LC_BigInt *dest, LC_BigInt *src);
LC_FUNCTION void      LC_Bigint_negate(LC_BigInt *dest, LC_BigInt *source);
LC_FUNCTION size_t    LC_Bigint_clz(LC_BigInt *big_int, size_t bit_count);
LC_FUNCTION bool      LC_Bigint_eql(LC_BigInt a, LC_BigInt b);
LC_FUNCTION bool      LC_Bigint_fits_in_bits(LC_BigInt *big_int, size_t bit_count, bool is_signed);
LC_FUNCTION uint64_t  LC_Bigint_as_unsigned(LC_BigInt *bigint);
LC_FUNCTION void      LC_Bigint_add(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_add_wrap(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2, size_t bit_count, bool is_signed);
LC_FUNCTION void      LC_Bigint_sub(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_sub_wrap(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2, size_t bit_count, bool is_signed);
LC_FUNCTION void      LC_Bigint_mul(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_mul_wrap(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2, size_t bit_count, bool is_signed);
LC_FUNCTION void      LC_Bigint_unsigned_division(LC_BigInt *op1, LC_BigInt *op2, LC_BigInt *Quotient, LC_BigInt *Remainder);
LC_FUNCTION void      LC_Bigint_div_trunc(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_div_floor(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_rem(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_mod(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_or(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_and(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_xor(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_shl(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_shl_int(LC_BigInt *dest, LC_BigInt *op1, uint64_t shift);
LC_FUNCTION void      LC_Bigint_shl_trunc(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2, size_t bit_count, bool is_signed);
LC_FUNCTION void      LC_Bigint_shr(LC_BigInt *dest, LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION void      LC_Bigint_not(LC_BigInt *dest, LC_BigInt *op, size_t bit_count, bool is_signed);
LC_FUNCTION void      LC_Bigint_truncate(LC_BigInt *dst, LC_BigInt *op, size_t bit_count, bool is_signed);
LC_FUNCTION LC_CmpRes LC_Bigint_cmp(LC_BigInt *op1, LC_BigInt *op2);
LC_FUNCTION char     *LC_Bigint_str(LC_BigInt *bigint, uint64_t base);
LC_FUNCTION int64_t   LC_Bigint_as_signed(LC_BigInt *bigint);
LC_FUNCTION LC_CmpRes LC_Bigint_cmp_zero(LC_BigInt *op);
LC_FUNCTION double    LC_Bigint_as_float(LC_BigInt *bigint);

//
// Unicode API
//
struct LC_UTF32Result {
    uint32_t out_str;
    int      advance;
    int      error;
};

struct LC_UTF8Result {
    uint8_t out_str[4];
    int     len;
    int     error;
};

struct LC_UTF16Result {
    uint16_t out_str[2];
    int      len;
    int      error;
};

LC_FUNCTION LC_UTF32Result LC_ConvertUTF16ToUTF32(uint16_t *c, int max_advance);
LC_FUNCTION LC_UTF8Result  LC_ConvertUTF32ToUTF8(uint32_t codepoint);
LC_FUNCTION LC_UTF32Result LC_ConvertUTF8ToUTF32(char *c, int max_advance);
LC_FUNCTION LC_UTF16Result LC_ConvertUTF32ToUTF16(uint32_t codepoint);
LC_FUNCTION int64_t        LC_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen);
LC_FUNCTION int64_t        LC_CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen);

//
// Filesystem API
//
LC_FUNCTION bool      LC_IsDir(LC_Arena *temp, LC_String path);
LC_FUNCTION LC_String LC_GetAbsolutePath(LC_Arena *arena, LC_String relative);
LC_FUNCTION bool      LC_EnableTerminalColors(void);
LC_FUNCTION LC_String LC_ReadFile(LC_Arena *arena, LC_String path);

LC_FUNCTION bool        LC_IsValid(LC_FileIter it);
LC_FUNCTION void        LC_Advance(LC_FileIter *it);
LC_FUNCTION LC_FileIter LC_IterateFiles(LC_Arena *scratch_arena, LC_String path);

//
// Arena API
//
#define LC_PushSize(a, size) LC__PushSize(a, size)
#define LC_PushSizeNonZeroed(a, size) LC__PushSizeNonZeroed(a, size)

#define LC_PushArrayNonZeroed(a, T, c) (T *)LC__PushSizeNonZeroed(a, sizeof(T) * (c))
#define LC_PushStructNonZeroed(a, T) (T *)LC__PushSizeNonZeroed(a, sizeof(T))
#define LC_PushStruct(a, T) (T *)LC__PushSize(a, sizeof(T))
#define LC_PushArray(a, T, c) (T *)LC__PushSize(a, sizeof(T) * (c))

#define LC_Assertf(cond, ...) LC_ASSERT(NULL, cond)

#ifndef LC_USE_CUSTOM_ARENA
LC_FUNCTION void      LC_InitArenaEx(LC_Arena *a, size_t reserve);
LC_FUNCTION void      LC_InitArena(LC_Arena *a);
LC_FUNCTION void      LC_InitArenaFromBuffer(LC_Arena *arena, void *buffer, size_t size);
LC_FUNCTION LC_Arena *LC_BootstrapArena(void);
LC_FUNCTION LC_Arena  LC_PushArena(LC_Arena *arena, size_t size);
LC_FUNCTION LC_Arena *LC_PushArenaP(LC_Arena *arena, size_t size);

LC_FUNCTION void        *LC__PushSizeNonZeroed(LC_Arena *a, size_t size);
LC_FUNCTION void        *LC__PushSize(LC_Arena *arena, size_t size);
LC_FUNCTION LC_TempArena LC_BeginTemp(LC_Arena *arena);
LC_FUNCTION void         LC_EndTemp(LC_TempArena checkpoint);

LC_FUNCTION void LC_PopToPos(LC_Arena *arena, size_t pos);
LC_FUNCTION void LC_PopSize(LC_Arena *arena, size_t size);
LC_FUNCTION void LC_DeallocateArena(LC_Arena *arena);
LC_FUNCTION void LC_ResetArena(LC_Arena *arena);

LC_FUNCTION LC_VMemory LC_VReserve(size_t size);
LC_FUNCTION bool       LC_VCommit(LC_VMemory *m, size_t commit);
LC_FUNCTION void       LC_VDeallocate(LC_VMemory *m);
LC_FUNCTION bool       LC_VDecommitPos(LC_VMemory *m, size_t pos);
#endif // LC_USE_CUSTOM_ARENA

LC_FUNCTION size_t LC_GetAlignOffset(size_t size, size_t align);
LC_FUNCTION size_t LC_AlignUp(size_t size, size_t align);
LC_FUNCTION size_t LC_AlignDown(size_t size, size_t align);

#define LC_IS_POW2(x) (((x) & ((x)-1)) == 0)
#define LC_MIN(x, y) ((x) <= (y) ? (x) : (y))
#define LC_MAX(x, y) ((x) >= (y) ? (x) : (y))
#define LC_StrLenof(x) ((int64_t)((sizeof(x) / sizeof((x)[0]))))

#define LC_CLAMP_TOP(x, max) ((x) >= (max) ? (max) : (x))
#define LC_CLAMP_BOT(x, min) ((x) <= (min) ? (min) : (x))
#define LC_CLAMP(x, min, max) ((x) >= (max) ? (max) : (x) <= (min) ? (min) \
                                                                   : (x))
#define LC_KIB(x) ((x##ull) * 1024ull)
#define LC_MIB(x) (LC_KIB(x) * 1024ull)
#define LC_GIB(x) (LC_MIB(x) * 1024ull)
#define LC_TIB(x) (LC_GIB(x) * 1024ull)

//
// String API
//

typedef int LC_FindFlag;
enum {
    LC_FindFlag_None          = 0,
    LC_FindFlag_IgnoreCase    = 1,
    LC_FindFlag_MatchFindLast = 2,
};

typedef int LC_SplitFlag;
enum {
    LC_SplitFlag_None           = 0,
    LC_SplitFlag_IgnoreCase     = 1,
    LC_SplitFlag_SplitInclusive = 2,
};

static const bool LC_IgnoreCase = true;

#if defined(__has_attribute)
    #if __has_attribute(format)
        #define LC__PrintfFormat(fmt, va) __attribute__((format(printf, fmt, va)))
    #endif
#endif

#ifndef LC__PrintfFormat
    #define LC__PrintfFormat(fmt, va)
#endif

#define LC_Lit(string) LC_MakeString((char *)string, sizeof(string) - 1)
#define LC_Expand(string) (int)(string).len, (string).str

#define LC_FORMAT(allocator, str, result)                 \
    va_list args1;                                        \
    va_start(args1, str);                                 \
    LC_String result = LC_FormatV(allocator, str, args1); \
    va_end(args1)

#ifdef __cplusplus
    #define LC_IF_CPP(x) x
#else
    #define LC_IF_CPP(x)
#endif

LC_FUNCTION char LC_ToLowerCase(char a);
LC_FUNCTION char LC_ToUpperCase(char a);
LC_FUNCTION bool LC_IsWhitespace(char w);
LC_FUNCTION bool LC_IsAlphabetic(char a);
LC_FUNCTION bool LC_IsIdent(char a);
LC_FUNCTION bool LC_IsDigit(char a);
LC_FUNCTION bool LC_IsAlphanumeric(char a);

LC_FUNCTION int64_t       LC_StrLen(char *string);
LC_FUNCTION bool          LC_AreEqual(LC_String a, LC_String b, unsigned ignore_case LC_IF_CPP(= false));
LC_FUNCTION bool          LC_EndsWith(LC_String a, LC_String end, unsigned ignore_case LC_IF_CPP(= false));
LC_FUNCTION bool          LC_StartsWith(LC_String a, LC_String start, unsigned ignore_case LC_IF_CPP(= false));
LC_FUNCTION LC_String     LC_MakeString(char *str, int64_t len);
LC_FUNCTION LC_String     LC_CopyString(LC_Arena *allocator, LC_String string);
LC_FUNCTION LC_String     LC_CopyChar(LC_Arena *allocator, char *s);
LC_FUNCTION LC_String     LC_NormalizePath(LC_Arena *allocator, LC_String s);
LC_FUNCTION void          LC_NormalizePathUnsafe(LC_String s); // make sure there is no way string is const etc.
LC_FUNCTION LC_String     LC_Chop(LC_String string, int64_t len);
LC_FUNCTION LC_String     LC_Skip(LC_String string, int64_t len);
LC_FUNCTION LC_String     LC_GetPostfix(LC_String string, int64_t len);
LC_FUNCTION LC_String     LC_GetPrefix(LC_String string, int64_t len);
LC_FUNCTION bool          LC_Seek(LC_String string, LC_String find, LC_FindFlag flags LC_IF_CPP(= LC_FindFlag_None), int64_t *index_out LC_IF_CPP(= 0));
LC_FUNCTION int64_t       LC_Find(LC_String string, LC_String find, LC_FindFlag flags LC_IF_CPP(= LC_FindFlag_None));
LC_FUNCTION LC_String     LC_ChopLastSlash(LC_String s);
LC_FUNCTION LC_String     LC_ChopLastPeriod(LC_String s);
LC_FUNCTION LC_String     LC_SkipToLastSlash(LC_String s);
LC_FUNCTION LC_String     LC_SkipToLastPeriod(LC_String s);
LC_FUNCTION LC_String     LC_GetNameNoExt(LC_String s);
LC_FUNCTION LC_String     LC_MakeFromChar(char *string);
LC_FUNCTION LC_String     LC_MakeEmptyString(void);
LC_FUNCTION LC_StringList LC_MakeEmptyList(void);
LC_FUNCTION LC_String     LC_FormatV(LC_Arena *allocator, const char *str, va_list args1);
LC_FUNCTION LC_String     LC_Format(LC_Arena *allocator, const char *str, ...) LC__PrintfFormat(2, 3);

LC_FUNCTION LC_StringList LC_Split(LC_Arena *allocator, LC_String string, LC_String find, LC_SplitFlag flags LC_IF_CPP(= LC_SplitFlag_None));
LC_FUNCTION LC_String     LC_MergeWithSeparator(LC_Arena *allocator, LC_StringList list, LC_String separator LC_IF_CPP(= LC_Lit(" ")));
LC_FUNCTION LC_String     LC_MergeString(LC_Arena *allocator, LC_StringList list);

LC_FUNCTION LC_StringNode *LC_CreateNode(LC_Arena *allocator, LC_String string);
LC_FUNCTION void           LC_ReplaceNodeString(LC_StringList *list, LC_StringNode *node, LC_String new_string);
LC_FUNCTION void           LC_AddExistingNode(LC_StringList *list, LC_StringNode *node);
LC_FUNCTION void           LC_AddArray(LC_Arena *allocator, LC_StringList *list, char **array, int count);
LC_FUNCTION void           LC_AddArrayWithPrefix(LC_Arena *allocator, LC_StringList *list, char *prefix, char **array, int count);
LC_FUNCTION LC_StringList  LC_MakeList(LC_Arena *allocator, LC_String a);
LC_FUNCTION LC_StringList  LC_CopyList(LC_Arena *allocator, LC_StringList a);
LC_FUNCTION LC_StringList  LC_ConcatLists(LC_Arena *allocator, LC_StringList a, LC_StringList b);
LC_FUNCTION LC_StringNode *LC_AddNode(LC_Arena *allocator, LC_StringList *list, LC_String string);
LC_FUNCTION LC_StringNode *LC_Add(LC_Arena *allocator, LC_StringList *list, LC_String string);
LC_FUNCTION LC_String      LC_Addf(LC_Arena *allocator, LC_StringList *list, const char *str, ...) LC__PrintfFormat(3, 4);

LC_FUNCTION wchar_t *LC_ToWidechar(LC_Arena *allocator, LC_String string);

//
// Linked list API
//
#define LC_SLLAddMod(f, l, n, next) \
    do {                            \
        (n)->next = 0;              \
        if ((f) == 0) {             \
            (f) = (l) = (n);        \
        } else {                    \
            (l) = (l)->next = (n);  \
        }                           \
    } while (0)
#define LC_AddSLL(f, l, n) LC_SLLAddMod(f, l, n, next)

#define LC_SLLPopFirstMod(f, l, next) \
    do {                              \
        if ((f) == (l)) {             \
            (f) = (l) = 0;            \
        } else {                      \
            (f) = (f)->next;          \
        }                             \
    } while (0)
#define LC_SLLPopFirst(f, l) LC_SLLPopFirstMod(f, l, next)

#define LC_SLLStackAddMod(stack_base, new_stack_base, next) \
    do {                                                    \
        (new_stack_base)->next = (stack_base);              \
        (stack_base)           = (new_stack_base);          \
    } while (0)

#define LC_DLLAddMod(f, l, node, next, prev) \
    do {                                     \
        if ((f) == 0) {                      \
            (f) = (l)    = (node);           \
            (node)->prev = 0;                \
            (node)->next = 0;                \
        } else {                             \
            (l)->next    = (node);           \
            (node)->prev = (l);              \
            (node)->next = 0;                \
            (l)          = (node);           \
        }                                    \
    } while (0)
#define LC_DLLAdd(f, l, node) LC_DLLAddMod(f, l, node, next, prev)
#define LC_DLLAddFront(f, l, node) LC_DLLAddMod(l, f, node, prev, next)
#define LC_DLLRemoveMod(first, last, node, next, prev) \
    do {                                               \
        if ((first) == (last)) {                       \
            (first) = (last) = 0;                      \
        } else if ((last) == (node)) {                 \
            (last)       = (last)->prev;               \
            (last)->next = 0;                          \
        } else if ((first) == (node)) {                \
            (first)       = (first)->next;             \
            (first)->prev = 0;                         \
        } else {                                       \
            (node)->prev->next = (node)->next;         \
            (node)->next->prev = (node)->prev;         \
        }                                              \
    } while (0)
#define LC_DLLRemove(first, last, node) LC_DLLRemoveMod(first, last, node, next, prev)

#define LC_ASTFor(it, x) for (LC_AST *it = x; it; it = it->next)
#define LC_DeclFor(it, x) for (LC_Decl *it = x; it; it = it->next)
#define LC_TypeFor(it, x) for (LC_TypeMember *it = x; it; it = it->next)

#if defined(__APPLE__) && defined(__MACH__)
    #define LC_OPERATING_SYSTEM_MAC 1
    #define LC_OPERATING_SYSTEM_UNIX 1
#elif defined(_WIN32)
    #define LC_OPERATING_SYSTEM_WINDOWS 1
#elif defined(__linux__)
    #define LC_OPERATING_SYSTEM_UNIX 1
    #define LC_OPERATING_SYSTEM_LINUX 1
#endif

#ifndef LC_OPERATING_SYSTEM_MAC
    #define LC_OPERATING_SYSTEM_MAC 0
#endif

#ifndef LC_OPERATING_SYSTEM_UNIX
    #define LC_OPERATING_SYSTEM_UNIX 0
#endif

#ifndef LC_OPERATING_SYSTEM_WINDOWS
    #define LC_OPERATING_SYSTEM_WINDOWS 0
#endif

#ifndef LC_OPERATING_SYSTEM_LINUX
    #define LC_OPERATING_SYSTEM_LINUX 0
#endif

#endif