#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "Compiler.h"
#include "DataType.h"
#include "tokenizer/Tokenizer.h"
#include "utils/Functions.h"
#include "utils/Variables.h"
#include "vm/Operation.h"

#define ERROR_LENGTH 256
#define RETURN_BUFFER 16
#define BREAK_BUFFER 32

static jmp_buf errorJump;
static char error[ERROR_LENGTH] = {'\0'};

static ByteCode* code;

static int16 line = 1;

static Variables vars;
static Functions functions;
static Functions functionQueue;

static int returns[RETURN_BUFFER];
static int returnIndex = 0;
static DataType returnType = DT_VOID;

static int breaks[BREAK_BUFFER];
static int breakIndex = 0;
static int forWhileStack = 0;
static int continueAt = 0;

typedef struct {
    Operation intOp;
    Operation floatOp;
    Operation boolOp;
    const char* name;
} TypedOp;

static const TypedOp TYPED_MUL = {OP_MUL_INT, OP_MUL_FLOAT, OP_NOTHING, "*"};
static const TypedOp TYPED_DIV = {OP_DIV_INT, OP_DIV_FLOAT, OP_NOTHING, "/"};
static const TypedOp TYPED_MOD = {OP_MOD_INT, OP_NOTHING, OP_NOTHING, "%"};
static const TypedOp TYPED_ADD = {OP_ADD_INT, OP_ADD_FLOAT, OP_NOTHING, "+"};
static const TypedOp TYPED_SUB = {OP_SUB_INT, OP_SUB_FLOAT, OP_NOTHING, "-"};
static const TypedOp TYPED_LESS = {OP_LESS_INT, OP_LESS_FLOAT, OP_NOTHING, "<"};
static const TypedOp TYPED_LESS_EQUAL = {OP_GREATER_INT, OP_GREATER_FLOAT,
                                         OP_NOTHING, "<="};
static const TypedOp TYPED_GREATER = {OP_GREATER_INT, OP_GREATER_FLOAT,
                                      OP_NOTHING, ">"};
static const TypedOp TYPED_GREATER_EQUAL = {OP_LESS_INT, OP_LESS_FLOAT,
                                            OP_NOTHING, ">="};
static const TypedOp TYPED_EQUAL = {OP_EQUAL_INT, OP_EQUAL_FLOAT, OP_EQUAL_BOOL,
                                    "=="};
static const TypedOp TYPED_NOT_EQUAL = {OP_EQUAL_INT, OP_EQUAL_FLOAT,
                                        OP_EQUAL_BOOL, "!="};
static const TypedOp TYPED_BIT_OR = {OP_BIT_OR, OP_NOTHING, OP_NOTHING, "|"};
static const TypedOp TYPED_BIT_XOR = {OP_BIT_XOR, OP_NOTHING, OP_NOTHING, "^"};
static const TypedOp TYPED_BIT_AND = {OP_BIT_AND, OP_NOTHING, OP_NOTHING, "&"};
static const TypedOp TYPED_LEFT_SHIFT = {OP_LEFT_SHIFT, OP_NOTHING, OP_NOTHING,
                                         "<<"};
static const TypedOp TYPED_RIGHT_SHIFT = {OP_RIGHT_SHIFT, OP_NOTHING,
                                          OP_NOTHING, ">>"};

static void cError(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vsnprintf(error, ERROR_LENGTH, format, args);
    va_end(args);
    longjmp(errorJump, 0);
}

static void cInvalidOperation(DataType a, DataType b, const char* op) {
    cError("invalid operation: %s %s %s", dtGetName(a), op, dtGetName(b));
}

static void cNotDeclared(const char* name) {
    cError("variable %s has not been declared", name);
}

static void cDeclared(const char* name) {
    cError("%s has already been declared", name);
}

static void cTooMuchArguments() {
    cError("too much function arguments");
}

static void cUnexpectedToken(Token t) {
    cError("unexpected token on line %d: %s", line, tGetName(t));
}

