// $Id: BRbDebug.cpp,v 1.26 2003/10/08 05:19:53 yuya Exp $

#include "BRbDebug.h"
#include <stdarg.h>

////////////////////////////////////////////////////////////////////////////////

static const char* NODE_NAMES[] = {
	"NODE_METHOD",
	"NODE_FBODY",
	"NODE_CFUNC",
	"NODE_IFUNC",
	"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_HASH",
	"NODE_RETURN",
	"NODE_YIELD",
	"NODE_LVAR",
	"NODE_DVAR",
	"NODE_GVAR",
	"NODE_IVAR",
	"NODE_CONST",
	"NODE_CVAR",
	"NODE_CVAR2",
	"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_RESTARGS",
	"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_DEFINED",
	"NODE_NEWLINE",
	"NODE_POSTEXE",
#ifdef C_ALLOCA
	"NODE_ALLOCA",
#endif
	"NODE_DMETHOD",
	"NODE_BMETHOD",
	"NODE_MEMO",
	"NODE_LAST"
};

////////////////////////////////////////////////////////////////////////////////

BRbDebug::BRbDebug(bool debug, FILE* out, VALUE exception)
{
	m_debug     = debug;
	m_out       = out;
	m_exception = exception;
}

BRbDebug::~BRbDebug()
{
}

////////////////////////////////////////////////////////////////////////////////

void
BRbDebug::printf(const char* format, ...)
{
	if ( m_debug ) {
		va_list ap;
		va_start(ap, format);
		::vfprintf(m_out, format, ap);
		va_end(ap);
	}
}

void
BRbDebug::raise(const char* fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);

#ifdef WIN32
	const int size = 512;
#else
	const int size = ::vsprintf(NULL, fmt, ap);
#endif

	char* msg = new char[size + 1];
	msg[size] = '\0';

	::vsprintf(msg, fmt, ap);
	::rb_raise(m_exception, msg);

	delete[] msg;

	va_end(ap);
}

void
BRbDebug::dumpnode(NODE* node)
{
#ifdef DEBUG
	if ( m_debug ) {
		dump_node(node, 0);
	}
#endif
}

////////////////////////////////////////////////////////////////////////////////

#ifdef DEBUG

void
BRbDebug::p(const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	::vfprintf(m_out, fmt, ap);
	va_end(ap);
}

void
BRbDebug::lp(int indent, const char *fmt, ...)
{
	for ( int i = 0; i < indent; i++ ) {
		::fprintf(m_out, "  ");
	}

	va_list ap;
	va_start(ap, fmt);
	::vfprintf(m_out, fmt, ap);
	va_end(ap);
}

void
BRbDebug::lpdump(int indent, char *name, NODE *node)
{
	if ( node ) {
		lp(indent, "%s:\n", name);
		dump_node(node, indent + 1);
	} else {
		lp(indent, "%s: (null)\n", name);
	}
}

void
BRbDebug::nl()
{
	putc('\n', m_out);
}

VALUE
BRbDebug::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;
}

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

