/**
 *  $Id: disasm.c 430 2006-02-16 10:58:39Z ko1 $
 * Create : K.S. 04/01/01 16:26:18
 * 
 * Disassembler
 */

#include <ruby.h>
#include <node.h>

#include "yarvcore.h"
#include "insns.inc"
#include "insns_info.inc"

/*
  now, search algorithm is brute force. but this should be binary search.
 */
static unsigned short
find_line_no(yarv_iseq_t *iseqdat, unsigned long pos)
{
    unsigned long i, size = iseqdat->insn_info_size;
    struct insn_info_struct *iiary = iseqdat->insn_info_tbl;

    for (i = 0; i < size; i++) {
	if (iiary[i].position == pos) {
	    return iiary[i].line_no;
	}
    }
    // rb_bug("find_line_no: can't find %lu", pos);
    return 0;
}

static unsigned short
find_prev_line_no(yarv_iseq_t *iseqdat, unsigned long pos)
{
    unsigned long i, size = iseqdat->insn_info_size;
    struct insn_info_struct *iiary = iseqdat->insn_info_tbl;

    for (i = 0; i < size; i++) {
	if (iiary[i].position == pos) {
	    if (i > 0) {
		return iiary[i - 1].line_no;
	    }
	    else {
		return 0;
	    }
	}
    }
    rb_bug("find_prev_line_no: can't find - %lu", pos);
    return 0;
}

static VALUE
insn_operand_intern(yarv_iseq_t *iseq,
		    int insn, int op_no, VALUE op,
		    int len, int pos, VALUE *pnop, VALUE child)
{
    char *types = insn_op_types(insn);
    char type = types[op_no];
    VALUE ret;
    char buff[0x100];

    switch (type) {
    case TS_OFFSET:		/* LONG */
	snprintf(buff, sizeof(buff), "%ld", pos + len + op);
	ret = rb_str_new2(buff);
	break;

    case TS_NUM:		/* ULONG */
	snprintf(buff, sizeof(buff), "%lu", op);
	ret = rb_str_new2(buff);
	break;
    case TS_LINDEX:{
	    yarv_iseq_t *ip = iseq->local_iseq;
	    ret =
		rb_str_new2(rb_id2name
			    (ip->local_tbl[ip->local_size - op + 1]));
	    break;
	}
    case TS_DINDEX:{
	    if (insn == BIN(getdynamic) || insn == BIN(setdynamic)) {
		yarv_iseq_t *ip = iseq;
		int level = *pnop;
		int i;
		for (i = 0; i < level; i++) {
		    ip = ip->parent_iseq;
		}
		ret =
		    rb_str_new2(rb_id2name
				(ip->local_tbl[ip->local_size - op]));
	    }
	    else {
		ret = rb_inspect(INT2FIX(op));
	    }
	    break;
	}
    case TS_ID:		/* ID (symbol) */
	op = ID2SYM(op);
    case TS_VALUE:		/* VALUE */
	ret = rb_inspect(op);
	if (CLASS_OF(op) == cYarvISeq) {
	    rb_ary_push(child, op);
	}
	break;

    case TS_BLOCKISEQ:		/* block */
	{
	    yarv_iseq_t *block = (yarv_iseq_t *)op;
	    if (block) {
		ret = block->name;
		if (child) {
		    rb_ary_push(child, block->self);
		}
	    }
	    else {
		ret = rb_str_new2("nil");
	    }
	    break;
	}
    case TS_GENTRY:
	{
	    struct global_entry *entry = (struct global_entry *)op;
	    ret = rb_str_new2(rb_id2name(entry->id));
	}
	break;

    case TS_IC:
	ret = rb_str_new2("<ic>");
	break;

    case TS_CDHASH:
	ret = rb_str_new2("<cdhash>");
	break;

    default:
	rb_bug("iseq_disasm: unknown operand type: %c", type);
    }
    return ret;
}

/**
 * Disassemble a instruction
 * Iseq -> Iseq inspect object
 */