static void cAddOperation(Operation token) {
    unsigned char c = token;
    bcAddBytes(code, &c, 1);
}

static int cReserveInt() {
    return bcReserveBytes(code, sizeof(int));
}

static void cSetInt(int p, int i) {
    bcSetBytes(code, p, &i, sizeof(int));
}

static void cAddInt(int i) {
    bcAddBytes(code, &i, sizeof(int));
}

static void cAddInt16(int16 i) {
    bcAddBytes(code, &i, sizeof(int16));
}

static Token cReadTokenAndLine() {
    Token t = tReadToken();
    if(tReadInt16(&line)) {
        return t;
    }
    return T_END;
}

static void cConsumeToken(Token wanted) {
    Token t = cReadTokenAndLine();
    if(wanted != t) {
        cError("unexpected token on line %d: expected '%s' got '%s'", line,
               tGetName(wanted), tGetName(t));
    }
}

static bool cConsumeTokenIf(Token t) {
    if(tPeekToken() == t) {
        cReadTokenAndLine();
        return true;
    }
    return false;
}

static void cConstantInt() {
    int value;
    if(!tReadInt(&value)) {
        cError("int token without an int on line %d", line);
    }
    cAddOperation(OP_PUSH_INT);
    cAddInt(value);
}

static void cConstantFloat() {
    float value;
    if(!tReadFloat(&value)) {
        cError("float token without a float on line %d", line);
    }
    cAddOperation(OP_PUSH_FLOAT);
    bcAddBytes(code, &value, sizeof(float));
}

static const char* cReadString() {
    int length;
    const char* literal = tReadString(&length);
    if(literal == NULL) {
        cError("literal without string on line %d", line);
    }
    return literal;
}

static DataType cExpression();

static void cCallFunctionArguments(Function* f) {
    while(!cConsumeTokenIf(T_CLOSE_BRACKET)) {
        DataType dt = cExpression();
        if(fAddArgument(f, dt)) {
            cTooMuchArguments();
        }
        if(cConsumeTokenIf(T_COMMA) && tPeekToken() == T_CLOSE_BRACKET) {
            cUnexpectedToken(tPeekToken());
        }
    }
}

static DataType cCallFunction(const char* name) {
    cAddOperation(OP_PUSH_INT);
    cAddInt(0);
    Function f;
    fInit(&f, name, line);
    cCallFunctionArguments(&f);
    cAddOperation(OP_GOSUB);
    Function* found = fsSearch(&functions, &f);
    if(found == NULL) {
        cError("unknown function");
    }
    if(found->address == -1) {
        f.returnType = found->returnType;
        f.address = cReserveInt();
        fsAdd(&functionQueue, &f);
    } else {
        cAddInt(found->address);
    }
    cAddInt(found->size);
    return found->returnType;
}

static DataType cLoadVariable(Variable* v) {
    switch(v->type) {
        case DT_INT: cAddOperation(OP_LOAD_INT); break;
        case DT_BOOL: cAddOperation(OP_LOAD_BOOL); break;
        case DT_FLOAT: cAddOperation(OP_LOAD_FLOAT); break;
        default: cError("cannot load type %s", dtGetName(v->type));
    }
    cAddInt(v->address);
    return v->type;
}

static void cStoreVariable(Variable* v, DataType dt, const char* name) {
    if(v->type != dt) {
        cInvalidOperation(v->type, dt, name);
    }
    switch(v->type) {
        case DT_INT: cAddOperation(OP_STORE_INT); break;
        case DT_BOOL: cAddOperation(OP_STORE_BOOL); break;
        case DT_FLOAT: cAddOperation(OP_STORE_FLOAT); break;
        default: cError("cannot store type %s", dtGetName(v->type));
    }
    cAddInt(v->address);
}

static DataType cPostChange(Variable* v, int change, const char* name) {
    if(v->type != DT_INT) {
        cError("%s needs an int", name);
    }
    cAddOperation(OP_LOAD_INT);
    cAddInt(v->address);
    cAddOperation(OP_LOAD_INT);
    cAddInt(v->address);
    cAddOperation(OP_PUSH_INT);
    cAddInt(change);
    cAddOperation(OP_ADD_INT);
    cAddOperation(OP_STORE_INT);
    cAddInt(v->address);
    return DT_INT;
}

