/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// compiler.cpp : Parse, then compile to op-codes.

#include "scriptcompiler.h"
#include "scriptvm.h"
#include "../fgame/level.h"
#include "../fgame/parm.h"
#include "../fgame/game.h"
#include "../fgame/scriptmaster.h"
#include "../fgame/scriptthread.h"
#include "scriptclass.h"
#include "scriptexception.h"
#include "../parser/parsetree.h"
#include "../parser/generated/yyParser.hpp"
#include "../parser/generated/yyLexer.h"

ScriptCompiler Compiler;
int            ScriptCompiler::current_label;

ScriptCompiler::ScriptCompiler()
{
    Reset();
}

void ScriptCompiler::Reset()
{
    code_pos     = NULL;
    code_ptr     = NULL;
    prog_ptr     = NULL;
    prog_end_ptr = NULL;

    for (int i = 0; i < BREAK_JUMP_LOCATION_COUNT; i++) {
        apucBreakJumpLocations[i]      = 0;
        apucContinueJumpLocations[i]   = 0;
        prev_opcodes[i].opcode         = 0;
        prev_opcodes[i].VarStackOffset = 0;
    }

    iBreakJumpLocCount    = 0;
    iContinueJumpLocCount = 0;

    m_iHasExternal               = 0;
    m_iInternalMaxVarStackOffset = 0;
    m_iMaxCallStackOffset        = 0;
    m_iMaxExternalVarStackOffset = 0;
    m_iVarStackOffset            = 0;

    bCanBreak    = false;
    bCanContinue = false;

    prev_opcode_pos = 0;
}

unsigned char ScriptCompiler::PrevOpcode()
{
    return prev_opcodes[prev_opcode_pos].opcode;
}

signed char ScriptCompiler::PrevVarStackOffset()
{
    return prev_opcodes[prev_opcode_pos].VarStackOffset;
}

void ScriptCompiler::AbsorbPrevOpcode()
{
    m_iVarStackOffset -= PrevVarStackOffset();

    code_pos -= OpcodeLength(PrevOpcode());

    if (!prev_opcode_pos) {
        prev_opcode_pos = 100;
    }

    prev_opcode_pos--;
}

void ScriptCompiler::ClearPrevOpcode()
{
    prev_opcodes[prev_opcode_pos].opcode = OP_PREVIOUS;
}

void ScriptCompiler::AccumulatePrevOpcode(int opcode, int iVarStackOffset)
{
    if (showopcodes->integer) {
        glbs.DPrintf("\t\t%08d: %s (%d)\n", code_pos - code_ptr, OpcodeName(OP_BOOL_TO_VAR), m_iVarStackOffset);
    }

    int pos = (prev_opcode_pos + 1) % 100;

    prev_opcode_pos                      = pos;
    prev_opcodes[pos].opcode             = OP_BOOL_TO_VAR;
    prev_opcodes[pos].VarStackOffset     = 0;
    prev_opcodes[(pos + 1) % 100].opcode = OP_PREVIOUS;
}

void ScriptCompiler::AddBreakJumpLocation(unsigned char *pos)
{
    if (iBreakJumpLocCount < BREAK_JUMP_LOCATION_COUNT) {
        apucBreakJumpLocations[iBreakJumpLocCount++] = pos;
    } else {
        iBreakJumpLocCount = 0;
        CompileError(-1, "Increase BREAK_JUMP_LOCATION_COUNT and recompile.\n");
    }
}

void ScriptCompiler::AddContinueJumpLocation(unsigned char *pos)
{
    if (iContinueJumpLocCount < CONTINUE_JUMP_LOCATION_COUNT) {
        apucContinueJumpLocations[iContinueJumpLocCount++] = pos;
    } else {
        iContinueJumpLocCount = 0;
        CompileError(-1, "Increase CONTINUE_JUMP_LOCATION_COUNT and recompile.\n");
    }
}

void ScriptCompiler::AddJumpLocation(unsigned char *pos)
{
    unsigned int offset = code_pos - sizeof(unsigned int) - pos;

    EmitAt(pos, offset, sizeof(offset));
    ClearPrevOpcode();
}

void ScriptCompiler::AddJumpBackLocation(unsigned char *pos)
{
    int offset = (code_pos - pos);

    EmitOpcodeValue((unsigned int)offset, sizeof(unsigned int));
    ClearPrevOpcode();
}

void ScriptCompiler::AddJumpToLocation(unsigned char *pos)
{
    int offset = (pos - code_pos - 1);

    EmitOpcodeValue((unsigned int)offset, sizeof(unsigned int));
    ClearPrevOpcode();
}

bool ScriptCompiler::BuiltinReadVariable(unsigned int sourcePos, int type, int eventnum)
{
    ClassDef *c;
    EventDef *def;

    switch (type) {
    case method_game:
        c = Game::classinfostatic();
        break;

    case method_level:
        c = Level::classinfostatic();
        break;

    case method_local:
        c = ScriptThread::classinfostatic();
        break;

    case method_parm:
        c = Parm::classinfostatic();
        break;

    case method_group:
        c = ScriptClass::classinfostatic();
        break;

    default:
        return true;
    }

    def = c->GetDef(eventnum);

    if (def) {
        if (def->type == EV_GETTER) {
            return true;
        } else {
            CompileError(sourcePos, "Cannot get a write-only variable");
            return false;
        }
    } else {
        return false;
    }
}

bool ScriptCompiler::BuiltinWriteVariable(unsigned int sourcePos, int type, int eventnum)
{
    ClassDef *c;
    EventDef *def;

    switch (type) {
    case method_game:
        c = Game::classinfostatic();
        break;

    case method_level:
        c = Level::classinfostatic();
        break;

    case method_local:
        c = ScriptThread::classinfostatic();
        break;

    case method_parm:
        c = Parm::classinfostatic();
        break;

    case method_group:
        c = ScriptClass::classinfostatic();
        break;

    default:
        return true;
    }

    def = c->GetDef(eventnum);

    if (def) {
        if (def->type == EV_SETTER) {
            return true;
        } else {
            CompileError(sourcePos, "Cannot get a read-only variable");
            return false;
        }
    } else {
        return false;
    }
}

