/**
 *  $Id: disasm.c 144 2005-02-20 23:40:59Z ko1 $
 * Create : K.S. 04/01/01 16:26:18
 * 
 * Disassembler
 */

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

#include "yarvcore.h"

/*
  now, search algorithm is brute force. but this should be binary search.
 */
unsigned short find_line_no(struct iseq_object *iseqobj, unsigned long pos){
  unsigned long i, size = iseqobj->insn_info_size;
  struct insn_info_struct *iiary = iseqobj->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 - %d", pos);
  return 0;
}

unsigned short find_prev_line_no(struct iseq_object *iseqobj, unsigned long pos){
  unsigned long i, size = iseqobj->insn_info_size;
  struct insn_info_struct *iiary = iseqobj->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 - %d", pos);
  return 0;
}

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

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

  case 'U': /* ULONG */
    snprintf(buff, sizeof(buff), "%u", op);
    ret = rb_str_new2(buff);
    break;

  case 'I': /* ID (symbol) */
    op = ID2SYM(op);
    /* fall through */
  case 'V': /* VALUE */
    ret = rb_inspect(op);

    if(child && CLASS_OF(op) == cYarvISeq){
      rb_ary_push(child, op);
    }
    break;

  case 'G':
    {
      struct global_entry *entry = (struct global_entry*)op;
      ret = rb_str_new2(rb_id2name(entry->id));
    }
    break;

  case 'M':
    ret = rb_str_new2("<ic>");
    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,
                       struct iseq_object *iseqobj, VALUE child){
  int insn = iseq[pos];
  int len  = insn_len(insn);
  int slen = 0;
  int j;
  long idx;
  char buff[0x100];
  char *types = insn_op_types(insn);
  VALUE str = rb_str_new(0, 0);
  
  snprintf(buff, sizeof(buff),
           "%04d %-16s", pos, insn_name(insn));
  rb_str_cat2(str, buff);
  
  for(j=0;types[j]; j++){
    char *types = insn_op_types(insn);
    VALUE opstr = insn_operand_intern(insn, j, iseq[pos+j+1],
                                      len, pos, child);

    rb_str_concat(str, opstr);
    
    GC_CHECK();
    if(types[j+1]){
      rb_str_cat2(str, ", ");
    }
  }
  
  {
    int line_no = find_line_no(iseqobj, pos);
    int prev    = find_prev_line_no(iseqobj, 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;
}

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

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

  rb_str_cat2  (str, "== disasm: ");
  // dp(iseq_inspect(self));
  
  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(iseqobj->catch_table_size != 0){
    rb_str_cat2(str, "== catch table\n");
  }
  for(i=0; i<iseqobj->catch_table_size; i++){
    struct catch_table_entry *entry = &iseqobj->catch_table[i];
    sprintf(buff, "|type: %02d, st: %04d, ed: %04d, sp: %04d, cont: %04d\n",
            entry->type, entry->start, entry->end, entry->sp, entry->cont);
    rb_str_cat2(str, buff);
    if(entry->iseq){
      rb_str_concat(str, iseq_disasm(entry->iseq));
    }
  }
  if(iseqobj->catch_table_size != 0){
    rb_str_cat2(str, "|-------------------------------------"
                     "-----------------------------------\n");
  }

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

  if(tbl){
    snprintf(buff, sizeof(buff),
             "local scope table (size: %d, argc: %d)\n",
             iseqobj->local_size, iseqobj->argc);
    rb_str_cat2(str, buff);
    
    for(i=0; i<iseqobj->local_size - 1; i++){
      char *name = rb_id2name(tbl[i+1]);
      char info[0x100];
      char argi[0x100] = "";
      char opti[0x100] = "";
      
      if(iseqobj->arg_opts){
        int argc = iseqobj->argc;
        int opts = iseqobj->arg_opts;
        if(i >= argc && i < argc + opts - 1){
          snprintf(opti, sizeof(opti), "Opt=%d",
                   iseqobj->arg_opt_tbl[i - argc]);
        }
      }

      snprintf(argi, sizeof(argi), "%s%s%s%s", /* arg, opts, rest, block */
               iseqobj->argc > i ? "Arg"         : "",
               opti,
               iseqobj->arg_rest == i  ? "Rest"  : "",
               iseqobj->arg_block == i ? "Block" : "");
      
      snprintf(info, sizeof(info), "%s%s%s%s", name ? name : "?",
               *argi ? "<" : "", argi, *argi ? ">" : "");
      
      snprintf(buff, sizeof(buff), "[%2d] %-11s",
               iseqobj->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, iseqobj, child);
  }
  
  for(i=0; i<RARRAY(child)->len; i++){
    rb_str_concat(str, iseq_disasm(RARRAY(child)->ptr[i]));
  }

  return str;
}

/**
 * raw dump
 */
VALUE iseq_disasm_ary_dump(VALUE aobj){
  struct RArray *ary = RARRAY(aobj);
  int i, pos = 0;
  VALUE obj, str;
  
  printf("-- raw disasm--------\n");
  for(i=0; i<ary->len; i++){
    obj = ary->ptr[i];
    
    if(CLASS_OF(obj) == cYarvInsn){
      str = insn_to_s_detail(obj);
      printf("%04d %-65s(%4d)\n",pos, StringValueCStr(str), insn_line_no(obj));
      pos += insn_length(obj);
    }
    else if(CLASS_OF(obj) == cYarvLabel){
      str = rb_inspect(obj);
      printf("%s\n", StringValueCStr(str));
    }
    else if(obj == Qnil){
      // ignore
    }
    else{
      dpv("error", CLASS_OF(obj));
      rb_bug("(iseq_disasm_ary_dump) must be LABEL");
    }
    GC_CHECK();
  }
  
  return Qtrue;
}


/*
  node.h
  $Date: 2004/05/07 08:44:14 $
 */
static char *node_names[] = {
    "NODE_METHOD",
    "NODE_FBODY",
    "NODE_CFUNC",
    "NODE_SCOPE",
    "NODE_BLOCK",
    "NODE_IF",
    "NODE_CASE",
    "NODE_WHEN",
    "NODE_OPT_N",
    "NODE_WHILE",
    "NODE_UNTIL",
    "NODE_ITER",
    "NODE_FOR",
    "NODE_BREAK",
    "NODE_NEXT",
    "NODE_REDO",
    "NODE_RETRY",
    "NODE_BEGIN",
    "NODE_RESCUE",
    "NODE_RESBODY",
    "NODE_ENSURE",
    "NODE_AND",
    "NODE_OR",
    "NODE_NOT",
    "NODE_MASGN",
    "NODE_LASGN",
    "NODE_DASGN",
    "NODE_DASGN_CURR",
    "NODE_GASGN",
    "NODE_IASGN",
    "NODE_CDECL",
    "NODE_CVASGN",
    "NODE_CVDECL",
    "NODE_OP_ASGN1",
    "NODE_OP_ASGN2",
    "NODE_OP_ASGN_AND",
    "NODE_OP_ASGN_OR",
    "NODE_CALL",
    "NODE_FCALL",
    "NODE_VCALL",
    "NODE_SUPER",
    "NODE_ZSUPER",
    "NODE_ARRAY",
    "NODE_ZARRAY",
    "NODE_VALUES",
    "NODE_HASH",
    "NODE_RETURN",
    "NODE_YIELD",
    "NODE_LVAR",
    "NODE_DVAR",
    "NODE_GVAR",
    "NODE_IVAR",
    "NODE_CONST",
    "NODE_CVAR",
    "NODE_NTH_REF",
    "NODE_BACK_REF",
    "NODE_MATCH",
    "NODE_MATCH2",
    "NODE_MATCH3",
    "NODE_LIT",
    "NODE_STR",
    "NODE_DSTR",
    "NODE_XSTR",
    "NODE_DXSTR",
    "NODE_EVSTR",
    "NODE_DREGX",
    "NODE_DREGX_ONCE",
    "NODE_ARGS",
    "NODE_ARGSCAT",
    "NODE_ARGSPUSH",
    "NODE_SPLAT",
    "NODE_TO_ARY",
    "NODE_SVALUE",
    "NODE_BLOCK_ARG",
    "NODE_BLOCK_PASS",
    "NODE_DEFN",
    "NODE_DEFS",
    "NODE_ALIAS",
    "NODE_VALIAS",
    "NODE_UNDEF",
    "NODE_CLASS",
    "NODE_MODULE",
    "NODE_SCLASS",
    "NODE_COLON2",
    "NODE_COLON3",
    "NODE_CREF",
    "NODE_DOT2",
    "NODE_DOT3",
    "NODE_FLIP2",
    "NODE_FLIP3",
    "NODE_ATTRSET",
    "NODE_SELF",
    "NODE_NIL",
    "NODE_TRUE",
    "NODE_FALSE",
    "NODE_ERRINFO",
    "NODE_DEFINED",
    "NODE_POSTEXE",
#ifdef C_ALLOCA
    "NODE_ALLOCA",
#endif
    "NODE_BMETHOD",
    "NODE_MEMO",
    "NODE_IFUNC",
    "NODE_DSYM",
    "NODE_ATTRASGN",
    "NODE_LAST"
};

char* node_name(int node){
  return node_names[node];
}

