/******************************************************************************
  Copyright (c) 1994, 1995, 1996 Xerox Corporation.  All rights reserved.
  Portions of this code were written by Stephen White, aka ghond.
  Use and copying of this software and preparation of derivative works based
  upon this software are permitted.  Any distribution of this software or
  derivative works must comply with all applicable United States export
  control laws.  This software is made available AS IS, and Xerox Corporation
  makes no warranty about the software, its performance or its conformity to
  any specification.  Any person obtaining a copy of this software is requested
  to send their name and post office or electronic mail address to:
    Pavel Curtis
    Xerox PARC
    3333 Coyote Hill Rd.
    Palo Alto, CA 94304
    Pavel@Xerox.Com
 *****************************************************************************/

#include "my-stdio.h"

#include "bf_register.h"
#include "config.h"
#include "db.h"
#include "functions.h"
#include "list.h"
#include "opcode.h"
#include "program.h"
#include "storage.h"
#include "streams.h"
#include "unparse.h"
#include "utils.h"
#include "verbs.h"

const char *mnemonics[256], *ext_mnemonics[256];

struct mapping {
    unsigned value;
    const char *name;
};

struct mapping mappings[] =
{
    {OP_IF, "IF"},
    {OP_WHILE, "WHILE"},
    {OP_EIF, "ELSEIF"},
    {OP_FORK, "FORK"},
    {OP_FORK_WITH_ID, "FORK_NAMED"},
    {OP_FOR_LIST, "FOR_LIST"},
    {OP_FOR_RANGE, "FOR_RANGE"},
    {OP_INDEXSET, "INDEXSET"},
    {OP_PUSH_GET_PROP, "PUSH_GET_PROP"},
    {OP_GET_PROP, "GET_PROP"},
    {OP_CALL_VERB, "CALL_VERB"},
    {OP_PUT_PROP, "PUT_PROP"},
    {OP_BI_FUNC_CALL, "CALL_FUNC"},
    {OP_IF_QUES, "IF_EXPR"},
    {OP_REF, "INDEX"},
    {OP_RANGE_REF, "RANGE"},
    {OP_MAKE_SINGLETON_LIST, "MAKE_SINGLETON_LIST"},
    {OP_CHECK_LIST_FOR_SPLICE, "CHECK_LIST_FOR_SPLICE"},
    {OP_MULT, "MULTIPLY"},
    {OP_DIV, "DIVIDE"},
    {OP_MOD, "MOD"},
    {OP_ADD, "ADD"},
    {OP_MINUS, "SUBTRACT"},
    {OP_EQ, "EQ"},
    {OP_NE, "NE"},
    {OP_LT, "LT"},
    {OP_LE, "LE"},
    {OP_GT, "GT"},
    {OP_GE, "GE"},
    {OP_IN, "IN"},
    {OP_AND, "AND"},
    {OP_OR, "OR"},
    {OP_UNARY_MINUS, "NEGATE"},
    {OP_NOT, "NOT"},
    {OP_G_PUT, "PUT"},
    {OP_G_PUSH, "PUSH"},
#ifdef BYTECODE_REDUCE_REF
    {OP_G_PUSH_CLEAR, "PUSH_CLEAR"},
#endif /* BYTECODE_REDUCE_REF */
    {OP_IMM, "PUSH_LITERAL"},
    {OP_MAKE_EMPTY_LIST, "MAKE_EMPTY_LIST"},
    {OP_LIST_ADD_TAIL, "LIST_ADD_TAIL"},
    {OP_LIST_APPEND, "LIST_APPEND"},
    {OP_PUSH_REF, "PUSH_INDEX"},
    {OP_PUT_TEMP, "PUT_TEMP"},
    {OP_PUSH_TEMP, "PUSH_TEMP"},
    {OP_JUMP, "JUMP"},
    {OP_RETURN, "RETURN"},
    {OP_RETURN0, "RETURN 0"},
    {OP_DONE, "DONE"},
    {OP_POP, "POP"}};