static DataType cLiteral() {
    const char* literal = cReadString();
    if(cConsumeTokenIf(T_OPEN_BRACKET)) {
        DataType dt = cCallFunction(literal);
        if(dt == DT_VOID) {
            cError("function returns void");
        }
        return dt;
    }
    Variable* v = vSearch(&vars, literal);
    if(v == NULL) {
        cNotDeclared(literal);
    }
    if(cConsumeTokenIf(T_INCREMENT)) {
        return cPostChange(v, 1, "++");
    } else if(cConsumeTokenIf(T_DECREMENT)) {
        return cPostChange(v, -1, "--");
    }
    return cLoadVariable(v);
}

static DataType cBracketPrimary() {
    DataType result = cExpression();
    cConsumeToken(T_CLOSE_BRACKET);
    return result;
}

static DataType cPrimary() {
    Token t = cReadTokenAndLine();
    switch(t) {
        case T_CONST_INT: cConstantInt(); return DT_INT;
        case T_CONST_FLOAT: cConstantFloat(); return DT_FLOAT;
        case T_TRUE: cAddOperation(OP_PUSH_TRUE); return DT_BOOL;
        case T_FALSE: cAddOperation(OP_PUSH_FALSE); return DT_BOOL;
        case T_OPEN_BRACKET: return cBracketPrimary();
        case T_LITERAL: return cLiteral();
        default: cUnexpectedToken(t); return DT_VOID;
    }
}

static DataType cPreChange(int change, const char* name) {
    cConsumeToken(T_LITERAL);
    const char* literal = cReadString();
    Variable* v = vSearch(&vars, literal);
    if(v == NULL) {
        cNotDeclared(literal);
    } else if(v->type != DT_INT) {
        cError("%s needs an int", name);
    }
    cAddOperation(OP_LOAD_INT);
    cAddInt(v->address);
    cAddOperation(OP_PUSH_INT);
    cAddInt(change);
    cAddOperation(OP_ADD_INT);
    cAddOperation(OP_STORE_INT);
    cAddInt(v->address);
    cAddOperation(OP_LOAD_INT);
    cAddInt(v->address);
    return DT_INT;
}

static DataType cPreUnary() {
    if(cConsumeTokenIf(T_SUB)) {
        DataType result = cPrimary();
        switch(result) {
            case DT_INT: cAddOperation(OP_INVERT_SIGN_INT); break;
            case DT_FLOAT: cAddOperation(OP_INVERT_SIGN_FLOAT); break;
            default: cError("cannot invert sign of %s", dtGetName(result));
        }
        return result;
    } else if(cConsumeTokenIf(T_INCREMENT)) {
        return cPreChange(1, "++");
    } else if(cConsumeTokenIf(T_DECREMENT)) {
        return cPreChange(-1, "--");
    } else if(cConsumeTokenIf(T_NOT)) {
        int counter = 1;
        while(cConsumeTokenIf(T_NOT)) {
            counter++;
        }
        DataType result = cPrimary();
        if(result != DT_BOOL) {
            cError("! needs a bool not %s", dtGetName(result));
        }
        cAddOperation(OP_NOT);
        if((counter & 1) == 0) {
            cAddOperation(OP_NOT);
        }
        return DT_BOOL;
    } else if(cConsumeTokenIf(T_BIT_NOT)) {
        DataType result = cPrimary();
        if(result == DT_INT) {
            cAddOperation(OP_BIT_NOT);
        } else {
            cError("~ needs an int not %s", dtGetName(result));
        }
        return result;
    }
    return cPrimary();
}

