
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "AST/ast.h"
#include "AST/meta_directives.h"
#include "DRVR/compiler.h"
#include "DRVR/object.h"
#include "LEX/token.h"
#include "PARSE/parse_ctx.h"
#include "PARSE/parse_dependency.h"
#include "PARSE/parse_expr.h"
#include "PARSE/parse_meta.h"
#include "PARSE/parse_pragma.h"
#include "PARSE/parse_util.h"
#include "TOKEN/token_data.h"
#include "UTIL/color.h"
#include "UTIL/datatypes.h"
#include "UTIL/filename.h"
#include "UTIL/ground.h"
#include "UTIL/search.h"
#include "UTIL/string.h"
#include "UTIL/util.h"

static const char *standard_directives[] = {
    "default", "define", "done", "elif", "else", "end", "error", "get", "halt", "if", "import", "input", "place", "place_error", "place_warning",
    "pragma", "print", "print_error", "print_warning", "runtime_resource", "set", "unless", "warning"
};

enum meta_directive {
    INVALID_META_DIRECTIVE = -1,
    META_DIRECTIVE_DEFAULT,
    META_DIRECTIVE_DEFINE,
    META_DIRECTIVE_DONE,
    META_DIRECTIVE_ELIF,
    META_DIRECTIVE_ELSE,
    META_DIRECTIVE_END,
    META_DIRECTIVE_ERROR,
    META_DIRECTIVE_GET,
    META_DIRECTIVE_HALT,
    META_DIRECTIVE_IF,
    META_DIRECTIVE_IMPORT,
    META_DIRECTIVE_INPUT,
    META_DIRECTIVE_PLACE,
    META_DIRECTIVE_PLACE_ERROR,
    META_DIRECTIVE_PLACE_WARNING,
    META_DIRECTIVE_PRAGMA,
    META_DIRECTIVE_PRINT,
    META_DIRECTIVE_PRINT_ERROR,
    META_DIRECTIVE_PRINT_WARNING,
    META_DIRECTIVE_RUNTIME_RESOURCE,
    META_DIRECTIVE_SET,
    META_DIRECTIVE_UNLESS,
    META_DIRECTIVE_WARNING,
};

static errorcode_t skip_until_matching_end(parse_ctx_t *ctx, bool allow_else, source_t source_on_failure){
    length_t *i = ctx->i;
    tokenlist_t *tokenlist = ctx->tokenlist;
    length_t ends_needed = 1;

    while(++(*i) != tokenlist->length && ends_needed != 0){
        if(parse_ctx_peek(ctx) != TOKEN_META) continue;

        weak_cstr_t pass_over_directive_name = (weak_cstr_t) parse_ctx_peek_data(ctx);
        maybe_index_t passover_meta_id = binary_string_search_const(standard_directives, sizeof standard_directives / sizeof(weak_cstr_t), pass_over_directive_name);

        switch(passover_meta_id){
        case META_DIRECTIVE_IF:
        case META_DIRECTIVE_UNLESS:
            ends_needed++;
            break;
        case META_DIRECTIVE_END:
            ends_needed--;
            break;
        case META_DIRECTIVE_ELSE:
            // Raise an error if we are passing over an `#else` at the same level as the origin `#else` block
            if(!allow_else && ends_needed == 1){
                compiler_panic(ctx->compiler, parse_ctx_peek_source(ctx), "Unexpected '#else' meta directive");
                return FAILURE;
            }
            break;
        case INVALID_META_DIRECTIVE:
            // That meta directive that we are passing over isn't recognized
            compiler_panicf(ctx->compiler, parse_ctx_peek_source(ctx), "Unrecognized meta directive #%s", pass_over_directive_name);
            return FAILURE;
        default:;
            // Recognized meta directive that we don't care about
        }
    }

    if(parse_ctx_at_end(ctx)){
        compiler_panic(ctx->compiler, source_on_failure, "Expected '#end' meta directive before termination");
        return FAILURE;
    }

    return SUCCESS;
}