struct mapping ext_mappings[] =
{
    {EOP_RANGESET, "RANGESET"},
    {EOP_LENGTH, "LENGTH"},
    {EOP_PUSH_LABEL, "PUSH_LABEL"},
    {EOP_SCATTER, "SCATTER"},
    {EOP_EXP, "EXPONENT"},
    {EOP_CATCH, "CATCH"},
    {EOP_END_CATCH, "END_CATCH"},
    {EOP_TRY_EXCEPT, "TRY_EXCEPT"},
    {EOP_END_EXCEPT, "END_EXCEPT"},
    {EOP_TRY_FINALLY, "TRY_FINALLY"},
    {EOP_END_FINALLY, "END_FINALLY"},
    {EOP_CONTINUE, "CONTINUE"},
    {EOP_WHILE_ID, "WHILE_ID"},
    {EOP_EXIT, "EXIT"},
    {EOP_EXIT_ID, "EXIT_ID"},
    {EOP_MAKE_HASH, "MAKE_HASH"},
    {EOP_MAKE_HASHENTRY, "MAKE_HASHENTRY"},
    {EOP_YIELD, "YIELD"},
    {EOP_YIELD0, "YIELD DEFAULT_YIELD_TIME"}
};

static void
initialize_tables(void)
{
    static int tables_initialized = 0;
    unsigned i;

    if (tables_initialized)
	return;

    for (i = 0; i < 256; i++) {
	mnemonics[i] = "*** Unknown opcode ***";
	ext_mnemonics[i] = "*** Unknown extended opcode ***";
    }

    for (i = 0; i < Arraysize(mappings); i++)
	mnemonics[mappings[i].value] = mappings[i].name;

    for (i = 0; i < Arraysize(ext_mappings); i++)
	ext_mnemonics[ext_mappings[i].value] = ext_mappings[i].name;

    tables_initialized = 1;
}

typedef void (*Printer) (const char *, void *);
static Printer print;
static void *print_data;
static int bytes_width, max_bytes_width;

static void
output(Stream * s)
{
    (*print) (reset_stream(s), print_data);
}

static void
new_insn(Stream * s, unsigned pc)
{
    stream_printf(s, "%3d:", pc);
    bytes_width = max_bytes_width;
}

static unsigned
add_bytes(Stream * s, Byte * vector, unsigned pc, unsigned length)
{
    unsigned arg = 0, b;

    while (length--) {
	if (bytes_width == 0) {
	    output(s);
	    stream_add_string(s, "    ");
	    bytes_width = max_bytes_width;
	}
	b = vector[pc++];
	stream_printf(s, " %03d", b);
	arg = (arg << 8) + b;
	bytes_width--;
    }

    return arg;
}

static void
finish_insn(Stream * s, Stream * insn)
{
    while (bytes_width--)
	stream_add_string(s, "    ");
    stream_add_string(s, reset_stream(insn));
    output(s);
}