void ScriptCompiler::EmitAssignmentStatement(sval_t lhs, unsigned int sourcePos)
{
    int          eventnum;
    unsigned int index;
    sval_t       listener_val;
    const char  *name = lhs.node[2].stringValue;
    //str name_lowered;

    if (lhs.node[0].type != ENUM_field) {
        if (lhs.node[0].type == ENUM_array_expr) {
            EmitRef(lhs.node[1], sourcePos);
            EmitValue(lhs.node[2]);
            EmitOpcode(OP_LOAD_ARRAY_VAR, lhs.node[3].sourcePosValue);
        } else {
            CompileError(sourcePos, "bad lvalue: %d (expecting field or array)", lhs.node[0].type);
        }
        return;
    }

    //name_lowered = name;
    //name_lowered.tolower();
    index    = Director.AddString(name);
    eventnum = Event::FindSetterEventNum(name);

    listener_val = lhs.node[1];

    if (listener_val.node[0].type != ENUM_listener
        || (eventnum && BuiltinWriteVariable(sourcePos, listener_val.node[1].intValue, eventnum))) {
        EmitValue(listener_val);
        EmitOpcode(OP_LOAD_FIELD_VAR, sourcePos);
    } else {
        EmitOpcode(OP_LOAD_GAME_VAR + listener_val.node[1].intValue, sourcePos);
    }

    EmitOpcodeValue((unsigned int)index, sizeof(unsigned int));
}

void ScriptCompiler::EmitBoolJumpFalse(unsigned int sourcePos)
{
    if (PrevOpcode() == OP_UN_CAST_BOOLEAN) {
        AbsorbPrevOpcode();
        EmitOpcode(OP_VAR_JUMP_FALSE4, sourcePos);
    } else {
        EmitOpcode(OP_BOOL_JUMP_FALSE4, sourcePos);
    }
}

void ScriptCompiler::EmitBoolJumpTrue(unsigned int sourcePos)
{
    if (PrevOpcode() == OP_UN_CAST_BOOLEAN) {
        AbsorbPrevOpcode();
        EmitOpcode(OP_VAR_JUMP_TRUE4, sourcePos);
    } else {
        EmitOpcode(OP_BOOL_JUMP_TRUE4, sourcePos);
    }
}

void ScriptCompiler::EmitBoolNot(unsigned int sourcePos)
{
    int prev = PrevOpcode();

    if (prev == OP_BOOL_STORE_TRUE) {
        AbsorbPrevOpcode();
        return EmitOpcode(OP_BOOL_STORE_FALSE, sourcePos);
    } else if (prev > OP_BOOL_STORE_TRUE && prev == OP_BOOL_UN_NOT) {
        AbsorbPrevOpcode();
        return EmitNil(sourcePos);
    } else if (prev == OP_BOOL_STORE_FALSE) {
        AbsorbPrevOpcode();
        return EmitOpcode(OP_BOOL_STORE_TRUE, sourcePos);
    }

    return EmitOpcode(OP_BOOL_UN_NOT, sourcePos);
}

void ScriptCompiler::EmitBoolToVar(unsigned int sourcePos)
{
    if (PrevOpcode() == OP_UN_CAST_BOOLEAN) {
        AbsorbPrevOpcode();
        EmitOpcode(OP_UN_CAST_BOOLEAN, sourcePos);
    } else {
        if (showopcodes->integer) {
            glbs.DPrintf("\t\t%08d: %s (%d)\n", code_pos - code_ptr, OpcodeName(OP_BOOL_TO_VAR), m_iVarStackOffset);
        }

        int pos = (prev_opcode_pos + 1) % 100;

        prev_opcode_pos                      = pos;
        prev_opcodes[pos].opcode             = OP_BOOL_TO_VAR;
        prev_opcodes[pos].VarStackOffset     = 0;
        prev_opcodes[(pos + 1) % 100].opcode = OP_PREVIOUS;
    }
}

void ScriptCompiler::EmitBreak(unsigned int sourcePos)
{
    if (bCanBreak) {
        EmitOpcode(OP_JUMP4, sourcePos);
        unsigned char *pos = code_pos;
        code_pos += 4;
        ClearPrevOpcode();

        AddBreakJumpLocation(pos);
    } else {
        CompileError(sourcePos, "illegal break\n");
    }
}

void ScriptCompiler::EmitCatch(sval_t val, unsigned char *try_begin_code_pos, unsigned int sourcePos)
{
    unsigned char *old_code_pos;
    StateScript   *m_oldStateScript;

    EmitOpcode(OP_JUMP4, sourcePos);

    old_code_pos = code_pos;
    code_pos += sizeof(unsigned int);

    ClearPrevOpcode();

    m_oldStateScript = stateScript;
    stateScript      = script->CreateCatchStateScript(try_begin_code_pos, code_pos);

    EmitValue(val);

    stateScript = m_oldStateScript;

    AddJumpLocation(old_code_pos);
}

void ScriptCompiler::EmitConstArray(sval_t lhs, sval_t rhs, unsigned int sourcePos)
{
    uint32_t iCount = 1;

    EmitValue(lhs);

    for (const sval_t *node = rhs.node[0].node; node; node = node[1].node, iCount++) {
        EmitValue(*node);
    }

    EmitConstArrayOpcode(iCount);
}

void ScriptCompiler::EmitConstArrayOpcode(int iCount)
{
    SetOpcodeVarStackOffset(OP_LOAD_CONST_ARRAY1, 1 - iCount);
    EmitOpcode(OP_LOAD_CONST_ARRAY1, -1);

    EmitOpcodeValue((short)iCount, sizeof(short));
}

void ScriptCompiler::EmitContinue(unsigned int sourcePos)
{
    if (bCanContinue) {
        EmitOpcode(OP_JUMP4, sourcePos);
        unsigned char *pos = code_pos;
        code_pos += sizeof(unsigned int);
        ClearPrevOpcode();

        AddContinueJumpLocation(pos);
    } else {
        CompileError(sourcePos, "illegal continue\n");
    }
}