void
BRbDebug::dump_node(NODE* n, int level)
{
	NODE *n1 = NULL;
	int i   = 0;
	int id  = 0;
	int id1 = 0;

	if ( !n ) return;

	int type = nd_type(n);
	lp(level, "%s:\n", NODE_NAMES[type]);

	switch (type) {

	case NODE_ALIAS:
	case NODE_VALIAS:
		lp(level + 1, "nd_new = %d (%s)\n", n->nd_new, ::rb_id2name(n->nd_new));
		lp(level + 1, "nd_old = %d (%s)\n", n->nd_old, ::rb_id2name(n->nd_old));
		break;

	case NODE_AND:
	case NODE_OR:
		lpdump(level + 1, "nd_1st", n->nd_1st);
		lpdump(level + 1, "nd_2nd", n->nd_2nd);
		break;

	case NODE_ARGS:
		lp(level + 1, "nd_cnt  = %d\n", n->nd_cnt);
		lp(level + 1, "nd_rest = %d\n", n->nd_rest);
		lpdump(level + 1, "nd_opt", n->nd_opt);
		break;

	case NODE_ARGSCAT:
	case NODE_ARGSPUSH:
		lpdump(level + 1, "nd_head(push on to)", n->nd_head);
		lpdump(level + 1, "nd_body(values)", n->nd_body);
		break;

	case NODE_ARRAY:
		lp(level + 1, "nd_alen = %d\n", n->nd_alen);
		n1 = n;
		while ( n1 ) {
			dump_node(n1->nd_head, level + 1);
			n1 = n1->nd_next;
		}
		break;

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

	case NODE_BACK_REF:
		lp(level + 1, "nd_nth = $%c\n", n->nd_nth);
		break;

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

	case NODE_BLOCK:
		while ( n->nd_next ) {
			dump_node(n->nd_head, level + 1);
			n = n->nd_next;
		}
		dump_node(n->nd_head,level + 1);
		break;

	case NODE_BLOCK_ARG:
		lp(level + 1, "nd_vid = %d (%s)\n", n->nd_vid, ::rb_id2name(n->nd_vid));
		break;

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

	case NODE_BMETHOD:
		break;

	case NODE_BREAK:
		lpdump(level + 1, "nd_stts", n->nd_stts);
		break;

	case NODE_CALL:
		lp(level + 1, "nd_mid = %d (%s)\n", n->nd_mid, ::rb_id2name(n->nd_mid));
		lpdump(level + 1, "nd_recv", n->nd_recv);
		lpdump(level + 1, "nd_args", n->nd_args);
		break;

	case NODE_FCALL:
		lp(level + 1, "nd_mid = %d (%s)\n", n->nd_mid, ::rb_id2name(n->nd_mid));
		lpdump(level + 1, "nd_args", n->nd_args);
		break;

	case NODE_CASE:
		if ( n->nd_head ) {
			dump_node(n->nd_head, level + 1);
		}
		n = n->nd_body;
		while ( n ) {
			if ( nd_type(n) != NODE_WHEN ) {
				lp(level+1, "Else:\n");
				dump_node(n, level+2);
				break;
			}
			dump_node(n->nd_head, level + 1);
			dump_node(n->nd_body, level + 3);
			n = n->nd_next;
		}
		break;

	case NODE_WHEN:
		n1 = n->nd_head;
		while ( n1 ) {
			dump_node(n1->nd_head, level + 2);
			n1 = n1->nd_next;
		}
		dump_node(n->nd_body, level + 1);
		break;

#ifdef NODE_CASGN
	case NODE_CASGN:  // UNUSED?
		lp(level + 1, "nd_vid = %d (%s)\n", n->nd_vid, ::rb_id2name(n->nd_vid));
		lpdump(level, "nd_value", n->nd_value);
		break;
#endif

	case NODE_CDECL:
		lp(level + 1, "nd_vid = %d (%s)\n", n->nd_vid, ::rb_id2name(n->nd_vid));
		lpdump(level + 1, "nd_value", n->nd_value);
		break;

	case NODE_CLASS:
//		lp(level + 1, "nd_cname = %d (%s)\n", n->nd_cname, ::rb_id2name(n->nd_cname));
		lpdump(level + 1, "nd_super", n->nd_super);
		lpdump(level + 1, "nd_body", n->nd_body);
		break;

	case NODE_COLON2:
		lp(level + 1, "nd_mid = %d (%s)\n", n->nd_mid, ::rb_id2name(n->nd_mid));
		lpdump(level + 1, "nd_head", n->nd_head);
		break;

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

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

	case NODE_CREF:
		lpdump(level, "u2.node", n->u2.node);
		lpdump(level, "u3.node", n->u3.node);
		break;

	case NODE_CVAR:
#ifdef NODE_CVAR2
	case NODE_CVAR2:
#endif
		lp(level + 1, "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
		lp(level + 1, "nd_vid = %d (%s)\n", n->nd_vid, ::rb_id2name(n->nd_vid));
		lpdump(level + 1, "nd_value", n->nd_value);
		break;

	case NODE_DASGN:
	case NODE_DASGN_CURR:
		lp(level + 1, "nd_vid = %d (%s)\n", n->nd_vid, ::rb_id2name(n->nd_vid));
		lpdump(level + 1, "nd_value", n->nd_value);
		break;

	case NODE_DEFINED:
		lpdump(level + 1, "nd_head", n->nd_head);
		break;

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

	case NODE_DEFS:
		lp(level + 1, "nd_mid = %d (%s)\n", n->nd_mid, ::rb_id2name(n->nd_mid));
		lpdump(level + 1, "nd_recv", n->nd_recv);
		lpdump(level + 1, "nd_defn", n->nd_defn);
		break;

	case NODE_DMETHOD:
		break;

	case NODE_DVAR:
		lp(level + 1, "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:
		dump_literal(n->nd_lit, level + 1);
		n1 = n->nd_next;
		while ( n1 ) {
			dump_node(n1->nd_head, level + 1);
			n1 = n1->nd_next;
		}
		break;

	case NODE_ENSURE:
		lpdump(level + 1, "nd_head", n->nd_head);
		lpdump(level + 1, "nd_ensr", n->nd_ensr);
		break;

	case NODE_FALSE:
		break;

	case NODE_DOT2:
	case NODE_DOT3:
	case NODE_FLIP2:
	case NODE_FLIP3:
		lpdump(level + 1, "nd_beg", n->nd_beg);
		lpdump(level + 1, "nd_end", n->nd_end);
		break;

	case NODE_FBODY:
		lp(level + 1, "nd_mid = %d (%s)\n", n->nd_mid, ::rb_id2name(n->nd_mid));
		lpdump(level + 1, "nd_head", n->nd_head);
		break;

	case NODE_FOR:
	case NODE_ITER:
		lpdump(level + 1, "nd_iter", n->nd_iter);
		if ( (int)n->nd_var == 1 ) lp(level + 1, "nd_var = %d\n", n->nd_var);
		else                       lpdump(level + 1, "nd_var",  n->nd_var);
		lpdump(level + 1, "nd_body", n->nd_body);
		break;

	case NODE_GASGN:
		id = ((struct global_entry*)n->nd_entry)->id;
		lp(level + 1, "nd_entry->id = %d (%s)\n", id, ::rb_id2name(id));
		lpdump(level + 1, "nd_value", n->nd_value);
		break;

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

	case NODE_HASH:
		n1 = n->nd_head;
		while (n1) {
			dump_node(n1->nd_head, level + 1);
			n1 = n1->nd_next;
			if ( !n1 ) {
				break;
			}
			lp(level + 2, "=>\n");
			dump_node(n1->nd_head, level+3);
			n1 = n1->nd_next;
		}
		break;

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

	case NODE_IF:
		lpdump(level + 1, "nd_cond", n->nd_cond);
		lpdump(level + 1, "nd_body", n->nd_body);
		lpdump(level + 1, "nd_else", n->nd_else);
		break;

	case NODE_IVAR:
		lp(level + 1, "nd_vid = %d (%s)\n", n->nd_vid, ::rb_id2name(n->nd_vid));
		break;

	case NODE_LASGN:
		lp(level + 1, "nd_cnt = %d (%s)\n", n->nd_cnt, ::rb_id2name(n->nd_vid));
		lpdump(level + 1, "nd_value", n->nd_value);
		break;

	case NODE_LIT:
	case NODE_XSTR:
		dump_literal(n->nd_lit, level + 1);
		break;

	case NODE_EVSTR:
		lpdump(level + 1, "nd_body", n->nd_body);
		break;

	case NODE_LVAR:
		lp(level + 1, "nd_cnt = %d (%s)\n", n->nd_cnt, ::rb_id2name(n->nd_vid));
		break;

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

	case NODE_MATCH:
		dump_literal(n->nd_head->nd_lit, level + 1);
		break;

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

	case NODE_METHOD:
		lp(level + 1, "nd_noex = %d (%s)\n", n->nd_noex, RSTRING(noex_name(n->nd_noex))->ptr);
		lp(level + 1, "nd_cnt = %d\n", n->nd_cnt);
		lpdump(level + 1, "nd_body", n->nd_body);
		break;

	case NODE_MODULE:
//		lp(level + 1, "nd_cname = %d (%s)\n", n->nd_cname, ::rb_id2name(n->nd_cname));
		lpdump(level + 1, "nd_body", n->nd_body);
		break;

	case NODE_NEWLINE:
		lp(level + 1, "nd_file = \"%s\"\n", n->nd_file);
		lp(level + 1, "nd_nth  = %d\n", n->nd_nth);
		lpdump(level + 1, "nd_next", n->nd_next);
		break;

	case NODE_NEXT:
		lpdump(level + 1, "nd_stts", n->nd_stts);
		break;

	case NODE_NIL:
		break;

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

	case NODE_NTH_REF:
		lp(level + 1, "nd_nth = %d ($%d)\n", n->nd_nth, n->nd_nth);
		break;

	case NODE_OP_ASGN1:
		switch ( n->nd_mid ) {
		case 0:  lp(level + 1, "nd_mid = 0 (||=)\n"); break;
		case 1:  lp(level + 1, "nd_mid = 1 (&&=)\n"); break;
		default: lp(level + 1, "nd_mid = %d (%s=)\n", n->nd_mid, ::rb_id2name(n->nd_mid)); break;
		}
		lpdump(level + 1, "nd_recv", n->nd_recv);
		lpdump(level + 1, "nd_args", n->nd_args);
		break;

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

	case NODE_OP_ASGN_AND:
	case NODE_OP_ASGN_OR:
		lpdump(level + 1, "nd_head(1st)", n->nd_head);
		lpdump(level + 1, "nd_value(2nd)", n->nd_value);
		break;

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

	case NODE_POSTEXE:
		break;

	case NODE_REDO:
		break;

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

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

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

	case NODE_RETRY:
		break;

	case NODE_RETURN:
		lpdump(level + 1, "nd_stts", n->nd_stts);
		break;

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

	case NODE_SCOPE:
		if ( n->nd_tbl ) {
			lp(level + 1, "nd_tbl = %d [ _", n->nd_tbl[0]);
			for ( int i = 2; i < (int)n->nd_tbl[0] + 1; i++ ) {
				p(" %s", ::rb_id2name(n->nd_tbl[i]));
			}
			p(" ]\n");
		}
		lpdump(level + 1, "nd_next", n->nd_next);
		break;

	case NODE_SELF:
		break;

	case NODE_STR:
		dump_literal(n->nd_lit, level + 1);
		break;

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

	case NODE_TRUE:
		break;

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

	case NODE_UNTIL:
		lp(level + 1, "nd_state = %d (%s)\n", n->nd_state, (n->nd_state ? "until" : "do...until"));
		lpdump(level + 1, "nd_cond", n->nd_cond);
		lpdump(level + 1, "nd_body", n->nd_body);
		break;

	case NODE_WHILE:
		lp(level + 1, "nd_state = %d (%s)\n", n->nd_state, (n->nd_state ? "while" : "do...while"));
		lpdump(level + 1, "nd_cond", n->nd_cond);
		lpdump(level + 1, "nd_body", n->nd_body);
		break;

	case NODE_VCALL:
		lp(level + 1, "nd_mid = %d (%s)\n", n->nd_mid, ::rb_id2name(n->nd_mid));
		break;

	case NODE_YIELD:
		lpdump(level, "nd_stts", n->nd_stts);
		break;

	case NODE_ZARRAY:
		break;

	case NODE_ZSUPER:
		break;

	default:
		p("\n\n** unhandled **\n\n");
		break;

	}
}

#endif

////////////////////////////////////////////////////////////////////////////////
