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

#ifdef BRB_WRITER

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

#include "BRbWriteNode.h"
#include "BRbLevelCounter.h"

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

BRbWriteNode::BRbWriteNode(BRbDebug& debug)
	: BRbNode(), m_debug(debug), m_strtbl(debug)
{
}

BRbWriteNode::~BRbWriteNode()
{
}

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

void
BRbWriteNode::write(NODE* node, BRbOutputBuffer& buffer)
{
	write_header();

#ifdef DEBUG
	m_debug.printf("============================================================\n");
	m_debug.dumpnode(node);
	m_debug.printf("============================================================\n");
#endif

	write_node_opt(node);

#ifdef DEBUG
	m_debug.printf("============================================================\n");
#endif

	m_strtbl.dump(m_stbuf);

	buffer.write_buffer(m_headbuf);
	buffer.write_buffer(m_stbuf);
	buffer.write_buffer(m_ndbuf);
}

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

void
BRbWriteNode::write_header()
{
	BRbHeader header;
	header.signature     = BRB_FILE_SIGNATURE;
	header.file_version  = BRB_FILE_VERSION;
	header.ruby_major    = RUBY_VERSION[0] - '0';
	header.ruby_minor    = RUBY_VERSION[2] - '0';
	header.ruby_revision = RUBY_VERSION[4] - '0';

	m_headbuf.write_bytes(&header, sizeof(header));
}

