/*

    nodedump.c

    Copyright (c) 2000 The Pragmatic Programmers, LLC

    2002-04-15  modified by Minero Aoki <aamine@loveruby.net>
      * support latest ruby.
      * support NODE_RESBODY
      * do not skip NODE_NEWLINE, NODE_BLOCK
    
    2002-08-03
      * does not output NODE_NEWLINE/BLOCK if $nodedump_short_format defined.

*/

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

#include "ruby.h"
#include "node.h"
#include "st.h"

EXTERN NODE *ruby_eval_tree;
EXTERN NODE *ruby_eval_tree_begin;

#include "rubynode.h"

#define ND_VERSION "0.1.3a"

/* print spaces */
static void
indent(int level)
{
    int i;
    for (i = 0; i < level; i++) {
        printf("    ");
    }
}
  
/* indented printf */
static void
indented_printf(int level, const char *fmt, ...)
{
    va_list ap;

    indent(level);
    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);
}

static void
lp_literal(int level, VALUE lit)
{
    if (FIXNUM_P(lit)) {
        indented_printf(level, "nd_lit = %d:Fixnum\n", FIX2LONG(lit));
    }
    else if (SYMBOL_P(lit)) {
        indented_printf(level, "nd_lit = %d:Symbol\n", SYM2ID(lit));
    }
    else if (TYPE(lit) == T_STRING) {
        indented_printf(level, "nd_lit = \"%.*s\":String\n", RSTRING(lit)->len, RSTRING(lit)->ptr);
    }
    else {
        VALUE str = rb_inspect(lit);
        indented_printf(level, "nd_lit = %.*s\n", RSTRING(str)->len, RSTRING(str)->ptr);
    }
}

static void dump_node(NODE *n, int longp, int level);

static void
lpdump(int level, char *name, NODE *node, int longp)
{
    indented_printf(level, "%s:\n", name);
    dump_node(node, longp, level + 1);
}

static void
safe_lpdump(int level, char *name, NODE *node, int longp)
{
    if (node)
        lpdump(level, name, node, longp);
    else
        indented_printf(level, "%s = (null)\n", name);
}

static VALUE
noex_name(int flag)
{
    VALUE s;
    if (flag & NOEX_PRIVATE) {
        s = rb_str_new2("NOEX_PRIVATE");
    }
    else if (flag & NOEX_PROTECTED) {
        s = rb_str_new2("NOEX_PROTECTED");
    }
    else {
        s = rb_str_new2("NOEX_PUBLIC");
    }
    if (flag & NOEX_CFUNC)
        rb_str_cat2(s, "|NOEX_CFUNC");
    if (flag & NOEX_UNDEF)
        rb_str_cat2(s, "|NOEX_UNDEF");
    return s;
}

static VALUE
cref_path(NODE *n)
{
    if (!n->nd_next) {
        return rb_str_new2(rb_class2name(n->nd_clss));
    }
    else {
        VALUE s = cref_path(n->nd_next);
        rb_str_cat2(s, " <- ");
        rb_str_cat2(s, rb_class2name(n->nd_clss));
        return s;
    }
}

/* cite from variable.c, to get gvar name */
struct global_entry {
    int *vartable;
    ID id;
};

#define DUMP(n,l) dump_node(n,l,0)

