/****************************************************************************
*                                                                           *
* Project64 - A Nintendo 64 emulator.                                       *
* http://www.pj64-emu.com/                                                  *
* Copyright (C) 2012 Project64. All rights reserved.                        *
*                                                                           *
* License:                                                                  *
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html                        *
*                                                                           *
****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "stdafx.h"

#include "Assembler.h"
#include "Project64-core\N64System\Mips\OpCode.h"

ASM_PARSE_ERROR CAssembler::m_ParseError = ERR_NONE;
uint32_t CAssembler::m_Address = 0;

char* CAssembler::m_TokContext = NULL;

const ASM_SYNTAX_FN CAssembler::syn_jump[] = { arg_jump, NULL };
const ASM_SYNTAX_FN CAssembler::syn_loadstore[] = { arg_reg_t, arg_imm16, arg_reg_s, NULL };
const ASM_SYNTAX_FN CAssembler::syn_arith[] = { arg_reg_d, arg_reg_s, arg_reg_t, NULL };
const ASM_SYNTAX_FN CAssembler::syn_arith2[] = { arg_reg_s, arg_reg_t, NULL };
const ASM_SYNTAX_FN CAssembler::syn_shiftv[] = { arg_reg_d, arg_reg_t, arg_reg_s, NULL };
const ASM_SYNTAX_FN CAssembler::syn_arith_i[] = { arg_reg_t, arg_reg_s, arg_imm16, NULL };
const ASM_SYNTAX_FN CAssembler::syn_load_i[] = { arg_reg_t, arg_imm16, NULL };
const ASM_SYNTAX_FN CAssembler::syn_branch_z[] = { arg_reg_s, arg_bra_target, NULL };
const ASM_SYNTAX_FN CAssembler::syn_branch[] = { arg_reg_s, arg_reg_t, arg_bra_target, NULL };
const ASM_SYNTAX_FN CAssembler::syn_branch_unc[] = { arg_bra_target, NULL };
const ASM_SYNTAX_FN CAssembler::syn_trap_i[] = { arg_reg_s, arg_imm16, NULL };
const ASM_SYNTAX_FN CAssembler::syn_shift[] = { arg_reg_d, arg_reg_t, arg_shamt, NULL };
const ASM_SYNTAX_FN CAssembler::syn_mf[] = { arg_reg_d, NULL };
const ASM_SYNTAX_FN CAssembler::syn_jr[] = { arg_reg_s, NULL };
const ASM_SYNTAX_FN CAssembler::syn_jalr[] = { arg_reg_d, arg_reg_s, NULL };
const ASM_SYNTAX_FN CAssembler::syn_cop1_arith[] = { arg_reg_fd, arg_reg_fs, arg_reg_ft, NULL };
const ASM_SYNTAX_FN CAssembler::syn_cop1[] = { arg_reg_fd, arg_reg_fs, NULL };
const ASM_SYNTAX_FN CAssembler::syn_cop1_cmp[] = { arg_reg_fs, arg_reg_ft, NULL };
const ASM_SYNTAX_FN CAssembler::syn_cop_mv[] = { arg_reg_t, arg_reg_d, NULL };
const ASM_SYNTAX_FN CAssembler::syn_cache[] = { arg_cache_op, arg_imm16, arg_reg_s, NULL };
const ASM_SYNTAX_FN CAssembler::syn_syscall[] = { arg_syscall_code, NULL };

const ASM_INSTRUCTION CAssembler::m_Instructions[] =
{
    { "j",      R4300i_J,      base_op, syn_jump },
    { "jal",    R4300i_JAL,    base_op, syn_jump },
    { "beq",    R4300i_BEQ,    base_op, syn_branch },
    { "beqz",   R4300i_BEQ,    base_op, syn_branch_z },
    { "b",      R4300i_BEQ,    base_op, syn_branch_unc },
    { "bne",    R4300i_BNE,    base_op, syn_branch },
    { "bnez",   R4300i_BNE,    base_op, syn_branch_z },
    { "blez",   R4300i_BLEZ,   base_op, syn_branch_z },
    { "bgtz",   R4300i_BGTZ,   base_op, syn_branch_z },
    { "addi",   R4300i_ADDI,   base_op, syn_arith_i },
    { "addiu",  R4300i_ADDIU,  base_op, syn_arith_i },
    { "slti",   R4300i_SLTI,   base_op, syn_arith_i },
    { "sltiu",  R4300i_SLTIU,  base_op, syn_arith_i },
    { "andi",   R4300i_ANDI,   base_op, syn_arith_i },
    { "ori",    R4300i_ORI,    base_op, syn_arith_i },
    { "xori",   R4300i_XORI,   base_op, syn_arith_i },
    { "lui",    R4300i_LUI,    base_op, syn_load_i },
    { "beql",   R4300i_BEQL,   base_op, syn_branch },
    { "beqzl",  R4300i_BEQL,   base_op, syn_branch_z },
    { "bnel",   R4300i_BNEL,   base_op, syn_branch },
    { "bnezl",  R4300i_BNEL,   base_op, syn_branch_z },
    { "blezl",  R4300i_BLEZL,  base_op, syn_branch_z },
    { "bgtzl",  R4300i_BGTZL,  base_op, syn_branch_z },
    { "daddi",  R4300i_DADDI,  base_op, syn_arith_i },
    { "daddiu", R4300i_DADDIU, base_op, syn_arith_i },
    { "ldl",    R4300i_LDL,    base_op, syn_loadstore },
    { "ldr",    R4300i_LDR,    base_op, syn_loadstore },
    { "lb",     R4300i_LB,     base_op, syn_loadstore },
    { "lh",     R4300i_LH,     base_op, syn_loadstore },
    { "lwl",    R4300i_LWL,    base_op, syn_loadstore },
    { "lw",     R4300i_LW,     base_op, syn_loadstore },
    { "lbu",    R4300i_LBU,    base_op, syn_loadstore },
    { "lhu",    R4300i_LHU,    base_op, syn_loadstore },
    { "lwr",    R4300i_LWR,    base_op, syn_loadstore },
    { "lwu",    R4300i_LWU,    base_op, syn_loadstore },
    { "sb",     R4300i_SB,     base_op, syn_loadstore },
    { "sh",     R4300i_SH,     base_op, syn_loadstore },
    { "swl",    R4300i_SWL,    base_op, syn_loadstore },
    { "sw",     R4300i_SW,     base_op, syn_loadstore },
    { "sdl",    R4300i_SDL,    base_op, syn_loadstore },
    { "sdr",    R4300i_SDR,    base_op, syn_loadstore },
    { "swr",    R4300i_SWR,    base_op, syn_loadstore },
    { "cache",  R4300i_CACHE,  base_op, syn_cache },
    { "ll",     R4300i_LL,     base_op, syn_loadstore },
    { "lwc1",   R4300i_LWC1,   base_op, syn_loadstore },
    { "ldc1",   R4300i_LDC1,   base_op, syn_loadstore },
    { "ld",     R4300i_LD,     base_op, syn_loadstore },
    { "sc",     R4300i_SC,     base_op, syn_loadstore },
    { "swc1",   R4300i_SWC1,   base_op, syn_loadstore },
    { "sdc1",   R4300i_SDC1,   base_op, syn_loadstore },
    { "sdc2",   R4300i_SDC2,   base_op, syn_loadstore },
    { "sd",     R4300i_SD,     base_op, syn_loadstore },

    { "sll",     R4300i_SPECIAL_SLL,     base_spec, syn_shift },
    { "nop",     R4300i_SPECIAL_SLL,     base_spec, NULL },
    { "srl",     R4300i_SPECIAL_SRL,     base_spec, syn_shift },
    { "sra",     R4300i_SPECIAL_SRA,     base_spec, syn_shift },
    { "sllv",    R4300i_SPECIAL_SLLV,    base_spec, syn_shiftv },
    { "srlv",    R4300i_SPECIAL_SRLV,    base_spec, syn_shiftv },
    { "srav",    R4300i_SPECIAL_SRAV,    base_spec, syn_shiftv },
    { "jr",      R4300i_SPECIAL_JR,      base_spec, syn_jr },
    { "jalr",    R4300i_SPECIAL_JALR,    base_spec, syn_jalr },
    { "jalr",    R4300i_SPECIAL_JALR,    base_spec_jalr_ra, syn_jr },
    { "syscall", R4300i_SPECIAL_SYSCALL, base_spec, syn_syscall },
    { "break",   R4300i_SPECIAL_BREAK,   base_spec, syn_syscall },
    { "sync",    R4300i_SPECIAL_SYNC,    base_spec, NULL },
    { "mfhi",    R4300i_SPECIAL_MFHI,    base_spec, syn_mf },
    { "mthi",    R4300i_SPECIAL_MTHI,    base_spec, syn_mf },
    { "mflo",    R4300i_SPECIAL_MFLO,    base_spec, syn_mf },
    { "mtlo",    R4300i_SPECIAL_MTLO,    base_spec, syn_mf },
    { "dsllv",   R4300i_SPECIAL_DSLLV,   base_spec, syn_shiftv },
    { "dsrlv",   R4300i_SPECIAL_DSRLV,   base_spec, syn_shiftv },
    { "dsrav",   R4300i_SPECIAL_DSRAV,   base_spec, syn_shiftv },
    { "mult",    R4300i_SPECIAL_MULT,    base_spec, syn_arith2 },
    { "multu",   R4300i_SPECIAL_MULTU,   base_spec, syn_arith2 },
    { "div",     R4300i_SPECIAL_DIV,     base_spec, syn_arith2 },
    { "divu",    R4300i_SPECIAL_DIVU,    base_spec, syn_arith2 },
    { "dmult",   R4300i_SPECIAL_DMULT,   base_spec, syn_arith2 },
    { "dmultu",  R4300i_SPECIAL_DMULTU,  base_spec, syn_arith2 },
    { "ddiv",    R4300i_SPECIAL_DDIV,    base_spec, syn_arith2 },
    { "ddivu",   R4300i_SPECIAL_DDIVU,   base_spec, syn_arith2 },
    { "add",     R4300i_SPECIAL_ADD,     base_spec, syn_arith },
    { "addu",    R4300i_SPECIAL_ADDU,    base_spec, syn_arith },
    { "sub",     R4300i_SPECIAL_SUB,     base_spec, syn_arith },
    { "subu",    R4300i_SPECIAL_SUBU,    base_spec, syn_arith },
    { "and",     R4300i_SPECIAL_AND,     base_spec, syn_arith },
    { "or",      R4300i_SPECIAL_OR,      base_spec, syn_arith },
    { "xor",     R4300i_SPECIAL_XOR,     base_spec, syn_arith },
    { "nor",     R4300i_SPECIAL_NOR,     base_spec, syn_arith },
    { "slt",     R4300i_SPECIAL_SLT,     base_spec, syn_arith },
    { "sltu",    R4300i_SPECIAL_SLTU,    base_spec, syn_arith },
    { "dadd",    R4300i_SPECIAL_DADD,    base_spec, syn_arith },
    { "daddu",   R4300i_SPECIAL_DADDU,   base_spec, syn_arith },
    { "dsub",    R4300i_SPECIAL_DSUB,    base_spec, syn_arith },
    { "dsubu",   R4300i_SPECIAL_DSUBU,   base_spec, syn_arith },
    { "tge",     R4300i_SPECIAL_TGE,     base_spec, syn_arith2 }, // note: no code field
    { "tgeu",    R4300i_SPECIAL_TGEU,    base_spec, syn_arith2 }, //
    { "tlt",     R4300i_SPECIAL_TLT,     base_spec, syn_arith2 }, //
    { "tltu",    R4300i_SPECIAL_TLTU,    base_spec, syn_arith2 }, //
    { "teq",     R4300i_SPECIAL_TEQ,     base_spec, syn_arith2 }, //
    { "tne",     R4300i_SPECIAL_TNE,     base_spec, syn_arith2 }, //
    { "dsll",    R4300i_SPECIAL_DSLL,    base_spec, syn_shift },
    { "dsrl",    R4300i_SPECIAL_DSRL,    base_spec, syn_shift },
    { "dsra",    R4300i_SPECIAL_DSRA,    base_spec, syn_shift },
    { "dsll32",  R4300i_SPECIAL_DSLL32,  base_spec, syn_shift },
    { "dsrl32",  R4300i_SPECIAL_DSRL32,  base_spec, syn_shift },
    { "dsra32",  R4300i_SPECIAL_DSRA32,  base_spec, syn_shift },

    { "bltz",    R4300i_REGIMM_BLTZ,    base_regimm, syn_branch_z },
    { "bgez",    R4300i_REGIMM_BGEZ,    base_regimm, syn_branch_z },
    { "bltzl",   R4300i_REGIMM_BLTZL,   base_regimm, syn_branch_z },
    { "bgezl",   R4300i_REGIMM_BGEZL,   base_regimm, syn_branch_z },
    { "tgei",    R4300i_REGIMM_TGEI,    base_regimm, syn_trap_i },
    { "tgeiu",   R4300i_REGIMM_TGEIU,   base_regimm, syn_trap_i },
    { "tlti",    R4300i_REGIMM_TLTI,    base_regimm, syn_trap_i },
    { "tltiu",   R4300i_REGIMM_TLTIU,   base_regimm, syn_trap_i },
    { "teqi",    R4300i_REGIMM_TEQI,    base_regimm, syn_trap_i },
    { "tnei",    R4300i_REGIMM_TNEI,    base_regimm, syn_trap_i },
    { "bltzal",  R4300i_REGIMM_BLTZAL,  base_regimm, syn_branch_z },
    { "bgezal",  R4300i_REGIMM_BGEZAL,  base_regimm, syn_branch_z },
    { "bal",     R4300i_REGIMM_BGEZAL,  base_regimm, syn_branch_unc },
    { "bltzall", R4300i_REGIMM_BLTZALL, base_regimm, syn_branch_z },
    { "bgezall", R4300i_REGIMM_BGEZALL, base_regimm, syn_branch_z },

    { "mfc0", R4300i_COP0_MF , base_cop0_mv, syn_cop_mv },
    { "mtc0", R4300i_COP0_MT , base_cop0_mv, syn_cop_mv },

    { "tlbr",  R4300i_COP0_CO_TLBR,  base_cop0_co, NULL },
    { "tlbwi", R4300i_COP0_CO_TLBWI, base_cop0_co, NULL },
    { "tlbwr", R4300i_COP0_CO_TLBWR, base_cop0_co, NULL },
    { "tlbp",  R4300i_COP0_CO_TLBP,  base_cop0_co, NULL },
    { "eret",  R4300i_COP0_CO_ERET,  base_cop0_co, NULL },

    { "mfc1",  R4300i_COP1_MF,      base_cop1_mv, syn_cop_mv },
    { "dmfc1", R4300i_COP1_DMF,     base_cop1_mv, syn_cop_mv },
    { "cfc1",  R4300i_COP1_CF,      base_cop1_mv, syn_cop_mv },
    { "mtc1",  R4300i_COP1_MT,      base_cop1_mv, syn_cop_mv },
    { "dmtc1", R4300i_COP1_DMT,     base_cop1_mv, syn_cop_mv },
    { "ctc1",  R4300i_COP1_CT,      base_cop1_mv, syn_cop_mv },

    { "bc1f",  R4300i_COP1_BC_BCF,  base_cop1_bc, syn_branch_unc },
    { "bc1t",  R4300i_COP1_BC_BCT,  base_cop1_bc, syn_branch_unc },
    { "bc1fl", R4300i_COP1_BC_BCFL, base_cop1_bc, syn_branch_unc },
    { "bc1tl", R4300i_COP1_BC_BCTL, base_cop1_bc, syn_branch_unc },

    { "add.s",     R4300i_COP1_FUNCT_ADD,     base_cop1_s, syn_cop1_arith },
    { "sub.s",     R4300i_COP1_FUNCT_SUB,     base_cop1_s, syn_cop1_arith },
    { "mul.s",     R4300i_COP1_FUNCT_MUL,     base_cop1_s, syn_cop1_arith },
    { "div.s",     R4300i_COP1_FUNCT_DIV,     base_cop1_s, syn_cop1_arith },
    { "sqrt.s",    R4300i_COP1_FUNCT_SQRT,    base_cop1_s, syn_cop1 },
    { "abs.s",     R4300i_COP1_FUNCT_ABS,     base_cop1_s, syn_cop1 },
    { "mov.s",     R4300i_COP1_FUNCT_MOV,     base_cop1_s, syn_cop1 },
    { "neg.s",     R4300i_COP1_FUNCT_NEG,     base_cop1_s, syn_cop1 },
    { "round.l.s", R4300i_COP1_FUNCT_ROUND_L, base_cop1_s, syn_cop1 },
    { "trunc.l.s", R4300i_COP1_FUNCT_TRUNC_L, base_cop1_s, syn_cop1 },
    { "ceil.l.s",  R4300i_COP1_FUNCT_CEIL_L,  base_cop1_s, syn_cop1 },
    { "floor.l.s", R4300i_COP1_FUNCT_FLOOR_L, base_cop1_s, syn_cop1 },
    { "round.w.s", R4300i_COP1_FUNCT_ROUND_W, base_cop1_s, syn_cop1 },
    { "trunc.w.s", R4300i_COP1_FUNCT_TRUNC_W, base_cop1_s, syn_cop1 },
    { "ceil.w.s",  R4300i_COP1_FUNCT_CEIL_W,  base_cop1_s, syn_cop1 },
    { "floor.w.s", R4300i_COP1_FUNCT_FLOOR_W, base_cop1_s, syn_cop1 },
    { "cvt.s.s",   R4300i_COP1_FUNCT_CVT_S,   base_cop1_s, syn_cop1 },
    { "cvt.d.s",   R4300i_COP1_FUNCT_CVT_D,   base_cop1_s, syn_cop1 },
    { "cvt.w.s",   R4300i_COP1_FUNCT_CVT_W,   base_cop1_s, syn_cop1 },
    { "cvt.l.s",   R4300i_COP1_FUNCT_CVT_L,   base_cop1_s, syn_cop1 },
    { "c.f.s",     R4300i_COP1_FUNCT_C_F,     base_cop1_s, syn_cop1_cmp },
    { "c.un.s",    R4300i_COP1_FUNCT_C_UN,    base_cop1_s, syn_cop1_cmp },
    { "c.eq.s",    R4300i_COP1_FUNCT_C_EQ,    base_cop1_s, syn_cop1_cmp },
    { "c.ueq.s",   R4300i_COP1_FUNCT_C_UEQ,   base_cop1_s, syn_cop1_cmp },
    { "c.olt.s",   R4300i_COP1_FUNCT_C_OLT,   base_cop1_s, syn_cop1_cmp },
    { "c.ult.s",   R4300i_COP1_FUNCT_C_ULT,   base_cop1_s, syn_cop1_cmp },
    { "c.ole.s",   R4300i_COP1_FUNCT_C_OLE,   base_cop1_s, syn_cop1_cmp },
    { "c.ule.s",   R4300i_COP1_FUNCT_C_ULE,   base_cop1_s, syn_cop1_cmp },
    { "c.sf.s",    R4300i_COP1_FUNCT_C_SF,    base_cop1_s, syn_cop1_cmp },
    { "c.ngle.s",  R4300i_COP1_FUNCT_C_NGLE,  base_cop1_s, syn_cop1_cmp },
    { "c.seq.s",   R4300i_COP1_FUNCT_C_SEQ,   base_cop1_s, syn_cop1_cmp },
    { "c.ngl.s",   R4300i_COP1_FUNCT_C_NGL,   base_cop1_s, syn_cop1_cmp },
    { "c.lt.s",    R4300i_COP1_FUNCT_C_LT,    base_cop1_s, syn_cop1_cmp },
    { "c.nge.s",   R4300i_COP1_FUNCT_C_NGE,   base_cop1_s, syn_cop1_cmp },
    { "c.le.s",    R4300i_COP1_FUNCT_C_LE,    base_cop1_s, syn_cop1_cmp },
    { "c.ngt.s",   R4300i_COP1_FUNCT_C_NGT,   base_cop1_s, syn_cop1_cmp },

    { "add.d",     R4300i_COP1_FUNCT_ADD,     base_cop1_d, syn_cop1_arith },
    { "sub.d",     R4300i_COP1_FUNCT_SUB,     base_cop1_d, syn_cop1_arith },
    { "mul.d",     R4300i_COP1_FUNCT_MUL,     base_cop1_d, syn_cop1_arith },
    { "div.d",     R4300i_COP1_FUNCT_DIV,     base_cop1_d, syn_cop1_arith },
    { "sqrt.d",    R4300i_COP1_FUNCT_SQRT,    base_cop1_d, syn_cop1 },
    { "abs.d",     R4300i_COP1_FUNCT_ABS,     base_cop1_d, syn_cop1 },
    { "mov.d",     R4300i_COP1_FUNCT_MOV,     base_cop1_d, syn_cop1 },
    { "neg.d",     R4300i_COP1_FUNCT_NEG,     base_cop1_d, syn_cop1 },
    { "round.l.d", R4300i_COP1_FUNCT_ROUND_L, base_cop1_d, syn_cop1 },
    { "trunc.l.d", R4300i_COP1_FUNCT_TRUNC_L, base_cop1_d, syn_cop1 },
    { "ceil.l.d",  R4300i_COP1_FUNCT_CEIL_L,  base_cop1_d, syn_cop1 },
    { "floor.l.d", R4300i_COP1_FUNCT_FLOOR_L, base_cop1_d, syn_cop1 },
    { "round.w.d", R4300i_COP1_FUNCT_ROUND_W, base_cop1_d, syn_cop1 },
    { "trunc.w.d", R4300i_COP1_FUNCT_TRUNC_W, base_cop1_d, syn_cop1 },
    { "ceil.w.d",  R4300i_COP1_FUNCT_CEIL_W,  base_cop1_d, syn_cop1 },
    { "floor.w.d", R4300i_COP1_FUNCT_FLOOR_W, base_cop1_d, syn_cop1 },
    { "cvt.s.d",   R4300i_COP1_FUNCT_CVT_S,   base_cop1_d, syn_cop1 },
    { "cvt.d.d",   R4300i_COP1_FUNCT_CVT_D,   base_cop1_d, syn_cop1 },
    { "cvt.w.d",   R4300i_COP1_FUNCT_CVT_W,   base_cop1_d, syn_cop1 },
    { "cvt.l.d",   R4300i_COP1_FUNCT_CVT_L,   base_cop1_d, syn_cop1 },
    { "c.f.d",     R4300i_COP1_FUNCT_C_F,     base_cop1_d, syn_cop1_cmp },
    { "c.un.d",    R4300i_COP1_FUNCT_C_UN,    base_cop1_d, syn_cop1_cmp },
    { "c.eq.d",    R4300i_COP1_FUNCT_C_EQ,    base_cop1_d, syn_cop1_cmp },
    { "c.ueq.d",   R4300i_COP1_FUNCT_C_UEQ,   base_cop1_d, syn_cop1_cmp },
    { "c.olt.d",   R4300i_COP1_FUNCT_C_OLT,   base_cop1_d, syn_cop1_cmp },
    { "c.ult.d",   R4300i_COP1_FUNCT_C_ULT,   base_cop1_d, syn_cop1_cmp },
    { "c.ole.d",   R4300i_COP1_FUNCT_C_OLE,   base_cop1_d, syn_cop1_cmp },
    { "c.ule.d",   R4300i_COP1_FUNCT_C_ULE,   base_cop1_d, syn_cop1_cmp },
    { "c.sf.d",    R4300i_COP1_FUNCT_C_SF,    base_cop1_d, syn_cop1_cmp },
    { "c.ngle.d",  R4300i_COP1_FUNCT_C_NGLE,  base_cop1_d, syn_cop1_cmp },
    { "c.seq.d",   R4300i_COP1_FUNCT_C_SEQ,   base_cop1_d, syn_cop1_cmp },
    { "c.ngl.d",   R4300i_COP1_FUNCT_C_NGL,   base_cop1_d, syn_cop1_cmp },
    { "c.lt.d",    R4300i_COP1_FUNCT_C_LT,    base_cop1_d, syn_cop1_cmp },
    { "c.nge.d",   R4300i_COP1_FUNCT_C_NGE,   base_cop1_d, syn_cop1_cmp },
    { "c.le.d",    R4300i_COP1_FUNCT_C_LE,    base_cop1_d, syn_cop1_cmp },
    { "c.ngt.d",   R4300i_COP1_FUNCT_C_NGT,   base_cop1_d, syn_cop1_cmp },

    { "cvt.s.w",   R4300i_COP1_FUNCT_CVT_S,   base_cop1_w, syn_cop1 },
    { "cvt.d.w",   R4300i_COP1_FUNCT_CVT_D,   base_cop1_w, syn_cop1 },
    { "cvt.s.l",   R4300i_COP1_FUNCT_CVT_S,   base_cop1_l, syn_cop1 },
    { "cvt.d.l",   R4300i_COP1_FUNCT_CVT_D,   base_cop1_l, syn_cop1 },
    { NULL }
};

const ASM_REGISTER CAssembler::m_Registers[] =
{
    { "r0",  0 },{ "at",  1 },{ "v0",  2 },{ "v1",  3 },{ "a0",  4 },{ "a1",  5 },{ "a2",  6 },{ "a3",  7 },
    { "t0",  8 },{ "t1",  9 },{ "t2", 10 },{ "t3", 11 },{ "t4", 12 },{ "t5", 13 },{ "t6", 14 },{ "t7", 15 },
    { "s0", 16 },{ "s1", 17 },{ "s2", 18 },{ "s3", 19 },{ "s4", 20 },{ "s5", 21 },{ "s6", 22 },{ "s7", 23 },
    { "t8", 24 },{ "t9", 25 },{ "k0", 26 },{ "k1", 27 },{ "gp", 28 },{ "sp", 29 },{ "s8", 30 },{ "ra", 31 },

    { "fp", 30 },

    { "r0",   0 },{ "r1",   1 },{ "r2",   2 },{ "r3",   3 },{ "r4",   4 },{ "r5",   5 },{ "r6",   6 },{ "r7",   7 },
    { "r8",   8 },{ "r9",   9 },{ "r10", 10 },{ "r11", 11 },{ "r12", 12 },{ "r13", 13 },{ "r14", 14 },{ "r15", 15 },
    { "r16", 16 },{ "r17", 17 },{ "r18", 18 },{ "r19", 19 },{ "r20", 20 },{ "r21", 21 },{ "r22", 22 },{ "r23", 23 },
    { "r24", 24 },{ "r25", 25 },{ "r26", 26 },{ "r27", 27 },{ "r28", 28 },{ "r29", 29 },{ "r30", 30 },{ "r31", 31 },

    { "f0",   0 },{ "f1",   1 },{ "f2",   2 },{ "f3",   3 },{ "f4",   4 },{ "f5",   5 },{ "f6",   6 },{ "f7",   7 },
    { "f8",   8 },{ "f9",   9 },{ "f10", 10 },{ "f11", 11 },{ "f12", 12 },{ "f13", 13 },{ "f14", 14 },{ "f15", 15 },
    { "f16", 16 },{ "f17", 17 },{ "f18", 18 },{ "f19", 19 },{ "f20", 20 },{ "f21", 21 },{ "f22", 22 },{ "f23", 23 },
    { "f24", 24 },{ "f25", 25 },{ "f26", 26 },{ "f27", 27 },{ "f28", 28 },{ "f29", 29 },{ "f30", 30 },{ "f31", 31 },

    { "revision", 0 },{ "fcsr", 31 },{ "fcs", 31 },

    { "index",    0 },{ "random",    1 },{ "entrylo0", 2 },{ "entrylo1",  3 },{ "context",  4 },{ "pagemask", 5 },
    { "wired",    6 },{ "badvaddr",  8 },{ "count",    9 },{ "entryhi",  10 },{ "compare", 11 },{ "status",  12 },
    { "cause",   13 },{ "epc",      14 },{ "prid",    15 },{ "config",   16 },{ "lladdr",  17 },{ "watchlo", 18 },
    { "watchhi", 19 },{ "xcontext", 20 },{ "ecc",     26 },{ "cacheerr", 27 },{ "taglo",   28 },{ "taghi",   29 },
    { "errepc",  30 },

    { NULL }
};

bool CAssembler::AssembleLine(const char* line, uint32_t* opcode, uint32_t address)
{
    m_ParseError = ERR_NONE;
    m_Address = address;

    char line_c[128];
    strncpy(line_c, line, 128);
    StrToLower(line_c);

    if (line_c[0] == '\0')
    {
        *opcode = 0;
        return true;
    }

    char* name = strtok_s(line_c, " \t", &m_TokContext);
    
    // attempt to assemble the line
    // if a syntax error occurs, check if the command has alternative syntax forms and retry

    for(int nFallback = 0;; nFallback++)
    {
        const ASM_INSTRUCTION* instruction = LookupInstruction(name, nFallback);

        if (instruction == NULL)
        {
            m_ParseError = ERR_UNKNOWN_CMD;
            return false;
        }
        
        m_ParseError = ERR_NONE;

        if (nFallback > 0)
        {
            // prepare for re-tokenization
            strncpy(line_c, line, 128);
            StrToLower(line_c);
            name = strtok_s(line_c, " \t", &m_TokContext);
        }

        *opcode = instruction->base(instruction->val);

        if (instruction->syntax == NULL)
        {
            // No parameters
            return true;
        }

        for (int i = 0; instruction->syntax[i]; i++)
        {
            instruction->syntax[i](opcode);

            if (m_ParseError != ERR_NONE)
            {
                goto next_fallback;
            }
        }

        // assembled without errors
        return true;

    next_fallback:;
    }
}

const ASM_INSTRUCTION* CAssembler::LookupInstruction(char* name, int nFallback)
{
    for (int i = 0; m_Instructions[i].name != NULL; i++)
    {
        if (strcmp(name, m_Instructions[i].name) == 0)
        {
            if (nFallback != 0)
            {
                nFallback--;
                continue;
            }
            return &m_Instructions[i];
        }
    }
    return NULL;
}

const ASM_REGISTER* CAssembler::LookupRegister(char* name)
{
    for (int i = 0; m_Registers[i].name != NULL; i++)
    {
        if (strcmp(name, m_Registers[i].name) == 0)
        {
            return &m_Registers[i];
        }
    }
    return NULL;
}

void CAssembler::StrToLower(char* str)
{
    while (*str)
    {
        if (*str >= 'A' && *str <= 'Z')
        {
            *str |= 0x20;
        }
        str++;
    }
}

uint32_t CAssembler::pop_reg()
{
    char* r = strtok_s(NULL, " \t,()", &m_TokContext);

    if (r == NULL)
    {
        m_ParseError = ERR_EXPECTED_REG;
        return 0;
    }

    const ASM_REGISTER* reg = LookupRegister(r);

    if (reg == NULL)
    {
        m_ParseError = ERR_INVALID_REG;
        return 0;
    }

    return reg->val;
}

uint32_t CAssembler::pop_val()
{
    char* v = strtok_s(NULL, " \t,()", &m_TokContext);

    if (v == NULL)
    {
        m_ParseError = ERR_EXPECTED_VAL;
        return 0;
    }

    //if (isalpha(*v))
    //{
    //    // todo lookup label value
    //    return 0;
    //}

    int base = 0; // hex or dec

    if (*v == '$')
    {
        base = 16; // hex
        v++;
    }

    char* endptr;

    uint32_t val = strtoul(v, &endptr, base);

    if (*endptr != '\0')
    {
        m_ParseError = ERR_EXPECTED_VAL;
        return 0;
    }

    return val;
}

uint32_t CAssembler::base_op(uint32_t val)
{
    return val << 26;
}

uint32_t CAssembler::base_spec(uint32_t val)
{
    return val;
}

uint32_t CAssembler::base_spec_jalr_ra(uint32_t val)
{
    return (31 << 11) | val;
}

uint32_t CAssembler::base_regimm(uint32_t val)
{
    return (R4300i_REGIMM << 26) | (val << 16);
}

uint32_t CAssembler::base_cop0_co(uint32_t val)
{
    return (R4300i_CP0 << 26) | (1 << 25) | val;
}

uint32_t CAssembler::base_cop0_mv(uint32_t val)
{
    return (R4300i_CP0 << 26) | (val << 21);
}

uint32_t CAssembler::base_cop1_mv(uint32_t val)
{
    return (R4300i_CP1 << 26) | (val << 21);
}

uint32_t CAssembler::base_cop1_s(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_S << 21) | val;
}

uint32_t CAssembler::base_cop1_d(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_D << 21) | val;
}

uint32_t CAssembler::base_cop1_w(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_W << 21) | val;
}

uint32_t CAssembler::base_cop1_l(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_L << 21) | val;
}

uint32_t CAssembler::base_cop1_bc(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_BC << 21) | (val << 16);
}

void CAssembler::arg_reg_t(uint32_t* opcode)
{
    *opcode |= pop_reg() << 16;
}

void CAssembler::arg_reg_s(uint32_t* opcode)
{
    *opcode |= pop_reg() << 21;
}

void CAssembler::arg_reg_d(uint32_t* opcode)
{
    *opcode |= pop_reg() << 11;
}

void CAssembler::arg_reg_ft(uint32_t* opcode)
{
    *opcode |= pop_reg() << 16;
}

void CAssembler::arg_reg_fs(uint32_t* opcode)
{
    *opcode |= pop_reg() << 11;
}

void CAssembler::arg_reg_fd(uint32_t* opcode)
{
    *opcode |= pop_reg() << 6;
}

void CAssembler::arg_jump(uint32_t* opcode)
{
    *opcode |= (pop_val() / 4) & 0x3FFFFFF;
}

void CAssembler::arg_imm16(uint32_t* opcode)
{
    *opcode |= (pop_val() & 0xFFFF);
}

void CAssembler::arg_bra_target(uint32_t* opcode)
{
    uint16_t relTarget = (((pop_val() - m_Address) / 4) & 0xFFFF) - 1;
    *opcode |= relTarget;
}

void CAssembler::arg_shamt(uint32_t* opcode)
{
    *opcode |= (pop_val() & 0x1F) << 6;
}

void CAssembler::arg_cache_op(uint32_t* opcode)
{
    *opcode |= (pop_val() & 0x1F) << 16;
}

void CAssembler::arg_syscall_code(uint32_t* opcode)
{
    *opcode |= (pop_val() & 0xFFFFF) << 6;
}