void ScriptCompiler::EmitDoWhileJump(sval_t while_stmt, sval_t while_expr, unsigned int sourcePos)
{
    unsigned char *pos = code_pos;
    int            label1, label2;

    if (showopcodes->integer) {
        label1 = current_label++;
        glbs.DPrintf("<LABEL%d>:\n", label1);
    }

    ClearPrevOpcode();

    bool old_bCanBreak    = bCanBreak;
    bool old_bCanContinue = bCanContinue;
    int  breakCount       = iBreakJumpLocCount;
    int  continueCount    = iContinueJumpLocCount;

    bCanBreak    = true;
    bCanContinue = true;

    EmitValue(while_stmt);

    ProcessContinueJumpLocations(continueCount);

    bCanContinue = old_bCanContinue;

    EmitValue(while_expr);
    EmitVarToBool(sourcePos);

    label2 = EmitNot(sourcePos);

    unsigned char *jmp = code_pos;

    code_pos += sizeof(unsigned int);

    if (showopcodes->integer) {
        glbs.DPrintf("JUMP_BACK4 <LABEL%d>\n", label1);
    }

    EmitJumpBack(pos, sourcePos);

    ClearPrevOpcode();

    if (showopcodes->integer) {
        glbs.DPrintf("<LABEL%d>:\n", label2);
    }

    AddJumpLocation(jmp);

    ProcessBreakJumpLocations(breakCount);

    bCanBreak = old_bCanBreak;
}

void ScriptCompiler::EmitEof(unsigned int sourcePos)
{
    if (PrevOpcode()) {
        EmitOpcode(OP_DONE, -1);
    }
}

void ScriptCompiler::EmitField(sval_t listener_val, sval_t field_val, unsigned int sourcePos)
{
    unsigned int eventnum = 0;
    unsigned int index    = -1;
    unsigned int prev_index;
    const  char* name;
    //str name_lowered;

    /*
    if (field_val.node[0].stringValue) {
        str name  = field_val.stringValue;
        str name2 = field_val.stringValue;
        name2.tolower();

        index    = Director.AddString(name);
        eventnum = Event::FindGetterEventNum(name2);

        if (eventnum) {
            index = Director.GetString(name2);
        }
    }
    */

    name = field_val.stringValue;
    //name_lowered = name;
    //name_lowered.tolower();
    index    = Director.AddString(name);
    eventnum = Event::FindGetterEventNum(name);

    prev_index = GetOpcodeValue<unsigned int>(sizeof(unsigned int), sizeof(unsigned int));

    if (listener_val.node[0].type != ENUM_listener
        || (eventnum && BuiltinReadVariable(sourcePos, listener_val.node[1].intValue, eventnum))) {
        EmitValue(listener_val);
        EmitOpcode(OP_STORE_FIELD, sourcePos);
        EmitOpcodeValue((unsigned int)index, sizeof(unsigned int));
    } else if (PrevOpcode() != (OP_LOAD_GAME_VAR + listener_val.node[1].intValue) || prev_index != index) {
        EmitOpcode(OP_STORE_GAME_VAR + listener_val.node[1].intValue, sourcePos);
        EmitOpcodeValue((unsigned int)index, sizeof(unsigned int));
    } else {
        AbsorbPrevOpcode();
        EmitOpcode(OP_LOAD_STORE_GAME_VAR + listener_val.node[1].intValue, sourcePos);
        code_pos += sizeof(unsigned int);
    }
}

void ScriptCompiler::EmitFloat(float value, unsigned int sourcePos)
{
    EmitOpcode(OP_STORE_FLOAT, sourcePos);

    EmitOpcodeValue(value, sizeof(float));
}

void ScriptCompiler::EmitFunc1(int opcode, unsigned int sourcePos)
{
    if (opcode == OP_UN_MINUS) {
        ScriptVariable var;

        if (EvalPrevValue(var)) {
            AbsorbPrevOpcode();
            var.minus();

            return EmitValue(var, sourcePos);
        }
    }

    EmitOpcode(opcode, sourcePos);
}

/*
void ScriptCompiler::EmitFunction(int iParamCount, sval_t val, unsigned int sourcePos)
{
    const char *p = val.stringValue;

    str  filename;
    str  label;
    bool found = false;

    while (*p) {
        if (p[0] == ':' && p[1] == ':') {
            p[0] = 0;

            filename = val.stringValue;
            label    = p + 2;

            found = true;

            break;
        }

        p++;
    }

    SetOpcodeVarStackOffset(OP_FUNC, -iParamCount);
    EmitOpcode(OP_FUNC, sourcePos);

    if (!found) {
        label = val.stringValue;

        EmitOpcodeValue(false, sizeof(bool));
        EmitOpcodeValue(Director.AddString(label), sizeof(unsigned int));
    } else {
        EmitOpcodeValue(true, sizeof(bool));
        EmitOpcodeValue(Director.AddString(filename), sizeof(unsigned int));
        EmitOpcodeValue(Director.AddString(label), sizeof(unsigned int));
    }

    EmitOpcodeValue(iParamCount, sizeof(uint8_t));
}
*/

void ScriptCompiler::EmitIfElseJump(sval_t if_stmt, sval_t else_stmt, unsigned int sourcePos)
{
    unsigned char *jmp1, *jmp2;
    int            label1, label2;

    label1 = EmitNot(sourcePos);
    jmp1   = code_pos;
    code_pos += sizeof(unsigned int);
    ClearPrevOpcode();

    EmitValue(if_stmt);

    if (showopcodes->integer) {
        label2 = current_label++;
        glbs.DPrintf("JUMP <LABEL%d>\n", label2);
    }

    EmitOpcode(OP_JUMP4, sourcePos);
    jmp2 = code_pos;
    code_pos += sizeof(unsigned int);

    ClearPrevOpcode();

    if (showopcodes->integer) {
        glbs.DPrintf("<LABEL%d>:\n", label1);
    }

    AddJumpLocation(jmp1);
    EmitValue(else_stmt);

    if (showopcodes->integer) {
        glbs.DPrintf("<LABEL%d>:\n", label1);
    }

    AddJumpLocation(jmp2);
}

void ScriptCompiler::EmitIfJump(sval_t if_stmt, unsigned int sourcePos)
{
    unsigned char *jmp;

    int label = EmitNot(sourcePos);
    jmp       = code_pos;
    code_pos += sizeof(unsigned int);

    ClearPrevOpcode();

    EmitValue(if_stmt);

    if (showopcodes->integer) {
        glbs.DPrintf("<LABEL%d>:\n", label);
    }

    AddJumpLocation(jmp);
}

