Milán Major

netscape-revival

1 branch
Code

mocha/src/mo_bcode.c

/*
** Mocha bytecode descriptors, disassemblers, and decompilers.
**
** Brendan Eich, 6/24/95
*/
#include <ctype.h>
#include <memory.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "prarena.h"
#include "prlog.h"
#include "prmem.h"
#include "prprf.h"
#include "alloca.h"
#include "mo_atom.h"
#include "mo_bcode.h"
#include "mo_cntxt.h"
#include "mo_emit.h"
#include "mochaapi.h"

char mocha_new[]    = "new";
#ifdef MOCHA_HAS_DELETE_OPERATOR
char mocha_delete[] = "delete";
#endif
char mocha_typeof[] = "typeof";
char mocha_void[]   = "void";
char mocha_null[]   = "null";
char mocha_this[]   = "this";
char mocha_false[]  = "false";
char mocha_true[]   = "true";

/* Pollute the namespace locally for Win16 :-(  */
#ifndef FAR
#define FAR 
#endif

MochaCodeSpec FAR mocha_CodeSpec[] = {
#define MOPDEF(op,val,name,image,length,nuses,ndefs,pretty,format) \
    {name,image,length,nuses,ndefs,pretty,format},
#include "mocha.def"
#undef MOPDEF
};

unsigned mocha_NumCodeSpecs = sizeof mocha_CodeSpec
			    / sizeof mocha_CodeSpec[0];

/* ------------------------------------------------------------------------ */

#ifdef DEBUG

void
mocha_Disassemble(MochaContext *mc, MochaScript *script, FILE *fp)
{
    MochaCode *pc, *end;
    unsigned len;

    pc = script->code;
    end = pc + script->length;
    while (pc < end) {
	len = mocha_Disassemble1(mc, script, pc, pc - script->code, fp);
	pc += len;
    }
}

unsigned
mocha_Disassemble1(MochaContext *mc, MochaScript *script, MochaCode *pc,
		   unsigned loc, FILE *fp)
{
    MochaOp op;
    MochaCodeSpec *cs;
    int off;
    MochaAtom *atom;

    op = *pc;
    if (op >= MOP_MAX) {
	MOCHA_ReportError(mc, "bytecode %d too large", op);
	return 0;
    }
    cs = &mocha_CodeSpec[op];
    fprintf(fp, "%05u:  %s", loc, cs->name);
    switch (cs->format) {
      case MOF_BYTE:
	break;
      case MOF_JUMP:
	off = GET_JUMP_OFFSET(pc);
	fprintf(fp, " %u (%d)", loc + off, off);
	break;
      case MOF_INCOP:
	fprintf(fp, " (%s)", pc[1] ? "post" : "pre");
	break;
      case MOF_ARGC:
	fprintf(fp, " %u", pc[1]);
	break;
      case MOF_CONST:
	atom = GET_CONST_ATOM(mc, script, pc);
	fprintf(fp, (op == MOP_STRING) ? " \"%s\"" : " %s", atom_name(atom));
	break;
      default:
	MOCHA_ReportError(mc, "unknown bytecode format %d", cs->format);
	return 0;
    }
    fputs("\n", fp);
    return cs->length;
}

#endif /* DEBUG */

/* ------------------------------------------------------------------------ */

/*
** Compute a worst-case format-converted string length.
*/
size_t
GuessFormatConversionSize(const char *format, va_list ap)
{
    size_t nb, len;
    const char *s, *t;
    unsigned width;

    nb = strlen(format) + 1;
    for (t = format; (t = strchr(t, '%')) != 0; t++) {
	if (*++t == '%')
	    continue;
	for (width = 0; *t && !isalpha(*t); t++) {
	    if (*t == '*')
		width = va_arg(ap, int);
	}
	if (*t == 's') {
	    s = va_arg(ap, char *);
	    len = s ? strlen(s) : 6;
	    if (len < width) len = width;
	    nb += len;
	} else if (*t == 'e' || *t == 'f' || *t == 'g') {
	    (void) va_arg(ap, double);
	    nb += 32;
	} else if (*t == 'l') {
	    (void) va_arg(ap, long);
	    nb += 32;
	} else {
	    (void) va_arg(ap, int);
	    nb += 16;
	}
    }
    return nb;
}