void
BRbWriteNode::write_node(NODE* node)
{
	if ( !node ) {
#ifdef DEBUG
		BRbLevelCounter counter(0);
		counter.print_stack();
#endif
		m_debug.raise("[BUG] Node pointer is null");
		return;
	}

	byte type = (byte)nd_type(node);

#ifdef DEBUG
	BRbLevelCounter counter(type);
	if ( type == NODE_NEWLINE ) {
		m_debug.printf("W:%02i:%s [%s:%i]\n", counter.level(), BRbNode::get_node_name(type), node->nd_file, node->nd_nth);
	} else {
		m_debug.printf("W:%02i:%s\n", counter.level(), BRbNode::get_node_name(type));
	}
#endif

	write_byte(type);

	switch ( type ) {
		case NODE_ALIAS:
		{
			write_id(node->nd_new);
			write_id(node->nd_old);
			break;
		}
		case NODE_VALIAS:
		{
			write_id(node->nd_new);
			write_id(node->nd_old);
			break;
		}
		case NODE_AND:
		{
			write_node(node->nd_1st);
			write_node(node->nd_2nd);
			break;
		}
		case NODE_OR:
		{
			write_node(node->nd_1st);
			write_node(node->nd_2nd);
			break;
		}
		case NODE_ARGS:
		{
			write_dword(node->nd_cnt);
			write_dword(node->nd_rest);
			write_node_opt(node->nd_opt);
			break;
		}
		case NODE_ARGSCAT:
		{
			write_node(node->nd_head);
			write_node(node->nd_body);
			break;
		}
		case NODE_ARGSPUSH:
		{
			write_node(node->nd_head);
			write_node(node->nd_body);
			break;
		}
		case NODE_ARRAY:
		{
			NODE* cur = 0;
			int n = 0;
			for ( cur = node; cur; cur = cur->nd_next ) {
				n++;
			}
			write_word(n);
			write_word(node->nd_alen);
			for ( cur = node; cur; cur = cur->nd_next ) {
				write_node(cur->nd_head);
			}
			break;
		}
		case NODE_ATTRSET:
		{
			m_debug.raise("NODE_ATTRSET is not implemented");
			break;
		}
		case NODE_BACK_REF:
		{
			write_byte(node->nd_nth);
			write_dword(node->nd_cnt);
			break;
		}
		case NODE_BEGIN:
		{
			write_node(node->nd_body);
			break;
		}
		case NODE_BLOCK:
		{
			NODE* cur;

			int len = 0;
			for ( cur = node; cur; cur = cur->nd_next) {
				len++;
			}

			write_word(len);

			for ( cur = node; cur; cur = cur->nd_next) {
				write_node(cur->nd_head);
			}

			break;
		}
		case NODE_BLOCK_ARG:
		{
			write_id(node->nd_vid);
			write_dword(node->nd_cnt);
			break;
		}
		case NODE_BLOCK_PASS:
		{
			write_node(node->nd_body);
			write_node(node->nd_iter);
			break;
		}
		case NODE_BMETHOD:
		{
			m_debug.raise("NODE_BMETHOD is not implemented");
			break;
		}
		case NODE_BREAK:
		{
			write_node_opt(node->nd_stts);
			break;
		}
		case NODE_CALL:
		{
			write_id(node->nd_mid);
			write_node(node->nd_recv);
			write_node_opt(node->nd_args);
			break;
		}
		case NODE_FCALL:
		{
			write_id(node->nd_mid);
			write_node_opt(node->nd_args);
			break;
		}
		case NODE_CASE:
		{
			write_node(node->nd_head);
			write_node(node->nd_body);
			break;
		}
		case NODE_WHEN:
		{
			write_node_opts_flag(node->nd_body, node->nd_next);
			write_node(node->nd_head);
			write_node_opts(node->nd_body);
			write_node_opts(node->nd_next);
			break;
		}
		case NODE_CDECL:
		{
			write_id(node->nd_vid);
			write_node_opt(node->nd_value);
			write_node_opt(node->nd_else);
			break;
		}
		case NODE_CLASS:
		{
			write_node_opts_flag(node->nd_super, node->nd_body);
			write_node(node->nd_cpath);
			write_node_opts(node->nd_super);
			write_node_opts(node->nd_body);
			break;
		}
		case NODE_COLON2:
		{
			write_id(node->nd_mid);
			write_node_opt(node->nd_head);
			break;
		}
		case NODE_COLON3:
		{
			write_id(node->nd_mid);
			break;
		}
		case NODE_CONST:
		{
			write_id(node->nd_vid);
			break;
		}
		case NODE_CREF:
		{
			m_debug.raise("NODE_CREF is not implemented");
			break;
		}
		case NODE_CVAR:
		{
			write_id(node->nd_vid);
			break;
		}
		case NODE_CVDECL:
		{
			write_id(node->nd_vid);
			write_node(node->nd_value);
			break;
		}
		case NODE_CVASGN:
		{
			write_id(node->nd_vid);
			write_node(node->nd_value);
			break;
		}
		case NODE_DASGN:
		{
			write_id(node->nd_vid);
			write_node_opt(node->nd_value);
			break;
		}
		case NODE_DASGN_CURR:
		{
			write_id(node->nd_vid);
			write_node_opt(node->nd_value);
			break;
		}
		case NODE_DEFINED:
		{
			write_node(node->nd_head);
			break;
		}
		case NODE_DEFN:
		{
			write_dword(node->nd_noex);
			write_id(node->nd_mid);
			write_node(node->nd_defn);
			break;
		}
		case NODE_DEFS:
		{
			write_id(node->nd_mid);
			write_node(node->nd_recv);
			write_node(node->nd_defn);
			break;
		}
		case NODE_DMETHOD:
		{
			m_debug.raise("NODE_DMETHOD is not implemented");
			break;
		}
		case NODE_DVAR:
		{
			write_id(node->nd_vid);
			break;
		}
		case NODE_DSTR:
		{
			write_literal(node->nd_lit);
			write_node_opt(node->nd_next);
			break;
		}
		case NODE_DXSTR:
		{
			write_literal(node->nd_lit);
			write_node_opt(node->nd_next);
			break;
		}
		case NODE_DREGX:
		case NODE_DREGX_ONCE:
		{
			write_literal(node->nd_lit);
			write_node(node->nd_next);
			break;
		}
		case NODE_ENSURE:
		{
			write_node(node->nd_head);
			write_node(node->nd_ensr);
			break;
		}
		case NODE_FALSE:
		{
			break;
		}
		case NODE_DOT2:
		{
			write_node(node->nd_beg);
			write_node(node->nd_end);
			break;
		}
		case NODE_DOT3:
		{
			write_node(node->nd_beg);
			write_node(node->nd_end);
			break;
		}
		case NODE_FLIP2:
		{
			write_node(node->nd_beg);
			write_node(node->nd_end);
			break;
		}
		case NODE_FLIP3:
		{
			write_node(node->nd_beg);
			write_node(node->nd_end);
			break;
		}
		case NODE_FBODY:
		{
			m_debug.raise("NODE_FBODY is not implemented");
			break;
		}
		case NODE_FOR:
		{
			write_node(node->nd_iter);
			write_node(node->nd_body);
			write_node(node->nd_var);
			break;
		}
		case NODE_ITER:
		{
			write_node(node->nd_iter);
			write_node_opt(node->nd_body);

			if ( (int)node->nd_var == 1 ) {
				write_byte(1);
			} else {
				write_byte(0);
				write_node_opt(node->nd_var);
			}

			break;
		}
		case NODE_GASGN:
		{
			write_id(node->nd_entry->id);
			write_node_opt(node->nd_value);
			break;
		}
		case NODE_GVAR:
		{
			write_id(node->nd_entry->id);
			break;
		}
		case NODE_HASH:
		{
			write_node_opt(node->nd_head);
			break;
		}
		case NODE_IASGN:
		{
			write_id(node->nd_vid);
			write_node_opt(node->nd_value);
			break;
		}
		case NODE_IF:
		{
			write_node_opts_flag(node->nd_body, node->nd_else);
			write_node(node->nd_cond);
			write_node_opts(node->nd_body);
			write_node_opts(node->nd_else);
			break;
		}
		case NODE_IVAR:
		{
			write_id(node->nd_vid);
			break;
		}
		case NODE_LASGN:
		{
			write_id(node->nd_vid);
			write_node_opt(node->nd_value);
			break;
		}
		case NODE_EVSTR:
		{
			write_literal(node->nd_lit);
			break;
		}
		case NODE_XSTR:
		{
			write_literal(node->nd_lit);
			break;
		}
		case NODE_LIT:
		{
			write_literal(node->nd_lit);
			break;
		}
		case NODE_LVAR:
		{
			write_id(node->nd_vid);
			break;
		}
		case NODE_MASGN:
		{
			write_node_opts_flag(node->nd_head, node->nd_value);
			write_node_opts(node->nd_head);
			write_node_opts(node->nd_value);
			if ( (int)node->nd_args == 0xFFFFFFFF ) {
				write_byte(1);
			} else {
				write_byte(0);
				write_node_opt(node->nd_args);
			}
			break;
		}
		case NODE_MATCH:
		{
			write_node(node->nd_head);
			break;
		}
		case NODE_MATCH2:
		{
			write_node(node->nd_recv);
			write_node(node->nd_value);
			break;
		}
		case NODE_MATCH3:
		{
			write_node(node->nd_recv);
			write_node(node->nd_value);
			break;
		}
		case NODE_METHOD:
		{
			m_debug.raise("NODE_METHOD is not implemented");
			break;
		}
		case NODE_MODULE:
		{
			write_node(node->nd_cpath);
			write_node_opt(node->nd_body->nd_next);
			break;
		}
		case NODE_NEWLINE:
		{
			write_word(node->nd_nth);
			write_node(node->nd_next);
			break;
		}
		case NODE_NEXT:
		{
			write_node_opt(node->nd_stts);
			break;
		}
		case NODE_NIL:
		{
			break;
		}
		case NODE_NOT:
		{
			write_node(node->nd_body);
			break;
		}
		case NODE_NTH_REF:
		{
			write_byte(node->nd_nth);
			write_dword(node->nd_cnt);
			break;
		}
		case NODE_OP_ASGN1:
		{
			write_node(node->nd_recv);
			write_node(node->nd_args);
			if ( node->nd_mid == 0 || node->nd_mid == 1 ) {
				write_byte(1);
				write_byte(node->nd_mid);
			} else {
				write_byte(0);
				write_id(node->nd_mid);
			}
			break;
		}
		case NODE_OP_ASGN2:
		{
			write_node(node->nd_recv);
			write_node(node->nd_value);
			write_id(node->nd_next->nd_vid);
			write_id(node->nd_next->nd_aid);
			if ( node->nd_next->nd_mid == 0 || node->nd_next->nd_mid == 1 ) {
				write_byte(1);
				write_byte(node->nd_next->nd_mid);
			} else {
				write_byte(0);
				write_id(node->nd_next->nd_mid);
			}
			break;
		}
		case NODE_OP_ASGN_AND:
		case NODE_OP_ASGN_OR:
		{
			write_node(node->nd_head);
			write_node(node->nd_value);
			break;
		}
		case NODE_OPT_N:
		{
			m_debug.raise("NODE_OPT_N is not implemented");
			break;
		}
		case NODE_POSTEXE:
		{
			break;
		}
		case NODE_REDO:
		{
			break;
		}
		case NODE_RESCUE:
		{
			write_node(node->nd_head);
			write_node(node->nd_resq);
			write_node_opt(node->nd_else);
			break;
		}
		case NODE_RESBODY:
		{
			write_node_opts_flag(node->nd_body, node->nd_args, node->nd_head);
			write_node_opts(node->nd_body);
			write_node_opts(node->nd_args);
			write_node_opts(node->nd_head);
			break;
		}
/*
		case NODE_RESTARGS:
		{
			write_node(node->nd_head);
			break;
		}
*/
		case NODE_RETRY:
		{
			break;
		}
		case NODE_RETURN:
		{
			write_node_opt(node->nd_stts);
			break;
		}
		case NODE_SCLASS:
		{
			write_node(node->nd_recv);
			write_node(node->nd_body->nd_next);
			break;
		}
		case NODE_SCOPE:
		{
			if ( node->nd_tbl &&
				 node->nd_next && nd_type(node->nd_next) == NODE_BLOCK &&
				 node->nd_next->nd_head && nd_type(node->nd_next->nd_head) == NODE_ARGS ) {
				write_word(node->nd_tbl[0] - 2);
				for ( int i = 3; i <= node->nd_tbl[0]; i++ ) {
					write_id(node->nd_tbl[i]);
				}
			} else {
				write_word(0);
			}

			write_node_opt(node->nd_next);
			break;
		}
		case NODE_SELF:
		{
			break;
		}
		case NODE_STR:
		{
			write_string(node->nd_lit);
			break;    
		}
		case NODE_SUPER:
		{
			write_node_opt(node->nd_args);
			break;
		}
		case NODE_TRUE:
		{
			break;
		}
		case NODE_UNDEF:
		{
			write_id(node->nd_mid);
			break;
		}
		case NODE_UNTIL:
		{
			write_node(node->nd_cond);
			write_node_opt(node->nd_body);
			write_byte(node->nd_state);
			break;
		}
		case NODE_WHILE:
		{
			write_node(node->nd_cond);
			write_node_opt(node->nd_body);
			write_byte(node->nd_state);
			break;
		}
		case NODE_VCALL:
		{
			write_id(node->nd_mid);
			break;
		}
		case NODE_YIELD:
		{
			write_node_opt(node->nd_stts);
			write_dword(node->nd_state);
			break;
		}
		case NODE_ZARRAY:
		{
			break;
		}
		case NODE_ZSUPER:
		{
			break;
		}

		case NODE_ATTRASGN:
		{
			write_id(node->nd_mid);
			write_node(node->nd_recv);
			write_node_opt(node->nd_args);
			break;
		}
		case NODE_DSYM:
		{
			m_debug.raise("NODE_DSYM is not implemented");
			break;
		}
		case NODE_RESTARY:
		{
			m_debug.raise("NODE_RESTARY is not implemented");
			break;
		}
		case NODE_RESTARY2:
		{
			write_node(node->nd_head);
			break;
		}
		case NODE_SPLAT:
		{
			m_debug.raise("NODE_SPLAT is not implemented");
			break;
		}
		case NODE_SVALUE:
		{
			m_debug.raise("NODE_SVALUE is not implemented");
			break;
		}

		default:
		{
#ifdef DEBUG
			counter.print_stack();
#endif
			m_debug.raise("[BUG] Unknown node type -- 0x%02X", type);
			break;
		}
	}
}