static void cAddTypeOperation(DataType a, DataType b, const TypedOp* op) {
    if(a == DT_INT && b == DT_INT && op->intOp != OP_NOTHING) {
        cAddOperation(op->intOp);
    } else if(a == DT_FLOAT && b == DT_FLOAT && op->floatOp != OP_NOTHING) {
        cAddOperation(op->floatOp);
    } else if(a == DT_BOOL && b == DT_BOOL && op->boolOp != OP_NOTHING) {
        cAddOperation(op->boolOp);
    } else {
        cInvalidOperation(a, b, op->name);
    }
}

static DataType cMul() {
    DataType a = cPreUnary();
    while(true) {
        if(cConsumeTokenIf(T_MUL)) {
            cAddTypeOperation(a, cPreUnary(), &TYPED_MUL);
        } else if(cConsumeTokenIf(T_DIV)) {
            cAddTypeOperation(a, cPreUnary(), &TYPED_DIV);
        } else if(cConsumeTokenIf(T_MOD)) {
            cAddTypeOperation(a, cPreUnary(), &TYPED_MOD);
        } else {
            break;
        }
    }
    return a;
}

static DataType cAdd() {
    DataType a = cMul();
    while(true) {
        if(cConsumeTokenIf(T_ADD)) {
            cAddTypeOperation(a, cMul(), &TYPED_ADD);
        } else if(cConsumeTokenIf(T_SUB)) {
            cAddTypeOperation(a, cMul(), &TYPED_SUB);
        } else {
            break;
        }
    }
    return a;
}

static DataType cShift() {
    DataType a = cAdd();
    while(true) {
        if(cConsumeTokenIf(T_LEFT_SHIFT)) {
            cAddTypeOperation(a, cAdd(), &TYPED_LEFT_SHIFT);
        } else if(cConsumeTokenIf(T_RIGHT_SHIFT)) {
            cAddTypeOperation(a, cAdd(), &TYPED_RIGHT_SHIFT);
        } else {
            break;
        }
    }
    return a;
}

static DataType cComparison() {
    DataType a = cShift();
    while(true) {
        if(cConsumeTokenIf(T_LESS)) {
            cAddTypeOperation(a, cShift(), &TYPED_LESS);
            a = DT_BOOL;
        } else if(cConsumeTokenIf(T_LESS_EQUAL)) {
            cAddTypeOperation(a, cShift(), &TYPED_LESS_EQUAL);
            cAddOperation(OP_NOT);
            a = DT_BOOL;
        } else if(cConsumeTokenIf(T_GREATER)) {
            cAddTypeOperation(a, cShift(), &TYPED_GREATER);
            a = DT_BOOL;
        } else if(cConsumeTokenIf(T_GREATER_EQUAL)) {
            cAddTypeOperation(a, cShift(), &TYPED_GREATER_EQUAL);
            cAddOperation(OP_NOT);
            a = DT_BOOL;
        } else {
            break;
        }
    }
    return a;
}

static DataType cEqual() {
    DataType a = cComparison();
    while(true) {
        if(cConsumeTokenIf(T_EQUAL)) {
            cAddTypeOperation(a, cComparison(), &TYPED_EQUAL);
            a = DT_BOOL;
        } else if(cConsumeTokenIf(T_NOT_EQUAL)) {
            cAddTypeOperation(a, cComparison(), &TYPED_NOT_EQUAL);
            cAddOperation(OP_NOT);
            a = DT_BOOL;
        } else {
            break;
        }
    }
    return a;
}

static DataType cBitAnd() {
    DataType a = cEqual();
    while(cConsumeTokenIf(T_BIT_AND)) {
        DataType b = cEqual();
        cAddTypeOperation(a, b, &TYPED_BIT_AND);
    }
    return a;
}

static DataType cBitXor() {
    DataType a = cBitAnd();
    while(cConsumeTokenIf(T_BIT_XOR)) {
        DataType b = cBitAnd();
        cAddTypeOperation(a, b, &TYPED_BIT_XOR);
    }
    return a;
}

static DataType cBitOr() {
    DataType a = cBitXor();
    while(cConsumeTokenIf(T_BIT_OR)) {
        DataType b = cBitXor();
        cAddTypeOperation(a, b, &TYPED_BIT_OR);
    }
    return a;
}