/*
** Sprintf, but with unlimited and automatically allocated buffering.
*/
typedef struct Sprinter {
    MochaContext    *context;       /* recursion invariant argument */
    PRArenaPool     *pool;          /* string allocation pool */
    char            *base;          /* base address of buffer in pool */
    size_t          size;           /* size of buffer allocated at base */
    ptrdiff_t       offset;         /* offset of next free char in buffer */
} Sprinter;

#define INIT_SPRINTER(mc, sp, ap, off) \
    ((sp)->context = mc, (sp)->pool = ap, (sp)->base = 0, (sp)->size = 0, \
     (sp)->offset = off)

#define OFF2STR(sp,off) ((sp)->base + (off))
#define STR2OFF(sp,str) ((str) - (sp)->base)

static MochaBoolean
SprintAlloc(Sprinter *sp, size_t nb)
{
    if (!sp->base) {
	PR_ARENA_ALLOCATE(sp->base, sp->pool, nb);
    } else {
	PR_ARENA_GROW(sp->base, sp->pool, sp->size, nb);
    }
    if (!sp->base) {
	MOCHA_ReportOutOfMemory(sp->context);
	return MOCHA_FALSE;
    }
    sp->size += nb;
    return MOCHA_TRUE;
}

static ptrdiff_t
SprintPut(Sprinter *sp, const char *s, size_t len)
{
    ptrdiff_t nb, offset;
    char *bp;

    /* Allocate space for s, including the '\0' at the end. */
    nb = (sp->offset + len + 1) - sp->size;
    if (nb > 0 && !SprintAlloc(sp, nb))
	return -1;

    /* Advance offset and copy s into sp's buffer. */
    offset = sp->offset;
    sp->offset += len;
    bp = sp->base + offset;
    memcpy(bp, s, len);
    bp[len] = '\0';
    return offset;
}

static ptrdiff_t
Sprint(Sprinter *sp, const char *format, ...)
{
    va_list ap;
    int nb, cc;
    char *bp;

    va_start(ap, format);
    nb = GuessFormatConversionSize(format, ap);
    bp = alloca(nb);
    cc = PR_vsnprintf(bp, nb, format, ap);
    va_end(ap);
    if (cc < 0)
	return cc;
    return SprintPut(sp, bp, cc);
}

static char escapeMap[] = "\bb\ff\nn\rr\tt\vv\"\"";

static char *
EscapeString(Sprinter *sp, const char *s)
{
    ptrdiff_t offset;
    const char *t, *u;
    char c;
    MochaBoolean ok;

    offset = sp->offset;
    t = s;
    do {
	while (isprint(*t) && *t != '"')
	    t++;
	if (SprintPut(sp, s, t - s) < 0)
	    return 0;
	if ((c = *t) == '\0')
	    break;
	if ((u = strchr(escapeMap, c)) != 0)
	    ok = Sprint(sp, "\\%c", u[1]) >= 0;
	else
	    ok = Sprint(sp, "\\%03o", c) >= 0;
	if (!ok)
	    return 0;
    } while (*(s = ++t) != '\0');
    return OFF2STR(sp, offset);
}

/* ------------------------------------------------------------------------ */

struct MochaPrinter {
    Sprinter        sprinter;       /* base class state */
    PRArenaPool     pool;           /* string allocation pool */
    unsigned        indent;         /* indentation in spaces */
    MochaScript     *script;        /* script being printed */
};

MochaPrinter *
mocha_NewPrinter(MochaContext *mc, const char *name, unsigned indent)
{
    MochaPrinter *mp;

    mp = PR_NEW(MochaPrinter);
    if (!mp) {
	MOCHA_ReportOutOfMemory(mc);
	return 0;
    }
    INIT_SPRINTER(mc, &mp->sprinter, &mp->pool, 0);
    PR_InitArenaPool(&mp->pool, name, 256, 1);
    mp->indent = indent;
    mp->script = 0;
    return mp;
}

