コード例 #1
0
ファイル: debugger.c プロジェクト: fuzzball-muck/fuzzball
/**
 * Convert a line number to an instruction pointer
 *
 * @private
 * @param program dbref
 * @param whatline the line to find an instruction pointer for
 * @return pointer to starting instruction for the given line
 */
static struct inst *
linenum_to_pc(dbref program, int whatline)
{
    int siz;
    struct inst *code;

    code = PROGRAM_CODE(program);
    siz = PROGRAM_SIZ(program);

    for (int i = 0; i < siz; i++) {
        if (code[i].line == whatline) {
            return (code + i);
        }
    }

    return (NULL);
}
コード例 #2
0
ファイル: debugger.c プロジェクト: fuzzball-muck/fuzzball
/**
 * Convert a function name to an instruction pointer
 *
 * @private
 * @param program the program dbref
 * @param name the procedure name to look for
 * @return pointer to start of procedure
 */
static struct inst *
funcname_to_pc(dbref program, const char *name)
{
    int siz;
    struct inst *code;

    code = PROGRAM_CODE(program);
    siz = PROGRAM_SIZ(program);

    for (int i = 0; i < siz; i++) {
        if ((code[i].type == PROG_FUNCTION) &&
            !strcasecmp(name, code[i].data.mufproc->procname)) {
            return (code + i);
        }
    }

    return (NULL);
}
コード例 #3
0
ファイル: debugger.c プロジェクト: fuzzball-muck/fuzzball
/**
 * List the functions in a program
 *
 * Optionally provide a filter string that will match functions using
 * equalstr
 *
 * @see equalstr
 *
 * You cannot pass NULL into arg, you can pass empty string if you wish to
 * show all functions.
 *
 * @private
 * @param player the person to show the list of functions to
 * @param program the program to list functions from
 * @param arg a filter string or ""
 */
static void
list_program_functions(dbref player, dbref program, char *arg)
{
    struct inst *ptr;
    int count;

    ptr = PROGRAM_CODE(program);
    count = PROGRAM_SIZ(program);
    notify_nolisten(player, "*function words*", 1);

    while (count-- > 0) {
        if (ptr->type == PROG_FUNCTION) {
            if (ptr->data.mufproc) {
                if (!*arg || equalstr(arg, ptr->data.mufproc->procname)) {
                    notify_nolisten(player, ptr->data.mufproc->procname, 1);
                }
            }
        }

        ptr++;
    }

    notify_nolisten(player, "*done*", 1);
}
コード例 #4
0
ファイル: debugger.c プロジェクト: fuzzball-muck/fuzzball
/**
 * Implementation of the MUF debugger
 *
 * This implements the command parsing for the MUF debugger.  It also clears
 * temporary bookmarks if this was triggered from a temporary one.
 *
 * This relies on some static globals, so it is not threadsafe.  If the
 * 'prim' debugger command is ever used to trigger the MUF debugger somehow
 * in a recursive fashion, and then you call 'prim' again, it will probably
 * cause havock.
 *
 * @param descr the descriptor of the debugging player
 * @param player the debugging player
 * @param program the program we are debugging
 * @param text the input text from the user
 * @param fr the current frame pointer
 * @return boolean true if the program should exit, false if not
 */
