#include "inst.hpp"


Inst::Inst(type_t t): suff(NULL), type(t) {
}

Inst::~Inst() {
    if (suff) free(suff);
}


bool Inst::is(type_t t) const {
    return type == t;
}


const char* Inst::suffix() const {
    return suff ?: "";
}


void Inst::comment(const char* c) {
    if (suff) free(suff);
    while (*c == ' ') ++c;
    if (!*c || asprintf(&suff, " ; %s", c) == -1) {
        suff = NULL;
    }
}


Inst* Inst::parse(const char* line) {
    Inst* rv = NULL;

    int n = 0;
    char p;
    char a[512], b[512], c[512], d[512], e[512], f[512];

    try {
        if (sscanf(line, " asm%*[ ] \"%[^\"]%c %n", a, &p, &n) >= 2 && p == '"') {
            rv = new InstAsm(a);
        } else if (sscanf(line, " undef%*[ ] %[a-z_] %n", a, &n) >= 1) {
            rv = new InstUndef(Token::parseVal(a, false, true, false));
        } else if (sscanf(line, " goto%*[ ] %[a-z_] %n", a, &n) >= 1) {
            rv = new InstGoto(Token::parseVal(a, false, true, false));
        } else if (sscanf(line, " ext%*[ ] %[a-z_] %n", a, &n) >= 1) {
            rv = new InstExt(Token::parseVal(a, false, true, false));
        } else if (sscanf(line, " else %c %n", &p, &n) >= 1 && p == '{') {
            rv = new InstElse();
        } else if (sscanf(line, " %[^( ] ( %[^<>= ] %[<>=] %[^) ] ) goto%*[ ] %[a-z_] %n", a, b, c, d, e, &n) >= 5 && (!strcmp(a, "if") || !strcmp(a, "unless"))) {
            Inst* inst = new InstGoto(Token::parseVal(e, false, true, false));
            rv = new InstIf(strcmp(a, "if") != 0, Token::parseVal(b, false, true, true), Token::parseCmp(c), Token::parseVal(d, true, true, true), inst);
        } else if (sscanf(line, " %[^( ] ( %[^<>= ] %[<>=] %[^) ] ) %[][a-z0-9_] := %[][a-z0-9_] %n", a, b, c, d, e, f, &n) >= 6 && (!strcmp(a, "if") || !strcmp(a, "unless"))) {
            Inst* inst = new InstOp(Token::parseVal(e, false, true, true), Token::parseOp(':'), Token::parseVal(f, true, true, true));
            rv = new InstIf(strcmp(a, "if") != 0, Token::parseVal(b, false, true, true), Token::parseCmp(c), Token::parseVal(d, true, true, true), inst);
        } else if (sscanf(line, " %[^( ] ( %[^<>= ] %[<>=] %[^) ] ) %c %n", a, b, c, d, &p, &n) >= 5 && p == '{' && (!strcmp(a, "if") || !strcmp(a, "unless") || !strcmp(a, "while") || !strcmp(a, "until"))) {
            rv = new InstIf(!strcmp(a, "while") || !strcmp(a, "until"), !strcmp(a, "unless") || !strcmp(a, "until"), Token::parseVal(b, false, true, true), Token::parseCmp(c), Token::parseVal(d, true, true, true));
        } else if (sscanf(line, " %[^( ] ( %[^) ] ) goto%*[ ] %[a-z_] %n", a, b, c, &n) >= 3 && (!strcmp(a, "if") || !strcmp(a, "unless"))) {
            Inst* inst = new InstGoto(Token::parseVal(c, false, true, false));
            rv = new InstIf(strcmp(a, "if") != 0, Token::parseVal(b, false, true, true), inst);
        } else if (sscanf(line, " %[^( ] ( %[^) ] ) %[][a-z0-9_] := %[][a-z0-9_] %n", a, b, c, d, &n) >= 4 && (!strcmp(a, "if") || !strcmp(a, "unless"))) {
            Inst* inst = new InstOp(Token::parseVal(c, false, true, true), Token::parseOp(':'), Token::parseVal(d, true, true, true));
            rv = new InstIf(strcmp(a, "if") != 0, Token::parseVal(b, false, true, true), inst);
        } else if (sscanf(line, " %[^( ] ( %[^) ] ) %c %n", a, b, &p, &n) >= 3 && p == '{' && (!strcmp(a, "if") || !strcmp(a, "unless") || !strcmp(a, "while") || !strcmp(a, "until"))) {
            rv = new InstIf(!strcmp(a, "while") || !strcmp(a, "until"), !strcmp(a, "unless") || !strcmp(a, "until"), Token::parseVal(b, false, true, true));
        } else if (sscanf(line, " %[up0-9]%*[ ] %[^( ] ( ) %c %n", a, b, &p, &n) >= 3 && p == '{') {
            InstDecl* inst = new InstDecl(Token::parseType(a), Token::parseVal(b, false, true, false));
            rv = new InstFunc(inst);
        } else if (sscanf(line, " %[up0-9]%*[ ] %[^( ] ( %[a-z0-9_, ] ) %c %n", a, b, c, &p, &n) >= 4 && p == '{') {
            InstDecl* inst = new InstDecl(Token::parseType(a), Token::parseVal(b, false, true, false));
            std::vector<const InstDecl*> args;
            try {
                int nn;
                bool done = false;
                const char* cc = c;
                while (strlen(cc)) {
                    if (done) throw __LINE__;
                    if (sscanf(cc, " %[up0-9]%*[ ] %[a-z_] %c %n", d, e, &p, &nn) >= 3 && p == ',') {
                        args.push_back(new InstDecl(Token::parseType(d), Token::parseVal(e, false, true, false)));
                    } else if (sscanf(cc, " %[up0-9]%*[ ] %[a-z_] %n", d, e, &nn) >= 2) {
                        args.push_back(new InstDecl(Token::parseType(d), Token::parseVal(e, false, true, false)));
                        done = true;
                    } else {
                        throw __LINE__;
                    }
                    assert(nn);
                    cc += nn;
                }
                if (!done) throw __LINE__;
            } catch (...) {
                delete inst;
                for (std::vector<const InstDecl*>::iterator it = args.begin(); it != args.end(); ++it) delete *it;
                throw __LINE__;
            }
            rv = new InstFunc(inst, args);
        } else if (sscanf(line, " repeat ( %[a-z_] , %[0-9] , %[0-9] ) %c %n", a, b, c, &p, &n) >=4 && p == '{') {
            rv = new InstLoop(Token::parseVal(a, false, true, false), Token::parseVal(b, true, false, false), Token::parseVal(c, true, false, false));
        } else if (sscanf(line, " %[][a-z0-9_] %c= %[][a-z0-9_] %n", a, &p, b, &n) >= 3) {
            rv = new InstOp(Token::parseVal(a, false, true, true), Token::parseOp(p), Token::parseVal(b, true, true, true));
        } else if (sscanf(line, " %[a-z_]%c %n", a, &p, &n) >= 2 && p == ':') {
            rv = new InstLbl(Token::parseVal(a, false, true, false));
        } else if (sscanf(line, " %c %n", &p, &n) >= 1 && p == '}') {
            rv = new InstEnd();
        } else if (sscanf(line, " %[up0-9]%*[ ] %[a-z_] %n", a, b, &n) >= 2) {
            rv = new InstDecl(Token::parseType(a), Token::parseVal(b, false, true, false));
        } else if (sscanf(line, " %[s0-9]%*[ ] %[a-z_] %[^\n] %n", a, b, c, &n) >= 3) {
            rv = new InstStr(Token::parseType(a), Token::parseVal(b, false, true, false), c);
        } else { // trim in case of unknown line, empty line, or comment
            (void)sscanf(line, " %n", &n);
        }
    } catch (int lnum) {
        assert(rv == NULL);
        n = 0;
    }

    line += n;

    if (*line == '\0') {
        if (!rv) {
            rv = new InstNoop();
        }
    } else if (*line == '#') {
        if (!rv) {
            rv = new InstNoop();
        }
        if (line[1] != '#') { // ignore "double" comments
            rv->comment(line+1);
        }
        line += strlen(line);
    } else {
        if (rv) {
            delete rv;
            rv = NULL;
        }
    }

    return rv;
}


