// Copyright 2023 xensik. All rights reserved.
//
// Use of this source code is governed by a GNU GPLv3 license
// that can be found in the LICENSE file.

#include "stdinc.hpp"
#include "t6.hpp"
#include "parser.hpp"
#include "lexer.hpp"

namespace xsk::arc::t6
{

auto compiler::output() -> assembly::ptr
{
    return std::move(assembly_);
}

auto compiler::output_raw() -> std::vector<std::uint8_t>
{
    output_ = std::make_unique<utils::writer>();

    output_->write_string("// T6 GSC ASSEMBLY\n");
    output_->write_string("// Compiled by https://github.com/xensik/gsc-tool\n");

    for (const auto& func : assembly_->functions)
    {
        this->print_function(func);
    }

    std::vector<std::uint8_t> output;

    output.resize(output_->pos());
    std::memcpy(output.data(), output_->data(), output.size());

    return output;
}

void compiler::compile(const std::string& file, std::vector<std::uint8_t>& data)
{
    filename_ = file;

    auto prog = parse_buffer(filename_, reinterpret_cast<const char*>(data.data()), data.size());

    compile_program(prog);
}

void compiler::mode(build mode)
{
    mode_ = mode;
}

auto compiler::parse_buffer(const std::string& file, const char* data, size_t size) -> ast::program::ptr
{
    ast::program::ptr result(nullptr);

    lexer lexer(mode_, file, data, size);

    parser parser(lexer, result);

    if (parser.parse() || result == nullptr)
    {
        throw comp_error(location(&file), "An unknown error ocurred while parsing gsc file.");
    }

    return result;
}

auto compiler::parse_file(const std::string& file) -> ast::program::ptr
{
    auto data = resolver::file_data(file);

    return parse_buffer(file, std::get<1>(data), std::get<2>(data));
}

void compiler::compile_program(const ast::program::ptr& program)
{
    assembly_ = std::make_unique<assembly>();
    includes_.clear();
    animtrees_.clear();
    local_functions_.clear();
    index_ = 0;
    developer_thread_ = false;

    for (const auto& entry : program->declarations)
    {
        if (entry == ast::kind::decl_thread)
        {
            local_functions_.push_back(entry.as_thread->name->value);
        }
    }

    for (const auto& include : program->includes)
    {
        emit_include(include);
    }

    for (const auto& declaration : program->declarations)
    {
        emit_declaration(declaration);
    }

    std::reverse(assembly_->includes.begin(), assembly_->includes.end());
}

void compiler::emit_include(const ast::include::ptr& include)
{
    const auto& path = include->path->value;

    for (const auto& entry : assembly_->includes)
    {
        if (entry == path)
        {
            throw comp_error(include->loc(), "duplicated include file '" + path + "'.");
        }
    }

    assembly_->includes.push_back(path);
}

void compiler::emit_declaration(const ast::decl& decl)
{
    switch (decl.kind())
    {
        case ast::kind::decl_dev_begin:
            developer_thread_ = true;
            break;
        case ast::kind::decl_dev_end:
            developer_thread_ = false;
            break;
        case ast::kind::decl_usingtree:
            emit_decl_usingtree(decl.as_usingtree);
            break;
        case ast::kind::decl_thread:
            emit_decl_thread(decl.as_thread);
            break;
        default:
            throw comp_error(decl.loc(), "unknown declaration");
    }
}

void compiler::emit_decl_usingtree(const ast::decl_usingtree::ptr& animtree)
{
    if (developer_thread_)
    {
        throw comp_error(animtree->loc(), "cannot put #using_animtree inside developer block comment");
    }

    animtrees_.push_back({ animtree->name->value, false });
}

void compiler::emit_decl_thread(const ast::decl_thread::ptr& thread)
{
    function_ = std::make_unique<function>();
    function_->index = index_;
    function_->name = thread->name->value;
    function_->params = static_cast<std::uint8_t>(thread->params->list.size());
    function_->flags = static_cast<std::uint8_t>(thread->flags);

    stack_idx_ = 0;
    label_idx_ = 0;
    can_break_ = false;
    can_continue_ = false;
    local_stack_.clear();
    constants_.clear();
    blocks_.clear();

    process_thread(thread);

    blocks_.push_back(block());

    emit_expr_parameters(thread->params);
    emit_stmt_list(thread->stmt);

    if (blocks_.back().abort == abort_t::abort_none)
        emit_opcode(opcode::OP_End);

    blocks_.pop_back();

    function_->size = index_ - function_->index;
    assembly_->functions.push_back(std::move(function_));
}

void compiler::emit_stmt(const ast::stmt& stmt)
{
    switch (stmt.kind())
    {
        case ast::kind::stmt_list:
            emit_stmt_list(stmt.as_list);
            break;
        case ast::kind::stmt_dev:
            emit_stmt_dev(stmt.as_dev);
            break;
        case ast::kind::stmt_expr:
            emit_stmt_expr(stmt.as_expr);
            break;
        case ast::kind::stmt_call:
            emit_stmt_call(stmt.as_call);
            break;
        case ast::kind::stmt_const:
            emit_stmt_const(stmt.as_const);
            break;
        case ast::kind::stmt_assign:
            emit_stmt_assign(stmt.as_assign);
            break;
        case ast::kind::stmt_endon:
            emit_stmt_endon(stmt.as_endon);
            break;
        case ast::kind::stmt_notify:
            emit_stmt_notify(stmt.as_notify);
            break;
        case ast::kind::stmt_wait:
            emit_stmt_wait(stmt.as_wait);
            break;
        case ast::kind::stmt_waittill:
            emit_stmt_waittill(stmt.as_waittill);
            break;
        case ast::kind::stmt_waittillmatch:
            emit_stmt_waittillmatch(stmt.as_waittillmatch);
            break;
        case ast::kind::stmt_waittillframeend:
            emit_stmt_waittillframeend(stmt.as_waittillframeend);
            break;
        case ast::kind::stmt_if:
            emit_stmt_if(stmt.as_if);
            break;
        case ast::kind::stmt_ifelse:
            emit_stmt_ifelse(stmt.as_ifelse);
            break;
        case ast::kind::stmt_while:
            emit_stmt_while(stmt.as_while);
            break;
        case ast::kind::stmt_dowhile:
            emit_stmt_dowhile(stmt.as_dowhile);
            break;
        case ast::kind::stmt_for:
            emit_stmt_for(stmt.as_for);
            break;
        case ast::kind::stmt_foreach:
            emit_stmt_foreach(stmt.as_foreach);
            break;
        case ast::kind::stmt_switch:
            emit_stmt_switch(stmt.as_switch);
            break;
        case ast::kind::stmt_case:
            emit_stmt_case(stmt.as_case);;
            break;
        case ast::kind::stmt_default:
            emit_stmt_default(stmt.as_default);
            break;
        case ast::kind::stmt_break:
            emit_stmt_break(stmt.as_break);
            break;
        case ast::kind::stmt_continue:
            emit_stmt_continue(stmt.as_continue);
            break;
        case ast::kind::stmt_return:
            emit_stmt_return(stmt.as_return);
            break;
        case ast::kind::stmt_prof_begin:
            emit_stmt_prof_begin(stmt.as_prof_begin);
            break;
        case ast::kind::stmt_prof_end:
            emit_stmt_prof_end(stmt.as_prof_end);
            break;
        default:
            throw comp_error(stmt.loc(), "unknown statement");
    }
}

void compiler::emit_stmt_list(const ast::stmt_list::ptr& stmt)
{
    for (const auto& entry : stmt->list)
    {
        emit_stmt(entry);
    }
}

void compiler::emit_stmt_dev(const ast::stmt_dev::ptr& stmt)
{
    developer_thread_ = true;

    auto end = create_label();
    emit_opcode(opcode::OP_DevblockBegin, end);
    emit_stmt_list(stmt->list);
    insert_label(end);

    developer_thread_ = false;
}

void compiler::emit_stmt_expr(const ast::stmt_expr::ptr& stmt)
{
    switch (stmt->expr.kind())
    {
        case ast::kind::expr_increment:
            emit_expr_increment(stmt->expr.as_increment, true);
            break;
        case ast::kind::expr_decrement:
            emit_expr_decrement(stmt->expr.as_decrement, true);
            break;
        case ast::kind::expr_assign_equal:
        case ast::kind::expr_assign_add:
        case ast::kind::expr_assign_sub:
        case ast::kind::expr_assign_mul:
        case ast::kind::expr_assign_div:
        case ast::kind::expr_assign_mod:
        case ast::kind::expr_assign_shift_left:
        case ast::kind::expr_assign_shift_right:
        case ast::kind::expr_assign_bitwise_or:
        case ast::kind::expr_assign_bitwise_and:
        case ast::kind::expr_assign_bitwise_exor:
            emit_expr_assign(stmt->expr.as_assign);
            break;
        case ast::kind::null:
            break;
        default:
            throw comp_error(stmt->loc(), "unknown expr statement expression");
    }
}

void compiler::emit_stmt_call(const ast::stmt_call::ptr& stmt)
{
    if (stmt->expr == ast::kind::expr_call)
        emit_expr_call(stmt->expr.as_call, true);
    else if (stmt->expr == ast::kind::expr_method)
        emit_expr_method(stmt->expr.as_method, true);
    else
        throw comp_error(stmt->loc(), "unknown call statement expression");
}

void compiler::emit_stmt_const(const ast::stmt_const::ptr& stmt)
{
    const auto itr = constants_.find(stmt->lvalue->value);

    if (itr != constants_.end())
        throw comp_error(stmt->loc(), "duplicated constant '" + stmt->lvalue->value + "'");

    if (std::find(local_stack_.begin(), local_stack_.end(), stmt->lvalue->value) != local_stack_.end())
        throw comp_error(stmt->loc(), "constant already defined as local variable '" + stmt->lvalue->value + "'");

    constants_.insert({ stmt->lvalue->value, std::move(stmt->rvalue) });
}

void compiler::emit_stmt_assign(const ast::stmt_assign::ptr& stmt)
{
    switch (stmt->expr.kind())
    {
        case ast::kind::expr_increment:
            emit_expr_increment(stmt->expr.as_increment, true);
            break;
        case ast::kind::expr_decrement:
            emit_expr_decrement(stmt->expr.as_decrement, true);
            break;
        case ast::kind::expr_assign_equal:
        case ast::kind::expr_assign_add:
        case ast::kind::expr_assign_sub:
        case ast::kind::expr_assign_mul:
        case ast::kind::expr_assign_div:
        case ast::kind::expr_assign_mod:
        case ast::kind::expr_assign_shift_left:
        case ast::kind::expr_assign_shift_right:
        case ast::kind::expr_assign_bitwise_or:
        case ast::kind::expr_assign_bitwise_and:
        case ast::kind::expr_assign_bitwise_exor:
            emit_expr_assign(stmt->expr.as_assign);
            break;
        default:
            throw comp_error(stmt->loc(), "unknown assign statement expression");
    }
}

void compiler::emit_stmt_endon(const ast::stmt_endon::ptr& stmt)
{
    emit_expr(stmt->event);
    emit_expr(stmt->obj);
    emit_opcode(opcode::OP_EndOn);
}

void compiler::emit_stmt_notify(const ast::stmt_notify::ptr& stmt)
{
    emit_opcode(opcode::OP_VoidCodePos);

    std::reverse(stmt->args->list.begin(), stmt->args->list.end());

    for (const auto& arg : stmt->args->list)
    {
        emit_expr(arg);
    }

    emit_expr(stmt->event);
    emit_expr(stmt->obj);
    emit_opcode(opcode::OP_Notify);
}

void compiler::emit_stmt_wait(const ast::stmt_wait::ptr& stmt)
{
    emit_expr(stmt->time);
    emit_opcode(opcode::OP_Wait);
}

void compiler::emit_stmt_waittill(const ast::stmt_waittill::ptr& stmt)
{
    emit_expr(stmt->event);
    emit_expr(stmt->obj);
    emit_opcode(opcode::OP_WaitTill);

    for (const auto& entry : stmt->args->list)
    {
        emit_opcode(opcode::OP_SafeSetWaittillVariableFieldCached, variable_access(entry.as_identifier));
    }

    emit_opcode(opcode::OP_ClearParams);
}

void compiler::emit_stmt_waittillmatch(const ast::stmt_waittillmatch::ptr& stmt)
{
    emit_expr_arguments(stmt->args);
    emit_expr(stmt->event);
    emit_expr(stmt->obj);
    emit_opcode(opcode::OP_WaitTillMatch, utils::string::va("%d", stmt->args->list.size()));
    emit_opcode(opcode::OP_ClearParams);
}

void compiler::emit_stmt_waittillframeend(const ast::stmt_waittillframeend::ptr&)
{
    emit_opcode(opcode::OP_WaitTillFrameEnd);
}

void compiler::emit_stmt_if(const ast::stmt_if::ptr& stmt)
{
    auto end_loc = create_label();

    if (stmt->test == ast::kind::expr_not)
    {
        emit_expr(stmt->test.as_not->rvalue);
        emit_opcode(opcode::OP_JumpOnTrue, end_loc);
    }
    else
    {
        emit_expr(stmt->test);
        emit_opcode(opcode::OP_JumpOnFalse, end_loc);
    }

    auto& paren = blocks_.back();
    blocks_.push_back(block(paren.loc_break, paren.loc_continue));

    emit_stmt(stmt->stmt);

    blocks_.pop_back();

    insert_label(end_loc);
}

void compiler::emit_stmt_ifelse(const ast::stmt_ifelse::ptr& stmt)
{
    auto else_loc = create_label();
    auto end_loc = create_label();

    if (stmt->test == ast::kind::expr_not)
    {
        emit_expr(stmt->test.as_not->rvalue);
        emit_opcode(opcode::OP_JumpOnTrue, else_loc);
    }
    else
    {
        emit_expr(stmt->test);
        emit_opcode(opcode::OP_JumpOnFalse, else_loc);
    }

    auto& paren = blocks_.back();
    blocks_.push_back(block(paren.loc_break, paren.loc_continue));
    emit_stmt(stmt->stmt_if);
    blocks_.pop_back();

    emit_opcode(opcode::OP_Jump, end_loc);

    insert_label(else_loc);

    auto& root = blocks_.back();
    blocks_.push_back(block(root.loc_break, root.loc_continue));
    emit_stmt(stmt->stmt_else);
    blocks_.pop_back();

    insert_label(end_loc);
}

void compiler::emit_stmt_while(const ast::stmt_while::ptr& stmt)
{
    auto old_break = can_break_;
    auto old_continue = can_continue_;

    can_break_ = true;
    can_continue_ = true;
    auto break_loc = create_label();
    auto continue_loc = insert_label();

    if (stmt->test == ast::kind::expr_not)
    {
        emit_expr(stmt->test.as_not->rvalue);
        emit_opcode(opcode::OP_JumpOnTrue, break_loc);
    }
    else
    {
        emit_expr(stmt->test);
        emit_opcode(opcode::OP_JumpOnFalse, break_loc);
    }

    blocks_.push_back(block(break_loc, continue_loc));
    emit_stmt(stmt->stmt);
    blocks_.pop_back();

    emit_opcode(opcode::OP_Jump, continue_loc);
    insert_label(break_loc);

    can_break_ = old_break;
    can_continue_ = old_continue;
}

void compiler::emit_stmt_dowhile(const ast::stmt_dowhile::ptr& stmt)
{
    auto old_break = can_break_;
    auto old_continue = can_continue_;

    can_break_ = true;
    can_continue_ = true;
    auto break_loc = create_label();
    auto continue_loc = create_label();

    auto begin_loc = insert_label();

    blocks_.push_back(block(break_loc, continue_loc));
    emit_stmt(stmt->stmt);
    blocks_.pop_back();

    insert_label(continue_loc);

    if (stmt->test == ast::kind::expr_not)
    {
        emit_expr(stmt->test.as_not->rvalue);
        emit_opcode(opcode::OP_JumpOnFalse, begin_loc);
    }
    else
    {
        emit_expr(stmt->test);
        emit_opcode(opcode::OP_JumpOnTrue, begin_loc);
    }

    insert_label(break_loc);

    can_break_ = old_break;
    can_continue_ = old_continue;
}

void compiler::emit_stmt_for(const ast::stmt_for::ptr& stmt)
{
    auto old_break = can_break_;
    auto old_continue = can_continue_;

    emit_stmt(stmt->init);

    auto break_loc = create_label();
    auto continue_loc = create_label();

    auto begin_loc = insert_label();

    bool const_cond = is_constant_condition(stmt->test);

    if (!const_cond)
    {
        emit_expr(stmt->test);
        emit_opcode(opcode::OP_JumpOnFalse, break_loc);
    }

    can_break_ = true;
    can_continue_ = true;

    blocks_.push_back(block(break_loc, continue_loc));
    emit_stmt(stmt->stmt);
    blocks_.pop_back();

    can_break_ = false;
    can_continue_ = false;

    insert_label(continue_loc);
    emit_stmt(stmt->iter);
    emit_opcode(opcode::OP_Jump, begin_loc);
    insert_label(break_loc);

    can_break_ = old_break;
    can_continue_ = old_continue;
}

void compiler::emit_stmt_foreach(const ast::stmt_foreach::ptr& stmt)
{
    auto old_break = can_break_;
    auto old_continue = can_continue_;

    emit_expr(stmt->array_expr);
    emit_expr_variable_ref(stmt->array, true);
    emit_expr_variable(stmt->array);
    emit_opcode(opcode::OP_FirstArrayKey);
    emit_expr_variable_ref(stmt->key_expr, true);

    auto break_loc = create_label();
    auto continue_loc = create_label();

    auto begin_loc = insert_label();

    emit_expr_variable(stmt->key_expr);
    emit_opcode(opcode::OP_IsDefined);
    emit_opcode(opcode::OP_JumpOnFalse, break_loc);

    can_break_ = true;
    can_continue_ = true;

    emit_expr_variable(stmt->key_expr);
    emit_opcode(opcode::OP_EvalLocalVariableCached, variable_access(stmt->array.as_identifier));
    emit_opcode(opcode::OP_EvalArray);
    emit_expr_variable_ref(stmt->value_expr, true);

    blocks_.push_back(block(break_loc, continue_loc));
    emit_stmt(stmt->stmt);
    blocks_.pop_back();

    can_break_ = false;
    can_continue_ = false;

    insert_label(continue_loc);

    emit_expr_variable(stmt->key_expr);
    emit_expr_variable(stmt->array);
    emit_opcode(opcode::OP_NextArrayKey);
    emit_expr_variable_ref(stmt->key_expr, true);
    emit_opcode(opcode::OP_Jump, begin_loc);
    insert_label(break_loc);

    can_break_ = old_break;
    can_continue_ = old_continue;
}

void compiler::emit_stmt_switch(const ast::stmt_switch::ptr& stmt)
{
    auto old_break = can_break_;

    can_break_ = false;

    auto jmptable_loc = create_label();

    auto break_loc = create_label();

    emit_expr(stmt->test);
    emit_opcode(opcode::OP_Switch, jmptable_loc);

    can_break_ = true;

    blocks_.push_back(block(break_loc, blocks_.back().loc_continue));

    std::vector<std::string> data;
    data.push_back(utils::string::va("%d", stmt->stmt->list.size()));

    bool numerical = false;
    bool has_default = false;

    for (auto i = 0u; i < stmt->stmt->list.size(); i++)
    {
        auto& entry = stmt->stmt->list[i];

        if (entry == ast::kind::stmt_case)
        {
            if (has_default)
            {
                comp_error(stmt->loc(), "default must be last case");
            }

            auto& case_ = entry.as_case;
            if (case_->label == ast::kind::expr_integer)
            {
                numerical = true;
                auto loc = insert_label();
                data.push_back("case");
                data.push_back(case_->label.as_integer->value);
                data.push_back(loc);
            }
            else if (case_->label == ast::kind::expr_string)
            {
                auto loc = insert_label();
                data.push_back("case");
                data.push_back(case_->label.as_string->value);
                data.push_back(loc);
            }
            else
            {
                throw comp_error(stmt->loc(), "case type must be int or string");
            }

            auto& paren = blocks_.back();
            blocks_.push_back(block(paren.loc_break, paren.loc_continue));
            emit_stmt_list(case_->stmt);
            blocks_.pop_back();
        }
        else if (entry == ast::kind::stmt_default)
        {
            auto loc = insert_label();
            data.push_back("default");
            data.push_back(loc);
            has_default = true;

            auto& paren = blocks_.back();
            blocks_.push_back(block(paren.loc_break, paren.loc_continue));
            emit_stmt_list(entry.as_default->stmt);
            blocks_.pop_back();
        }
        else
        {
            throw comp_error(entry.loc(), "missing case statement");
        }
    }

    blocks_.pop_back();
    data.push_back(numerical ? "i" : "s");

    insert_label(jmptable_loc);
    emit_opcode(opcode::OP_EndSwitch, data);
    insert_label(break_loc);

    can_break_ = old_break;
}

void compiler::emit_stmt_case(const ast::stmt_case::ptr& stmt)
{
    throw comp_error(stmt->loc(), "illegal case statement");
}

void compiler::emit_stmt_default(const ast::stmt_default::ptr& stmt)
{
    throw comp_error(stmt->loc(), "illegal default statement");
}

void compiler::emit_stmt_break(const ast::stmt_break::ptr& stmt)
{
    if (!can_break_ || blocks_.back().abort != abort_t::abort_none || blocks_.back().loc_break == "")
        throw comp_error(stmt->loc(), "illegal break statement");

    blocks_.back().abort = abort_t::abort_break;
    emit_opcode(opcode::OP_Jump, blocks_.back().loc_break);
}

void compiler::emit_stmt_continue(const ast::stmt_continue::ptr& stmt)
{
    if (!can_continue_ || blocks_.back().abort != abort_t::abort_none || blocks_.back().loc_continue == "")
        throw comp_error(stmt->loc(), "illegal continue statement");

    blocks_.back().abort = abort_t::abort_continue;
    emit_opcode(opcode::OP_Jump, blocks_.back().loc_continue);
}

void compiler::emit_stmt_return(const ast::stmt_return::ptr& stmt)
{
    if (blocks_.back().abort == abort_t::abort_none)
        blocks_.back().abort = abort_t::abort_return;

    if (stmt->expr != ast::kind::null)
    {
        emit_expr(stmt->expr);
        emit_opcode(opcode::OP_Return);
    }
    else
        emit_opcode(opcode::OP_End);
}

void compiler::emit_stmt_prof_begin(const ast::stmt_prof_begin::ptr&)
{
    // TODO:
}

void compiler::emit_stmt_prof_end(const ast::stmt_prof_end::ptr&)
{
    // TODO:
}

void compiler::emit_expr(const ast::expr& expr)
{
    switch (expr.kind())
    {
        case ast::kind::expr_paren:
            emit_expr(expr.as_paren->child);
            break;
        case ast::kind::expr_ternary:
            emit_expr_ternary(expr.as_ternary);
            break;
        case ast::kind::expr_and:
            emit_expr_and(expr.as_and);
            break;
        case ast::kind::expr_or:
            emit_expr_or(expr.as_or);
            break;
        case ast::kind::expr_equality:
        case ast::kind::expr_inequality:
        case ast::kind::expr_less:
        case ast::kind::expr_greater:
        case ast::kind::expr_less_equal:
        case ast::kind::expr_greater_equal:
        case ast::kind::expr_bitwise_or:
        case ast::kind::expr_bitwise_and:
        case ast::kind::expr_bitwise_exor:
        case ast::kind::expr_shift_left:
        case ast::kind::expr_shift_right:
        case ast::kind::expr_add:
        case ast::kind::expr_sub:
        case ast::kind::expr_mul:
        case ast::kind::expr_div:
        case ast::kind::expr_mod:
            emit_expr_binary(expr.as_binary);
            break;
        case ast::kind::expr_complement:
            emit_expr_complement(expr.as_complement);
            break;
        case ast::kind::expr_negate:
            emit_expr_negate(expr.as_negate);
            break;
        case ast::kind::expr_not:
            emit_expr_not(expr.as_not);
            break;
        case ast::kind::expr_call:
            emit_expr_call(expr.as_call, false);
            break;
        case ast::kind::expr_method:
            emit_expr_method(expr.as_method, false);
            break;
        case ast::kind::expr_isdefined:
            emit_expr_isdefined(expr.as_isdefined);
            break;
        case ast::kind::expr_vectorscale:
            emit_expr_vectorscale(expr.as_vectorscale);
            break;
        case ast::kind::expr_anglestoup:
            emit_expr_anglestoup(expr.as_anglestoup);
            break;
        case ast::kind::expr_anglestoright:
            emit_expr_anglestoright(expr.as_anglestoright);
            break;
        case ast::kind::expr_anglestoforward:
            emit_expr_anglestoforward(expr.as_anglestoforward);
            break;
        case ast::kind::expr_angleclamp180:
            emit_expr_angleclamp180(expr.as_angleclamp180);
            break;
        case ast::kind::expr_vectortoangles:
            emit_expr_vectortoangles(expr.as_vectortoangles);
            break;
        case ast::kind::expr_abs:
            emit_expr_abs(expr.as_abs);
            break;
        case ast::kind::expr_gettime:
            emit_expr_gettime(expr.as_gettime);
            break;
        case ast::kind::expr_getdvar:
            emit_expr_getdvar(expr.as_getdvar);
            break;
        case ast::kind::expr_getdvarint:
            emit_expr_getdvarint(expr.as_getdvarint);
            break;
        case ast::kind::expr_getdvarfloat:
            emit_expr_getdvarfloat(expr.as_getdvarfloat);
            break;
        case ast::kind::expr_getdvarvector:
            emit_expr_getdvarvector(expr.as_getdvarvector);
            break;
        case ast::kind::expr_getdvarcolorred:
            emit_expr_getdvarcolorred(expr.as_getdvarcolorred);
            break;
        case ast::kind::expr_getdvarcolorgreen:
            emit_expr_getdvarcolorgreen(expr.as_getdvarcolorgreen);
            break;
        case ast::kind::expr_getdvarcolorblue:
            emit_expr_getdvarcolorblue(expr.as_getdvarcolorblue);
            break;
        case ast::kind::expr_getdvarcoloralpha:
            emit_expr_getdvarcoloralpha(expr.as_getdvarcoloralpha);
            break;
        case ast::kind::expr_getfirstarraykey:
            emit_expr_getfirstarraykey(expr.as_getfirstarraykey);
            break;
        case ast::kind::expr_getnextarraykey:
            emit_expr_getnextarraykey(expr.as_getnextarraykey);
            break;
        case ast::kind::expr_reference:
            emit_expr_reference(expr.as_reference);
            break;
        case ast::kind::expr_array:
            emit_expr_array(expr.as_array);
            break;
        case ast::kind::expr_field:
            emit_expr_field(expr.as_field);
            break;
        case ast::kind::expr_size:
            emit_expr_size(expr.as_size);
            break;
        case ast::kind::expr_empty_array:
            emit_opcode(opcode::OP_EmptyArray);
            break;
        case ast::kind::expr_undefined:
            emit_opcode(opcode::OP_GetUndefined);
            break;
        case ast::kind::expr_game:
            emit_opcode(opcode::OP_GetGame);
            break;
        case ast::kind::expr_self:
            emit_opcode(opcode::OP_GetSelf);
            break;
        case ast::kind::expr_anim:
            emit_opcode(opcode::OP_GetAnim);
            break;
        case ast::kind::expr_level:
            emit_opcode(opcode::OP_GetLevel);
            break;
        case ast::kind::expr_animation:
            emit_expr_animation(expr.as_animation);
            break;
        case ast::kind::expr_animtree:
            emit_expr_animtree(expr.as_animtree);
            break;
        case ast::kind::expr_identifier:
            emit_expr_local(expr.as_identifier);
            break;
        case ast::kind::expr_istring:
            emit_expr_istring(expr.as_istring);
            break;
        case ast::kind::expr_string:
            emit_expr_string(expr.as_string);
            break;
        case ast::kind::expr_vector:
            emit_expr_vector(expr.as_vector);
            break;
        case ast::kind::expr_hash:
            emit_expr_hash(expr.as_hash);
            break;
        case ast::kind::expr_float:
            emit_expr_float(expr.as_float);
            break;
        case ast::kind::expr_integer:
            emit_expr_integer(expr.as_integer);
            break;
        case ast::kind::expr_false:
            emit_expr_false(expr.as_false);
            break;
        case ast::kind::expr_true:
            emit_expr_true(expr.as_true);
            break;
        case ast::kind::null:
            break;
        default:
            throw comp_error(expr.loc(), "unknown expression");
    }
}

void compiler::emit_expr_assign(const ast::expr_assign::ptr& expr)
{
    if (expr->kind() == ast::kind::expr_assign_equal)
    {
        if (expr->rvalue == ast::kind::expr_undefined)
        {
            emit_expr_clear(expr->lvalue);
            return;
        }

        emit_expr(expr->rvalue);
        emit_expr_variable_ref(expr->lvalue, true);
        return;
    }

    emit_expr(expr->lvalue);
    emit_expr(expr->rvalue);

    switch (expr->kind())
    {
        case ast::kind::expr_assign_add:
            emit_opcode(opcode::OP_Plus);
            break;
        case ast::kind::expr_assign_sub:
            emit_opcode(opcode::OP_Minus);
            break;
        case ast::kind::expr_assign_mul:
            emit_opcode(opcode::OP_Multiply);
            break;
        case ast::kind::expr_assign_div:
            emit_opcode(opcode::OP_Divide);
            break;
        case ast::kind::expr_assign_mod:
            emit_opcode(opcode::OP_Modulus);
            break;
        case ast::kind::expr_assign_shift_left:
            emit_opcode(opcode::OP_ShiftLeft);
            break;
        case ast::kind::expr_assign_shift_right:
            emit_opcode(opcode::OP_ShiftRight);
            break;
        case ast::kind::expr_assign_bitwise_or:
            emit_opcode(opcode::OP_Bit_Or);
            break;
        case ast::kind::expr_assign_bitwise_and:
            emit_opcode(opcode::OP_Bit_And);
            break;
        case ast::kind::expr_assign_bitwise_exor:
            emit_opcode(opcode::OP_Bit_Xor);
            break;
        default:
            throw comp_error(expr->loc(), "unknown assign operation");
    }

    emit_expr_variable_ref(expr->lvalue, true);
}

void compiler::emit_expr_clear(const ast::expr& expr)
{
    switch (expr.kind())
    {
        case ast::kind::expr_array:
            emit_expr(expr.as_array->key);
            expr.as_array->obj == ast::kind::expr_game ? emit_opcode(opcode::OP_GetGameRef) : emit_expr_variable_ref(expr.as_array->obj, false);
            emit_opcode(opcode::OP_ClearArray);
            break;
        case ast::kind::expr_field:
            emit_expr_object(expr.as_field->obj);
            emit_opcode(opcode::OP_ClearFieldVariable, expr.as_field->field->value);
            break;
        case ast::kind::expr_identifier:
            emit_opcode(opcode::OP_GetUndefined);
            emit_expr_local_ref(expr.as_identifier, true);
            break;
        default:
            throw comp_error(expr.loc(), "unknown clear variable lvalue");
    }
}

void compiler::emit_expr_increment(const ast::expr_increment::ptr& expr, bool is_stmt)
{
    if (is_stmt)
    {
        emit_expr_variable_ref(expr->lvalue, false);
        emit_opcode(opcode::OP_Inc);
    }
    else
    {
        // TODO:
    }
}

void compiler::emit_expr_decrement(const ast::expr_decrement::ptr& expr, bool is_stmt)
{
    if (is_stmt)
    {
        emit_expr_variable_ref(expr->lvalue, false);
        emit_opcode(opcode::OP_Dec);
    }
    else
    {
        // TODO:
    }
}

void compiler::emit_expr_ternary(const ast::expr_ternary::ptr& expr)
{
    auto else_loc = create_label();
    auto end_loc = create_label();

    if (expr->test == ast::kind::expr_not)
    {
        emit_expr(expr->test.as_not->rvalue);
        emit_opcode(opcode::OP_JumpOnTrue, else_loc);
    }
    else
    {
        emit_expr(expr->test);
        emit_opcode(opcode::OP_JumpOnFalse, else_loc);
    }

    emit_expr(expr->true_expr);
    emit_opcode(opcode::OP_Jump, end_loc);

    insert_label(else_loc);
    emit_expr(expr->false_expr);
    insert_label(end_loc);
}

void compiler::emit_expr_binary(const ast::expr_binary::ptr& expr)
{
    emit_expr(expr->lvalue);
    emit_expr(expr->rvalue);

    switch (expr->kind())
    {
        case ast::kind::expr_equality:
            emit_opcode(opcode::OP_Equal);
            break;
        case ast::kind::expr_inequality:
            emit_opcode(opcode::OP_NotEqual);
            break;
        case ast::kind::expr_less:
            emit_opcode(opcode::OP_LessThan);
            break;
        case ast::kind::expr_greater:
            emit_opcode(opcode::OP_GreaterThan);
            break;
        case ast::kind::expr_less_equal:
            emit_opcode(opcode::OP_LessThanOrEqualTo);
            break;
        case ast::kind::expr_greater_equal:
            emit_opcode(opcode::OP_GreaterThanOrEqualTo);
            break;
        case ast::kind::expr_bitwise_or:
            emit_opcode(opcode::OP_Bit_Or);
            break;
        case ast::kind::expr_bitwise_and:
            emit_opcode(opcode::OP_Bit_And);
            break;
        case ast::kind::expr_bitwise_exor:
            emit_opcode(opcode::OP_Bit_Xor);
            break;
        case ast::kind::expr_shift_left:
            emit_opcode(opcode::OP_ShiftLeft);
            break;
        case ast::kind::expr_shift_right:
            emit_opcode(opcode::OP_ShiftRight);
            break;
        case ast::kind::expr_add:
            emit_opcode(opcode::OP_Plus);
            break;
        case ast::kind::expr_sub:
            emit_opcode(opcode::OP_Minus);
            break;
        case ast::kind::expr_mul:
            emit_opcode(opcode::OP_Multiply);
            break;
        case ast::kind::expr_div:
            emit_opcode(opcode::OP_Divide);
            break;
        case ast::kind::expr_mod:
            emit_opcode(opcode::OP_Modulus);
            break;
        default:
            throw comp_error(expr->loc(), "unknown binary expression");
    }
}

void compiler::emit_expr_and(const ast::expr_and::ptr& expr)
{
    auto label = create_label();

    emit_expr(expr->lvalue);
    emit_opcode(opcode::OP_JumpOnFalseExpr, label);
    emit_expr(expr->rvalue);

    insert_label(label);
}

void compiler::emit_expr_or(const ast::expr_or::ptr& expr)
{
    auto label = create_label();

    emit_expr(expr->lvalue);
    emit_opcode(opcode::OP_JumpOnTrueExpr, label);
    emit_expr(expr->rvalue);

    insert_label(label);
}

void compiler::emit_expr_complement(const ast::expr_complement::ptr& expr)
{
    emit_expr(expr->rvalue);
    emit_opcode(opcode::OP_BoolComplement);
}

void compiler::emit_expr_negate(const ast::expr_negate::ptr& expr)
{
    emit_opcode(opcode::OP_GetZero);
    emit_expr(expr->rvalue);
    emit_opcode(opcode::OP_Minus);
}

void compiler::emit_expr_not(const ast::expr_not::ptr& expr)
{
    emit_expr(expr->rvalue);
    emit_opcode(opcode::OP_BoolNot);
}

void compiler::emit_expr_call(const ast::expr_call::ptr& expr, bool is_stmt)
{
    if (expr->call == ast::kind::expr_pointer)
        emit_expr_call_pointer(expr->call.as_pointer, is_stmt);
    else if (expr->call == ast::kind::expr_function)
        emit_expr_call_function(expr->call.as_function, is_stmt);
    else
        throw comp_error(expr->loc(), "unknown function call expression");
}

void compiler::emit_expr_call_pointer(const ast::expr_pointer::ptr& expr, bool is_stmt)
{
    emit_opcode(opcode::OP_PreScriptCall);
    emit_expr_arguments(expr->args);
    emit_expr(expr->func);

    auto argcount = utils::string::va("%d", expr->args->list.size());

    switch (expr->mode)
    {
        case ast::call::mode::normal:
            emit_opcode(opcode::OP_ScriptFunctionCallPointer, argcount);
            break;
        case ast::call::mode::thread:
            emit_opcode(opcode::OP_ScriptThreadCallPointer, argcount);
            break;
        default:
            break;
    }

    if (is_stmt)
        emit_opcode(opcode::OP_DecTop);
}

void compiler::emit_expr_call_function(const ast::expr_function::ptr& expr, bool is_stmt)
{
    if (expr->path->value != "")
    {
        bool found = false;

        for (const auto& entry : assembly_->includes)
        {
            if (entry == expr->path->value)
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            assembly_->includes.push_back(expr->path->value);
        }
    }

    bool as_dev = false;
    std::string end;

    if (!developer_thread_ && is_stmt && expr->name->value == "assert")
    {
        as_dev = true;
        developer_thread_ = true;
        end = create_label();
        emit_opcode(opcode::OP_DevblockBegin, end);
    }

    emit_opcode(opcode::OP_PreScriptCall);
    emit_expr_arguments(expr->args);

    auto argcount = utils::string::va("%d", expr->args->list.size());
    auto flags = developer_thread_ ? static_cast<std::uint8_t>(import_flags::developer) : 0;

    switch (expr->mode)
    {
        case ast::call::mode::normal:
            flags |= static_cast<std::uint8_t>(import_flags::func_call);
            emit_opcode(opcode::OP_ScriptFunctionCall, { expr->path->value, expr->name->value, argcount, utils::string::va("%d", flags) });
            break;
        case ast::call::mode::thread:
            flags |= static_cast<std::uint8_t>(import_flags::func_call_thread);
            emit_opcode(opcode::OP_ScriptThreadCall, { expr->path->value, expr->name->value, argcount, utils::string::va("%d", flags) });
            break;
        default:
            break;
    }

    if (is_stmt)
        emit_opcode(opcode::OP_DecTop);

    if (as_dev)
    {
        insert_label(end);
        developer_thread_ = false;
    }
}

void compiler::emit_expr_method(const ast::expr_method::ptr& expr, bool is_stmt)
{
    if (expr->call == ast::kind::expr_pointer)
        emit_expr_method_pointer(expr->call.as_pointer, expr->obj, is_stmt);
    else if (expr->call == ast::kind::expr_function)
        emit_expr_method_function(expr->call.as_function, expr->obj, is_stmt);
    else
        throw comp_error(expr->loc(), "unknown method call expression");
}

void compiler::emit_expr_method_pointer(const ast::expr_pointer::ptr& expr, const ast::expr& obj, bool is_stmt)
{
    emit_opcode(opcode::OP_PreScriptCall);
    emit_expr_arguments(expr->args);
    emit_expr(obj);
    emit_expr(expr->func);

    auto argcount = utils::string::va("%d", expr->args->list.size());

    switch (expr->mode)
    {
        case ast::call::mode::normal:
            emit_opcode(opcode::OP_ScriptMethodCallPointer, argcount);
            break;
        case ast::call::mode::thread:
            emit_opcode(opcode::OP_ScriptMethodThreadCallPointer, argcount);
            break;
        default:
            break;
    }

    if (is_stmt)
        emit_opcode(opcode::OP_DecTop);
}

void compiler::emit_expr_method_function(const ast::expr_function::ptr& expr, const ast::expr& obj, bool is_stmt)
{
    if (expr->path->value != "")
    {
        bool found = false;

        for (const auto& entry : assembly_->includes)
        {
            if (entry == expr->path->value)
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            assembly_->includes.push_back(expr->path->value);
        }
    }

    emit_opcode(opcode::OP_PreScriptCall);
    emit_expr_arguments(expr->args);
    emit_expr(obj);

    auto argcount = utils::string::va("%d", expr->args->list.size());
    auto flags = developer_thread_ ? static_cast<std::uint8_t>(import_flags::developer) : 0;

    switch (expr->mode)
    {
        case ast::call::mode::normal:
            flags |= static_cast<std::uint8_t>(import_flags::meth_call);
            emit_opcode(opcode::OP_ScriptMethodCall, { expr->path->value, expr->name->value, argcount, utils::string::va("%d", flags) });
            break;
        case ast::call::mode::thread:
            flags |= static_cast<std::uint8_t>(import_flags::meth_call_thread);
            emit_opcode(opcode::OP_ScriptMethodThreadCall, { expr->path->value, expr->name->value, argcount, utils::string::va("%d", flags) });
            break;
        default:
            break;
    }

    if (is_stmt)
        emit_opcode(opcode::OP_DecTop);
}

void compiler::emit_expr_parameters(const ast::expr_parameters::ptr& expr)
{
    if (local_stack_.size() == 0)
    {
        emit_opcode(opcode::OP_CheckClearParams);
    }
    else
    {
        emit_opcode(opcode::OP_SafeCreateLocalVariables, local_stack_);
    }

    for (auto& entry : expr->list)
    {
        if (entry == ast::kind::expr_assign_equal)
        {
            auto end_loc = create_label();
            emit_expr_variable(entry.as_assign_equal->lvalue);
            emit_opcode(opcode::OP_IsDefined);
            emit_opcode(opcode::OP_JumpOnTrue, end_loc);
            emit_expr(entry.as_assign_equal->rvalue);
            emit_expr_variable_ref(entry.as_assign_equal->lvalue, true);
            insert_label(end_loc);
        }
    }
}

void compiler::emit_expr_arguments(const ast::expr_arguments::ptr& expr)
{
    std::reverse(expr->list.begin(), expr->list.end());

    for (auto& entry : expr->list)
    {
        emit_expr(entry);
    }
}

void compiler::emit_expr_isdefined(const ast::expr_isdefined::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_IsDefined);
}