static DataType cAnd() {
    DataType a = cBitOr();
    while(cConsumeTokenIf(T_AND)) {
        cAddOperation(OP_PEEK_FALSE_GOTO);
        int p = cReserveInt();
        DataType b = cBitOr();
        if(a != DT_BOOL || b != DT_BOOL) {
            cInvalidOperation(a, b, "&&");
        }
        cAddOperation(OP_AND);
        cSetInt(p, code->length);
    }
    return a;
}

static DataType cOr() {
    DataType a = cAnd();
    while(cConsumeTokenIf(T_OR)) {
        cAddOperation(OP_PEEK_TRUE_GOTO);
        int p = cReserveInt();
        DataType b = cAnd();
        if(a != DT_BOOL || b != DT_BOOL) {
            cInvalidOperation(a, b, "||");
        }
        cAddOperation(OP_OR);
        cSetInt(p, code->length);
    }
    return a;
}

static DataType cExpression() {
    return cOr();
}

static void cOperationSet(Variable* v, const TypedOp* op) {
    DataType a = cLoadVariable(v);
    DataType b = cExpression();
    cAddTypeOperation(a, b, op);
    cStoreVariable(v, b, "=");
}

static void cAddPostLineChange(Variable* v, int change, const char* name) {
    if(v->type != DT_INT) {
        cError("%s needs an int", name);
    }
    cAddOperation(OP_LOAD_INT);
    cAddInt(v->address);
    cAddOperation(OP_PUSH_INT);
    cAddInt(change);
    cAddOperation(OP_ADD_INT);
    cAddOperation(OP_STORE_INT);
    cAddInt(v->address);
}

static void cLineLiteral() {
    const char* literal = cReadString();
    if(cConsumeTokenIf(T_OPEN_BRACKET)) {
        DataType dt = cCallFunction(literal);
        if(dt != DT_VOID) {
            cError("function returns %s not void", dtGetName(dt));
        }
        return;
    }
    Variable* v = vSearch(&vars, literal);
    if(v == NULL) {
        cNotDeclared(literal);
    }
    Token t = cReadTokenAndLine();
    switch(t) {
        case T_SET: cStoreVariable(v, cExpression(), "="); break;
        case T_ADD_SET: cOperationSet(v, &TYPED_ADD); break;
        case T_SUB_SET: cOperationSet(v, &TYPED_SUB); break;
        case T_MUL_SET: cOperationSet(v, &TYPED_MUL); break;
        case T_DIV_SET: cOperationSet(v, &TYPED_DIV); break;
        case T_MOD_SET: cOperationSet(v, &TYPED_MOD); break;
        case T_BIT_AND_SET: cOperationSet(v, &TYPED_BIT_AND); break;
        case T_BIT_OR_SET: cOperationSet(v, &TYPED_BIT_OR); break;
        case T_BIT_XOR_SET: cOperationSet(v, &TYPED_BIT_XOR); break;
        case T_LEFT_SHIFT_SET: cOperationSet(v, &TYPED_LEFT_SHIFT); break;
        case T_RIGHT_SHIFT_SET: cOperationSet(v, &TYPED_RIGHT_SHIFT); break;
        case T_INCREMENT: cAddPostLineChange(v, 1, "++"); break;
        case T_DECREMENT: cAddPostLineChange(v, -1, "--"); break;
        default: cUnexpectedToken(t);
    }
}

static void cLine(Token t);

static void cConsumeBody() {
    int oldLine = line;
    while(!cConsumeTokenIf(T_CLOSE_CURVED_BRACKET)) {
        Token t = cReadTokenAndLine();
        if(t == T_END) {
            line = oldLine;
            cError("unexpected end of file: non closed curved bracket");
        }
        cLine(t);
    }
}

static void cConsumeScope() {
    Scope scope;
    vEnterScope(&vars, &scope);
    cConsumeBody();
    vLeaveScope(&vars, &scope);
}