MochaBoolean
mocha_GetPrinterOutput(MochaPrinter *mp, char **sp)
{
    char *str;

    if (!mp->sprinter.base)
	return MOCHA_TRUE;
    str = MOCHA_strdup(mp->sprinter.context, mp->sprinter.base);
    if (!str)
	return MOCHA_FALSE;
    *sp = str;
    return MOCHA_TRUE;
}

void
mocha_DestroyPrinter(MochaPrinter *mp)
{
    PR_FinishArenaPool(&mp->pool);
    PR_DELETE(mp);
}

int
mocha_printf(MochaPrinter *mp, char *format, ...)
{
    va_list ap;
    int nb, cc;
    char *bp;

    va_start(ap, format);

    /* Expand magic tab into a run of mp->indent spaces. */
    if (*format == '\t') {
	if (Sprint(&mp->sprinter, "%*s", mp->indent, "") < 0)
	    return -1;
	format++;
    }

    /* Allocate temp space, convert format, and put. */
    nb = GuessFormatConversionSize(format, ap);
    bp = (char *)alloca(nb);
    cc = PR_vsnprintf(bp, nb, format, ap);
    if (cc > 0 && SprintPut(&mp->sprinter, bp, cc) < 0)
	return -1;

    va_end(ap);
    return cc;
}

MochaBoolean
mocha_puts(MochaPrinter *mp, char *s)
{
    return SprintPut(&mp->sprinter, s, strlen(s)) >= 0;
}

/* ------------------------------------------------------------------------ */

typedef struct SprintStack {
    Sprinter    sprinter;       /* base class state */
    ptrdiff_t   *offsets;       /* stack of postfix string offsets */
    MochaCode   *opcodes;       /* parallel stack of Mocha opcodes */
    unsigned    top;            /* top of stack index */
} SprintStack;

/* Gap between stacked strings to allow for insertion of parens and commas. */
#define PARENSLOP       (2 + 1)

static MochaBoolean
PushOff(SprintStack *ss, ptrdiff_t off, MochaOp op)
{
    if (!SprintAlloc(&ss->sprinter, PARENSLOP))
	return MOCHA_FALSE;
    ss->offsets[ss->top] = off;
    ss->opcodes[ss->top++] = op;
    ss->sprinter.offset += PARENSLOP;
    return MOCHA_TRUE;
}

static ptrdiff_t
PopOff(SprintStack *ss, MochaOp op)
{
    ptrdiff_t off;

    if (ss->opcodes[--ss->top] < (MochaCode)op) {
	ss->offsets[ss->top] -= 2;
	ss->sprinter.offset = ss->offsets[ss->top];
	off = Sprint(&ss->sprinter, "(%s)",
		     OFF2STR(&ss->sprinter, ss->sprinter.offset + 2));
    } else {
	off = ss->sprinter.offset = ss->offsets[ss->top];
    }
    return off;
}