inline void
BRbWriteNode::write_node_opt(NODE* node)
{
	if ( node ) {
		write_byte(0xFC);
		write_node(node);
	} else {
		write_byte(0xFD);
	}
}

inline void
BRbWriteNode::write_node_opts(NODE* node)
{
	if ( node ) {
		write_node(node);
	}
}

inline void
BRbWriteNode::write_node_opts_flag(NODE* n1, NODE* n2)
{
	write_byte((n1 ? 0xF1 : 0) | (n2 ? 0xF2 : 0));
}

inline void
BRbWriteNode::write_node_opts_flag(NODE* n1, NODE* n2, NODE* n3)
{
	write_byte((n1 ? 0xF1 : 0) | (n2 ? 0xF2 : 0) | (n3 ? 0xF4 : 0));
}

inline void
BRbWriteNode::write_byte(byte value)
{
	m_ndbuf.write_byte(value);
}

inline void
BRbWriteNode::write_word(word value)
{
	m_ndbuf.write_word(value);
}

inline void
BRbWriteNode::write_dword(dword value)
{
	m_ndbuf.write_dword(value);
}

inline void
BRbWriteNode::write_cstr(const char* str)
{
	m_ndbuf.write_word(m_strtbl.set(str));
}

inline void
BRbWriteNode::write_cstr(const char* str, word len)
{
	m_ndbuf.write_word(m_strtbl.set(str, len));
}

inline void
BRbWriteNode::write_string(VALUE str)
{
	write_cstr(RSTRING(str)->ptr, (word)RSTRING(str)->len);
}

inline void
BRbWriteNode::write_id(ID id)
{
	write_cstr(::rb_id2name(id));
}

inline void
BRbWriteNode::write_literal(VALUE obj)
{
	if ( FIXNUM_P(obj) ) {
		write_byte(0);
		write_dword(obj);
	} else if ( SYMBOL_P(obj) ) {
		write_byte(1);
		write_id(SYM2ID(obj));
	} else if ( TYPE(obj) == T_STRING ) {
		write_byte(2);
		write_string(obj);
	} else {
		write_byte(3);
		write_object(obj);
	}
}

inline void
BRbWriteNode::write_object(VALUE obj)
{
	static VALUE mod = ::rb_eval_string("::Marshal");
	static ID    mid = ::rb_intern("dump");
	write_string(::rb_funcall(mod, mid, 1, obj));
}

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

#endif

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