static errorcode_t parse_meta_directive_default(parse_ctx_t *ctx){
    weak_cstr_t definition_name = parse_grab_word(ctx, "Expected transcendent variable name after #default");
    if(definition_name == NULL) return FAILURE;

    parse_ctx_advance(ctx);

    meta_expr_t *value;
    if(parse_meta_expr(ctx, &value)) return FAILURE;
    if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

    meta_definition_t *existing = meta_definition_find(ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, definition_name);

    if(existing == NULL){
        meta_definition_add(&ctx->ast->meta_definitions, &ctx->ast->meta_definitions_length, &ctx->ast->meta_definitions_capacity, definition_name, value);
    } else {
        meta_expr_free_fully(value);
    }

    return SUCCESS;
}

static errorcode_t parse_meta_directive_done(parse_ctx_t *ctx){
    ctx->compiler->result_flags |= COMPILER_RESULT_SUCCESS;
    return FAILURE;
}

static errorcode_t parse_meta_directive_elif(parse_ctx_t *ctx, source_t source_on_failure){
    // This gets triggered when #if was true

    // Ensure #elif is allowed here
    if(!parse_ctx_get_meta_else_allowed(ctx, ctx->meta_ends_expected)){
        compiler_panic(ctx->compiler, source_on_failure, "Unexpected '#elif' meta directive");
        return FAILURE;
    }

    // Pass over #elif expression
    parse_ctx_advance(ctx);
    meta_expr_t *value;
    if(parse_meta_expr(ctx, &value)) return FAILURE;
    meta_expr_free_fully(value);

    if(skip_until_matching_end(ctx, true, source_on_failure)) return FAILURE;
    return SUCCESS;
}

static errorcode_t parse_meta_directive_else(parse_ctx_t *ctx, source_t source_on_failure){
    // This gets triggered when #if was true

    // Ensure #else if allowed here
    if(!parse_ctx_get_meta_else_allowed(ctx, ctx->meta_ends_expected)){
        compiler_panic(ctx->compiler, source_on_failure, "Unexpected '#else' meta directive");
        return FAILURE;
    }

    if(skip_until_matching_end(ctx, false, source_on_failure)) return FAILURE;

    return SUCCESS;
}

static errorcode_t parse_meta_directive_end(parse_ctx_t *ctx, source_t source_on_failure){
    if(ctx->meta_ends_expected-- == 0){
        compiler_panic(ctx->compiler, source_on_failure, "Unexpected '#end' meta directive");
        return FAILURE;
    }
    
    parse_ctx_advance(ctx);
    return SUCCESS;
}

static errorcode_t parse_meta_directive_error(parse_ctx_t *ctx, source_t source_on_failure){
    parse_ctx_advance(ctx);

    meta_expr_t *value;
    if(parse_meta_expr(ctx, &value)) return FAILURE;
    if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

    strong_cstr_t s = meta_expr_str(value);
    compiler_panicf(ctx->compiler, source_on_failure, "%s", s);
    free(s);

    meta_expr_free_fully(value);
    
    #ifdef ADEPT_INSIGHT_BUILD
        return SUCCESS;
    #else
        return FAILURE;
    #endif
}