InstEnd::InstEnd(): Inst(END) {
}


InstElse::InstElse(): Inst(ELSE) {
}


InstNoop::InstNoop(): Inst(NOOP) {
}


InstAsm::InstAsm(const char* a): Inst(ASM), asmstr(strdup(a?:"")) {
    unless (asmstr && *asmstr) { cln(); throw __LINE__; }
}
InstAsm::~InstAsm() {
    cln();
}
void InstAsm::cln() {
    if (asmstr) free((void*)asmstr);
}


InstDecl::InstDecl(Token* t, Token* name): Inst(DECL), type(t), id(name) { //, init(NULL) {
    unless (type && (type->is(Token::TOK_TYPE_UINT) || type->is(Token::TOK_TYPE_PTR))) { cln(); throw __LINE__; }
    unless (id && id->vartype() == Token::TOK_VAR_ID) { cln(); throw __LINE__; }
}
InstDecl::~InstDecl() {
    cln();
}
void InstDecl::cln() {
    if (type) delete type;
    if (id) delete id;
}


InstExt::InstExt(Token* name): Inst(EXT), id(name) {
    unless (id && id->vartype() == Token::TOK_VAR_ID) { cln(); throw __LINE__; }
}
InstExt::~InstExt() {
    cln();
}
void InstExt::cln() {
    if (id) delete id;
}


InstStr::InstStr(Token* t, Token* name, const char* s): Inst(STR), type(t), id(name), str(strdup(s?:"")) {
    unless (type && type->is(Token::TOK_TYPE_PTR)) { cln(); throw __LINE__; }
    unless (id && id->vartype() == Token::TOK_VAR_ID) { cln(); throw __LINE__; }
    unless (str && *str) { cln(); throw __LINE__; }
}
InstStr::~InstStr() {
    cln();
}
void InstStr::cln() {
    if (type) delete type;
    if (id) delete id;
    if (str) free((void*)str);
}