static MochaBoolean
Decompile(MochaCode *pc, int nb, SprintStack *ss, MochaPrinter *mp)
{
    unsigned len;
    MochaCode *end, *ifeq, *ifne, *done;
    ptrdiff_t todo, cond, next, tail;
    MochaOp op, lastop;
    MochaCodeSpec *cs;
    SourceNote *sn;
    char *lval, *rval, **argv;
    int i, argc;
    MochaAtom *atom;
    MochaBoolean ok;

/*
** Local macros
*/
#define DECOMPILE_CODE(pc,nb)	if (!Decompile(pc,nb,ss,mp)) return MOCHA_FALSE
#define POP_STR()		OFF2STR(&ss->sprinter, PopOff(ss, op))
#define LOCAL_ASSERT(expr)	PR_ASSERT(expr); if (!(expr)) return MOCHA_FALSE

    end = pc + nb;
    todo = -1;
    op = MOP_NOP;
    while (pc < end) {
	lastop = op;
	op = *pc;
	cs = &mocha_CodeSpec[op];
	len = cs->length;

	if (cs->pretty) {
	    switch (cs->nuses) {
	      case 2:
		rval = POP_STR();
		lval = POP_STR();
		if (cs->pretty == 2 &&
		    (sn = mocha_GetSourceNote(mp->script, pc)) &&
		    SN_TYPE(sn) == SRC_ASSIGNOP) {
		    /* Print only the right operand of the assignment-op. */
		    todo = Sprint(&ss->sprinter, "%s", rval);
		} else {
		    todo = Sprint(&ss->sprinter, "%s %s %s",
				  lval, cs->image, rval);
		}
		break;

	      case 1:
		rval = POP_STR();
		todo = Sprint(&ss->sprinter, "%s%s", cs->image, rval);
		break;

	      case 0:
		todo = Sprint(&ss->sprinter, "%s", cs->image);
		break;

	      default:
		todo = -1;
	    }
	} else {
	    switch (op) {
	      case MOP_NOP:
		/*
		** Check for extra user parenthesization, or a for-loop with
		** an empty initializer part.
		*/
		sn = mocha_GetSourceNote(mp->script, pc);
		todo = -1;
		switch (sn ? SN_TYPE(sn) : SRC_NULL) {
		  case SRC_PAREN:
		    /* Use last real op so PopOff adds parens if needed. */
		    op = lastop;
		    todo = PopOff(ss, op);

		    /* Now add user-supplied parens only if PopOff did not. */
		    if (ss->opcodes[ss->top] >= (MochaCode)op) {
			todo = Sprint(&ss->sprinter, "(%s)",
				      OFF2STR(&ss->sprinter, todo));
		    }

		    /* Set op so the next Pop won't add extra parens. */
		    op = MOP_MAX;
		    break;

		  case SRC_FOR:
		    rval = "";

		forloop:
		    /* Skip the MOP_NOP or MOP_POP bytecode. */
		    pc++;

		    /* Get the cond, next, and loop-closing tail offsets. */
		    cond = SN_OFFSET(&sn[1]);
		    next = SN_OFFSET(&sn[2]);
		    tail = SN_OFFSET(&sn[3]);
		    LOCAL_ASSERT(tail + GET_JUMP_OFFSET(pc + tail) == 0);

		    /* Print the keyword and the possibly empty init-part. */
		    mocha_printf(mp, "\tfor (%s;", rval);

		    if (pc[cond] == MOP_IFEQ) {
			/* Decompile the loop condition. */
			DECOMPILE_CODE(pc, cond);
			mocha_printf(mp, " %s", POP_STR());
		    }

		    /* Need a semicolon whether or not there was a cond. */
		    mocha_printf(mp, ";");

		    if (pc[next] != MOP_GOTO) {
			/* Decompile the loop updater. */
			DECOMPILE_CODE(pc + next, tail - next - 1);
			mocha_printf(mp, " %s", POP_STR());
		    }

		    /* Do the loop body. */
		    mocha_printf(mp, ") {\n");
		    mp->indent += 4;
		    DECOMPILE_CODE(pc + cond + 3, next - cond - 3);
		    mp->indent -= 4;
		    mocha_printf(mp, "\t}\n");

		    /* Set len so pc skips over the entire loop. */
		    len = tail + 3;
		    break;
		}
		break;

	      case MOP_PUSH:
		todo = Sprint(&ss->sprinter, "");
		break;

	      case MOP_POP:
		sn = mocha_GetSourceNote(mp->script, pc);
		switch (sn ? SN_TYPE(sn) : SRC_NULL) {
		  case SRC_FOR:
		    rval = POP_STR();
		    goto forloop;
		  case SRC_COMMA:
		    pc += len;
		    for (done = pc; pc < end && *pc != MOP_POP; pc += len)
			len = mocha_CodeSpec[*pc].length;
		    DECOMPILE_CODE(done, pc - done);
		    rval = POP_STR();
		    lval = POP_STR();
		    todo = Sprint(&ss->sprinter, "%s, %s", lval, rval);
		    len = 0;
		    break;
		  case SRC_HIDDEN:
		    /* hide this pop, it's for return nested in with/for-in */
		    break;
		  default:
		    op = MOP_NOP;	/* XXX should reorder bytecodes? */
		    rval = POP_STR();
		    if (*rval != '\0')
			mocha_printf(mp, "\t%s;\n", rval);
		    todo = -1;
		    break;
		}
		break;

	      case MOP_ENTER:
		rval = POP_STR();
		mocha_printf(mp, "\twith %s {\n", rval);
		mp->indent += 4;
		todo = -1;
		break;

	      case MOP_LEAVE:
		sn = mocha_GetSourceNote(mp->script, pc);
		if (sn && SN_TYPE(sn) == SRC_HIDDEN)
		    break;
		mp->indent -= 4;
		mocha_printf(mp, "\t}\n");
		todo = -1;
		break;

	      case MOP_RETURN:
		op = MOP_NOP;	/* XXX should reorder bytecodes? */
		rval = POP_STR();
		mocha_printf(mp, "\t%s%s%s;\n",
			     cs->name, *rval != '\0' ? " " : "", rval);
		todo = -1;
		break;

	      case MOP_GOTO:
		sn = mocha_GetSourceNote(mp->script, pc);
		if (sn && SN_TYPE(sn) == SRC_CONTINUE)
		    mocha_printf(mp, "\tcontinue;\n");
		else
		    mocha_printf(mp, "\tbreak;\n");
		todo = -1;
		break;

	      case MOP_IFEQ:
		len = GET_JUMP_OFFSET(pc);
		sn = mocha_GetSourceNote(mp->script, pc);

		switch (sn ? SN_TYPE(sn) : SRC_NULL) {
		  case SRC_IF:
		  case SRC_IF_ELSE:
		    rval = POP_STR();
		    mocha_printf(mp, "\tif (%s) {\n", rval);
		    mp->indent += 4;
		    if (SN_TYPE(sn) == SRC_IF) {
			DECOMPILE_CODE(pc + 3, len - 3);
		    } else {
			DECOMPILE_CODE(pc + 3, len - 6);
			mp->indent -= 4;
			pc += len - 3;
			len = GET_JUMP_OFFSET(pc);
			mocha_printf(mp, "\t} else {\n");
			mp->indent += 4;
			DECOMPILE_CODE(pc + 3, len - 3);
		    }
		    mp->indent -= 4;
		    mocha_printf(mp, "\t}\n");
		    todo = -1;
		    break;

		  case SRC_WHILE:
		    rval = POP_STR();
		    mocha_printf(mp, "\twhile (%s) {\n", rval);
		    mp->indent += 4;
		    DECOMPILE_CODE(pc + 3, len - 6);
		    mp->indent -= 4;
		    mocha_printf(mp, "\t}\n");
		    todo = -1;
		    break;

		  case SRC_COND:
		    DECOMPILE_CODE(pc + 3, len - 6);
		    pc += len - 3;
		    LOCAL_ASSERT(*pc == MOP_GOTO);
		    len = GET_JUMP_OFFSET(pc);
		    DECOMPILE_CODE(pc + 3, len - 3);
		    rval = POP_STR();
		    lval = POP_STR();
		    todo = Sprint(&ss->sprinter, "%s ? %s : %s",
				  POP_STR(), lval, rval);
		    break;

		  default:
		    /* top is the first clause in a disjunction (||). */
		    ifeq = pc + len;
		    LOCAL_ASSERT(pc[3] == MOP_TRUE);
		    pc += 4;
		    LOCAL_ASSERT(*pc == MOP_GOTO);
		    done = pc + GET_JUMP_OFFSET(pc);
		    pc += 3;
		    DECOMPILE_CODE(pc, done - ifeq);
		    rval = POP_STR();
		    lval = POP_STR();
		    todo = Sprint(&ss->sprinter, "%s || %s", lval, rval);
		    len = done - pc;
		    break;
		}
		break;

	      case MOP_IFNE:
		/* This bytecode is used only for conjunction (&&). */
		ifne = pc + GET_JUMP_OFFSET(pc);
		len++;
		pc += len;
		LOCAL_ASSERT(pc[-1] == MOP_FALSE);
		LOCAL_ASSERT(*pc == MOP_GOTO);
		done = pc + GET_JUMP_OFFSET(pc);
		pc += 3;
		DECOMPILE_CODE(pc, done - ifne);
		rval = POP_STR();
		lval = POP_STR();
		todo = Sprint(&ss->sprinter, "%s && %s", lval, rval);
		len = done - pc;
		break;

	      case MOP_IN:
		rval = POP_STR();
		lval = POP_STR();
		pc++;
		LOCAL_ASSERT(*pc == MOP_IFEQ);
		len = GET_JUMP_OFFSET(pc);
		mocha_printf(mp, "\tfor (%s in %s) {\n", lval, rval);
		mp->indent += 4;
		DECOMPILE_CODE(pc + 3, len - 6);
		mp->indent -= 4;
		mocha_printf(mp, "\t}\n");
		todo = -1;
		break;

	      case MOP_DUP:
		todo = Sprint(&ss->sprinter, "%s",
			      OFF2STR(&ss->sprinter, ss->offsets[ss->top-1]));
		break;

	      case MOP_ASSIGN:
		rval = POP_STR();
		lval = POP_STR();
		if ((sn = mocha_GetSourceNote(mp->script, pc - 1)) &&
		    SN_TYPE(sn) == SRC_ASSIGNOP &&
		    (cs = &mocha_CodeSpec[pc[-1]])->pretty == 2) {
		    todo = Sprint(&ss->sprinter, "%s %s= %s",
				  lval, cs->image, rval);
		} else {
		    todo = Sprint(&ss->sprinter, "%s = %s", lval, rval);
		}
		break;

	      case MOP_TYPEOF:
	      case MOP_VOID:
		rval = POP_STR();
		todo = Sprint(&ss->sprinter, "%s %s", cs->image, rval);
		break;

	      case MOP_INC:
	      case MOP_DEC:
		lval = POP_STR();
		if (pc[1]) {
		    todo = Sprint(&ss->sprinter, "%s%s", lval, cs->image);
		} else {
		    todo = Sprint(&ss->sprinter, "%s%s", cs->image, lval);
		}
		break;

	      case MOP_MEMBER:
	      case MOP_LMEMBER:
		rval = POP_STR();
		lval = POP_STR();
		todo = Sprint(&ss->sprinter, "%s.%s", lval, rval);
		break;

	      case MOP_INDEX:
	      case MOP_LINDEX:
		op = MOP_NOP;           /* turn off parens */
		rval = POP_STR();
		op = MOP_INDEX;
		lval = POP_STR();
		todo = Sprint(&ss->sprinter, "%s[%s]", lval, rval);
		break;

	      case MOP_NEW:
	      case MOP_CALL:
		op = MOP_NOP;           /* turn off parens */
		argc = pc[1];
		argv = MOCHA_malloc(mp->sprinter.context,
				    (argc + 1) * sizeof *argv);
		if (!argv)
		    return MOCHA_FALSE;

		ok = MOCHA_TRUE;
		for (i = argc; i >= 0; i--) {
		    argv[i] = MOCHA_strdup(mp->sprinter.context, POP_STR());
		    if (!argv[i]) {
			ok = MOCHA_FALSE;
			break;
		    }
		}

		if (cs->image) {
		    todo = Sprint(&ss->sprinter, "%s %s(", cs->image, argv[0]);
		    /* balance) */
		} else {
		    todo = Sprint(&ss->sprinter, "%s(", argv[0]);
		}
		if (todo < 0)
		    ok = MOCHA_FALSE;

		for (i = 1; i <= argc; i++) {
		    if (!argv[i] ||
			Sprint(&ss->sprinter, "%s%s",
			       argv[i], (i < argc) ? ", " : "") < 0) {
			ok = MOCHA_FALSE;
			break;
		    }
		}
		if (Sprint(&ss->sprinter, ")") < 0)
		    ok = MOCHA_FALSE;

		for (i = 0; i <= argc; i++) {
		    if (argv[i])
			MOCHA_free(mp->sprinter.context, argv[i]);
		}
		MOCHA_free(mp->sprinter.context, argv);
		if (!ok)
		    return MOCHA_FALSE;
		op = MOP_CALL;
		break;

	      case MOP_NAME:
		atom = GET_CONST_ATOM(mp->sprinter.context, mp->script, pc);
		sn = mocha_GetSourceNote(mp->script, pc);
		todo = Sprint(&ss->sprinter,
			      (sn && SN_TYPE(sn) == SRC_VAR) ? "var %s" : "%s",
			      atom_name(atom));
		break;

	      case MOP_NUMBER:
		atom = GET_CONST_ATOM(mp->sprinter.context, mp->script, pc);
		todo = Sprint(&ss->sprinter, atom_name(atom));
		break;

	      case MOP_STRING:
		atom = GET_CONST_ATOM(mp->sprinter.context, mp->script, pc);
		rval = EscapeString(&ss->sprinter, atom_name(atom));
		if (!rval)
		    return MOCHA_FALSE;
		todo = Sprint(&ss->sprinter, "\"%s\"", rval);
		break;

	      default:
		todo = -1;
	    }
	}

	if (todo >= 0 && !PushOff(ss, todo, op)) {
	    return MOCHA_FALSE;
	}
	pc += len;
    }

/*
** Undefine local macros.
*/
#undef DECOMPILE_CODE
#undef POP_STR
#undef LOCAL_ASSERT

    return MOCHA_TRUE;
}