static void cAddReturn(Operation op) {
    cAddOperation(op);
    returns[returnIndex++] = cReserveInt();
}

static void cReturn() {
    if(returnIndex >= RETURN_BUFFER) {
        cError("too much returns in function");
    }
    if(returnType == DT_VOID) {
        cConsumeToken(T_SEMICOLON);
        cAddReturn(OP_RETURN);
        return;
    }
    DataType dt = cExpression();
    if(dt != returnType) {
        cError("wrong return type, should be %s", dtGetName(returnType));
    }
    switch(dt) {
        case DT_INT: cAddReturn(OP_RETURN_INT); break;
        case DT_BOOL: cAddReturn(OP_RETURN_BOOL); break;
        case DT_FLOAT: cAddReturn(OP_RETURN_FLOAT); break;
        default: cError("cannot return %s", dtGetName(dt));
    }
    cConsumeToken(T_SEMICOLON);
}

static void cPrint() {
    DataType dt = cExpression();
    switch(dt) {
        case DT_INT: cAddOperation(OP_PRINT_INT); break;
        case DT_FLOAT: cAddOperation(OP_PRINT_FLOAT); break;
        case DT_BOOL: cAddOperation(OP_PRINT_BOOL); break;
        default: cError("cannot print type %s", dtGetName(dt));
    }
    cConsumeToken(T_SEMICOLON);
}

static void cIf() {
    cConsumeToken(T_OPEN_BRACKET);
    DataType dt = cExpression();
    if(dt != DT_BOOL) {
        cError("if expects a bool not %s", dtGetName(dt));
    }
    cConsumeToken(T_CLOSE_BRACKET);
    cAddOperation(OP_IF_GOTO);
    int ifP = cReserveInt();
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    cConsumeScope();
    cSetInt(ifP, code->length);

    if(cConsumeTokenIf(T_ELSE)) {
        cAddOperation(OP_GOTO);
        int elseP = cReserveInt();
        cSetInt(ifP, code->length);
        if(cConsumeTokenIf(T_IF)) {
            cIf();
        } else {
            cConsumeToken(T_OPEN_CURVED_BRACKET);
            cConsumeScope();
        }
        cSetInt(elseP, code->length);
    }
}

static void cConsumeBreaks(int start, int address) {
    for(int i = start; i < breakIndex; i++) {
        cSetInt(breaks[i], address);
    }
    breakIndex = start;
}

static void cWhile() {
    int start = code->length;
    cConsumeToken(T_OPEN_BRACKET);
    DataType dt = cExpression();
    if(dt != DT_BOOL) {
        cError("while expects a bool not %s", dtGetName(dt));
    }
    cConsumeToken(T_CLOSE_BRACKET);
    cAddOperation(OP_IF_GOTO);
    int ifP = cReserveInt();
    int breakStart = breakIndex;
    forWhileStack++;
    int oldContinue = continueAt;
    continueAt = start;
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    cConsumeScope();
    continueAt = oldContinue;
    forWhileStack--;
    cAddOperation(OP_GOTO);
    cAddInt(start);
    cSetInt(ifP, code->length);
    cConsumeBreaks(breakStart, code->length);
}

static void cDeclare(DataType dt) {
    cConsumeToken(T_LITERAL);
    const char* var = cReadString();
    Variable* v = vSearchScope(&vars, var);
    if(v != NULL) {
        cDeclared(var);
    }
    v = vAdd(&vars, var, dt);
    cConsumeToken(T_SET);
    cStoreVariable(v, cExpression(), "=");
}

static void cAddPreLineChange(int change, const char* name) {
    cConsumeToken(T_LITERAL);
    const char* literal = cReadString();
    Variable* v = vSearch(&vars, literal);
    if(v == NULL) {
        cNotDeclared(literal);
    }
    cAddPostLineChange(v, change, name);
}