InstUndef::InstUndef(Token* name): Inst(UNDEF), id(name) {
    unless (id && id->vartype() == Token::TOK_VAR_ID) { cln(); throw __LINE__; }
}
InstUndef::~InstUndef() {
    cln();
}
void InstUndef::cln() {
    if (id) delete id;
}


InstLbl::InstLbl(Token* name): Inst(LBL), id(name) {
    unless (id && id->vartype() == Token::TOK_VAR_ID) { cln(); throw __LINE__; }
}
InstLbl::~InstLbl() {
    cln();
}
void InstLbl::cln() {
    if (id) delete id;
}


InstGoto::InstGoto(Token* name): Inst(GOTO), id(name) {
    unless (id && id->vartype() == Token::TOK_VAR_ID) { cln(); throw __LINE__; }
}
InstGoto::~InstGoto() {
    cln();
}
void InstGoto::cln() {
    if (id) delete id;
}


InstOp::InstOp(Token* a, Token* o, Token* b): Inst(OP), a(a), op(o), b(b) {
    unless (a && a->is(Token::TOK_IDENTIFIER)) { cln(); throw __LINE__; }
    unless (op && op->is(Token::TOK_OP)) { cln(); throw __LINE__; }
    unless (b && b->vartype() != Token::TOK_VAR_NONE) { cln(); throw __LINE__; }
}
InstOp::~InstOp() {
    cln();
}
void InstOp::cln() {
    if (a) delete a;
    if (op) delete op;
    if (b) delete b;
}


InstLoop::InstLoop(Token* i, Token* f, Token* t): Inst(LOOP), id(i), from(f), to(t) {
    unless (id && id->vartype() == Token::TOK_VAR_ID) { cln(); throw __LINE__; }
    unless (from && from->vartype() == Token::TOK_VAR_IMM) { cln(); throw __LINE__; }
    unless (to && to->vartype() == Token::TOK_VAR_IMM) { cln(); throw __LINE__; }
    unless (from->val_i < to->val_i) { cln(); throw __LINE__; }
}
InstLoop::~InstLoop() {
    cln();
}
void InstLoop::cln() {
    if (id) delete id;
    if (from) delete from;
    if (to) delete to;
}


InstIf::InstIf(bool rep, bool neg, Token* a): Inst(IF), repeat(rep), negate(neg), a(a), cmp(NULL), b(NULL), op(NULL) {
    unless (a && a->is(Token::TOK_IDENTIFIER)) { cln(); throw __LINE__; }
}
InstIf::InstIf(bool neg, Token* a, Inst* o): Inst(IF), repeat(false), negate(neg), a(a), cmp(NULL), b(NULL), op(o) {
    unless (a && a->is(Token::TOK_IDENTIFIER)) { cln(); throw __LINE__; }
    unless (!op || op->is(GOTO) || (op->is(OP) && ((InstOp*)op)->op->val_op == Token::TOK_OP_ASSIGN)) { cln(); throw __LINE__; }
}
InstIf::InstIf(bool rep, bool neg, Token* a, Token* c, Token* b): Inst(IF), repeat(rep), negate(neg), a(a), cmp(c), b(b), op(NULL) {
    unless (a && a->is(Token::TOK_IDENTIFIER)) { cln(); throw __LINE__; }
    unless ((!cmp && !b) || (cmp && cmp->is(Token::TOK_CMP) && b && b->vartype() != Token::TOK_VAR_NONE)) { cln(); throw __LINE__; }
}
InstIf::InstIf(bool neg, Token* a, Token* c, Token* b, Inst* o): Inst(IF), repeat(false), negate(neg), a(a), cmp(c), b(b), op(o) {
    unless (a && a->is(Token::TOK_IDENTIFIER)) { cln(); throw __LINE__; }
    unless ((!cmp && !b) || (cmp && cmp->is(Token::TOK_CMP) && b && b->vartype() != Token::TOK_VAR_NONE)) { cln(); throw __LINE__; }
    unless (!op || op->is(GOTO) || (op->is(OP) && ((InstOp*)op)->op->val_op == Token::TOK_OP_ASSIGN)) { cln(); throw __LINE__; }
}
InstIf::~InstIf() {
    cln();
}
void InstIf::cln() {
    if (a) delete a;
    if (cmp) delete cmp;
    if (b) delete b;
    if (op) delete op;
}


InstFunc::InstFunc(InstDecl* d): Inst(FUNC), decl(d) {
    unless (d) { cln(); throw __LINE__; }
}
InstFunc::InstFunc(InstDecl* d, const std::vector<const InstDecl*>& a): Inst(FUNC), decl(d), args(a) {
    unless (d) { cln(); throw __LINE__; }
    unless (args.size()) { cln(); throw __LINE__; }
    for (std::vector<const InstDecl*>::const_iterator it = args.begin(); it != args.end(); ++it) {
        unless (*it) { cln(); throw __LINE__; }
    }
}
InstFunc::~InstFunc() {
    cln();
}
void InstFunc::cln() {
    if (decl) delete decl;
    for (std::vector<const InstDecl*>::const_iterator it = args.begin(); it != args.end(); ++it) {
        if (*it) delete *it;
    }
}