void ScriptCompiler::EmitInteger(unsigned int value, unsigned int sourcePos)
{
    if (value == 0) {
        EmitOpcode(OP_STORE_INT0, sourcePos);
    } else if (value < 127) {
        EmitOpcode(OP_STORE_INT1, sourcePos);
        EmitOpcodeValue((byte)value, sizeof(byte));
    } else if (value < 32767) {
        EmitOpcode(OP_STORE_INT2, sourcePos);
        EmitOpcodeValue((short)value, sizeof(short));
    } else if (value < 8388607) {
        EmitOpcode(OP_STORE_INT3, sourcePos);
        EmitOpcodeValue((short3)value, sizeof(short3));
    } else {
        EmitOpcode(OP_STORE_INT4, sourcePos);
        EmitOpcodeValue((int)value, sizeof(int));
    }
}

void ScriptCompiler::EmitJump(unsigned char *pos, unsigned int sourcePos)
{
    EmitOpcode(OP_JUMP4, sourcePos);
    AddJumpToLocation(pos);
}

void ScriptCompiler::EmitJumpBack(unsigned char *pos, unsigned int sourcePos)
{
    EmitOpcode(OP_JUMP_BACK4, sourcePos);
    AddJumpBackLocation(pos);
}

void ScriptCompiler::EmitLabel(const char *name, unsigned int sourcePos)
{
    if (showopcodes->integer) {
        glbs.DPrintf("<%s>:\n", name);
    }

    if (!stateScript->AddLabel(name, code_pos)) {
        CompileError(sourcePos, "Duplicate label '%s'", name);
    }

    // Make sure to clear the previous opcode since it's a new context
    ClearPrevOpcode();
}

void ScriptCompiler::EmitLabel(int name, unsigned int sourcePos)
{
    if (showopcodes->integer) {
        glbs.DPrintf("<%d>:\n", name);
    }

    if (!stateScript->AddLabel(str(name), code_pos)) {
        CompileError(sourcePos, "Duplicate label '%d'", name);
    }

    // Make sure to clear the previous opcode since it's a new context
    ClearPrevOpcode();
}

void ScriptCompiler::EmitLabelParameterList(sval_t parameter_list, unsigned int sourcePos)
{
    if (parameter_list.node) {
        EmitOpcode(OP_MARK_STACK_POS, sourcePos);

        for (const sval_t *param = parameter_list.node->node; param; param = param[1].node) {
            EmitParameter(*param, sourcePos);
        };

        EmitOpcode(OP_RESTORE_STACK_POS, sourcePos);
    }
}

void ScriptCompiler::EmitLabelPrivate(const char *name, unsigned int sourcePos)
{
    if (showopcodes->integer) {
        glbs.DPrintf("<%s>:\n", name);
    }

    if (!stateScript->AddLabel(name, code_pos, true)) {
        CompileError(sourcePos, "Duplicate label '%s'", name);
    }

    // Make sure to clear the previous opcode since it's a new context
    ClearPrevOpcode();
}

void ScriptCompiler::EmitAndJump(sval_t logic_stmt, unsigned int sourcePos)
{
    unsigned char *jmp;
    int            label;

    if (showopcodes->integer) {
        label = current_label++;

        glbs.DPrintf("BOOL_LOGICAL_AND <LABEL%d>\n", label);
    }

    EmitOpcode(OP_BOOL_LOGICAL_AND, sourcePos);
    jmp = code_pos;
    code_pos += sizeof(unsigned int);

    ClearPrevOpcode();

    EmitValue(logic_stmt);
    EmitVarToBool(sourcePos);

    if (showopcodes->integer) {
        glbs.DPrintf("<LABEL%d>:\n", label);
    }

    AddJumpLocation(jmp);
    AccumulatePrevOpcode(OP_BOOL_LOGICAL_AND, 0);
}

void ScriptCompiler::EmitOrJump(sval_t logic_stmt, unsigned int sourcePos)
{
    unsigned char *jmp;
    int            label;

    if (showopcodes->integer) {
        label = current_label++;

        glbs.DPrintf("BOOL_LOGICAL_OR <LABEL%d>\n", label);
    }

    EmitOpcode(OP_BOOL_LOGICAL_OR, sourcePos);
    jmp = code_pos;
    code_pos += sizeof(unsigned int);

    ClearPrevOpcode();

    EmitValue(logic_stmt);
    EmitVarToBool(sourcePos);

    if (showopcodes->integer) {
        glbs.DPrintf("<LABEL%d>:\n", label);
    }

    AddJumpLocation(jmp);
    AccumulatePrevOpcode(OP_BOOL_LOGICAL_OR, 0);
}

void ScriptCompiler::EmitMakeArray(sval_t val)
{
    int     iCount = 0;
    sval_t *node;

    for (node = val.node[0].node; node != NULL; iCount++, node = node[1].node) {
        EmitValue(node[0]);
    }

    EmitConstArrayOpcode(iCount);
}

void ScriptCompiler::EmitMethodExpression(int iParamCount, int eventnum, unsigned int sourcePos)
{
    if (iParamCount > 5) {
        SetOpcodeVarStackOffset(OP_EXEC_METHOD_COUNT1, -iParamCount);
        EmitOpcode(OP_EXEC_METHOD_COUNT1, sourcePos);

        EmitOpcodeValue((byte)iParamCount, sizeof(byte));
    } else {
        EmitOpcode(OP_EXEC_METHOD0 + iParamCount, sourcePos);
    }

    EmitOpcodeValue((unsigned int)eventnum, sizeof(unsigned int));
}

void ScriptCompiler::EmitNil(unsigned int sourcePos)
{
    if (showopcodes->integer) {
        glbs.DPrintf("\t\t%08d:\n", code_pos - code_ptr);
    }
}

void ScriptCompiler::EmitNop()
{
    EmitOpcode(OP_NOP, -1);
}

int ScriptCompiler::EmitNot(unsigned int sourcePos)
{
    int label = 0;

    if (PrevOpcode() == OP_BOOL_UN_NOT) {
        AbsorbPrevOpcode();

        if (showopcodes->integer) {
            label = current_label++;
            glbs.DPrintf("BOOL_JUMP_TRUE <LABEL%d>\n", label);
        }

        EmitBoolJumpTrue(sourcePos);
    } else {
        if (showopcodes->integer) {
            label = current_label++;
            glbs.DPrintf("BOOL_JUMP_FALSE <LABEL%d>\n", label);
        }

        EmitBoolJumpFalse(sourcePos);
    }

    return label;
}