static void
disassemble(Program * prog, Printer p, void *data)
{
    Stream *s = new_stream(100);
    Stream *insn = new_stream(50);
    int i, l;
    unsigned pc;
    Bytecodes bc;
    const char *ptr;
    const char **names = prog->var_names;
    unsigned tmp, num_names = prog->num_var_names;
#   define NAMES(i)	(tmp = i,					\
			 tmp < num_names ? names[tmp]			\
					 : "*** Unknown variable ***")
    Var *literals = prog->literals;

    initialize_tables();
    print = p;
    print_data = data;
    stream_printf(s, "Language version number: %d", (int) prog->version);
    output(s);
    stream_printf(s, "First line number: %d", prog->first_lineno);
    output(s);

    for (i = -1; i < 0 || i < prog->fork_vectors_size; i++) {
	output(s);
	if (i == -1) {
	    stream_printf(s, "Main code vector:");
	    output(s);
	    stream_printf(s, "=================");
	    output(s);
	    bc = prog->main_vector;
	} else {
	    stream_printf(s, "Forked code vector %d:", i);
	    l = stream_length(s);
	    output(s);
	    while (l--)
		stream_add_char(s, '=');
	    output(s);
	    bc = prog->fork_vectors[i];
	}

	stream_printf(s, "[Bytes for labels = %d, literals = %d, ",
		      bc.numbytes_label, bc.numbytes_literal);
	stream_printf(s, "forks = %d, variables = %d, stack refs = %d]",
		      bc.numbytes_fork, bc.numbytes_var_name,
		      bc.numbytes_stack);
	output(s);
	stream_printf(s, "[Maximum stack size = %d]", bc.max_stack);
	output(s);

	max_bytes_width = 5;

	for (pc = 0; pc < bc.size;) {
	    Byte b;
	    unsigned arg;
#	    define ADD_BYTES(n)	(arg = add_bytes(s, bc.vector, pc, n),	\
				 pc += n,				\
				 arg)
	    unsigned a1, a2;

	    new_insn(s, pc);
	    b = add_bytes(s, bc.vector, pc++, 1);
	    if (b != OP_EXTENDED)
		stream_add_string(insn, COUNT_TICK(b) ? " * " : "   ");
	    if (IS_OPTIM_NUM_OPCODE(b))
		stream_printf(insn, "NUM %d", OPCODE_TO_OPTIM_NUM(b));
#ifdef BYTECODE_REDUCE_REF
	    else if (IS_PUSH_CLEAR_n(b))
		stream_printf(insn, "PUSH_CLEAR %s", NAMES(PUSH_CLEAR_n_INDEX(b)));
#endif /* BYTECODE_REDUCE_REF */
	    else if (IS_PUSH_n(b))
		stream_printf(insn, "PUSH %s", NAMES(PUSH_n_INDEX(b)));
	    else if (IS_PUT_n(b))
		stream_printf(insn, "PUT %s", NAMES(PUT_n_INDEX(b)));
	    else if (b == OP_EXTENDED) {
		b = ADD_BYTES(1);
		stream_add_string(insn, COUNT_EOP_TICK(b) ? " * " : "   ");
		stream_add_string(insn, ext_mnemonics[b]);
		switch ((Extended_Opcode) b) {
		case EOP_WHILE_ID:
		    a1 = ADD_BYTES(bc.numbytes_var_name);
		    a2 = ADD_BYTES(bc.numbytes_label);
		    stream_printf(insn, " %s %d", NAMES(a1), a2);
		    break;
		case EOP_EXIT_ID:
		    stream_printf(insn, " %s",
				  NAMES(ADD_BYTES(bc.numbytes_var_name)));
		    /* fall thru */
		case EOP_EXIT:
		    a1 = ADD_BYTES(bc.numbytes_stack);
		    a2 = ADD_BYTES(bc.numbytes_label);
		    stream_printf(insn, " %d %d", a1, a2);
		    break;
		case EOP_PUSH_LABEL:
		case EOP_END_CATCH:
		case EOP_END_EXCEPT:
		case EOP_TRY_FINALLY:
		    stream_printf(insn, " %d", ADD_BYTES(bc.numbytes_label));
		    break;
		case EOP_TRY_EXCEPT:
		    stream_printf(insn, " %d", ADD_BYTES(1));
		    break;
		case EOP_LENGTH:
		    stream_printf(insn, " %d", ADD_BYTES(bc.numbytes_stack));
		    break;
		case EOP_SCATTER:
		    {
			int i, nargs = ADD_BYTES(1);

			a1 = ADD_BYTES(1);
			a2 = ADD_BYTES(1);
			stream_printf(insn, " %d/%d/%d:", nargs, a1, a2);
			for (i = 0; i < nargs; i++) {
			    a1 = ADD_BYTES(bc.numbytes_var_name);
			    a2 = ADD_BYTES(bc.numbytes_label);
			    stream_printf(insn, " %s/%d", NAMES(a1), a2);
			}
			stream_printf(insn, " %d",
				      ADD_BYTES(bc.numbytes_label));
		    }
		    break;
		default:
		    break;
		}
	    } else {
		stream_add_string(insn, mnemonics[b]);
		switch ((Opcode) b) {
		case OP_IF:
		case OP_IF_QUES:
		case OP_EIF:
		case OP_AND:
		case OP_OR:
		case OP_JUMP:
		case OP_WHILE:
		    stream_printf(insn, " %d", ADD_BYTES(bc.numbytes_label));
		    break;
		case OP_FORK:
		    stream_printf(insn, " %d", ADD_BYTES(bc.numbytes_fork));
		    break;
		case OP_FORK_WITH_ID:
		    a1 = ADD_BYTES(bc.numbytes_fork);
		    a2 = ADD_BYTES(bc.numbytes_var_name);
		    stream_printf(insn, " %d %s", a1, NAMES(a2));
		    break;
		case OP_FOR_LIST:
		case OP_FOR_RANGE:
		    a1 = ADD_BYTES(bc.numbytes_var_name);
		    a2 = ADD_BYTES(bc.numbytes_label);
		    stream_printf(insn, " %s %d", NAMES(a1), a2);
		    break;
		case OP_G_PUSH:
#ifdef BYTECODE_REDUCE_REF
		case OP_G_PUSH_CLEAR:
#endif /* BYTECODE_REDUCE_REF */
		case OP_G_PUT:
		    stream_printf(insn, " %s",
				  NAMES(ADD_BYTES(bc.numbytes_var_name)));
		    break;
		case OP_IMM:
		    {
			Var v;

			v = literals[ADD_BYTES(bc.numbytes_literal)];
			switch (v.type) {
			case TYPE_OBJ:
			    stream_printf(insn, " #%d", v.v.obj);
			    break;
			case TYPE_INT:
			    stream_printf(insn, " %d", v.v.num);
			    break;
			case TYPE_STR:
			    stream_add_string(insn, " \"");
			    for (ptr = v.v.str; *ptr; ptr++) {
				if (*ptr == '"' || *ptr == '\\')
				    stream_add_char(insn, '\\');
				stream_add_char(insn, *ptr);
			    }
			    stream_add_char(insn, '"');
			    break;
			case TYPE_ERR:
			    stream_printf(insn, " %s", error_name(v.v.err));
			    break;
			default:
			    stream_printf(insn, " <literal type = %d>",
					  v.type);
			    break;
			}
		    }
		    break;
		case OP_BI_FUNC_CALL:
		    stream_printf(insn, " %s", name_func_by_num(ADD_BYTES(1)));
		default:
		    break;
		}
	    }

	    finish_insn(s, insn);
	}
    }

    free_stream(s);
    free_stream(insn);
}