int
muf_debugger(int descr, dbref player, dbref program, const char *text, struct frame *fr)
{
    char cmd[BUFFER_LEN];
    char buf[BUFFER_LEN];
    char buf2[BUFFER_LEN];
    char *ptr, *ptr2, *arg;
    struct inst *pinst;
    int i, j, cnt;
    static struct inst primset[5];
    static struct muf_proc_data temp_muf_proc_data = {
        "__Temp_Debugger_Proc",
        0,
        0,
        NULL
    };

    /*
     * Basic massaging of the input - clearing spaces, finding the
     * argument.
     */
    skip_whitespace(&text);
    strcpyn(cmd, sizeof(cmd), text);

    ptr = cmd;
    remove_ending_whitespace(&ptr);

    for (arg = cmd; *arg && !isspace(*arg); arg++) ;

    if (*arg)
        *arg++ = '\0';

    /* Empty command means repeat last command, if available */
    if (!*cmd && fr->brkpt.lastcmd) {
        strcpyn(cmd, sizeof(cmd), fr->brkpt.lastcmd);
    } else {
        free(fr->brkpt.lastcmd);

        if (*cmd)
            fr->brkpt.lastcmd = strdup(cmd);
    }

    /* delete triggering breakpoint, if it's only temp. */
    j = fr->brkpt.breaknum;

    if (j >= 0 && fr->brkpt.temp[j]) {
        for (j++; j < fr->brkpt.count; j++) {
            fr->brkpt.temp[j - 1] = fr->brkpt.temp[j];
            fr->brkpt.level[j - 1] = fr->brkpt.level[j];
            fr->brkpt.line[j - 1] = fr->brkpt.line[j];
            fr->brkpt.linecount[j - 1] = fr->brkpt.linecount[j];
            fr->brkpt.pc[j - 1] = fr->brkpt.pc[j];
            fr->brkpt.pccount[j - 1] = fr->brkpt.pccount[j];
            fr->brkpt.prog[j - 1] = fr->brkpt.prog[j];
        }

        fr->brkpt.count--;
    }

    fr->brkpt.breaknum = -1;

    /**
     * @TODO This giant if statement is pretty gnarly; we'd be better
     *       of looping over an array of callbacks to break this up
     *       nicer and make it more extensible.
     */

    if (!strcasecmp(cmd, "cont")) {
        /* Nothing to do -- this will continue to next breakpoint */
    } else if (!strcasecmp(cmd, "finish")) {
        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player,
                            "Cannot finish because there are too many breakpoints set.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top - 1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "stepi")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot stepi because there are too many breakpoints set.",
                            1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = i;
        fr->brkpt.prog[j] = NOTHING;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "step")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot step because there are too many breakpoints set.",
                            1);

            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = i;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = NOTHING;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "nexti")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot nexti because there are too many breakpoints set.",
                            1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = i;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "next")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot next because there are too many breakpoints set.",
                            1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = i;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "exec")) {
        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player,
                            "Cannot finish because there are too many breakpoints set.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (!(pinst = funcname_to_pc(program, arg))) {
            notify_nolisten(player, "I don't know a function by that name.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (fr->system.top >= STACK_SIZE) {
            notify_nolisten(player,
                            "That would exceed the system stack size for this program.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        fr->system.st[fr->system.top].progref = program;
        fr->system.st[fr->system.top++].offset = fr->pc;
        fr->pc = pinst;
        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top - 1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "prim")) {
        /*
         * @TODO The way this works is a little funky.  It looks like
         *       it would be possible to cause some weird havoc if we
         *       manage to run a primitive that in turn triggers muf_debugger
         *
         *       I am uncertain if this is possible; looks like the only
         *       way it could happen is by typing 'prim debugger_break'
         *       but I don't know much about about how muf_debugger is
         *       triggered.  Some digging should be done to make this
         *       safe.
         *
         *       Even better would be to not use statics for this somehow
         *       without introducing a memory leak. (tanabi)
         */
        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player,
                            "Cannot finish because there are too many breakpoints set.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (!primitive(arg)) {
            notify_nolisten(player, "I don't recognize that primitive.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (fr->system.top >= STACK_SIZE) {
            notify_nolisten(player,
                            "That would exceed the system stack size for this program.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        primset[0].type = PROG_FUNCTION;
        primset[0].line = 0;
        primset[0].data.mufproc = &temp_muf_proc_data;
        primset[0].data.mufproc->vars = 0;
        primset[0].data.mufproc->args = 0;
        primset[0].data.mufproc->varnames = NULL;

        primset[1].type = PROG_PRIMITIVE;
        primset[1].line = 0;
        primset[1].data.number = get_primitive(arg);

        primset[2].type = PROG_PRIMITIVE;
        primset[2].line = 0;
        primset[2].data.number = IN_RET;

        /* primset[3].data.number = primitive("EXIT"); */

        fr->system.st[fr->system.top].progref = program;
        fr->system.st[fr->system.top++].offset = fr->pc;
        fr->pc = &primset[1];
        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = &primset[2];
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;
        fr->brkpt.dosyspop = 1;

        return 0;
    } else if (!strcasecmp(cmd, "break")) {
        add_muf_read_event(descr, player, program, fr);

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Too many breakpoints set.", 1);
            return 0;
        }

        if (number(arg)) {
            i = atoi(arg);
        } else {
            if (!(pinst = funcname_to_pc(program, arg))) {
                notify_nolisten(player, "I don't know a function by that name.", 1);
                return 0;
            } else {
                i = pinst->line;
            }
        }

        if (!i)
            i = fr->pc->line;

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 0;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = i;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        notify_nolisten(player, "Breakpoint set.", 1);

        return 0;
    } else if (!strcasecmp(cmd, "delete")) {
        add_muf_read_event(descr, player, program, fr);
        i = atoi(arg);

        if (!i) {
            notify_nolisten(player, "Which breakpoint did you want to delete?", 1);
            return 0;
        }

        if (i < 1 || i > fr->brkpt.count) {
            notify_nolisten(player, "No such breakpoint.", 1);
            return 0;
        }

        j = i - 1;

        for (j++; j < fr->brkpt.count; j++) {
            fr->brkpt.temp[j - 1] = fr->brkpt.temp[j];
            fr->brkpt.level[j - 1] = fr->brkpt.level[j];
            fr->brkpt.line[j - 1] = fr->brkpt.line[j];
            fr->brkpt.linecount[j - 1] = fr->brkpt.linecount[j];
            fr->brkpt.pc[j - 1] = fr->brkpt.pc[j];
            fr->brkpt.pccount[j - 1] = fr->brkpt.pccount[j];
            fr->brkpt.prog[j - 1] = fr->brkpt.prog[j];
        }

        fr->brkpt.count--;
        notify_nolisten(player, "Breakpoint deleted.", 1);

        return 0;
    } else if (!strcasecmp(cmd, "breaks")) {
        notify_nolisten(player, "Breakpoints:", 1);

        for (i = 0; i < fr->brkpt.count; i++) {
            ptr = unparse_breakpoint(fr, i);
            notify_nolisten(player, ptr, 1);
        }

        notify_nolisten(player, "*done*", 1);
        add_muf_read_event(descr, player, program, fr);

        return 0;
    } else if (!strcasecmp(cmd, "where")) {
        i = atoi(arg);
        muf_backtrace(player, program, i, fr);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "stack")) {
        notify_nolisten(player, "*Argument stack top*", 1);
        i = atoi(arg);

        if (!i)
            i = STACK_SIZE;

        ptr = "";

        for (j = fr->argument.top; j > 0 && i-- > 0;) {
            cnt = 0;

            do {
                strcpyn(buf, sizeof(buf), ptr);
                ptr = insttotext(NULL, 0, &fr->argument.st[--j], buf2, sizeof(buf2), 4000,
                                 program, 1);
                cnt++;
            } while (!strcasecmp(ptr, buf) && j > 0);

            if (cnt > 1)
                notifyf(player, "     [repeats %d times]", cnt);

            if (strcasecmp(ptr, buf))
                notifyf(player, "%3d) %s", j + 1, ptr);
        }

        notify_nolisten(player, "*done*", 1);
        add_muf_read_event(descr, player, program, fr);

        return 0;
    } else if (!strcasecmp(cmd, "list") || !strcasecmp(cmd, "listi")) {
        int startline, endline;

        add_muf_read_event(descr, player, program, fr);

        if ((ptr2 = (char *) strchr(arg, ','))) {
            *ptr2++ = '\0';
        } else {
            ptr2 = "";
        }

        if (!*arg) {
            if (fr->brkpt.lastlisted) {
                startline = fr->brkpt.lastlisted + 1;
            } else {
                startline = fr->pc->line;
            }

            endline = startline + 15;
        } else {
            if (!number(arg)) {
                if (!(pinst = funcname_to_pc(program, arg))) {
                    notify_nolisten(player,
                                    "I don't know a function by that name. (starting arg, 1)",
                                    1);
                    return 0;
                } else {
                    startline = pinst->line;
                    endline = startline + 15;
                }
            } else {
                if (*ptr2) {
                    endline = startline = atoi(arg);
                } else {
                    startline = atoi(arg) - 7;
                    endline = startline + 15;
                }
            }
        }

        if (*ptr2) {
            if (!number(ptr2)) {
                if (!(pinst = funcname_to_pc(program, ptr2))) {
                    notify_nolisten(player,
                                    "I don't know a function by that name. (ending arg, 1)",
                                    1);
                    return 0;
                } else {
                    endline = pinst->line;
                }
            } else {
                endline = atoi(ptr2);
            }
        }

        i = (PROGRAM_CODE(program) + PROGRAM_SIZ(program) - 1)->line;

        if (startline > i) {
            notify_nolisten(player, "Starting line is beyond end of program.", 1);
            return 0;
        }

        if (startline < 1)
            startline = 1;

        if (endline > i)
            endline = i;

        if (endline < startline)
            endline = startline;

        notify_nolisten(player, "Listing:", 1);

        if (!strcasecmp(cmd, "listi")) {
            for (i = startline; i <= endline; i++) {
                pinst = linenum_to_pc(program, i);

                if (pinst) {
                    notifyf_nolisten(player, "line %d: %s", i, (i == fr->pc->line) ?
                                     show_line_prims(program, fr->pc, STACK_SIZE, 1) :
                                     show_line_prims(program, pinst, STACK_SIZE, 0));
                }
            }
        } else {
            list_proglines(player, program, fr, startline, endline);
        }

        fr->brkpt.lastlisted = endline;
        notify_nolisten(player, "*done*", 1);

        return 0;
    } else if (!strcasecmp(cmd, "quit")) {
        notify_nolisten(player, "Halting execution.", 1);
        return 1;
    } else if (!strcasecmp(cmd, "trace")) {
        add_muf_read_event(descr, player, program, fr);

        if (!strcasecmp(arg, "on")) {
            fr->brkpt.showstack = 1;
            notify_nolisten(player, "Trace turned on.", 1);
        } else if (!strcasecmp(arg, "off")) {
            fr->brkpt.showstack = 0;
            notify_nolisten(player, "Trace turned off.", 1);
        } else {
            notifyf_nolisten(player, "Trace is currently %s.",
                             fr->brkpt.showstack ? "on" : "off");
        }

        return 0;
    } else if (!strcasecmp(cmd, "words")) {
        list_program_functions(player, program, arg);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "print")) {
        debug_printvar(player, program, fr, arg);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "push")) {
        push_arg(player, fr, arg);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "pop")) {
        add_muf_read_event(descr, player, program, fr);

        if (fr->argument.top < 1) {
            notify_nolisten(player, "Nothing to pop.", 1);
            return 0;
        }

        fr->argument.top--;
        CLEAR(fr->argument.st + fr->argument.top);
        notify_nolisten(player, "Stack item popped.", 1);

        return 0;
    } else if (!strcasecmp(cmd, "help")) {
        do_helpfile(player, tp_file_man_dir, tp_file_man, "debugger_commands", "");

        return 0;
    } else {
        notify_nolisten(player,
                        "I don't understand that debugger command. Type 'help' for help.", 1);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    }

    return 0;
}
コード例 #5
0
ファイル: debugger.c プロジェクト: fuzzball-muck/fuzzball
/**
 * This is for showing line listings in "instruction" format.
 *
 * This returns a single line of code's primitives.  It uses a static buffer,
 * so be careful with it.  Not multi-threaded.
 *
 * @param program the program to show listings for
 * @param pc the program counter where the listing starts
 * @param maxprims the maximum primitives
 * @param markpc do a special mark when listing the 'pc' line
 * @return pointer to static buffer containing primitives
 */