void compiler::emit_expr_vectorscale(const ast::expr_vectorscale::ptr& expr)
{
    emit_expr(expr->arg2);
    emit_expr(expr->arg1);
    emit_opcode(opcode::OP_VectorScale);
}

void compiler::emit_expr_anglestoup(const ast::expr_anglestoup::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_AnglesToUp);
}

void compiler::emit_expr_anglestoright(const ast::expr_anglestoright::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_AnglesToRight);
}

void compiler::emit_expr_anglestoforward(const ast::expr_anglestoforward::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_AnglesToForward);
}

void compiler::emit_expr_angleclamp180(const ast::expr_angleclamp180::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_AngleClamp180);
}

void compiler::emit_expr_vectortoangles(const ast::expr_vectortoangles::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_VectorToAngles);
}

void compiler::emit_expr_abs(const ast::expr_abs::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_Abs);
}

void compiler::emit_expr_gettime(const ast::expr_gettime::ptr&)
{
    emit_opcode(opcode::OP_GetTime);
}

void compiler::emit_expr_getdvar(const ast::expr_getdvar::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvar);
}

void compiler::emit_expr_getdvarint(const ast::expr_getdvarint::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvarInt);
}

void compiler::emit_expr_getdvarfloat(const ast::expr_getdvarfloat::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvarFloat);
}