static void
print_line(const char *line, void *data)
{
    FILE *f = data;

    fprintf(f, "%s\n", line);
}

void
disassemble_to_file(FILE * fp, Program * prog)
{
    disassemble(prog, print_line, fp);
}

void
disassemble_to_stderr(Program * prog)
{
    disassemble_to_file(stderr, prog);
}

struct data {
    char **lines;
    int used, max;
};

static void
add_line(const char *line, void *data)
{
    struct data *d = data;

    if (d->used >= d->max) {
	int new_max = (d->max == 0 ? 20 : d->max * 2);
	char **new = mymalloc(sizeof(char **) * new_max, M_DISASSEMBLE);
	int i;

	for (i = 0; i < d->used; i++)
	    new[i] = d->lines[i];
	if (d->lines)
	    myfree(d->lines, M_DISASSEMBLE);
	d->lines = new;
	d->max = new_max;
    }
    d->lines[d->used++] = str_dup(line);
}

static package
bf_disassemble(Var arglist, Byte next, void *vdata, Objid progr)
{
    Objid oid = arglist.v.list[1].v.obj;
    Var desc = arglist.v.list[2];
    db_verb_handle h;
    struct data data;
    Var r;
    int i;
    enum error e;

    if ((e = validate_verb_descriptor(desc)) != E_NONE
	|| (e = E_INVARG, !valid(oid))) {
	free_var(arglist);
	return make_error_pack(e);
    }
    h = find_described_verb(oid, desc);
    free_var(arglist);

    if (!h.ptr)
	return make_error_pack(E_VERBNF);
    if (!db_verb_allows(h, progr, VF_READ))
	return make_error_pack(E_PERM);

    data.lines = 0;
    data.used = data.max = 0;
    disassemble(db_verb_program(h), add_line, &data);
    r = new_list(data.used);
    for (i = 1; i <= data.used; i++) {
	r.v.list[i].type = TYPE_STR;
	r.v.list[i].v.str = data.lines[i - 1];
    }
    if (data.lines)
	myfree(data.lines, M_DISASSEMBLE);
    return make_var_pack(r);
}