MochaBoolean
mocha_DecompileScript(MochaScript *script, MochaPrinter *mp)
{
    MochaContext *mc;
    void *mark;
    SprintStack ss;
    MochaBoolean ok;

    /* Initialize a sprinter for use with the offset stack. */
    mc = mp->sprinter.context;
    mark = PR_ARENA_MARK(&mc->tempPool);
    INIT_SPRINTER(mc, &ss.sprinter, &mc->tempPool, PARENSLOP);

    /* Initialize the offset and opcode stacks. */
    ss.offsets = (ptrdiff_t *)alloca(script->depth * sizeof *ss.offsets);
    ss.opcodes = (MochaCode *)alloca(script->depth * sizeof *ss.opcodes);
    ss.top = 0;

    /* Set mp->script for source note referencing. */
    mp->script = script;

    /* Call recursive subroutine to do the hard work. */
    ok = Decompile(script->code, script->length, &ss, mp);
    PR_ARENA_RELEASE(&mc->tempPool, mark);
    return ok;
}

MochaBoolean
mocha_DecompileFunction(MochaFunction *fun, MochaPrinter *mp)
{
    MochaSymbol *arg;
    const MochaAtom *atom;
    unsigned indent;

    mocha_printf(mp, "\nfunction %s(", atom_name(fun->atom));
    if (fun->script) {
	for (arg = fun->script->args; arg; arg = arg->next) {
	    atom = sym_atom(arg);
	    mocha_printf(mp, "%s%s", atom_name(atom), arg->next ? ", " : "");
	}
    }
    mocha_printf(mp, ") {\n");
    if (fun->call) {
	mocha_printf(mp, "    [native code]\n");
    } else {
	indent = mp->indent;
	mp->indent += 4;
	if (!mocha_DecompileScript(fun->script, mp)) {
	    mp->indent = indent;
	    return MOCHA_FALSE;
	}
	mp->indent -= 4;
    }
    mocha_printf(mp, "}\n");
    return MOCHA_TRUE;
}