static errorcode_t parse_meta_directive_conditional(parse_ctx_t *ctx, enum meta_directive directive, source_t source_on_failure){
    tokenlist_t *tokenlist = ctx->tokenlist;
    length_t *i = ctx->i;

    bool is_unless = directive == META_DIRECTIVE_UNLESS;
    
    parse_ctx_advance(ctx);

    meta_expr_t *value;
    if(parse_meta_expr(ctx, &value)) return FAILURE;

    bool whether;
    if(meta_expr_into_bool(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value, &whether)){
        meta_expr_free_fully(value);
        return FAILURE;
    }
    
    whether ^= is_unless;
    meta_expr_free_fully(value);

    // If true, continue parsing as normal
    if(whether){
        if(ctx->meta_ends_expected++ == 256){
            compiler_panic(ctx->compiler, source_on_failure, "Exceeded maximum meta conditional depth of 256");
            return FAILURE;
        }
        parse_ctx_set_meta_else_allowed(ctx, ctx->meta_ends_expected, true);
        return SUCCESS;
    }

    // Otherwise, skip over this section until either #else, #elif, or #end
    length_t ends_needed = 1;

    while(++(*i) != tokenlist->length && ends_needed != 0){
        if(tokenlist->tokens[*i].id != TOKEN_META) continue;

        char *pass_over_directive_name = (char*) tokenlist->tokens[*i].data;
        maybe_index_t pass_id = binary_string_search_const(standard_directives, sizeof(standard_directives) / sizeof(char*), pass_over_directive_name);

        if(pass_id == META_DIRECTIVE_IF || pass_id == META_DIRECTIVE_UNLESS){
            ends_needed++;
        } else if(pass_id == META_DIRECTIVE_END){
            ends_needed--;
        } else if(pass_id == META_DIRECTIVE_ELIF){
            if(ends_needed == 1){
                (*i)++;

                meta_expr_t *value;
                if(parse_meta_expr(ctx, &value)) return FAILURE;

                bool whether;
                if(meta_expr_into_bool(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value, &whether)){
                    meta_expr_free_fully(value);
                    return FAILURE;
                }

                meta_expr_free_fully(value);

                if(whether){   
                    if(ctx->meta_ends_expected++ == 256){
                        compiler_panic(ctx->compiler, tokenlist->sources[*i], "Exceeded maximum meta conditional depth of 256");
                        return FAILURE;
                    }
                    parse_ctx_set_meta_else_allowed(ctx, ctx->meta_ends_expected, true);
                    break;
                }
            }
        } else if(pass_id == META_DIRECTIVE_ELSE){
            if(ends_needed == 1){
                if(ctx->meta_ends_expected++ == 256){
                    compiler_panic(ctx->compiler, tokenlist->sources[*i], "Exceeded maximum meta conditional depth of 256");
                    return FAILURE;
                }
                parse_ctx_set_meta_else_allowed(ctx, ctx->meta_ends_expected, false);
                break;
            }
        } else if(pass_id == -1){
            compiler_panicf(ctx->compiler, tokenlist->sources[*i], "Unrecognized meta directive #%s", pass_over_directive_name);
            return FAILURE;
        }
    }

    if(parse_ctx_at_end(ctx)){
        compiler_panic(ctx->compiler, source_on_failure, "Expected '#end' meta directive before termination");
        return FAILURE;
    }
    
    return SUCCESS;
}