void ScriptCompiler::EmitOpcode(int opcode, unsigned int sourcePos)
{
    int IsExternal;
    int iVarStackOffset;

    if (code_pos == NULL) {
        Com_Printf("Compiler not initialized !\n");
        return;
    }

    if (script->m_ProgToSource) {
        sourceinfo_t info;

        info.sourcePos = sourcePos;

        script->GetSourceAt(sourcePos, NULL, info.column, info.line);
        script->m_ProgToSource->addKeyValue(code_pos) = info;
    }

    IsExternal      = IsExternalOpcode(opcode);
    iVarStackOffset = OpcodeVarStackOffset(opcode);

    if (IsExternal) {
        if (m_iVarStackOffset > m_iMaxExternalVarStackOffset) {
            m_iMaxExternalVarStackOffset = m_iVarStackOffset;
        }

        m_iHasExternal = 1;
    }

    m_iVarStackOffset += iVarStackOffset;

    if (!IsExternal) {
        if (m_iVarStackOffset > m_iInternalMaxVarStackOffset) {
            m_iInternalMaxVarStackOffset = m_iVarStackOffset;
        }
    }

    /*if( m_iInternalMaxVarStackOffset + 9 * m_iMaxExternalVarStackOffset + 1 > 255 )
	{
		CompileError( sourcePos,
			"The required variable stack size of %d exceeds the statically allocated variable stack of size %d.\nIncrease SCRIPTTHREAD_VARSTACK_SIZE to at least %d and recompile.\n",
			m_iInternalMaxVarStackOffset + 9 * m_iMaxExternalVarStackOffset + 1,
			255,
			m_iInternalMaxVarStackOffset + 9 * m_iMaxExternalVarStackOffset + 1
		);
	}*/

    if (showopcodes->integer) {
        glbs.DPrintf(
            "\t\t%08d: %s (%d) %s\n",
            code_pos - code_ptr,
            OpcodeName(opcode),
            m_iVarStackOffset,
            IsExternal ? "[external]" : ""
        );
    }

    prev_opcode_pos                                  = (prev_opcode_pos + 1) % 100;
    prev_opcodes[prev_opcode_pos].opcode             = opcode;
    prev_opcodes[prev_opcode_pos].VarStackOffset     = iVarStackOffset;
    prev_opcodes[(prev_opcode_pos + 1) % 100].opcode = OP_PREVIOUS;

    EmitOpcodeValue((byte)opcode, sizeof(byte));
}

void ScriptCompiler::EmitParameter(sval_t lhs, unsigned int sourcePos)
{
    str name_lowered;

    if (lhs.node[0].type != ENUM_field) {
        CompileError(sourcePos, "bad parameter lvalue: %d (expecting field)", lhs.node[0].type);
    }

    sval_u      listener_val = lhs.node[1];
    const char *name         = lhs.node[2].stringValue;
    //name_lowered = name;
    //name_lowered.tolower();

    int eventnum = Event::FindSetterEventNum(name);

    if (listener_val.node[0].type != ENUM_listener
        || (eventnum && BuiltinWriteVariable(sourcePos, listener_val.node[1].intValue, eventnum))) {
        CompileError(sourcePos, "built-in field '%s' not allowed", name);
    } else {
        EmitOpcode(OP_STORE_PARAM, sourcePos);
        EmitOpcode(OP_LOAD_GAME_VAR + listener_val.node[1].intValue, sourcePos);

        unsigned int index = Director.AddString(name);
        EmitOpcodeValue((unsigned int)index, sizeof(unsigned int));
    }
}

int ScriptCompiler::EmitParameterList(sval_t event_parameter_list)
{
    sval_t  *node;
    uint32_t iParamCount = 0;

    if (!event_parameter_list.node) {
        return 0;
    }

    for (node = event_parameter_list.node->node; node; node = node[1].node) {
        EmitValue(*node);

        iParamCount++;
    }

    return iParamCount;
}

void ScriptCompiler::EmitRef(sval_t val, unsigned int sourcePos)
{
    unsigned int index;
    const char* name;
    //str name_lowered;

    if (val.node[0].type != ENUM_field) {
        if (val.node[0].type == ENUM_array_expr) {
            EmitRef(val.node[1], sourcePos);
            EmitValue(val.node[2]);
            EmitOpcode(OP_STORE_ARRAY_REF, val.node[3].sourcePosValue);
        } else {
            CompileError(sourcePos, "bad lvalue: %d (expecting field or array)", val.node[0].type);
        }
        return;
    }

    name = val.node[2].stringValue;
    //name_lowered = name;
    //name_lowered.tolower();
    index = Director.AddString(name);

    EmitValue(val.node[1]);
    EmitOpcode(OP_STORE_FIELD_REF, sourcePos);
    EmitOpcodeValue((unsigned int)index, sizeof(unsigned int));
}

void ScriptCompiler::EmitStatementList(sval_t val)
{
    sval_t *node;

    for (node = val.node[0].node; node != NULL; node = node[1].node) {
        EmitValue(*node);
    }
}

void ScriptCompiler::EmitString(str value, unsigned int sourcePos)
{
    unsigned int index = Director.AddString(value);

    if (showopcodes->integer) {
        glbs.DPrintf("\t\tSTRING \"%s\"\n", value.c_str());
    }

    EmitOpcode(OP_STORE_STRING, sourcePos);
    EmitOpcodeValue((unsigned int)index, sizeof(unsigned int));
}

void ScriptCompiler::EmitSwitch(sval_t val, unsigned int sourcePos)
{
    bool         bStartCanBreak;
    int          iStartBreakJumpLocCount;
    StateScript *m_oldStateScript;

    m_oldStateScript = stateScript;
    stateScript      = script->CreateSwitchStateScript();

    EmitOpcode(OP_SWITCH, sourcePos);
    EmitOpcodeValue(stateScript, sizeof(StateScript *));

    bStartCanBreak          = bCanBreak;
    iStartBreakJumpLocCount = iBreakJumpLocCount;

    bCanBreak = true;

    EmitBreak(sourcePos);

    EmitValue(val);

    ProcessBreakJumpLocations(iStartBreakJumpLocCount);

    bCanBreak = bStartCanBreak;

    stateScript = m_oldStateScript;
}

void ScriptCompiler::EmitValue(ScriptVariable& var, unsigned int sourcePos)
{
    if (var.GetType() == VARIABLE_INTEGER) {
        EmitInteger(var.intValue(), sourcePos);
    } else if (var.GetType() == VARIABLE_FLOAT) {
        EmitFloat(var.floatValue(), sourcePos);
    }
}