void compiler::emit_expr_getdvarvector(const ast::expr_getdvarvector::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvarVector);
}

void compiler::emit_expr_getdvarcolorred(const ast::expr_getdvarcolorred::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvarColorRed);
}

void compiler::emit_expr_getdvarcolorgreen(const ast::expr_getdvarcolorgreen::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvarColorGreen);
}

void compiler::emit_expr_getdvarcolorblue(const ast::expr_getdvarcolorblue::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvarColorBlue);
}

void compiler::emit_expr_getdvarcoloralpha(const ast::expr_getdvarcoloralpha::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_GetDvarColorAlpha);
}

void compiler::emit_expr_getfirstarraykey(const ast::expr_getfirstarraykey::ptr& expr)
{
    emit_expr(expr->arg);
    emit_opcode(opcode::OP_FirstArrayKey);
}

void compiler::emit_expr_getnextarraykey(const ast::expr_getnextarraykey::ptr& expr)
{
    emit_expr(expr->arg2);
    emit_expr(expr->arg1);
    emit_opcode(opcode::OP_NextArrayKey);
}

void compiler::emit_expr_reference(const ast::expr_reference::ptr& expr)
{
    if (expr->path->value != "")
    {
        bool found = false;

        for (const auto& entry : assembly_->includes)
        {
            if (entry == expr->path->value)
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            assembly_->includes.push_back(expr->path->value);
        }
    }

    // TODO: resolve import calls path

    auto flags = developer_thread_ ? static_cast<std::uint8_t>(import_flags::developer) : 0;

    flags |= static_cast<std::uint8_t>(import_flags::func_reference);

    emit_opcode(opcode::OP_GetFunction, { expr->path->value, expr->name->value, "0", utils::string::va("%d", flags) });
}