errorcode_t parse_meta(parse_ctx_t *ctx){
    assert(parse_ctx_peek(ctx) == TOKEN_META);

    length_t *i = ctx->i;
    tokenlist_t *tokenlist = ctx->tokenlist;
    source_t source = tokenlist->sources[*i];

    weak_cstr_t directive_name = (weak_cstr_t) parse_ctx_peek_data(ctx);
    maybe_index_t directive = binary_string_search_const(standard_directives, sizeof(standard_directives) / sizeof(char*), directive_name);

    if(directive == INVALID_META_DIRECTIVE){
        compiler_panicf(ctx->compiler, source, "Unrecognized meta directive #%s", directive_name);
        return FAILURE;
    }

    switch(directive){
    case META_DIRECTIVE_DEFAULT:
        return parse_meta_directive_default(ctx);
    case META_DIRECTIVE_DONE:
        return parse_meta_directive_done(ctx);
    case META_DIRECTIVE_ELIF:
        return parse_meta_directive_elif(ctx, source);
    case META_DIRECTIVE_ELSE:
        return parse_meta_directive_else(ctx, source);
    case META_DIRECTIVE_END:
        return parse_meta_directive_end(ctx, source);
    case META_DIRECTIVE_ERROR:
        return parse_meta_directive_error(ctx, source);
    case META_DIRECTIVE_HALT:
        return FAILURE;
    case META_DIRECTIVE_IF:
    case META_DIRECTIVE_UNLESS:
        return parse_meta_directive_conditional(ctx, directive, source);
    case META_DIRECTIVE_IMPORT: {
            length_t old_i = (*i)++;

            if(parse_ctx_peek(ctx) == TOKEN_LESSTHAN){
                // Do special stuff for #import <library>
                if(parse_meta_import_stdlib(ctx, ctx->tokenlist->sources[old_i])) return FAILURE;
                return SUCCESS;
            }
            
            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            length_t new_i = *i;

            strong_cstr_t file;
            bool should_exit = meta_expr_into_string(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value, &file);
            meta_expr_free_fully(value);
            if(should_exit) return FAILURE;
            
            *i = old_i;
            
            maybe_null_strong_cstr_t target = parse_find_import(ctx, file, source, true);
            free(file);

            if(target == NULL){
                return FAILURE;
            }

            maybe_null_strong_cstr_t absolute = parse_resolve_import(ctx, target);
            if(absolute == NULL){
                free(target);
                return FAILURE;
            }

            if(already_imported(ctx, absolute)){
                free(target);
                free(absolute);
                return SUCCESS;
            }

            if(parse_import_object(ctx, target, absolute)) return FAILURE;
            *i = new_i;
        }
        break;
    case META_DIRECTIVE_INPUT: {
            maybe_null_weak_cstr_t definition_name = parse_grab_word(ctx, "Expected transcendent variable name after #set");
            if(!definition_name) return FAILURE;
            (*i)++;

            char input_str[512];
            fgets(input_str, 512, stdin);
            input_str[strcspn(input_str, "\n")] = '\0';

            meta_expr_str_t *value = malloc_init(meta_expr_str_t, {
                .id = META_EXPR_STR,
                .value = strclone(input_str),
            });

            meta_definition_t *existing = meta_definition_find(ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, definition_name);

            if(existing){
                meta_expr_free_fully(existing->value);
                existing->value = (meta_expr_t*) value;
            } else {
                meta_definition_add(&ctx->ast->meta_definitions, &ctx->ast->meta_definitions_length, &ctx->ast->meta_definitions_capacity, definition_name, (meta_expr_t*) value);
            }
        }
        break;
    case META_DIRECTIVE_PLACE: { // place
            (*i)++;

            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            strong_cstr_t print_value = meta_expr_str(value);
            printf("%s", print_value);
            free(print_value);

            meta_expr_free_fully(value);
        }
        break;
    case META_DIRECTIVE_PLACE_ERROR: { // place_error
            (*i)++;

            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            strong_cstr_t print_value = meta_expr_str(value);
            redprintf("%s", print_value);
            free(print_value);

            meta_expr_free_fully(value);
        }
        break;
    case META_DIRECTIVE_PLACE_WARNING: { // place_warning
            (*i)++;

            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            strong_cstr_t print_value = meta_expr_str(value);
            yellowprintf("%s", print_value);
            free(print_value);

            meta_expr_free_fully(value);
        }
        break;
    case META_DIRECTIVE_PRAGMA: // pragma
        if(parse_pragma(ctx)) return FAILURE;
        break;
    case META_DIRECTIVE_PRINT: { // print
            (*i)++;

            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            char *print_value = meta_expr_str(value);
            printf("%s\n", print_value);
            free(print_value);

            meta_expr_free_fully(value);
        }
        break;
    case META_DIRECTIVE_PRINT_ERROR: { // print_error
            (*i)++;

            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            char *print_value = meta_expr_str(value);
            redprintf("%s\n", print_value);
            free(print_value);

            meta_expr_free_fully(value);
        }
        break;
    case META_DIRECTIVE_PRINT_WARNING: { // print_warning
            (*i)++;

            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            char *print_value = meta_expr_str(value);
            yellowprintf("%s\n", print_value);
            free(print_value);

            meta_expr_free_fully(value);
        }
        break;
    case META_DIRECTIVE_RUNTIME_RESOURCE: {
            (*i)++;

            meta_expr_t *value;
            source_t source_on_error = ctx->tokenlist->sources[*i];

            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            errorcode_t res = SUCCESS;

            #ifndef ADEPT_INSIGHT_BUILD
            {
                // Will work based on the most recently specified output location
                weak_cstr_t raw_output_filename = ctx->compiler->output_filename ? ctx->compiler->output_filename : ctx->compiler->objects[0]->filename;

                strong_cstr_t filename = meta_expr_str(value);

                strong_cstr_t to_path = filename_path(raw_output_filename);
                strong_cstr_t from_path = filename_path(ctx->object->filename);

                strong_cstr_t to_filename = mallocandsprintf("%s%s", to_path, filename);
                strong_cstr_t from_filename = mallocandsprintf("%s%s", from_path, filename_name_const(filename));

                if(!file_exists(to_filename)){
                    blueprintf("[NOTE] ");
                    printf("Creating a local copy of required runtime resource '%s'\n", filename);

                    res = file_copy(from_filename, to_filename);

                    if(res){
                        compiler_panicf(ctx->compiler, source_on_error, "Unable to copy file '%s' to '%s'", from_filename, to_filename);
                    }
                }

                free(to_path);
                free(from_path);
                free(to_filename);
                free(from_filename);
                free(filename);
            }
            #else
            (void) source_on_error;
            #endif

            meta_expr_free_fully(value);
            if(res) return res;
        }
        break;
    case META_DIRECTIVE_SET: case META_DIRECTIVE_DEFINE: { // set, define
            char *definition_name = parse_grab_word(ctx, "Expected transcendent variable name after #set");
            if(!definition_name) return FAILURE;

            meta_expr_t *value = NULL;

            if(tokenlist->tokens[++(*i)].id == TOKEN_NEWLINE){
                if(directive == META_DIRECTIVE_DEFINE){
                    compiler_panicf(ctx->compiler, tokenlist->sources[*i], "Expected initial value for meta variable definition");
                } else {
                    compiler_panicf(ctx->compiler, tokenlist->sources[*i], "Expected new value for meta variable");
                }

                printf("Did you mean to use:\n    #%s %s true\n", directive_name, definition_name);

                #ifndef _WIN32
                printf("\n"); // Extra newline for non-windows systems to make more readable
                #endif
                return FAILURE;
            }

            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;
            
            meta_definition_t *existing = meta_definition_find(ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, definition_name);

            if(existing){
                meta_expr_free_fully(existing->value);
                existing->value = value;
            } else {
                meta_definition_add(&ctx->ast->meta_definitions, &ctx->ast->meta_definitions_length, &ctx->ast->meta_definitions_capacity, definition_name, value);
            }
        }
        break;
    case META_DIRECTIVE_GET: { // get
            compiler_panicf(ctx->compiler, source, "Meta directive #%s must be used in an inline expression", directive_name);
            return FAILURE;
        }
        break;
    case META_DIRECTIVE_WARNING: { // warning
            (*i)++;

            meta_expr_t *value;
            if(parse_meta_expr(ctx, &value)) return FAILURE;
            if(meta_collapse(ctx->compiler, ctx->object, ctx->ast->meta_definitions, ctx->ast->meta_definitions_length, &value)) return FAILURE;

            strong_cstr_t print_value = meta_expr_str(value);
            compiler_warnf(ctx->compiler, source, "%s", print_value);
            free(print_value);

            meta_expr_free_fully(value);
        }
        break;
    default:
        compiler_panicf(ctx->compiler, source, "Unimplemented meta directive #%s", directive_name);
        return FAILURE;
    }

    return SUCCESS;
}