static void cLineExpression(Token t) {
    switch(t) {
        case T_LITERAL: cLineLiteral(); break;
        case T_INT: cDeclare(DT_INT); break;
        case T_BOOL: cDeclare(DT_BOOL); break;
        case T_FLOAT: cDeclare(DT_FLOAT); break;
        case T_INCREMENT: cAddPreLineChange(1, "++"); break;
        case T_DECREMENT: cAddPreLineChange(-1, "--"); break;
        default: cUnexpectedToken(t);
    }
}

static void cFor() {
    Scope scope;
    vEnterScope(&vars, &scope);

    cConsumeToken(T_OPEN_BRACKET);
    cLineExpression(cReadTokenAndLine());
    cConsumeToken(T_SEMICOLON);
    int startCheck = code->length;
    DataType dt = cExpression();
    if(dt != DT_BOOL) {
        cError("for expects a bool not %s", dtGetName(dt));
    }
    cConsumeToken(T_SEMICOLON);
    cAddOperation(OP_IF_GOTO);
    int end = cReserveInt();
    cAddOperation(OP_GOTO);
    int beginBody = cReserveInt();
    int startPerLoop = code->length;
    cLineExpression(cReadTokenAndLine());
    cAddOperation(OP_GOTO);
    cAddInt(startCheck);
    cConsumeToken(T_CLOSE_BRACKET);
    cSetInt(beginBody, code->length);
    int breakStart = breakIndex;
    forWhileStack++;
    int oldContinue = continueAt;
    continueAt = startPerLoop;
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    cConsumeBody();
    continueAt = oldContinue;
    forWhileStack--;
    cAddOperation(OP_GOTO);
    cAddInt(startPerLoop);
    cSetInt(end, code->length);
    cConsumeBreaks(breakStart, code->length);

    vLeaveScope(&vars, &scope);
}

static void cBreak() {
    if(forWhileStack == 0) {
        cError("break without for or while on line %d", line);
    } else if(breakIndex >= BREAK_BUFFER) {
        cError("too much breaks around line %d", line);
    }
    cAddOperation(OP_GOTO);
    breaks[breakIndex++] = cReserveInt();
    cConsumeToken(T_SEMICOLON);
}

static void cContinue() {
    if(forWhileStack == 0) {
        cError("continue without for or while on line %d", line);
    }
    cAddOperation(OP_GOTO);
    cAddInt(continueAt);
    cConsumeToken(T_SEMICOLON);
}

static void cLine(Token t) {
    cAddOperation(OP_LINE);
    cAddInt16(line);
    switch(t) {
        case T_OPEN_CURVED_BRACKET: cConsumeScope(); break;
        case T_PRINT: cPrint(); break;
        case T_RETURN: cReturn(); break;
        case T_IF: cIf(); break;
        case T_WHILE: cWhile(); break;
        case T_FOR: cFor(); break;
        case T_BREAK: cBreak(); break;
        case T_CONTINUE: cContinue(); break;
        default: cLineExpression(t); cConsumeToken(T_SEMICOLON);
    }
}

static void cFunctionArgument(Function* f);

static void cFunctionCommaOrEnd(Function* f) {
    if(cConsumeTokenIf(T_CLOSE_BRACKET)) {
        return;
    }
    cConsumeToken(T_COMMA);
    cFunctionArgument(f);
}

static void cFunctionAddArgument(Function* f, DataType dt) {
    cConsumeToken(T_LITERAL);
    const char* name = cReadString();
    Variable* v = vSearchScope(&vars, name);
    if(v != NULL) {
        cDeclared(name);
    }
    vAdd(&vars, name, dt);
    if(fAddArgument(f, dt)) {
        cTooMuchArguments();
    }
    cFunctionCommaOrEnd(f);
}

static void cFunctionArgument(Function* f) {
    Token t = cReadTokenAndLine();
    switch(t) {
        case T_INT: cFunctionAddArgument(f, DT_INT); break;
        default: cUnexpectedToken(t);
    }
}

static void cFunctionArguments(Function* f) {
    cConsumeToken(T_OPEN_BRACKET);
    if(!cConsumeTokenIf(T_CLOSE_BRACKET)) {
        cFunctionArgument(f);
    }
}