VALUE
iseq_disasm_insn(VALUE ret, VALUE *iseq, int pos,
		 yarv_iseq_t *iseqdat, VALUE child)
{
    int insn = iseq[pos];
    int len = insn_len(insn);
    int i, j;
    char *types = insn_op_types(insn);
    VALUE str = rb_str_new(0, 0);
    char buff[0x100];
    char insn_name_buff[0x100];

    strcpy(insn_name_buff, insn_name(insn));
    if (0) {
	for (i = 0; insn_name_buff[i]; i++) {
	    if (insn_name_buff[i] == '_') {
		insn_name_buff[i] = 0;
	    }
	}
    }

    snprintf(buff, sizeof(buff), "%04d %-16s ", pos, insn_name_buff);
    rb_str_cat2(str, buff);

    for (j = 0; types[j]; j++) {
	char *types = insn_op_types(insn);
	VALUE opstr = insn_operand_intern(iseqdat, insn, j, iseq[pos + j + 1],
					  len, pos, &iseq[pos + j + 2],
					  child);
	rb_str_concat(str, opstr);

	GC_CHECK();
	if (types[j + 1]) {
	    rb_str_cat2(str, ", ");
	}
    }

    {
	int line_no = find_line_no(iseqdat, pos);
	int prev = find_prev_line_no(iseqdat, pos);
	if (line_no && line_no != prev) {
	    snprintf(buff, sizeof(buff), "%-70s(%4d)", RSTRING(str)->ptr,
		     line_no);
	    str = rb_str_new2(buff);
	}
    }
    if (ret) {
	rb_str_cat2(str, "\n");
	rb_str_concat(ret, str);
    }
    else {
	printf("%s\n", RSTRING(str)->ptr);
    }
    return len;
}

static char *
catch_type(int type)
{
    switch (type) {
    case CATCH_TYPE_RESCUE:
	return "rescue";
    case CATCH_TYPE_ENSURE:
	return "ensure";
    case CATCH_TYPE_RETRY:
	return "retry";
    case CATCH_TYPE_BREAK:
	return "break";
    case CATCH_TYPE_REDO:
	return "redo";
    case CATCH_TYPE_NEXT:
	return "next";
    default:
	rb_bug("unknown catch type (%d)", type);
	return 0;
    }
}

/**
 * Disassemble yarv instruction sequence
 *
 * return disassembled string
 */
VALUE
iseq_disasm(VALUE self)
{
    yarv_iseq_t *iseqdat;
    VALUE *iseq;
    VALUE str = rb_str_new(0, 0);
    VALUE child = rb_ary_new();
    unsigned long size;
    int i;
    ID *tbl;
    char buff[0x200];

    GetISeqVal(self, iseqdat);
    iseq = iseqdat->iseq;
    size = iseqdat->size;

    rb_str_cat2(str, "== disasm: ");

    rb_str_concat(str, iseq_inspect(self));
    for (i = RSTRING(str)->len; i < 72; i++) {
	rb_str_cat2(str, "=");
    }
    rb_str_cat2(str, "\n");

    /* show catch table information */
    if (iseqdat->catch_table_size != 0) {
	rb_str_cat2(str, "== catch table\n");
    }
    for (i = 0; i < iseqdat->catch_table_size; i++) {
	struct catch_table_entry *entry = &iseqdat->catch_table[i];
	sprintf(buff,
		"| catch type: %-6s st: %04d ed: %04d sp: %04d cont: %04d\n",
		catch_type((int)entry->type), (int)entry->start,
		(int)entry->end, (int)entry->sp, (int)entry->cont);
	rb_str_cat2(str, buff);
	if (entry->iseq) {
	    rb_str_concat(str, iseq_disasm(entry->iseq));
	}
    }
    if (iseqdat->catch_table_size != 0) {
	rb_str_cat2(str, "|-------------------------------------"
		    "-----------------------------------\n");
    }

    /* show local table information */
    tbl = iseqdat->local_tbl;

    if (tbl) {
	int opt = 0;
	if (iseqdat->type == ISEQ_TYPE_METHOD ||
	    iseqdat->type == ISEQ_TYPE_TOP ||
	    iseqdat->type == ISEQ_TYPE_CLASS) {
	    opt = 1;
	}

	snprintf(buff, sizeof(buff),
		 "local scope table (size: %d, argc: %d)\n",
		 iseqdat->local_size, iseqdat->argc);
	rb_str_cat2(str, buff);

	for (i = 0; i < iseqdat->local_size - opt; i++) {
	    char *name = rb_id2name(tbl[i + opt]);
	    char info[0x100];
	    char argi[0x100] = "";
	    char opti[0x100] = "";

	    if (iseqdat->arg_opts) {
		int argc = iseqdat->argc;
		int opts = iseqdat->arg_opts;
		if (i >= argc && i < argc + opts - 1) {
		    snprintf(opti, sizeof(opti), "Opt=%ld",
			     iseqdat->arg_opt_tbl[i - argc]);
		}
	    }

	    snprintf(argi, sizeof(argi), "%s%s%s%s",	/* arg, opts, rest, block */
		     iseqdat->argc > i ? "Arg" : "",
		     opti,
		     iseqdat->arg_rest - 1 == i ? "Rest" : "",
		     iseqdat->arg_block - 1 == i ? "Block" : "");

	    snprintf(info, sizeof(info), "%s%s%s%s", name ? name : "?",
		     *argi ? "<" : "", argi, *argi ? ">" : "");

	    snprintf(buff, sizeof(buff), "[%2d] %-11s",
		     iseqdat->local_size - i, info);

	    rb_str_cat2(str, buff);
	}
	rb_str_cat2(str, "\n");
    }

    GC_CHECK();

    /* show each line */
    for (i = 0; i < size;) {
	i += iseq_disasm_insn(str, iseq, i, iseqdat, child);
    }

    for (i = 0; i < RARRAY(child)->len; i++) {
	VALUE isv = rb_ary_entry(child, i);
	rb_str_concat(str, iseq_disasm(isv));
    }

    return str;
}