static void
dump_node(NODE *n, int longp, int level)
{
#define safe_lpdump(l,nm,n) safe_lpdump(l,nm,n,longp)
#define      lpdump(l,nm,n)      lpdump(l,nm,n,longp)
    int type;
    int id;

    if (!n) return;

    type = nd_type(n);
    if (longp || !(type == NODE_NEWLINE || type == NODE_BLOCK || type == NODE_ARRAY)) {
        indented_printf(level, "%s\n", node_names[type]);
    }

    switch (type) {
    case NODE_ALIAS:
    case NODE_VALIAS:
        indented_printf(level, "nd_new = %d (%s)\n", n->nd_new, rb_id2name(n->nd_new));
        indented_printf(level, "nd_old = %d (%s)\n", n->nd_old, rb_id2name(n->nd_old));
        break;

    case NODE_AND:
    case NODE_OR:
        lpdump(level, "nd_1st", n->nd_1st);
        lpdump(level, "nd_2nd", n->nd_2nd);
        break;
    
    case NODE_ARGS:
        indented_printf(level, "nd_cnt  = %d\n", n->nd_cnt);
        indented_printf(level, "nd_rest = %d\n", n->nd_rest);
        if (n->nd_rest == -1) {
            safe_lpdump(level, "nd_opt", n->nd_opt);
        }
        else {
            indented_printf(level, "nd_opt = (null)\n");
        }
        break;
    
    case NODE_ARGSCAT:
    case NODE_ARGSPUSH:
        lpdump(level, "nd_head", n->nd_head);  /* push on to */
        lpdump(level, "nd_body", n->nd_body);  /* values */
        break;  

    case NODE_ARRAY:
        if (longp) {
            indented_printf(level, "nd_alen = %d\n", n->nd_alen); /* size */
            lpdump(level, "nd_head", n->nd_head);
            safe_lpdump(level, "nd_next", n->nd_next);
        }
        else {
            NODE *a;
            int i;
            indented_printf(level, "NODE_ARRAY [\n");
            for (a = n, i = 0; a; a = a->nd_next, i++) {
                indented_printf(level, "%d:\n", i);
                dump_node(a->nd_head, longp, level + 1);
            }
            indented_printf(level, "]\n");
        }
        break;

    case NODE_ATTRSET:
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        break;

    case NODE_BACK_REF:
        indented_printf(level, "nd_nth = %c\n", n->nd_nth);
        break;

    case NODE_BEGIN:
        lpdump(level, "nd_body", n->nd_body);
        break;

    case NODE_BLOCK:
        if (!longp) {
            dump_node(n->nd_head, longp, level);
            dump_node(n->nd_next, longp, level);
        }
        else {
            lpdump(level, "nd_head", n->nd_head);
            safe_lpdump(level, "nd_next", n->nd_next);
        }
        break;

    case NODE_BLOCK_ARG:   /* &arg in param list */
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        break;

    case NODE_BLOCK_PASS:
        lpdump(level, "nd_body", n->nd_body);
        lpdump(level, "nd_iter", n->nd_iter);
        break;

    case NODE_BMETHOD:
        /* the method which is defined by define_method (Proc) */
        break;
    
    case NODE_BREAK:
        safe_lpdump(level, "nd_stts", n->nd_stts);
        break;

    case NODE_CALL:
    case NODE_FCALL:
        indented_printf(level, "nd_mid = %d (%s)\n", n->nd_mid, rb_id2name(n->nd_mid));
        if (type == NODE_CALL) {
            lpdump(level, "nd_recv", n->nd_recv);
        }
        safe_lpdump(level, "nd_args", n->nd_args);
        break;

    case NODE_CASE:   /*** FIXME ***/
        safe_lpdump(level, "nd_head", n->nd_head);
        safe_lpdump(level, "nd_body", n->nd_body);
        break;

    case NODE_WHEN:
        safe_lpdump(level, "nd_head", n->nd_head);
        safe_lpdump(level, "nd_body", n->nd_body);
        safe_lpdump(level, "nd_next", n->nd_next);
        break;

#ifdef NODE_CASGN
    case NODE_CASGN:    /* UNUSED (1.5?) */
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        safe_lpdump(level, "nd_value", n->nd_value);
        break;
#endif
    
    case NODE_CDECL:
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_CFUNC:
        indented_printf(level, "nd_argc = %d\n", n->nd_argc);
        indented_printf(level, "nd_cfnc = 0x%lx\n", n->nd_cfnc);
        break;

    case NODE_CLASS:
        indented_printf(level, "nd_cname = %d (%s)\n", n->nd_cname, rb_id2name(n->nd_cname));
        safe_lpdump(level, "nd_super", n->nd_super);
        safe_lpdump(level, "nd_body", n->nd_body);
        break;
    
    case NODE_COLON2:
        indented_printf(level, "nd_mid = %d (%s)\n", n->nd_mid, rb_id2name(n->nd_mid));
        safe_lpdump(level, "nd_head", n->nd_head);
        break;

    case NODE_COLON3:
        indented_printf(level, "nd_mid = %d (%s)\n", n->nd_mid, rb_id2name(n->nd_mid));
        break;

    case NODE_CONST:
        indented_printf(level, "nd_vid  = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        break;

    case NODE_CREF:
        /* safe_lpdump(n->u1.node, "u1.node", level); */
        safe_lpdump(level, "u2.node", n->u2.node);
        safe_lpdump(level, "u3.node", n->u3.node);
        break;
    
    case NODE_CVAR:
#ifdef NODE_CVAR2               /* ruby 1.7 2002-02-13 (node.h 1.28) */
    case NODE_CVAR2:
#endif
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        break;

    case NODE_CVDECL:
#ifdef NODE_CVASGN2		/* RUBY_VERSION_CODE < 162 */
    case NODE_CVASGN2:
#endif
#ifdef NODE_CVASGN		/* RUBY_VERSION_CODE < 160 */
    case NODE_CVASGN:
#endif
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        safe_lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_DASGN:
    case NODE_DASGN_CURR:
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        safe_lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_DEFINED:
        safe_lpdump(level, "nd_head", n->nd_head);
        break;

    case NODE_DEFN:
        indented_printf(level, "nd_mid  = %d (%s)\n", n->nd_mid, rb_id2name(n->nd_mid));
        indented_printf(level, "nd_noex = %d (%s)\n", n->nd_noex,
           RSTRING(noex_name(n->nd_noex))->ptr);
        safe_lpdump(level, "nd_defn", n->nd_defn);
        break;

    case NODE_DEFS:
        indented_printf(level, "nd_mid = %d (%s)\n", n->nd_mid, rb_id2name(n->nd_mid));
        safe_lpdump(level, "nd_recv", n->nd_recv);
        safe_lpdump(level, "nd_defn", n->nd_defn);
        break;

    case NODE_DMETHOD:
        /* the method which is defined by define_method; (UnboundMethod) */
        break;
    
    case NODE_DVAR:
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        break;

    case NODE_DSTR:
    case NODE_DXSTR:
    case NODE_DREGX:
    case NODE_DREGX_ONCE:
        lp_literal(level, n->nd_lit);
        safe_lpdump(level, "nd_next", n->nd_next);
        break;

    case NODE_ENSURE:
        safe_lpdump(level, "nd_head", n->nd_head);
        safe_lpdump(level, "nd_ensr", n->nd_ensr);
        break;
    
    case NODE_FALSE:
        break;
    
    case NODE_DOT2:
    case NODE_DOT3:
    case NODE_FLIP2:
    case NODE_FLIP3:
        safe_lpdump(level, "nd_beg", n->nd_beg);
        safe_lpdump(level, "nd_end", n->nd_end);
        break;

    case NODE_FBODY:
        indented_printf(level, "nd_mid = %d (%s)\n", n->nd_mid, rb_id2name(n->nd_mid));
        safe_lpdump(level, "nd_head", n->nd_head);
        break;
    
    case NODE_FOR:
    case NODE_ITER:
        safe_lpdump(level, "nd_iter", n->nd_iter);
        safe_lpdump(level, "nd_var",  n->nd_var);
        safe_lpdump(level, "nd_body", n->nd_body);
        break;
    
    case NODE_GASGN:
        id = ((struct global_entry*)n->nd_entry)->id;
        indented_printf(level, "nd_entry->id = %d (%s)\n", id, rb_id2name(id));
        safe_lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_GVAR:
        id = ((struct global_entry*)n->nd_entry)->id;
        indented_printf(level, "nd_entry->id = %d (%s)\n", id, rb_id2name(id));
        break;

    case NODE_HASH:
        safe_lpdump(level, "nd_head", n->nd_head);
        break;

    case NODE_IASGN:
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_IF:
        lpdump(level, "nd_cond", n->nd_cond);
        lpdump(level, "nd_body", n->nd_body);
        lpdump(level, "nd_else", n->nd_else);
        break;
    
    case NODE_IVAR:
        indented_printf(level, "nd_vid = %d (%s)\n", n->nd_vid, rb_id2name(n->nd_vid));
        break;

    case NODE_LASGN:
        indented_printf(level, "nd_cnt = %d\n", n->nd_cnt);
        lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_LIT:
    case NODE_XSTR:
        lp_literal(level, n->nd_lit);
        break;

    case NODE_EVSTR:
        safe_lpdump(level, "nd_body", n->nd_body);
        break;
    
    case NODE_LVAR:
        indented_printf(level, "nd_cnt = %d\n", n->nd_cnt);
        break;

    case NODE_MASGN:
        lpdump(level, "nd_head", n->nd_head);
        if (n->nd_args) {
            if (n->nd_args == (NODE *)-1) {
                indented_printf(level, "nd_args = -1 (*)");
            }
            else {
                lpdump(level, "nd_args", n->nd_args);
            }
        }
        lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_MATCH:
        lp_literal(level, n->nd_lit);
        break;

    case NODE_MATCH3:
    case NODE_MATCH2:
        lpdump(level, "nd_recv", n->nd_recv);
        lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_METHOD:
        indented_printf(level, "nd_noex = %d (%s)\n", n->nd_noex,
           RSTRING(noex_name(n->nd_noex))->ptr);
        indented_printf(level, "nd_cnt = %d\n", n->nd_cnt);
        safe_lpdump(level, "nd_body", n->nd_body);
        break;
    
    case NODE_MODULE:	
        indented_printf(level, "nd_cname = %d (%s)\n", n->nd_cname, rb_id2name(n->nd_cname));
        lpdump(level, "nd_body", n->nd_body);
        break;

    case NODE_NEWLINE:
        if (!longp) {
            dump_node(n->nd_next, longp, level);
        }
        else {
            indented_printf(level, "nd_file = \"%s\"\n", n->nd_file);
            indented_printf(level, "nd_nth  = %d\n", n->nd_nth);
            lpdump(level, "nd_next", n->nd_next);
        }
        break;

    case NODE_NEXT:
        safe_lpdump(level, "nd_stts", n->nd_stts);
        break;

    case NODE_NIL:
        break;

    case NODE_NOT:
        lpdump(level, "nd_body", n->nd_body);
        break;

    case NODE_NTH_REF:    /* $1, $2, $3... */
        indented_printf(level, "nd_nth = %d ($%d)\n", n->nd_nth, n->nd_nth);
        break;

    case NODE_OP_ASGN1:   /* recv[*args.next] mid= args.head */
        lpdump(level, "nd_recv", n->nd_recv);
        switch (n->nd_mid) {
        case 0:
            indented_printf(level, "nd_mid = 0 (||=)\n");
            break;
        case 1:
            indented_printf(level, "nd_mid = 1 (&&=)\n");
            break;
        default:
            indented_printf(level, "nd_mid = %d (%s=)\n", n->nd_mid, rb_id2name(n->nd_mid));
            break;
        }
        lpdump(level, "nd_args", n->nd_args);
        break;

    case NODE_OP_ASGN2:
        lpdump(level, "nd_recv", n->nd_recv);
        id = n->nd_next->nd_vid;
        indented_printf(level, "nd_next->nd_vid = %d (%s)\n", id, rb_id2name(id));
        id = n->nd_next->nd_mid;
        switch (id) {
        case 0:
            indented_printf(level, "nd_next->nd_mid = 0 (||=)\n");
            break;
        case 1:
            indented_printf(level, "nd_next->nd_mid = 1 (&&=)\n");
            break;
        default:
            indented_printf(level, "nd_next->nd_mid = %d (%s=)\n", id, rb_id2name(id));
        }
        lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_OP_ASGN_AND:  /* &&= */
    case NODE_OP_ASGN_OR:   /* ||= */
        lpdump(level, "nd_head", n->nd_head);
        lpdump(level, "nd_value", n->nd_value);
        break;

    case NODE_OPT_N:
        lpdump(level, "nd_body", n->nd_body);
        break;
    
    case NODE_POSTEXE:
        break;

    case NODE_REDO:
        break;

    case NODE_RESCUE:
        lpdump(level, "nd_head", n->nd_head);
        lpdump(level, "nd_resq", n->nd_resq);
        safe_lpdump(level, "nd_else", n->nd_else);
        break;

    case NODE_RESBODY:
        safe_lpdump(level, "nd_args", n->nd_args);
        lpdump(level, "nd_body", n->nd_body);
        safe_lpdump(level, "nd_head", n->nd_head);
        break;

    case NODE_RESTARGS:
#ifdef NODE_RESTARY
    case NODE_RESTARY:
#endif
#ifdef NODE_REXPAND
    case NODE_REXPAND:
#endif
        lpdump(level, "nd_head", n->nd_head);
        break;

    case NODE_RETRY:
        break;

    case NODE_RETURN:
        safe_lpdump(level, "nd_stts", n->nd_stts);
        break;

    case NODE_SCLASS:
        lpdump(level, "nd_recv", n->nd_recv);
        lpdump(level, "nd_body", n->nd_body);
        break;

    case NODE_SCOPE:
        if (n->nd_rval) {
            volatile VALUE s = cref_path((NODE*)n->nd_rval);
            indented_printf(level, "nd_rval = %s\n", RSTRING(s)->ptr);
        }
        else {
            indented_printf(level, "nd_rval = (null)\n");
        }
        if (n->nd_tbl) {
            int i;
            indented_printf(level, "nd_tbl = %d [ _", n->nd_tbl[0]);
            for (i = 2; i < n->nd_tbl[0] + 1; i++) {
                printf(" %s", rb_id2name(n->nd_tbl[i]));
            }
            puts(" ]");
        }
        else {
            indented_printf(level, "nd_tbl = (null)\n");
        }
        lpdump(level, "nd_next", n->nd_next);
        break;

    case NODE_SELF:
        break;

    case NODE_STR:
        lp_literal(level, n->nd_lit);
        break;

    case NODE_SUPER:
        lpdump(level, "nd_args", n->nd_args);
        break;
    
    case NODE_TRUE:
        break;

    case NODE_UNDEF:
        id = n->nd_mid;
        indented_printf(level, "nd_mid = %d (%s)\n", id, rb_id2name(id));
        break;

    case NODE_UNTIL:
    case NODE_WHILE:
        indented_printf(level, "nd_state = %d (%s)\n", n->nd_state,
           (nd_type(n) == NODE_WHILE)
               ? (n->nd_state ? "while" : "do...while")
               : (n->nd_state ? "until" : "do...until"));
               
        lpdump(level, "nd_cond", n->nd_cond);
        lpdump(level, "nd_body", n->nd_body);
        break;
    
    case NODE_VCALL:   /* self.method */
        indented_printf(level, "nd_mid = %d (%s)\n",n->nd_mid,  rb_id2name(n->nd_mid));
        break;
    
    case NODE_YIELD:
        safe_lpdump(level, "nd_stts", n->nd_stts);
        break;

    case NODE_ZARRAY:
        break;

    case NODE_ZSUPER:
        break;

    default:
        puts("\n\n** unhandled**\n\n");
        break;
    }
}


static NODE*
method_body(VALUE klass, VALUE mname)
{
    NODE *body;
    VALUE id = rb_to_id(mname);

    switch (TYPE(klass)) {
      case T_MODULE:
      case T_CLASS:
        break;
      default:
        rb_raise(rb_eTypeError, "not a Module");
    }
    if (!st_lookup(RCLASS(klass)->m_tbl, id, &body)) {
        rb_raise(rb_eNoMethodError, "no such method: %s#%s",
                 rb_class2name(klass),
                 rb_id2name(id));
    }
    return body;
}

static VALUE
nodedump_dump(int argc, VALUE *argv, VALUE self)
{
    VALUE klass, mname, longp;

    if (rb_scan_args(argc, argv, "21", &klass, &mname, &longp) == 2) {
        longp = Qfalse;
    }
    DUMP(method_body(klass, mname), RTEST(longp));
    return Qnil;
}

static void
nodedump_dump_eval_tree(void)
{
    NODE *n = ruby_eval_tree;
    VALUE tmp;
    int longp;

    tmp = rb_gv_get("$nodedump_long_format");
    longp = RTEST(tmp);

    if (ruby_eval_tree_begin) {
        DUMP(ruby_eval_tree_begin, longp);
    }
    if (n) {
        DUMP(n, longp);
    }
    return;
}

static VALUE
nodedump_trigger(VALUE self, VALUE yielded, VALUE param)
{
    static int lock = 0;
    if (lock) return Qnil;

    if (ruby_eval_tree_begin || ruby_eval_tree) {
        lock = 1;
        if (ruby_verbose) {
            printf("NodeDump V" ND_VERSION "\n");
        }
        nodedump_dump_eval_tree();
        exit(1);
    }
    return Qnil;
}


static VALUE cNodeDump;

void
Init_nodedump()
{
    VALUE tmp;

    cNodeDump = rb_define_module("NodeDump");
    rb_define_singleton_method(cNodeDump, "dump", nodedump_dump, -1);

    tmp = rb_gv_get("$nodedump_auto");
    if (RTEST(tmp)) {
        rb_funcall(rb_mKernel, rb_intern("set_trace_func"),
                   1, rb_proc_new(nodedump_trigger, Qnil));
    }
}