void
register_disassemble(void)
{
    register_function("disassemble", 2, 2, bf_disassemble, TYPE_OBJ, TYPE_ANY);
}

char rcsid_disassemble[] = "$Id";

/* 
 * $Log: disassemble.c,v $
 * Revision 1.4  2009/03/27 20:26:49  blacklite
 * add optional argument to YIELD statement, make no-arg version into YIELD0 expression/op. add newer ops/exprs to disassembly. handle PF_PRIVATE in execute. make some vars 'register' in execute.
 *
 * Revision 1.3  2007/09/12 07:33:29  spunky
 * This is a working version of the current HellMOO server
 *
 * Revision 1.3  1998/12/14 13:17:42  nop
 * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims
 *
 * Revision 1.2.2.1  1997/09/09 07:01:16  bjj
 * Change bytecode generation so that x=f(x) calls f() without holding a ref
 * to the value of x in the variable slot.  See the options.h comment for
 * BYTECODE_REDUCE_REF for more details.
 *
 * This checkin also makes x[y]=z (OP_INDEXSET) take advantage of that (that
 * new code is not conditional and still works either way).
 *
 * Revision 1.2  1997/03/03 04:18:34  nop
 * GNU Indent normalization
 *
 * Revision 1.1.1.1  1997/03/03 03:44:59  nop
 * LambdaMOO 1.8.0p5
 *
 * Revision 2.6  1996/04/08  01:09:40  pavel
 * Added missing mnemonic-table entry for EOP_PUSH_LABEL.  Release 1.8.0p3.
 *
 * Revision 2.5  1996/02/08  07:15:40  pavel
 * Added support for exponentiation expression, named WHILE loop, and BREAK
 * and CONTINUE statements.  Removed redundant error-value unparsing code.
 * Added printing of language version number.  Added support for EOPs that
 * cost ticks.  Renamed TYPE_NUM to TYPE_INT.  Added disassemble_to_file().
 * Updated copyright notice for 1996.  Release 1.8.0beta1.
 *
 * Revision 2.4  1996/01/16  07:16:38  pavel
 * Add EOP_SCATTER handling.  Added support for line-wrapping in the display
 * of the bytes of an instruction.  Release 1.8.0alpha6.
 *
 * Revision 2.3  1995/12/31  00:07:49  pavel
 * Added support for EOP_LENGTH and stack references in general.
 * Release 1.8.0alpha4.
 *
 * Revision 2.2  1995/12/28  00:31:29  pavel
 * Added support for numeric verb descriptors in disassemble() built-in.
 * Release 1.8.0alpha3.
 *
 * Revision 2.1  1995/12/11  08:10:32  pavel
 * Accounted for verb programs never being NULL any more.
 * Release 1.8.0alpha2.
 *
 * Revision 2.0  1995/11/30  05:00:54  pavel
 * New baseline version, corresponding to release 1.8.0alpha1.
 *
 * Revision 1.1  1995/11/30  05:00:43  pavel
 * Initial revision
 */