char *
node_name(int node)
{
    switch (node) {
    case NODE_METHOD:
	return "NODE_METHOD";
    case NODE_FBODY:
	return "NODE_FBODY";
    case NODE_CFUNC:
	return "NODE_CFUNC";
    case NODE_SCOPE:
	return "NODE_SCOPE";
    case NODE_BLOCK:
	return "NODE_BLOCK";
    case NODE_IF:
	return "NODE_IF";
    case NODE_CASE:
	return "NODE_CASE";
    case NODE_WHEN:
	return "NODE_WHEN";
    case NODE_OPT_N:
	return "NODE_OPT_N";
    case NODE_WHILE:
	return "NODE_WHILE";
    case NODE_UNTIL:
	return "NODE_UNTIL";
    case NODE_ITER:
	return "NODE_ITER";
    case NODE_FOR:
	return "NODE_FOR";
    case NODE_BREAK:
	return "NODE_BREAK";
    case NODE_NEXT:
	return "NODE_NEXT";
    case NODE_REDO:
	return "NODE_REDO";
    case NODE_RETRY:
	return "NODE_RETRY";
    case NODE_BEGIN:
	return "NODE_BEGIN";
    case NODE_RESCUE:
	return "NODE_RESCUE";
    case NODE_RESBODY:
	return "NODE_RESBODY";
    case NODE_ENSURE:
	return "NODE_ENSURE";
    case NODE_AND:
	return "NODE_AND";
    case NODE_OR:
	return "NODE_OR";
    case NODE_NOT:
	return "NODE_NOT";
    case NODE_MASGN:
	return "NODE_MASGN";
    case NODE_LASGN:
	return "NODE_LASGN";
    case NODE_DASGN:
	return "NODE_DASGN";
    case NODE_DASGN_CURR:
	return "NODE_DASGN_CURR";
    case NODE_GASGN:
	return "NODE_GASGN";
    case NODE_IASGN:
	return "NODE_IASGN";
    case NODE_CDECL:
	return "NODE_CDECL";
    case NODE_CVASGN:
	return "NODE_CVASGN";
    case NODE_CVDECL:
	return "NODE_CVDECL";
    case NODE_OP_ASGN1:
	return "NODE_OP_ASGN1";
    case NODE_OP_ASGN2:
	return "NODE_OP_ASGN2";
    case NODE_OP_ASGN_AND:
	return "NODE_OP_ASGN_AND";
    case NODE_OP_ASGN_OR:
	return "NODE_OP_ASGN_OR";
    case NODE_CALL:
	return "NODE_CALL";
    case NODE_FCALL:
	return "NODE_FCALL";
    case NODE_VCALL:
	return "NODE_VCALL";
    case NODE_SUPER:
	return "NODE_SUPER";
    case NODE_ZSUPER:
	return "NODE_ZSUPER";
    case NODE_ARRAY:
	return "NODE_ARRAY";
    case NODE_ZARRAY:
	return "NODE_ZARRAY";
    case NODE_VALUES:
	return "NODE_VALUES";
    case NODE_HASH:
	return "NODE_HASH";
    case NODE_RETURN:
	return "NODE_RETURN";
    case NODE_YIELD:
	return "NODE_YIELD";
    case NODE_LVAR:
	return "NODE_LVAR";
    case NODE_DVAR:
	return "NODE_DVAR";
    case NODE_GVAR:
	return "NODE_GVAR";
    case NODE_IVAR:
	return "NODE_IVAR";
    case NODE_CONST:
	return "NODE_CONST";
    case NODE_CVAR:
	return "NODE_CVAR";
    case NODE_NTH_REF:
	return "NODE_NTH_REF";
    case NODE_BACK_REF:
	return "NODE_BACK_REF";
    case NODE_MATCH:
	return "NODE_MATCH";
    case NODE_MATCH2:
	return "NODE_MATCH2";
    case NODE_MATCH3:
	return "NODE_MATCH3";
    case NODE_LIT:
	return "NODE_LIT";
    case NODE_STR:
	return "NODE_STR";
    case NODE_DSTR:
	return "NODE_DSTR";
    case NODE_XSTR:
	return "NODE_XSTR";
    case NODE_DXSTR:
	return "NODE_DXSTR";
    case NODE_EVSTR:
	return "NODE_EVSTR";
    case NODE_DREGX:
	return "NODE_DREGX";
    case NODE_DREGX_ONCE:
	return "NODE_DREGX_ONCE";
    case NODE_ARGS:
	return "NODE_ARGS";
    case NODE_ARGSCAT:
	return "NODE_ARGSCAT";
    case NODE_ARGSPUSH:
	return "NODE_ARGSPUSH";
    case NODE_SPLAT:
	return "NODE_SPLAT";
    case NODE_TO_ARY:
	return "NODE_TO_ARY";
    case NODE_SVALUE:
	return "NODE_SVALUE";
    case NODE_BLOCK_ARG:
	return "NODE_BLOCK_ARG";
    case NODE_BLOCK_PASS:
	return "NODE_BLOCK_PASS";
    case NODE_DEFN:
	return "NODE_DEFN";
    case NODE_DEFS:
	return "NODE_DEFS";
    case NODE_ALIAS:
	return "NODE_ALIAS";
    case NODE_VALIAS:
	return "NODE_VALIAS";
    case NODE_UNDEF:
	return "NODE_UNDEF";
    case NODE_CLASS:
	return "NODE_CLASS";
    case NODE_MODULE:
	return "NODE_MODULE";
    case NODE_SCLASS:
	return "NODE_SCLASS";
    case NODE_COLON2:
	return "NODE_COLON2";
    case NODE_COLON3:
	return "NODE_COLON3";
    case NODE_CREF:
	return "NODE_CREF";
    case NODE_DOT2:
	return "NODE_DOT2";
    case NODE_DOT3:
	return "NODE_DOT3";
    case NODE_FLIP2:
	return "NODE_FLIP2";
    case NODE_FLIP3:
	return "NODE_FLIP3";
    case NODE_ATTRSET:
	return "NODE_ATTRSET";
    case NODE_SELF:
	return "NODE_SELF";
    case NODE_NIL:
	return "NODE_NIL";
    case NODE_TRUE:
	return "NODE_TRUE";
    case NODE_FALSE:
	return "NODE_FALSE";
    case NODE_ERRINFO:
	return "NODE_ERRINFO";
    case NODE_DEFINED:
	return "NODE_DEFINED";
    case NODE_POSTEXE:
	return "NODE_POSTEXE";
    case NODE_ALLOCA:
	return "NODE_ALLOCA";
    case NODE_BMETHOD:
	return "NODE_BMETHOD";
    case NODE_MEMO:
	return "NODE_MEMO";
    case NODE_IFUNC:
	return "NODE_IFUNC";
    case NODE_DSYM:
	return "NODE_DSYM";
    case NODE_ATTRASGN:
	return "NODE_ATTRASGN";
    case NODE_PRELUDE:
	return "NODE_PRELUDE";
    case NODE_LAMBDA:
	return "NODE_LAMBDA";
    case NODE_OPTBLOCK:
	return "NODE_OPTBLOCK";
    case NODE_LAST:
	return "NODE_LAST";
    default:
	rb_bug("unknown node (%d)", node);
	return 0;
    }
}