void compiler::emit_expr_size(const ast::expr_size::ptr& expr)
{
    emit_expr(expr->obj);
    emit_opcode(opcode::OP_SizeOf);
}

void compiler::emit_expr_variable_ref(const ast::expr& expr, bool set)
{
    switch (expr.kind())
    {
        case ast::kind::expr_array:
            emit_expr_array_ref(expr.as_array, set);
            break;
        case ast::kind::expr_field:
            emit_expr_field_ref(expr.as_field, set);
            break;
        case ast::kind::expr_identifier:
            emit_expr_local_ref(expr.as_identifier, set);
            break;
        default:
            throw comp_error(expr.loc(), "invalid lvalue");
    }
}

void compiler::emit_expr_array_ref(const ast::expr_array::ptr& expr, bool set)
{
    emit_expr(expr->key);

    switch (expr->obj.kind())
    {
        case ast::kind::expr_game:
            emit_opcode(opcode::OP_GetGameRef);
            emit_opcode(opcode::OP_EvalArrayRef);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_array:
        case ast::kind::expr_field:
        case ast::kind::expr_identifier:
            emit_expr_variable_ref(expr->obj, false);
            emit_opcode(opcode::OP_EvalArrayRef);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_call:
        case ast::kind::expr_method:
        default:
            throw comp_error(expr->loc(), "invalid array lvalue");
    }
}