char *
show_line_prims(dbref program, struct inst *pc, int maxprims, int markpc)
{
    static char buf[BUFFER_LEN];
    static char buf2[BUFFER_LEN];
    int maxback;
    int thisline = pc->line;
    struct inst *code, *end, *linestart, *lineend;

    code = PROGRAM_CODE(program);
    end = code + PROGRAM_SIZ(program);
    buf[0] = '\0';

    /*
     * This code is to determine the end of the line.  There can be multiple
     * primitives on the same line.  This series of loops finds the end
     * of the line, so we can iterate over the primitives between start and
     * end to form our return string.
     */
    for (linestart = pc, maxback = maxprims; linestart > code &&
         linestart->line == thisline && linestart->type != PROG_FUNCTION &&
         --maxback; --linestart) ;

    if (linestart->line < thisline)
        ++linestart;

    for (lineend = pc + 1, maxback = maxprims; lineend < end &&
         lineend->line == thisline && lineend->type != PROG_FUNCTION &&
         --maxback; ++lineend) ;

    if (lineend >= end || lineend->line > thisline || lineend->type == PROG_FUNCTION)
        --lineend;

    if (lineend - linestart >= maxprims) {
        if (pc - (maxprims - 1) / 2 > linestart)
            linestart = pc - (maxprims - 1) / 2;

        if (linestart + maxprims - 1 < lineend)
            lineend = linestart + maxprims - 1;
    }

    if (linestart > code && (linestart - 1)->line == thisline)
        strcpyn(buf, sizeof(buf), "...");

    /*
     * We're in the right position; loop from linestart til lineend,
     * putting together text versions of the instructions.
     */ 
    while (linestart <= lineend) {
        if (strlen(buf) < BUFFER_LEN / 2) {
            if (*buf)
                strcatn(buf, sizeof(buf), " ");

            if (pc == linestart && markpc) {
                strcatn(buf, sizeof(buf), " {{");
                strcatn(buf, sizeof(buf),
                        insttotext(NULL, 0, linestart, buf2, sizeof(buf2), 30,
                                   program, 1));
                strcatn(buf, sizeof(buf), "}} ");
            } else {
                strcatn(buf, sizeof(buf),
                        insttotext(NULL, 0, linestart, buf2, sizeof(buf2), 30,
                                   program, 1));
            }
        } else {
            break;
        }

        linestart++;
    }

    if (lineend < end && (lineend + 1)->line == thisline)
        strcatn(buf, sizeof(buf), " ...");

    return buf;
}
コード例 #6
0
ファイル: disassem.c プロジェクト: PyroMuddypaw/fuzzball
void
disassemble(dbref player, dbref program)
{
	struct inst *curr;
	struct inst *codestart;
	int i;
	char buf[BUFFER_LEN];

	codestart = curr = PROGRAM_CODE(program);
	if (!PROGRAM_SIZ(program)) {
		notify(player, "Nothing to disassemble!");
		return;
	}
	for (i = 0; i < PROGRAM_SIZ(program); i++, curr++) {
		switch (curr->type) {
		case PROG_PRIMITIVE:
			if (curr->data.number >= BASE_MIN && curr->data.number <= BASE_MAX)
				snprintf(buf, sizeof(buf), "%d: (line %d) PRIMITIVE: %s", i,
						curr->line, base_inst[curr->data.number - BASE_MIN]);
			else
				snprintf(buf, sizeof(buf), "%d: (line %d) PRIMITIVE: %d", i, curr->line, curr->data.number);
			break;
		case PROG_MARK:
			snprintf(buf, sizeof(buf), "%d: (line %d) MARK", i, curr->line);
			break;
		case PROG_STRING:
			snprintf(buf, sizeof(buf), "%d: (line %d) STRING: \"%s\"", i, curr->line,
					curr->data.string ? curr->data.string->data : "");
			break;
		case PROG_ARRAY:
			snprintf(buf, sizeof(buf), "%d: (line %d) ARRAY: %d items", i, curr->line,
					curr->data.array ? curr->data.array->items : 0);
			break;
		case PROG_FUNCTION:
			snprintf(buf, sizeof(buf), "%d: (line %d) FUNCTION: %s, VARS: %d, ARGS: %d", i, curr->line,
					curr->data.mufproc->procname ? curr->data.mufproc->procname : "",
					curr->data.mufproc->vars, curr->data.mufproc->args);
			break;
		case PROG_LOCK:
			snprintf(buf, sizeof(buf), "%d: (line %d) LOCK: [%s]", i, curr->line,
					curr->data.lock == TRUE_BOOLEXP ? "TRUE_BOOLEXP" :
					unparse_boolexp(0, curr->data.lock, 0));
			break;
		case PROG_INTEGER:
			snprintf(buf, sizeof(buf), "%d: (line %d) INTEGER: %d", i, curr->line, curr->data.number);
			break;
		case PROG_FLOAT:
			snprintf(buf, sizeof(buf), "%d: (line %d) FLOAT: %.17g", i, curr->line, curr->data.fnumber);
			break;
		case PROG_ADD:
			snprintf(buf, sizeof(buf), "%d: (line %d) ADDRESS: %d", i,
					curr->line, (int)(curr->data.addr->data - codestart));
			break;
		case PROG_TRY:
			snprintf(buf, sizeof(buf), "%d: (line %d) TRY: %d", i, curr->line, (int)(curr->data.call - codestart));
			break;
		case PROG_IF:
			snprintf(buf, sizeof(buf), "%d: (line %d) IF: %d", i, curr->line, (int)(curr->data.call - codestart));
			break;
		case PROG_JMP:
			snprintf(buf, sizeof(buf), "%d: (line %d) JMP: %d", i, curr->line, (int)(curr->data.call - codestart));
			break;
		case PROG_EXEC:
			snprintf(buf, sizeof(buf), "%d: (line %d) EXEC: %d", i, curr->line, (int)(curr->data.call - codestart));
			break;
		case PROG_OBJECT:
			snprintf(buf, sizeof(buf), "%d: (line %d) OBJECT REF: %d", i, curr->line, curr->data.number);
			break;
		case PROG_VAR:
			snprintf(buf, sizeof(buf), "%d: (line %d) VARIABLE: %d", i, curr->line, curr->data.number);
			break;
		case PROG_SVAR:
			snprintf(buf, sizeof(buf), "%d: (line %d) SCOPEDVAR: %d (%s)", i, curr->line, curr->data.number, scopedvar_getname_byinst(curr, curr->data.number));
			break;
		case PROG_SVAR_AT:
			snprintf(buf, sizeof(buf), "%d: (line %d) FETCH SCOPEDVAR: %d (%s)", i, curr->line, curr->data.number, scopedvar_getname_byinst(curr, curr->data.number));
			break;
		case PROG_SVAR_AT_CLEAR:
			snprintf(buf, sizeof(buf), "%d: (line %d) FETCH SCOPEDVAR (clear optim): %d (%s)", i, curr->line, curr->data.number, scopedvar_getname_byinst(curr, curr->data.number));
			break;
		case PROG_SVAR_BANG:
			snprintf(buf, sizeof(buf), "%d: (line %d) SET SCOPEDVAR: %d (%s)", i, curr->line, curr->data.number, scopedvar_getname_byinst(curr, curr->data.number));
			break;
		case PROG_LVAR:
			snprintf(buf, sizeof(buf), "%d: (line %d) LOCALVAR: %d", i, curr->line, curr->data.number);
			break;
		case PROG_LVAR_AT:
			snprintf(buf, sizeof(buf), "%d: (line %d) FETCH LOCALVAR: %d", i, curr->line, curr->data.number);
			break;
		case PROG_LVAR_AT_CLEAR:
			snprintf(buf, sizeof(buf), "%d: (line %d) FETCH LOCALVAR (clear optim): %d", i, curr->line, curr->data.number);
			break;
		case PROG_LVAR_BANG:
			snprintf(buf, sizeof(buf), "%d: (line %d) SET LOCALVAR: %d", i, curr->line, curr->data.number);
			break;
		case PROG_CLEARED:
			snprintf(buf, sizeof(buf), "%d: (line ?) CLEARED INST AT %s:%d", i, (char *) curr->data.addr,
					curr->line);
		default:
			snprintf(buf, sizeof(buf), "%d: (line ?) UNKNOWN INST", i);
		}
		notify(player, buf);
	}
}