errorcode_t parse_meta_import_stdlib(parse_ctx_t *ctx, source_t source){
    // #import <library>
    //         ^

    // NOTE: Assumes '#import <' is present

    // Get stdlib location
    strong_cstr_t standard_library_folder = compiler_get_stdlib(ctx->compiler, ctx->object);

    // Get component name
    source_t component_source;
    strong_cstr_t full_component = parse_standard_library_component(ctx, &component_source);
    if(full_component == NULL) return FAILURE;

    // Combine standard library and component name to create the filename
    strong_cstr_t file = mallocandsprintf("%s%s.adept", standard_library_folder, full_component);
    free(full_component);
    free(standard_library_folder);

    if(parse_eat(ctx, TOKEN_GREATERTHAN, "Expected '>' after component name in #import")
    || parse_do_import(ctx, file, source, false)){
        free(file);
        return FAILURE;
    }

    free(file);
    return SUCCESS;
}

errorcode_t parse_meta_expr(parse_ctx_t *ctx, meta_expr_t **out_expr){
    if(parse_meta_primary_expr(ctx, out_expr)) return FAILURE;
    if(parse_meta_op_expr(ctx, 0, out_expr)) return FAILURE;
    return SUCCESS;
}

errorcode_t parse_meta_primary_expr(parse_ctx_t *ctx, meta_expr_t **out_expr){
    length_t *i = ctx->i;
    token_t *tokens = ctx->tokenlist->tokens;
    source_t *sources = ctx->tokenlist->sources;

    switch (tokens[*i].id){
    case TOKEN_TRUE:
        *out_expr = malloc(sizeof(meta_expr_t));
        (*out_expr)->id = META_EXPR_TRUE;
        (*i)++;
        break;
    case TOKEN_FALSE:
        *out_expr = malloc(sizeof(meta_expr_t));
        (*out_expr)->id = META_EXPR_FALSE;
        (*i)++;
        break;
    case TOKEN_NULL:
        *out_expr = malloc(sizeof(meta_expr_t));
        (*out_expr)->id = META_EXPR_NULL;
        (*i)++;
        break;
    case TOKEN_CSTRING: case TOKEN_STRING: {
            token_string_data_t *token_data = tokens[*i].data;
            meta_expr_str_t *str_expr = malloc(sizeof(meta_expr_str_t));
            str_expr->id = META_EXPR_STR;
            str_expr->value = malloc(token_data->length + 1);
            memcpy(str_expr->value, token_data->array, token_data->length);
            str_expr->value[token_data->length] = '\0';
            *out_expr = (meta_expr_t*) str_expr;
            (*i)++;
        }
        break;
    case TOKEN_WORD:
        *out_expr = malloc(sizeof(meta_expr_var_t));
        (*out_expr)->id = META_EXPR_VAR;
        ((meta_expr_var_t*) *out_expr)->name = strclone(tokens[*i].data);
        ((meta_expr_var_t*) *out_expr)->source = sources[*i];
        (*i)++;
        break;
    case TOKEN_GENERIC_INT:
        *out_expr = malloc(sizeof(meta_expr_int_t));
        (*out_expr)->id = META_EXPR_INT;
        ((meta_expr_int_t*) *out_expr)->value = *((adept_generic_int*) tokens[*i].data);
        (*i)++;
        break;
    case TOKEN_GENERIC_FLOAT:
        *out_expr = malloc(sizeof(meta_expr_float_t));
        (*out_expr)->id = META_EXPR_FLOAT;
        ((meta_expr_float_t*) *out_expr)->value = *((double*) tokens[*i].data);
        (*i)++;
        break;
    case TOKEN_OPEN:
        (*i)++;
        if(parse_meta_expr(ctx, out_expr) != 0) return FAILURE;
        if(parse_eat(ctx, TOKEN_CLOSE, "Expected ')' after meta expression")) return FAILURE;
        break;
    case TOKEN_NOT: {
            (*i)++;
            if(parse_meta_primary_expr(ctx, out_expr)) return FAILURE;
            meta_expr_t *not_expr = malloc(sizeof(meta_expr_not_t));
            ((meta_expr_not_t*) not_expr)->id = META_EXPR_NOT;
            ((meta_expr_not_t*) not_expr)->value = *out_expr;
            *out_expr = not_expr;
        }
        break;
    default:
        parse_panic_token(ctx, sources[*i], tokens[*i].id, "Unexpected token '%s' in meta expression");
        return FAILURE;
    }

    return SUCCESS;
}