void ScriptCompiler::EmitValue(sval_t val)
{
__emit:

    switch (val.node[0].type) {
    case ENUM_NOP:
        break;

    case ENUM_logical_and:
        EmitValue(val.node[1]);
        EmitVarToBool(val.node[3].sourcePosValue);
        EmitAndJump(val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_logical_or:
        EmitValue(val.node[1]);
        EmitVarToBool(val.node[3].sourcePosValue);
        EmitOrJump(val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_array_expr:
        EmitValue(val.node[1]);
        EmitValue(val.node[2]);
        EmitOpcode(OP_STORE_ARRAY, val.node[3].sourcePosValue);
        break;

    case ENUM_assignment_statement:
        EmitValue(val.node[2]);
        EmitAssignmentStatement(val.node[1], val.node[3].sourcePosValue);
        break;

    case ENUM_break:
        return EmitBreak(val.node[1].sourcePosValue);

    case ENUM_vector:
        EmitValue(val.node[1]);
        EmitValue(val.node[2]);
        EmitValue(val.node[3]);
        EmitOpcode(OP_CALC_VECTOR, val.node[4].sourcePosValue);
        break;

    case ENUM_neg_int_labeled_statement:
        val.node[1].intValue = -val.node[1].intValue;
    case ENUM_int_labeled_statement:
        {
            sval_u case_parm = val.node[1];

            if (case_parm.node[0].type == ENUM_integer) {
                EmitLabel(case_parm.node[1].intValue, val.node[3].sourcePosValue);
            } else if (case_parm.node[0].type == ENUM_string) {
                EmitLabel(case_parm.node[1].stringValue, val.node[3].sourcePosValue);
            } else if (case_parm.node[0].type == ENUM_func1_expr && case_parm.node[1].byteValue == OP_UN_MINUS) {
                EmitLabel(-case_parm.node[2].node[1].intValue, val.node[3].sourcePosValue);
            } else {
                CompileError(
                    val.node[3].sourcePosValue,
                    "bad case value: %d (expected integer or string)",
                    case_parm.node[0].type
                );
            }

            EmitLabelParameterList(val.node[2], val.node[3].sourcePosValue);

            break;
        }

    case ENUM_try:
        {
            unsigned char *old_code_pos = code_pos;

            ClearPrevOpcode();
            EmitValue(val.node[1]);
            EmitCatch(val.node[2], old_code_pos, val.node[3].sourcePosValue);
            break;
        }

    case ENUM_cmd_event_statement:
        {
            const int      eventnum       = Event::FindNormalEventNum(val.node[1].stringValue);
            sval_t         parameter_list = val.node[2];
            const uint32_t iParamCount    = EmitParameterList(parameter_list);

            if (!eventnum) {
                CompileError(val.node[3].sourcePosValue, "unknown command: %s", val.node[1].stringValue);
            }

            if (iParamCount > 5) {
                SetOpcodeVarStackOffset(OP_EXEC_CMD_COUNT1, -(int32_t)iParamCount);
                EmitOpcode(OP_EXEC_CMD_COUNT1, val.node[3].sourcePosValue);

                EmitOpcodeValue((byte)iParamCount, sizeof(byte));
            } else {
                EmitOpcode(OP_EXEC_CMD0 + iParamCount, val.node[3].sourcePosValue);
            }

            EmitOpcodeValue((op_ev_t)eventnum, sizeof(op_ev_t));
            break;
        }

    case ENUM_cmd_event_expr:
        {
            const int      eventnum       = Event::FindReturnEventNum(val.node[1].stringValue);
            sval_t         parameter_list = val.node[2];
            const uint32_t iParamCount    = EmitParameterList(parameter_list);

            if (!eventnum) {
                CompileError(val.node[3].sourcePosValue, "unknown command: %s", val.node[1].stringValue);
            }

            EmitOpcode(OP_STORE_LOCAL, val.node[3].sourcePosValue);
            EmitMethodExpression(iParamCount, eventnum, val.node[3].sourcePosValue);
            break;
        }

    case ENUM_method_event_statement:
        {
            const int      eventnum       = Event::FindNormalEventNum(val.node[2].stringValue);
            sval_t         parameter_list = val.node[3];
            const uint32_t iParamCount    = EmitParameterList(parameter_list);

            if (!eventnum) {
                CompileError(val.node[4].sourcePosValue, "unknown command: %s", val.node[2].stringValue);
            }

            EmitValue(val.node[1]);

            if (iParamCount > 5) {
                SetOpcodeVarStackOffset(OP_EXEC_CMD_COUNT1, -(int32_t)iParamCount);
                EmitOpcode(OP_EXEC_CMD_METHOD_COUNT1, val.node[4].sourcePosValue);

                EmitOpcodeValue((byte)iParamCount, sizeof(byte));
            } else {
                EmitOpcode(OP_EXEC_CMD_METHOD0 + iParamCount, val.node[4].sourcePosValue);
            }

            EmitOpcodeValue((op_ev_t)eventnum, sizeof(op_ev_t));
            break;
        }

    case ENUM_method_event_expr:
        {
            const int      eventnum       = Event::FindReturnEventNum(val.node[2].stringValue);
            sval_t         parameter_list = val.node[3];
            const uint32_t iParamCount    = EmitParameterList(parameter_list);

            if (!eventnum) {
                CompileError(val.node[4].sourcePosValue, "unknown command: %s", val.node[2].stringValue);
            }

            EmitValue(val.node[1]);
            EmitMethodExpression(iParamCount, eventnum, val.node[4].sourcePosValue);
            break;
        }

    case ENUM_const_array_expr:
        EmitConstArray(val.node[1], val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_continue:
        EmitContinue(val.node[1].sourcePosValue);
        break;

    case ENUM_do:
        EmitDoWhileJump(val.node[1], val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_field:
        EmitField(val.node[1], val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_func1_expr:
        EmitValue(val.node[2]);
        EmitFunc1(val.node[1].byteValue, val.node[3].sourcePosValue);
        break;

    case ENUM_if_statement:
        EmitValue(val.node[1]);
        EmitVarToBool(val.node[3].sourcePosValue);
        EmitIfJump(val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_if_else_statement:
        EmitValue(val.node[1]);
        EmitVarToBool(val.node[4].sourcePosValue);
        EmitIfElseJump(val.node[2], val.node[3], val.node[4].sourcePosValue);
        break;

    case ENUM_labeled_statement:
        EmitLabel(val.node[1].stringValue, val.node[3].sourcePosValue);
        EmitLabelParameterList(val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_privatelabel:
        EmitLabelPrivate(val.node[1].stringValue, val.node[3].sourcePosValue);
        EmitLabelParameterList(val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_makearray:
        return EmitMakeArray(val.node[1]);

    case ENUM_bool_not:
        EmitValue(val.node[1]);
        EmitVarToBool(val.node[2].sourcePosValue);
        EmitBoolNot(val.node[2].sourcePosValue);
        EmitBoolToVar(val.node[2].sourcePosValue);
        break;

    case ENUM_func2_expr:
        EmitValue(val.node[2]);
        EmitValue(val.node[3]);
        EmitOpcode(val.node[1].byteValue, val.node[4].sourcePosValue);
        break;

    case ENUM_statement_list:
        EmitStatementList(val.node[1]);
        break;

    case ENUM_float:
        EmitFloat(val.node[1].floatValue, val.node[2].sourcePosValue);
        break;

    case ENUM_integer:
        EmitInteger(val.node[1].intValue, val.node[2].sourcePosValue);
        break;

    case ENUM_listener:
        EmitOpcode(OP_STORE_GAME + val.node[1].intValue, val.node[2].sourcePosValue);
        break;

    case ENUM_NIL:
        EmitOpcode(OP_STORE_NIL, val.node[1].sourcePosValue);
        break;

    case ENUM_NULL:
        EmitOpcode(OP_STORE_NULL, val.node[1].sourcePosValue);
        break;

    case ENUM_string:
        EmitString(val.node[1].stringValue, val.node[2].sourcePosValue);
        break;

    case ENUM_switch:
        EmitValue(val.node[1]);
        EmitSwitch(val.node[2], val.node[3].sourcePosValue);
        break;

    case ENUM_ptr:
        val = val.node[1];
        goto __emit; // prevent stack overflow

    case ENUM_while_statement:
        EmitWhileJump(val.node[1], val.node[2], val.node[3], val.node[4].sourcePosValue);
        break;

    default:
        CompileError(-1, "unknown type %d\n", val.node[0].type);
        break;
    }
}

void ScriptCompiler::EmitVarToBool(unsigned int sourcePos)
{
    int prev = PrevOpcode();

    if (prev == OP_STORE_INT0) {
        AbsorbPrevOpcode();
        return EmitOpcode(OP_BOOL_STORE_FALSE, sourcePos);
    } else if (prev > OP_STORE_INT0) {
        if (prev <= OP_STORE_INT4) {
            AbsorbPrevOpcode();
            return EmitOpcode(OP_BOOL_STORE_TRUE, sourcePos);
        }
    } else if (prev == OP_BOOL_TO_VAR) {
        AbsorbPrevOpcode();
        return EmitNil(sourcePos);
    }

    return EmitOpcode(OP_UN_CAST_BOOLEAN, sourcePos);
}

void ScriptCompiler::EmitWhileJump(sval_t while_expr, sval_t while_stmt, sval_t inc_stmt, unsigned int sourcePos)
{
    unsigned char *pos = code_pos;
    int            label1, label2;

    if (showopcodes->integer) {
        label1 = current_label++;
        glbs.DPrintf("<LABEL%d>:\n", label1);
    }

    ClearPrevOpcode();

    EmitValue(while_expr);
    EmitVarToBool(sourcePos);

    label2             = EmitNot(sourcePos);
    unsigned char *jmp = code_pos;
    code_pos += sizeof(unsigned int);
    ClearPrevOpcode();

    bool old_bCanBreak    = bCanBreak;
    bool old_bCanContinue = bCanContinue;
    int  breakCount       = iBreakJumpLocCount;
    int  continueCount    = iContinueJumpLocCount;

    bCanBreak    = true;
    bCanContinue = true;

    EmitValue(while_stmt);
    ProcessContinueJumpLocations(continueCount);

    bCanContinue = old_bCanContinue;

    EmitValue(inc_stmt);

    if (showopcodes->integer) {
        glbs.DPrintf("JUMP_BACK4 <LABEL%d>\n", label1);
    }

    EmitJumpBack(pos, sourcePos);

    ClearPrevOpcode();

    if (showopcodes->integer) {
        glbs.DPrintf("<LABEL%d>:\n", label2);
    }

    AddJumpLocation(jmp);

    ProcessBreakJumpLocations(breakCount);

    bCanBreak = old_bCanBreak;
}

bool ScriptCompiler::EvalPrevValue(ScriptVariable& var)
{
    int   intValue   = 0;
    float floatValue = 0.0f;

    switch (PrevOpcode()) {
    case OP_STORE_INT0:
        intValue = 0;
        break;

    case OP_STORE_INT1:
        intValue = GetOpcodeValue<byte>(sizeof(byte), sizeof(byte));
        break;

    case OP_STORE_INT2:
        intValue = GetOpcodeValue<short>(sizeof(short), sizeof(short));
        break;

    case OP_STORE_INT3:
        intValue = GetOpcodeValue<short3>(sizeof(short3), sizeof(short3));
        break;

    case OP_STORE_INT4:
        intValue = GetOpcodeValue<int>(sizeof(int), sizeof(int));
        break;

    case OP_STORE_FLOAT:
        floatValue = GetOpcodeValue<float>(sizeof(float), sizeof(float));
        var.setFloatValue(floatValue);
        return true;

    default:
        return false;
    }

    var.setIntValue(intValue);

    return true;
}

void ScriptCompiler::ProcessBreakJumpLocations(int iStartBreakJumpLocCount)
{
    if (iBreakJumpLocCount > iStartBreakJumpLocCount) {
        do {
            iBreakJumpLocCount--;

            unsigned int offset = code_pos - sizeof(unsigned int) - apucBreakJumpLocations[iBreakJumpLocCount];

            EmitAt(apucBreakJumpLocations[iBreakJumpLocCount], offset, sizeof(unsigned int));
        } while (iBreakJumpLocCount > iStartBreakJumpLocCount);

        ClearPrevOpcode();
    }
}

void ScriptCompiler::ProcessContinueJumpLocations(int iStartContinueJumpLocCount)
{
    if (iContinueJumpLocCount > iStartContinueJumpLocCount) {
        do {
            iContinueJumpLocCount--;

            unsigned int offset = code_pos - sizeof(unsigned int) - apucContinueJumpLocations[iContinueJumpLocCount];

            EmitAt(apucContinueJumpLocations[iContinueJumpLocCount], offset, sizeof(unsigned int));
        } while (iContinueJumpLocCount > iStartContinueJumpLocCount);

        ClearPrevOpcode();
    }
}

unsigned char *ScriptCompiler::GetPosition()
{
    return code_pos;
}

void ScriptCompiler::CompileError(unsigned int sourcePos, const char *format, ...)
{
    char    buffer[4000];
    va_list va;

    va_start(va, format);
    Q_vsnprintf(buffer, sizeof(buffer), format, va);
    va_end(va);

    compileSuccess = false;

    glbs.DPrintf("%s\n", buffer);
    script->PrintSourcePos(sourcePos, false);

    throw ScriptException(buffer);
}

int yyparse();

char *ScriptCompiler::Preprocess(char *sourceBuffer)
{
    return sourceBuffer;
}

void ScriptCompiler::Preclean(char *processedBuffer) {}

extern int          prev_yylex;
extern int          out_pos;
extern int          success_pos;
extern const char  *start_ptr;
extern const char  *in_ptr;
extern parseStage_e parseStage;

void yy_init_script();

int yyerror(const char *msg)
{
    //parsedata.pos -= yyleng;
    parsedata.exc.yylineno = prev_yylex != TOKEN_EOL ? yylineno : yylineno - 1;
    parsedata.exc.yytext   = yytext;
    parsedata.exc.yytoken  = msg;

    //str line = ScriptCompiler::GetLine( parsedata.sourceBuffer, parsedata.exc.yylineno );

    glbs.Printf("parse error:\n%s:\n", parsedata.exc.yytoken.c_str());

    parsedata.gameScript->PrintSourcePos(success_pos, false);
    parsedata.pos++;

    return 1;
}

bool ScriptCompiler::Parse(GameScript *gameScript, char *sourceBuffer, const char *type, size_t& outLength)
{
    parsedata = yyparsedata();

    parsedata.sourceBuffer = sourceBuffer;
    parsedata.gameScript   = gameScript;
    parsedata.braces_count = 0;

    start_ptr   = sourceBuffer;
    prev_yylex  = 0;
    out_pos     = 0;
    success_pos = 0;
    parseStage  = PS_TYPE;
    in_ptr      = type;

    script      = gameScript;
    stateScript = &gameScript->m_State;

    outLength = 0;

    yy_init_script();
    parsetree_init();

    try {
        if (yyparse() != 0 || parsedata.exc.yytoken != "") {
            // an error occured

            if (!parsedata.exc.yytext) {
                if (parsedata.braces_count) {
                    glbs.DPrintf("unmatching {} pair\n");
                } else {
                    glbs.DPrintf("unexpected end of file found\n");
                }
            }

            yylex_destroy();
            return false;
        }
    } catch (ScriptException& exc) {
        yylex_destroy();
        exc;
        return false;
    }

    yylex_destroy();

    outLength = parsedata.total_length;
    return true;
}

bool ScriptCompiler::Compile(GameScript *gameScript, unsigned char *progBuffer, size_t& outLength)
{
    bool success = false;

    if (progBuffer == NULL) {
        glbs.DPrintf("Invalid program buffer\n");
        return 0;
    }

    code_pos = progBuffer;
    code_ptr = progBuffer;
    prog_ptr = progBuffer;

    gameScript->m_ProgToSource = new con_set<const unsigned char *, sourceinfo_t>;

    compileSuccess = true;

    prev_opcodes[prev_opcode_pos].opcode = OP_PREVIOUS;

    try {
        EmitValue(parsedata.val);
        EmitEof(-1);

        if (compileSuccess) {
            stateScript->AddLabel("", code_ptr);

            outLength = code_pos - code_ptr;
            success = true;
        } else {
            outLength = 0;
        }

        prog_end_ptr = code_pos;
    } catch (ScriptException& exc) {
        exc;
        outLength = 0;
        prog_end_ptr = code_pos;
    }

    parsetree_freeall();

    return success;
}

str ScriptCompiler::GetLine(str content, int line)
{
    char *p;
    str   found;
    int   i = 1;

    p = (char *)content.c_str();

    while (*p) {
        if (*p == '\n') {
            i++;
        }

        if (i >= line) {
            found = strtok(p, "\n");
            break;
        }

        p++;
    }

    return found;
}

template<typename Value>
void ScriptCompiler::EmitOpcodeValue(const Value& value, size_t size)
{
    Com_Memcpy(code_pos, &value, size);
    code_pos += size;
}

template<typename Value>
void ScriptCompiler::EmitOpcodeValue(const Value& value)
{
    Com_Memcpy(code_pos, &value, sizeof(value));
    code_pos += sizeof(value);
}

template<typename Value>
void ScriptCompiler::EmitAt(unsigned char *location, const Value& value, size_t size)
{
    Com_Memcpy(location, &value, size);
}

template<typename Value>
void ScriptCompiler::SetOpcodeValue(const Value& value)
{
    Com_Memcpy(code_pos, &value, sizeof(value));
}

template<typename Value>
Value ScriptCompiler::GetOpcodeValue(size_t size) const
{
    Value val {0};
    Com_Memcpy(&val, code_pos, size);
    return val;
}

template<typename Value>
Value ScriptCompiler::GetOpcodeValue(size_t offset, size_t size) const
{
    Value val {0};
    Com_Memcpy(&val, code_pos - offset, size);
    return val;
}

void CompileAssemble(const char *filename, const char *outputfile)
{
    GameScript *gameScript = Director.GetGameScript(filename);
    Archiver    arc;

    if (!gameScript->m_ProgBuffer || !gameScript->m_ProgLength) {
        return;
    }

    arc.Create(outputfile);

    if (!arc.NoErrors()) {
        glbs.DPrintf("Error saving to file '%s'\n", filename);
        return;
    }

    gameScript->m_bPrecompiled = true;
    gameScript->Archive(arc);

    // dump the compiled script to file
    arc.Close();
}

bool GetCompiledScript(GameScript *scr)
{
    /*
    Archiver arc;

    arc.SetSilent(true);
    arc.Read(scr->Filename(), false);

    if (!arc.NoErrors()) {
        return false;
    }

    arc.SetSilent(false);

    scr->Archive(arc);

    arc.Close();

    return true;
    */
    // FIXME: not used?
    return false;
}