static int cReserve(int offset) {
    cAddOperation(OP_RESERVE);
    int p = cReserveInt();
    cAddInt(offset);
    return p;
}

static void cFree(int p, int bytes) {
    cAddOperation(OP_RETURN);
    cAddInt(bytes);
    cSetInt(p, bytes);
}

static void cLinkReturns(int bytes) {
    for(int i = 0; i < returnIndex; i++) {
        cSetInt(returns[i], bytes);
    }
    returnIndex = 0;
}

static void cInnerFunction(Function* f) {
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    int p = cReserve(f->size);
    returnIndex = 0;
    cConsumeScope();
    cFree(p, vars.maxAddress);
    cLinkReturns(vars.maxAddress);
}

static bool cForwardFunction(Function* found, Function* f) {
    if(!cConsumeTokenIf(T_SEMICOLON)) {
        return false;
    } else if(found != NULL) {
        cError("function registered twice");
    }
    f->address = -1;
    fsAdd(&functions, f);
    return true;
}

static void cBuildFunction(Function* f, DataType rType) {
    cConsumeToken(T_LITERAL);
    fInit(f, cReadString(), line);
    f->returnType = rType;
    vReset(&vars);
    cFunctionArguments(f);
}

static void cFunction(DataType rType) {
    Function f;
    cBuildFunction(&f, rType);
    Function* found = fsSearch(&functions, &f);
    if(cForwardFunction(found, &f)) {
        return;
    }
    cAddOperation(OP_LINE);
    cAddInt16(line);
    cAddOperation(OP_GOTO);
    int end = cReserveInt();
    f.address = code->length;
    if(found != NULL) {
        if(found->address == -1) {
            found->address = f.address;
        } else {
            cError("function registered twice");
        }
    } else {
        fsAdd(&functions, &f);
    }
    returnType = rType;
    cInnerFunction(&f);
    cSetInt(end, code->length);
}

static void cGlobalScope(Token t) {
    switch(t) {
        case T_VOID: cFunction(DT_VOID); break;
        case T_INT: cFunction(DT_INT); break;
        case T_BOOL: cFunction(DT_BOOL); break;
        case T_FLOAT: cFunction(DT_FLOAT); break;
        default: cUnexpectedToken(t);
    }
}

static void cCallMain() {
    Function f;
    fInit(&f, "main", line);
    Function* found = fsSearch(&functions, &f);
    if(found != NULL && found->returnType == DT_VOID) {
        cAddOperation(OP_PUSH_INT);
        cAddInt(0);
        cAddOperation(OP_GOSUB);
        cAddInt(found->address);
        cAddInt(found->size);
    }
}

static void cForEachLine() {
    Token t = cReadTokenAndLine();
    while(t != T_END) {
        cGlobalScope(t);
        t = cReadTokenAndLine();
    }
    cCallMain();
}

static void cLinkQueuedFunctions() {
    for(int i = 0; i < functionQueue.entries; i++) {
        Function* f = functionQueue.data + i;
        Function* found = fsSearch(&functions, f);
        if(found == NULL) {
            line = f->line;
            cError("unknown function");
        } else if(f->returnType != found->returnType) {
            line = f->line;
            cError("function return type is not %s", dtGetName(f->returnType));
        }
        cSetInt(f->address, found->address);
    }
}

static void cAllocAndCompile() {
    forWhileStack = 0;
    breakIndex = 0;
    vInit(&vars);
    fsInit(&functions);
    fsInit(&functionQueue);
    if(!setjmp(errorJump)) {
        cForEachLine();
        cLinkQueuedFunctions();
    }
    fsDelete(&functionQueue);
    fsDelete(&functions);
    vDelete(&vars);
}

ByteCode* cCompile() {
    error[0] = '\0';
    code = bcInit();
    cAllocAndCompile();
    if(error[0] != '\0') {
        bcDelete(code);
        return NULL;
    }
    return code;
}

const char* cGetError() {
    return error;
}

int cGetLine() {
    return line;
}