errorcode_t parse_meta_op_expr(parse_ctx_t *ctx, int precedence, meta_expr_t **inout_left){
    length_t *i = ctx->i;
    token_t *tokens = ctx->tokenlist->tokens;
    source_t *sources = ctx->tokenlist->sources;
    meta_expr_t *right, *expr;

    while(*i != ctx->tokenlist->length) {
        int operator = tokens[*i].id;
        int operator_precedence;

        switch(operator){
        case TOKEN_UBERAND: case TOKEN_UBEROR:
            operator_precedence = 1;
            break;
        case TOKEN_AND: case TOKEN_OR:
            operator_precedence =  2;
            break;
        case TOKEN_BIT_XOR:
            operator_precedence = 3;
            break;
        case TOKEN_EQUALS: case TOKEN_NOTEQUALS:
        case TOKEN_LESSTHAN: case TOKEN_GREATERTHAN:
        case TOKEN_LESSTHANEQ: case TOKEN_GREATERTHANEQ:
            operator_precedence =  4;
            break;
        case TOKEN_ADD: case TOKEN_SUBTRACT: case TOKEN_WORD:
            operator_precedence =  5;
            break;
        case TOKEN_DIVIDE: case TOKEN_MODULUS:
            operator_precedence =  6;
            break;
        case TOKEN_MULTIPLY:
            operator_precedence = (*i + 1 != ctx->tokenlist->length && tokens[*i + 1].id == TOKEN_MULTIPLY) ? 7 : 6;
            break;
        default:
            operator_precedence =  0;
        }

        if(operator_precedence < precedence) return SUCCESS;
        //source_t source = sources[*i];

        if(operator == TOKEN_NEWLINE || operator == TOKEN_CLOSE || operator == TOKEN_NEXT
            || operator == TOKEN_BEGIN || operator == TOKEN_BRACKET_CLOSE) return SUCCESS;

        #define BUILD_MATH_META_EXPR_MACRO(new_built_expr_id) { \
            if(meta_parse_rhs_expr(ctx, inout_left, &right, operator_precedence)) return FAILURE; \
            expr = malloc(sizeof(meta_expr_math_t)); \
            ((meta_expr_math_t*) expr)->id = new_built_expr_id; \
            ((meta_expr_math_t*) expr)->a = *inout_left; \
            ((meta_expr_math_t*) expr)->b = right; \
            *inout_left = expr; \
        }

        switch(operator){
        case TOKEN_ADD:            BUILD_MATH_META_EXPR_MACRO(META_EXPR_ADD);        break;
        case TOKEN_SUBTRACT:       BUILD_MATH_META_EXPR_MACRO(META_EXPR_SUB);        break;
        case TOKEN_MULTIPLY:
            if(*i + 1 != ctx->tokenlist->length && tokens[*i + 1].id == TOKEN_MULTIPLY){    
                (*i)++;
                BUILD_MATH_META_EXPR_MACRO(META_EXPR_POW);
                break;
            }
            BUILD_MATH_META_EXPR_MACRO(META_EXPR_MUL);
            break;
        case TOKEN_DIVIDE:         BUILD_MATH_META_EXPR_MACRO(META_EXPR_DIV);        break;
        case TOKEN_MODULUS:        BUILD_MATH_META_EXPR_MACRO(META_EXPR_MOD);        break;
        case TOKEN_AND:
        case TOKEN_UBERAND:        BUILD_MATH_META_EXPR_MACRO(META_EXPR_AND);        break;
        case TOKEN_OR:
        case TOKEN_UBEROR:         BUILD_MATH_META_EXPR_MACRO(META_EXPR_OR);         break;
        case TOKEN_BIT_XOR:        BUILD_MATH_META_EXPR_MACRO(META_EXPR_XOR);        break;
        case TOKEN_EQUALS:         BUILD_MATH_META_EXPR_MACRO(META_EXPR_EQ);         break;
        case TOKEN_NOTEQUALS:      BUILD_MATH_META_EXPR_MACRO(META_EXPR_NEQ);        break;
        case TOKEN_LESSTHAN:       BUILD_MATH_META_EXPR_MACRO(META_EXPR_LT);         break;
        case TOKEN_GREATERTHAN:    BUILD_MATH_META_EXPR_MACRO(META_EXPR_GT);         break;
        case TOKEN_LESSTHANEQ:     BUILD_MATH_META_EXPR_MACRO(META_EXPR_LTE);        break;
        case TOKEN_GREATERTHANEQ:  BUILD_MATH_META_EXPR_MACRO(META_EXPR_GTE);        break;
        default:
            parse_panic_token(ctx, sources[*i], tokens[*i].id, "Unrecognized operator '%s' in meta expression");
            meta_expr_free_fully(*inout_left);
            return FAILURE;
        }

        #undef BUILD_MATH_META_EXPR_MACRO
    }

    return SUCCESS;
}

errorcode_t meta_parse_rhs_expr(parse_ctx_t *ctx, meta_expr_t **left, meta_expr_t **out_right, int op_prec){
    // Expects from 'ctx': compiler, object, tokenlist, i

    // NOTE: Handles all of the work with parsing the right hand side of a meta expression
    // All errors are taken care of inside this function
    // Freeing of 'left' expression performed on error
    // Expects 'i' to point to the operator token

    length_t *i = ctx->i;
    token_t *tokens = ctx->tokenlist->tokens;

    (*i)++; // Skip over operator token

    if(parse_ignore_newlines(ctx, "Unexpected meta expression termination")) return FAILURE;

    if(parse_meta_primary_expr(ctx, out_right) || (op_prec < parse_get_precedence(tokens[*i].id) && parse_meta_op_expr(ctx, op_prec + 1, out_right))){
        meta_expr_free_fully(*left);
        return FAILURE;
    }

    return SUCCESS;
}