void compiler::emit_expr_field_ref(const ast::expr_field::ptr& expr, bool set)
{
    const auto& field = expr->field->value;

    switch (expr->obj.kind())
    {
        case ast::kind::expr_level:
            emit_opcode(opcode::OP_GetLevelObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_anim:
            emit_opcode(opcode::OP_GetAnimObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_self:
            emit_opcode(opcode::OP_GetSelfObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_array:
            emit_expr_array(expr->obj.as_array);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_field:
            emit_expr_field(expr->obj.as_field);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_identifier:
            emit_opcode(opcode::OP_EvalLocalVariableCached, variable_access(expr->obj.as_identifier));
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_call:
            emit_expr_call(expr->obj.as_call, false);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        case ast::kind::expr_method:
            emit_expr_method(expr->obj.as_method, false);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariableRef, field);
            if (set) emit_opcode(opcode::OP_SetVariableField);
            break;
        default:
            throw comp_error(expr->obj.loc(), "not an object");
    }
}

void compiler::emit_expr_local_ref(const ast::expr_identifier::ptr& expr, bool set)
{
    const auto itr = constants_.find(expr->value);

    if (itr != constants_.end())
    {
        throw comp_error(expr->loc(), "variable name already defined as constant '" + expr->value + "'");
    }

    emit_opcode(opcode::OP_EvalLocalVariableRefCached, variable_access(expr));

    if (set)
    {
        emit_opcode(opcode::OP_SetVariableField);
    }
}

void compiler::emit_expr_variable(const ast::expr& expr)
{
    switch (expr.kind())
    {
        case ast::kind::expr_array:
            emit_expr_array(expr.as_array);
            break;
        case ast::kind::expr_field:
            emit_expr_field(expr.as_field);
            break;
        case ast::kind::expr_identifier:
            emit_expr_local(expr.as_identifier);
            break;
        default:
            throw comp_error(expr.loc(), "invalid variable type.");
    }
}

void compiler::emit_expr_array(const ast::expr_array::ptr& expr)
{
    emit_expr(expr->key);
    emit_expr(expr->obj);
    emit_opcode(opcode::OP_EvalArray);
}

void compiler::emit_expr_field(const ast::expr_field::ptr& expr)
{
    const auto& field = expr->field->value;

    switch (expr->obj.kind())
    {
        case ast::kind::expr_level:
            emit_opcode(opcode::OP_GetLevelObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        case ast::kind::expr_anim:
            emit_opcode(opcode::OP_GetAnimObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        case ast::kind::expr_self:
            emit_opcode(opcode::OP_GetSelfObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        case ast::kind::expr_array:
            emit_expr_array(expr->obj.as_array);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        case ast::kind::expr_field:
            emit_expr_field(expr->obj.as_field);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        case ast::kind::expr_call:
            emit_expr_call(expr->obj.as_call, false);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        case ast::kind::expr_method:
            emit_expr_method(expr->obj.as_method, false);
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        case ast::kind::expr_identifier:
            emit_opcode(opcode::OP_EvalLocalVariableCached, variable_access(expr->obj.as_identifier));
            emit_opcode(opcode::OP_CastFieldObject);
            emit_opcode(opcode::OP_EvalFieldVariable, field);
            break;
        default:
            throw comp_error(expr->loc(), "unknown field variable object type");
    }
}

void compiler::emit_expr_local(const ast::expr_identifier::ptr& expr)
{
    // is constant ( should only allow: string, loc string, number, vector)
    const auto itr = constants_.find(expr->value);

    if (itr != constants_.end())
        emit_expr(itr->second);
    else
        emit_opcode(opcode::OP_EvalLocalVariableCached, variable_access(expr));
}

void compiler::emit_expr_object(const ast::expr& expr)
{
    switch (expr.kind())
    {
        case ast::kind::expr_level:
            emit_opcode(opcode::OP_GetLevelObject);
            break;
        case ast::kind::expr_anim:
            emit_opcode(opcode::OP_GetAnimObject);
            break;
        case ast::kind::expr_self:
            emit_opcode(opcode::OP_GetSelfObject);
            break;
        case ast::kind::expr_array:
            emit_expr_array(expr.as_array);
            emit_opcode(opcode::OP_CastFieldObject);
            break;
        case ast::kind::expr_field:
            emit_expr_field(expr.as_field);
            emit_opcode(opcode::OP_CastFieldObject);
            break;
        case ast::kind::expr_call:
            emit_expr_call(expr.as_call, false);
            emit_opcode(opcode::OP_CastFieldObject);
            break;
        case ast::kind::expr_method:
            emit_expr_method(expr.as_method, false);
            emit_opcode(opcode::OP_CastFieldObject);
            break;
        case ast::kind::expr_identifier:
            emit_opcode(opcode::OP_EvalLocalVariableCached, variable_access(expr.as_identifier));
            emit_opcode(opcode::OP_CastFieldObject);
            break;
        default:
            throw comp_error(expr.loc(), "not an object");
    }
}

void compiler::emit_expr_vector(const ast::expr_vector::ptr& expr)
{
    std::vector<std::string> data;
    bool isconst = true;
    auto flags = 0;

    if (expr->x == ast::kind::expr_integer)
    {
        auto value = std::atoi(expr->x.as_integer->value.data());
        data.push_back(expr->x.as_integer->value);

        if (value != 1 && value != -1 && value != 0)
            isconst = false;
        else
            flags |= (value == 1) ? 0x20 : (value == -1) ? 0x10 : 0;
    }
    else if (expr->x == ast::kind::expr_float)
    {
        auto value = std::stof(expr->x.as_float->value.data());
        data.push_back(expr->x.as_float->value);

        if (value != 1.0 && value != -1.0 && value != 0.0)        
            isconst = false;
        else
            flags |= (value == 1.0) ? 0x20 : (value == -1.0) ? 0x10 : 0;
    }
    else
    {
        isconst = false;
    }

    if (expr->y == ast::kind::expr_integer)
    {
        auto value = std::atoi(expr->y.as_integer->value.data());
        data.push_back(expr->y.as_integer->value);

        if (value != 1 && value != -1 && value != 0)
            isconst = false;
        else
            flags |= (value == 1) ? 0x08 : (value == -1) ? 0x04 : 0;
    }
    else if (expr->y == ast::kind::expr_float)
    {
        auto value = std::stof(expr->y.as_float->value.data());
        data.push_back(expr->y.as_float->value);

        if (value != 1.0 && value != -1.0 && value != 0.0)
            isconst = false;
        else
            flags |= (value == 1.0) ? 0x08 : (value == -1.0) ? 0x04 : 0;
    }
    else
    {
        isconst = false;
    }

    if (expr->z == ast::kind::expr_integer)
    {
        auto value = std::atoi(expr->z.as_integer->value.data());
        data.push_back(expr->z.as_integer->value);

        if (value != 1 && value != -1 && value != 0)
            isconst = false;
        else
            flags |= (value == 1) ? 0x02 : (value == -1) ? 0x01 : 0;
    }
    else if (expr->z == ast::kind::expr_float)
    {
        auto value = std::stof(expr->z.as_float->value.data());
        data.push_back(expr->z.as_float->value);

        if (value != 1.0 && value != -1.0 && value != 0.0)
            isconst = false;
        else
            flags |= (value == 1.0) ? 0x02 : (value == -1.0) ? 0x01 : 0;
    }
    else
    {
        isconst = false;
    }

    if (isconst)
    {
        emit_opcode(opcode::OP_VectorConstant, utils::string::va("%d", flags));
    }
    else
    {
        // OP_GetVector seems to be broken, always use OP_Vector
        emit_expr(expr->z);
        emit_expr(expr->y);
        emit_expr(expr->x);
        emit_opcode(opcode::OP_Vector);
    }
}

void compiler::emit_expr_animation(const ast::expr_animation::ptr& expr)
{
    if (animtrees_.size() == 0)
    {
        throw comp_error(expr->loc(), "trying to use animation without specified using animtree");
    }

    emit_opcode(opcode::OP_GetAnimation, { animtrees_.back(), expr->value });
}

void compiler::emit_expr_animtree(const ast::expr_animtree::ptr& expr)
{
    if (animtrees_.size() == 0)
    {
        throw comp_error( expr->loc(), "trying to use animtree without specified using animtree");
    }

   emit_opcode(opcode::OP_GetInteger, { animtrees_.back(), "-1" });
}

void compiler::emit_expr_istring(const ast::expr_istring::ptr& expr)
{
    emit_opcode(opcode::OP_GetIString, expr->value);
}

void compiler::emit_expr_string(const ast::expr_string::ptr& expr)
{
    emit_opcode(opcode::OP_GetString, expr->value);
}

void compiler::emit_expr_hash(const ast::expr_hash::ptr& expr)
{
    emit_opcode(opcode::OP_GetHash, expr->value);
}

void compiler::emit_expr_float(const ast::expr_float::ptr& expr)
{
    emit_opcode(opcode::OP_GetFloat, expr->value);
}

void compiler::emit_expr_integer(const ast::expr_integer::ptr& expr)
{
    auto value = std::atoi(expr->value.data());

    if (value == 0)
    {
        emit_opcode(opcode::OP_GetZero);
    }
    else if (value > 0 && value < 256)
    {
        emit_opcode(opcode::OP_GetByte, expr->value);
    }
    else if (value < 0 && value > -256)
    {
        emit_opcode(opcode::OP_GetNegByte, expr->value.substr(1));
    }
    else if (value > 0 && value < 65536)
    {
        emit_opcode(opcode::OP_GetUnsignedShort, expr->value);
    }
    else if (value < 0 && value > -65536)
    {
        emit_opcode(opcode::OP_GetNegUnsignedShort, expr->value.substr(1));
    }
    else
    {
        emit_opcode(opcode::OP_GetInteger, expr->value);
    }
}

void compiler::emit_expr_false(const ast::expr_false::ptr&)
{
    emit_opcode(opcode::OP_GetZero);
}

void compiler::emit_expr_true(const ast::expr_true::ptr&)
{
    emit_opcode(opcode::OP_GetByte, "1");
}

void compiler::emit_opcode(opcode op)
{
    function_->instructions.push_back(std::make_unique<instruction>());

    auto& inst = function_->instructions.back();
    inst->opcode = static_cast<std::uint8_t>(op);
    inst->size = opcode_size(std::uint8_t(op));
    inst->index = index_;

    index_ += inst->size;
}

void compiler::emit_opcode(opcode op, const std::string& data)
{
    function_->instructions.push_back(std::make_unique<instruction>());

    auto& inst = function_->instructions.back();
    inst->opcode = static_cast<std::uint8_t>(op);
    inst->size = opcode_size(std::uint8_t(op));
    inst->index = index_;
    inst->data.push_back(data);

    index_ += inst->size;
}

void compiler::emit_opcode(opcode op, const std::vector<std::string>& data)
{
    function_->instructions.push_back(std::make_unique<instruction>());

    auto& inst = function_->instructions.back();
    inst->opcode = static_cast<std::uint8_t>(op);
    inst->size = opcode_size(std::uint8_t(op));
    inst->index = index_;
    inst->data = data;

    index_ += inst->size;
}

void compiler::process_thread(const ast::decl_thread::ptr& decl)
{
    process_expr_parameters(decl->params);
    process_stmt_list(decl->stmt);
}

void compiler::process_stmt(const ast::stmt& stmt)
{
    switch (stmt.kind())
    {
        case ast::kind::stmt_list:
            process_stmt_list(stmt.as_list);
            break;
        case ast::kind::stmt_dev:
            process_stmt_dev(stmt.as_dev);
            break;
        case ast::kind::stmt_expr:
            process_stmt_expr(stmt.as_expr);
            break;
        case ast::kind::stmt_assign:
            process_stmt_assign(stmt.as_assign);
            break;
        case ast::kind::stmt_waittill:
            process_stmt_waittill(stmt.as_waittill);
            break;
        case ast::kind::stmt_if:
            process_stmt_if(stmt.as_if);
            break;
        case ast::kind::stmt_ifelse:
            process_stmt_ifelse(stmt.as_ifelse);
            break;
        case ast::kind::stmt_while:
            process_stmt_while(stmt.as_while);
            break;
        case ast::kind::stmt_dowhile:
            process_stmt_dowhile(stmt.as_dowhile);
            break;
        case ast::kind::stmt_for:
            process_stmt_for(stmt.as_for);
            break;
        case ast::kind::stmt_foreach:
            process_stmt_foreach(stmt.as_foreach);
            break;
        case ast::kind::stmt_switch:
            process_stmt_switch(stmt.as_switch);
            break;
        case ast::kind::stmt_call:
        case ast::kind::stmt_const:
        case ast::kind::stmt_endon:
        case ast::kind::stmt_notify:
        case ast::kind::stmt_wait:
        case ast::kind::stmt_waittillmatch:
        case ast::kind::stmt_waittillframeend:
        case ast::kind::stmt_case:
        case ast::kind::stmt_default:
        case ast::kind::stmt_break:
        case ast::kind::stmt_continue:
        case ast::kind::stmt_return:
        case ast::kind::stmt_prof_begin:
        case ast::kind::stmt_prof_end:
            break;
        default:
            throw comp_error(stmt.loc(), "unknown statement");
    }
}

void compiler::process_stmt_list(const ast::stmt_list::ptr& stmt)
{
    for (const auto& entry : stmt->list)
    {
        process_stmt(entry);
    }
}

void compiler::process_stmt_dev(const ast::stmt_dev::ptr& stmt)
{
    process_stmt_list(stmt->list);
}

void compiler::process_stmt_expr(const ast::stmt_expr::ptr& stmt)
{
    switch (stmt->expr.kind())
    {
        case ast::kind::expr_increment:
            process_expr(stmt->expr.as_increment->lvalue);
            break;
        case ast::kind::expr_decrement:
            process_expr(stmt->expr.as_decrement->lvalue);
            break;
        case ast::kind::expr_assign_equal:
        case ast::kind::expr_assign_add:
        case ast::kind::expr_assign_sub:
        case ast::kind::expr_assign_mul:
        case ast::kind::expr_assign_div:
        case ast::kind::expr_assign_mod:
        case ast::kind::expr_assign_shift_left:
        case ast::kind::expr_assign_shift_right:
        case ast::kind::expr_assign_bitwise_or:
        case ast::kind::expr_assign_bitwise_and:
        case ast::kind::expr_assign_bitwise_exor:
            process_expr(stmt->expr.as_assign->lvalue);
            break;
        case ast::kind::null:
            break;
        default:
            throw comp_error(stmt->loc(), "unknown expr statement expression");
    }
}

void compiler::process_stmt_assign(const ast::stmt_assign::ptr& stmt)
{
    switch (stmt->expr.kind())
    {
        case ast::kind::expr_increment:
            process_expr(stmt->expr.as_increment->lvalue);
            break;
        case ast::kind::expr_decrement:
            process_expr(stmt->expr.as_decrement->lvalue);
            break;
        case ast::kind::expr_assign_equal:
        case ast::kind::expr_assign_add:
        case ast::kind::expr_assign_sub:
        case ast::kind::expr_assign_mul:
        case ast::kind::expr_assign_div:
        case ast::kind::expr_assign_mod:
        case ast::kind::expr_assign_shift_left:
        case ast::kind::expr_assign_shift_right:
        case ast::kind::expr_assign_bitwise_or:
        case ast::kind::expr_assign_bitwise_and:
        case ast::kind::expr_assign_bitwise_exor:
            process_expr(stmt->expr.as_assign->lvalue);
            break;
        default:
            throw comp_error(stmt->loc(), "unknown assign statement expression");
    }
}

void compiler::process_stmt_waittill(const ast::stmt_waittill::ptr& stmt)
{
    for (const auto& entry : stmt->args->list)
    {
        if (entry != ast::kind::expr_identifier)
        {
            throw comp_error(entry.loc(), "illegal waittill param, must be a local variable");
        }

        variable_register(entry.as_identifier->value);
    }
}

void compiler::process_stmt_if(const ast::stmt_if::ptr& stmt)
{
    process_stmt(stmt->stmt);
}

void compiler::process_stmt_ifelse(const ast::stmt_ifelse::ptr& stmt)
{
    process_stmt(stmt->stmt_if);
    process_stmt(stmt->stmt_else);
}

void compiler::process_stmt_while(const ast::stmt_while::ptr& stmt)
{
    process_stmt(stmt->stmt);
}

void compiler::process_stmt_dowhile(const ast::stmt_dowhile::ptr& stmt)
{
    process_stmt(stmt->stmt);
}

void compiler::process_stmt_for(const ast::stmt_for::ptr& stmt)
{
    process_stmt(stmt->init);
    process_stmt(stmt->stmt);
    process_stmt(stmt->iter);
}

void compiler::process_stmt_foreach(const ast::stmt_foreach::ptr& stmt)
{
    auto array_name = utils::string::va("_a%d", ++label_idx_);
    auto key_name = utils::string::va("_k%d", label_idx_);

    stmt->array = ast::expr(std::make_unique<ast::expr_identifier>(stmt->loc(), array_name));

    if (!stmt->use_key)
        stmt->key_expr = ast::expr(std::make_unique<ast::expr_identifier>(stmt->loc(), key_name));

    key_name = stmt->key_expr.as_identifier->value;

    if (stmt->use_key)
    {
        variable_register(key_name);
        process_expr(stmt->value_expr);
    }
    else
    {
        process_expr(stmt->value_expr);
        variable_register(key_name);
    }

    variable_register(array_name);
    process_stmt(stmt->stmt);
}

void compiler::process_stmt_switch(const ast::stmt_switch::ptr& stmt)
{
    auto stmt_list = std::make_unique<ast::stmt_list>(stmt->stmt->loc());
    auto current_case = ast::stmt(nullptr);

    auto num = stmt->stmt->list.size();

    for (auto i = 0u; i < num; i++)
    {
        auto& entry = stmt->stmt->list[0];

        if (entry == ast::kind::stmt_case || entry == ast::kind::stmt_default)
        {
            if (current_case.as_node != nullptr)
            {
                stmt_list->list.push_back(std::move(current_case));
            }

            current_case = std::move(stmt->stmt->list[0]);
            stmt->stmt->list.erase(stmt->stmt->list.begin());
        }
        else
        {
            if (current_case.as_node != nullptr)
            {
                if (current_case == ast::kind::stmt_case)
                {
                    current_case.as_case->stmt->list.push_back(std::move(entry));
                    stmt->stmt->list.erase(stmt->stmt->list.begin());
                }
                else
                {
                    current_case.as_default->stmt->list.push_back(std::move(entry));
                    stmt->stmt->list.erase(stmt->stmt->list.begin());
                }
            }
            else
            {
                throw comp_error(entry.loc(), "missing case statement");
            }
        }
    }

    if (current_case.as_node != nullptr)
    {
        stmt_list->list.push_back(std::move(current_case));
    }

    for (auto i = 0u; i < stmt_list->list.size(); i++)
    {
        auto& entry = stmt_list->list[i];

        if (entry == ast::kind::stmt_case)
        {
            process_stmt_list(entry.as_case->stmt);
        }
        else if (entry == ast::kind::stmt_default)
        {
            process_stmt_list(entry.as_default->stmt);
        }
    }

    stmt->stmt =std::move(stmt_list);
}

void compiler::process_expr(const ast::expr& expr)
{
    if (expr == ast::kind::expr_identifier)
    {
        variable_register(expr.as_identifier->value);
    }
    else if (expr == ast::kind::expr_array)
    {
        process_expr(expr.as_array->obj);
    }
}

void compiler::process_expr_parameters(const ast::expr_parameters::ptr& expr)
{
    for (const auto& entry : expr->list)
    {
        if (entry == ast::kind::expr_identifier)
        {
            variable_register(entry.as_identifier->value);
        }
        else if (entry == ast::kind::expr_assign_equal)
        {
            process_expr(entry.as_assign_equal->lvalue);
        }
    }
}

void compiler::variable_register(const std::string& name)
{
    auto found = false;

    for (std::size_t i = 0; i < local_stack_.size(); i++)
    {
        if (local_stack_[i] == name)
        {
            found = true;
            break;
        }
    }

    if (!found) local_stack_.push_back(name);
}

auto compiler::variable_access(const ast::expr_identifier::ptr& name) -> std::string
{
    for (std::size_t i = 0; i < local_stack_.size(); i++)
    {
        if (local_stack_[i] == name->value)
        {
            return utils::string::va("%d", local_stack_.size() - 1 - i);
        }
    }

    throw comp_error(name->loc(), "local variable '" + name->value + "' not found.");
}

auto compiler::is_constant_condition(const ast::expr& expr) -> bool
{
    switch (expr.kind())
    {
        case ast::kind::null:
        case ast::kind::expr_true:
            return true;
        case ast::kind::expr_false:
            throw comp_error(expr.loc(), "condition can't be always false!");
        case ast::kind::expr_integer:
        {
            auto num = std::stoi(expr.as_integer->value);
            if (num != 0)
                return true;
            else
                throw comp_error(expr.loc(), "condition can't be always false!");
        }
        default:
            break;
    }

    return false;
}

auto compiler::create_label() -> std::string
{
    label_idx_++;
    return utils::string::va("loc_%d", label_idx_);
}

auto compiler::insert_label() -> std::string
{
    const auto itr = function_->labels.find(index_);

    if (itr != function_->labels.end())
    {
       return itr->second;
    }
    else
    {
        label_idx_++;
        auto name = utils::string::va("loc_%d", label_idx_);
        function_->labels.insert({ index_, name });
        return name;
    }
}

void compiler::insert_label(const std::string& name)
{
    const auto itr = function_->labels.find(index_);

    if (itr != function_->labels.end())
    {
       for (auto& inst : function_->instructions)
       {
           switch (static_cast<opcode>(inst->opcode))
           {
                case opcode::OP_JumpOnFalse:
                case opcode::OP_JumpOnTrue:
                case opcode::OP_JumpOnFalseExpr:
                case opcode::OP_JumpOnTrueExpr:
                case opcode::OP_Jump:
                case opcode::OP_JumpBack:
                case opcode::OP_Switch:
                case opcode::OP_DevblockBegin:
                    if (inst->data[0] == name)
                        inst->data[0] = itr->second;
                    break;
                case opcode::OP_EndSwitch:
                default:
                    break;
           }
       }
    }
    else
    {
        function_->labels.insert({ index_, name });
    }
}

auto compiler::map_known_includes(const std::string&) -> bool
{
    return false;
}

void compiler::print_function(const function::ptr& func)
{
    output_->write_string("\n");
    output_->write_string(utils::string::va("sub_%s %i %i\n", func->name.data(), func->params, func->flags));

    for (const auto& inst : func->instructions)
    {
        const auto itr = func->labels.find(inst->index);

        if (itr != func->labels.end())
        {
            output_->write_string(utils::string::va("\t%s\n", itr->second.data()));
        }

        this->print_instruction(inst);
    }

    output_->write_string(utils::string::va("end_%s\n", func->name.data()));
}

void compiler::print_instruction(const instruction::ptr& inst)
{
    output_->write_string(utils::string::va("\t\t%s(", resolver::opcode_name(inst->opcode).data()));

    switch (static_cast<opcode>(inst->opcode))
    {
        case opcode::OP_GetHash:
        case opcode::OP_GetString:
        case opcode::OP_GetIString:
        case opcode::OP_ClearFieldVariable:
        case opcode::OP_EvalFieldVariable:
        case opcode::OP_EvalFieldVariableRef:
            output_->write_string(utils::string::va("\"%s\"", inst->data[0].data()));
            break;
        case opcode::OP_GetAnimation:
        case opcode::OP_GetFunction:
        case opcode::OP_CallBuiltin:
        case opcode::OP_CallBuiltinMethod:
        case opcode::OP_ScriptFunctionCall:
        case opcode::OP_ScriptMethodCall:
        case opcode::OP_ScriptThreadCall:
        case opcode::OP_ScriptMethodThreadCall:
            output_->write_string(utils::string::va("\"%s\", \"%s\"", inst->data[0].data(), inst->data[1].data()));
            break;
        case opcode::OP_SafeCreateLocalVariables:
            for (const auto& d : inst->data)
            {
                output_->write_string(utils::string::va("\"%s\"%s", d.data(), &d == &inst->data.back() ? "" : ", "));
            }
            break;
        case opcode::OP_EndSwitch:
            output_->write_string(utils::string::va("%s", inst->data[0].data()));
            {
                std::uint32_t totalcase = std::stoul(inst->data[0]);
                auto numerical = inst->data.back() == "i";
                auto index = 0;
                for (auto casenum = 0u; casenum < totalcase; casenum++)
                {
                    if (inst->data[1 + index] == "case")
                    {
                        auto fmt = numerical ? ", %s, %s, %s"s : ", %s, \"%s\", %s"s;
                        output_->write_string(utils::string::va(fmt, inst->data[1 + index].data(), inst->data[1 + index + 1].data(), inst->data[1 + index + 2].data()));
                        index += 3;
                    }
                    else if (inst->data[1 + index] == "default")
                    {
                        output_->write_string(utils::string::va(", %s, %s", inst->data[1 + index].data(), inst->data[1 + index + 1].data()));
                        index += 2;
                    }
                }
            }
            break;
        default:
            for (const auto& d : inst->data)
            {
                output_->write_string(utils::string::va("%s%s", d.data(), &d == &inst->data.back() ? "" : ", "));
            }
            break;
    }

    output_->write_string(");\n");
}

} // namespace xsk::arc::t6
