/* @@@HEAD@@@
// Routines for executing ColdC tasks.
*/

#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include "config.h"
#include "defs.h"
#include "y.tab.h"
#include "execute.h"
#include "memory.h"
#include "cdc_types.h"
#include "io.h"
#include "cache.h"
#include "util.h"
#include "opcodes.h"
#include "log.h"
#include "decode.h"

#define STACK_STARTING_SIZE                (256 - STACK_MALLOC_DELTA)
#define ARG_STACK_STARTING_SIZE                (32 - ARG_STACK_MALLOC_DELTA)

extern int running;

internal void execute(void);
internal void out_of_ticks_error(void);
internal void start_error(Ident error, string_t *explanation, data_t *arg,
                        list_t * location);
internal list_t * traceback_add(list_t * traceback, Ident error);
internal void fill_in_method_info(data_t *d);

internal Frame *frame_store = NULL;
internal int frame_depth;
string_t *numargs_str;

Frame *cur_frame, *suspend_frame;
Connection *cur_conn;
data_t *stack;
int stack_pos, stack_size;
int *arg_starts, arg_pos, arg_size;
long task_id;
long tick;

#define DEBUG_VM 0
#define DEBUG_EXECUTE 0

VMState *tasks = NULL, *paused = NULL, *vmstore = NULL;
VMStack *stack_store = NULL, *holder_store = NULL; 

/*
// ---------------------------------------------------------------
//
// These two defines add and remove tasks from task lists.
//
*/
#define ADD_TASK(the_list, the_value) { \
        if (!the_list) { \
            the_list = the_value; \
            the_value->next = NULL; \
        } else { \
            the_value->next = the_list; \
            the_list = the_value; \
        } \
    }

#define REMOVE_TASK(the_list, the_value) { \
        if (the_list == the_value) { \
            the_list = the_list->next; \
        } else { \
            task_delete(the_list, the_value); \
        } \
    }

/*
// ---------------------------------------------------------------
*/
void store_stack(void) {
    VMStack * holder;

    if (holder_store) {
        holder = holder_store;
        holder_store = holder_store->next;
#if DEBUG_VM
        write_err("store_stack:  reusing holder %d", holder);
#endif
    } else {
        holder = EMALLOC(VMStack, 1);
#if DEBUG_VM
        write_err("store_stack:  allocating holder %d", holder);
#endif
    }
  
    holder->stack = stack;
    holder->stack_size = stack_size;

    holder->arg_starts = arg_starts;
    holder->arg_size = arg_size;

    holder->next = stack_store;
    stack_store = holder;
}

/*
// ---------------------------------------------------------------
*/
VMState *suspend_vm(void) {
    VMState * vm;

    if (vmstore) {
        vm = vmstore;
        vmstore = vmstore->next;
#if DEBUG_VM
        write_err("suspend_vm:  reusing vm %d", vm);
#endif
  } else {
        vm = EMALLOC(VMState, 1);
#if DEBUG_VM
        write_err("suspend_vm:  allocating vm %d", vm);
#endif
  }

    vm->paused = 0;
    vm->cur_frame = cur_frame;
    vm->cur_conn = cur_conn;
    vm->stack = stack;
    vm->stack_pos = stack_pos;
    vm->stack_size = stack_size;
    vm->arg_starts = arg_starts;
    vm->arg_pos = arg_pos;
    vm->arg_size = arg_size;
    vm->task_id = task_id;
    vm->next = NULL;

    return vm;
}

/*
// ---------------------------------------------------------------
*/
void restore_vm(VMState *vm) {
    task_id = vm->task_id;
    cur_frame = vm->cur_frame;
    cur_conn = vm->cur_conn;
    stack = vm->stack;
    stack_pos = vm->stack_pos;
    stack_size = vm->stack_size;
    arg_starts = vm->arg_starts;
    arg_pos = vm->arg_pos;
    arg_size = vm->arg_size;
#if DEBUG_VM
    write_err("restore_vm: tid %d opcode %s",
              vm->task_id, op_table[cur_frame->opcodes[cur_frame->pc]].name);
#endif
}


/*
// ---------------------------------------------------------------
*/
void task_delete(VMState *list, VMState *elem) {
    if (list != tasks)
        list = list->next;
    while (list && (list->next != elem))
        list = list->next;
    if (list)
        list->next = elem->next;
}

/*
// ---------------------------------------------------------------
*/
VMState *task_lookup(long tid) {
    VMState * vm;

    for (vm = tasks;  vm;  vm = vm->next)
        if (vm->task_id == tid)
            return vm;

    for (vm = paused;  vm;  vm = vm->next)
        if (vm->task_id == tid)
            return vm;

    return NULL;
}

/*
// ---------------------------------------------------------------
// we assume tid is a non-paused task
//
*/
void task_resume(long tid, data_t *ret) {
    VMState * vm = task_lookup(tid),
            * old_vm;

    old_vm = suspend_vm();
    restore_vm(vm);
    REMOVE_TASK(tasks, vm);
    ADD_TASK(vmstore, vm);
    if (ret) {
        check_stack(1);
        data_dup(&stack[stack_pos], ret);
        stack_pos++;
    } else {
        push_int(0);
    }
    execute();
    store_stack();
    restore_vm(old_vm);
    ADD_TASK(vmstore, old_vm);
}

/*
// ---------------------------------------------------------------
*/
void task_suspend(void) {
    VMState * vm = suspend_vm();

    ADD_TASK(tasks, vm);
    init_execute();
    cur_frame = NULL;
}

/*
// ---------------------------------------------------------------
*/
void task_cancel(long tid) {
    VMState * vm = task_lookup(tid),
            * old_vm;

    old_vm = suspend_vm();
    restore_vm(vm);
    while (cur_frame)
        frame_return();
    if (vm->paused) {
        REMOVE_TASK(paused, vm);
    } else {
        REMOVE_TASK(tasks, vm);
    }
    store_stack();
    ADD_TASK(vmstore, vm);
    restore_vm(old_vm);
    ADD_TASK(vmstore, old_vm);
}

/*
// ---------------------------------------------------------------
*/
void task_pause(void) {
    VMState * vm = suspend_vm();

    vm->paused = 1;
    ADD_TASK(paused, vm);
    init_execute();
    cur_frame = NULL;  
}

/*
// ---------------------------------------------------------------
*/
void run_paused_tasks(void) {
    VMState * vm = suspend_vm(),
            * task = paused,
            * last_task;

    /* don't want any task adding itself while we're running the paused tasks */
    paused = NULL;
    while (task) {
        restore_vm(task);
        cur_frame->ticks = PAUSED_METHOD_TICKS;
        last_task = task;
        task = task->next;
        ADD_TASK(vmstore, last_task);
        execute();
        store_stack();
    }
    restore_vm(vm);
    ADD_TASK(vmstore, vm);

#if DEBUG_VM
    for (vm = paused; vm; vm = vm->next)
        write_err("paused task tid %d", vm->task_id);
#endif
}

/*
// ---------------------------------------------------------------
*/
list_t * task_list(void) {
    list_t  * r;
    data_t      elem;
    VMState * vm = tasks;
  
    r = list_new(0);
  
    elem.type = INTEGER;
    for (; vm; vm = vm->next) {
        elem.u.val = vm->task_id;
        list_add(r, &elem); 
    }
    
    vm = paused;
    for (; vm; vm = vm->next) {
        elem.u.val = vm->task_id;
        list_add(r, &elem); 
    }
  
    return r;
}

/*
// ---------------------------------------------------------------
*/
list_t *task_callers(void) {
    list_t * r;
    data_t   elem,
           * d;
    Frame  * f;
  
    r = list_new(0);
    elem.type = LIST;
    for (f = cur_frame; f; f = f->caller_frame) {
        elem.u.list = list_new(4);
        d = list_empty_spaces(elem.u.list, 4);
        d->type = DBREF;
        d->u.dbref = f->object->dbref;
        d++;
        d->type = DBREF;
        d->u.dbref = f->method->object->dbref;
        d++;
        d->type = SYMBOL;
        d->u.symbol = f->method->name;
        d++;
        d->type = INTEGER;
        d->u.val = line_number(f->method, f->pc - 1);
        list_add(r, &elem);
    }
    return r;
}

/*
// ---------------------------------------------------------------
*/
void init_execute(void) {
    if (stack_store) {
        VMStack *holder;
        
        stack = stack_store->stack;
        stack_size = stack_store->stack_size;
    
        arg_starts = stack_store->arg_starts;
        arg_size = stack_store->arg_size;
    
        holder = stack_store;
        stack_store = holder->next;
        holder->next = holder_store;
        holder_store = holder;

#if DEBUG_VM
        write_err("resuing execution state");
#endif

    } else {
        stack = EMALLOC(data_t, STACK_STARTING_SIZE);
        stack_size = STACK_STARTING_SIZE;
    
        arg_starts = EMALLOC(int, ARG_STACK_STARTING_SIZE);
        arg_size = ARG_STACK_STARTING_SIZE;

#if DEBUG_VM
        write_err("allocating execution state");
#endif

    }
    stack_pos = 0;
    arg_pos = 0;
}

/*
// ---------------------------------------------------------------
//
// Execute a task by sending a message to an object.
//
*/
void task(Connection *conn, Dbref dbref, long message, int num_args, ...) {
    va_list arg;

    /* Don't execute if a shutdown() has occured. */
    if (!running) {
        va_end(arg);
        return;
    }

    /* Set global variables. */
    cur_conn = conn;
    frame_depth = 0;

    va_start(arg, num_args);
    check_stack(num_args);
    while (num_args--)
        data_dup(&stack[stack_pos++], va_arg(arg, data_t *));
    va_end(arg);

    /* Send the message.  If this is succesful, start the task by calling
     * execute(). */
    ident_dup(message);
    if (send_message(dbref, message, 0, 0) == NOT_AN_IDENT) {
        execute();
        if (stack_pos != 0)
            panic("Stack not empty after interpretation.");
        task_id++;
    } else {
        pop(stack_pos);
    }
    ident_discard(message);
}

/*
// ---------------------------------------------------------------
//
// Execute a task by evaluating a method on an object.
//
*/
void task_method(Connection *conn, object_t *obj, method_t *method) {
    cur_conn = conn;
    frame_start(obj, method, NOT_AN_IDENT, NOT_AN_IDENT, 0, 0);
    execute();

    if (stack_pos != 0)
        panic("Stack not empty after interpretation.");
}

/*
// ---------------------------------------------------------------
*/
long frame_start(object_t * obj,
                 method_t * method,
                 Dbref    sender,
                 Dbref    caller,
                 int      stack_start,
                 int      arg_start)
{
    Frame      * frame;
    int          i,
                 num_args,
                 num_rest_args;
    list_t     * rest;
    data_t       * d;
    Number_buf   nbuf1,
                 nbuf2;

    num_args = stack_pos - arg_start;
    if (num_args < method->num_args || (num_args > method->num_args &&
                                        method->rest == -1)) {
        if (numargs_str)
            string_discard(numargs_str);
        numargs_str = format("#%l.%s called with %s argument%s, requires %s%s",
                             obj->dbref, ident_name(method->name),
                             english_integer(num_args, nbuf1),
                             (num_args == 1) ? "" : "s",
                             (method->num_args == 0) ? "none" :
                             english_integer(method->num_args, nbuf2),
                             (method->rest == -1) ? "." : " or more.");
        return numargs_id;
    }

    if (frame_depth > MAX_CALL_DEPTH)
        return maxdepth_id;
    frame_depth++;

    if (method->rest != -1) {
        /* Make a list for the remaining arguments. */
        num_rest_args = stack_pos - (arg_start + method->num_args);
        rest = list_new(num_rest_args);

        /* Move aforementioned remaining arguments into the list. */
        d = list_empty_spaces(rest, num_rest_args);
        MEMCPY(d, &stack[stack_pos - num_rest_args], num_rest_args);
        stack_pos -= num_rest_args;

        /* Push the list onto the stack. */
        push_list(rest);
        list_discard(rest);
    }

    if (frame_store) {
        frame = frame_store;
        frame_store = frame_store->caller_frame;
    } else {
        frame = EMALLOC(Frame, 1);
    }

    frame->object = cache_grab(obj);
    frame->sender = sender;
    frame->caller = caller;
    frame->method = method_grab(method);
    cache_grab(method->object);
    frame->opcodes = method->opcodes;
    frame->pc = 0;
    frame->ticks = METHOD_TICKS;

    frame->specifiers = NULL;
    frame->handler_info = NULL;

    /* Set up stack indices. */
    frame->stack_start = stack_start;
    frame->var_start = arg_start;

    /* Initialize local variables to 0. */
    check_stack(method->num_vars);
    for (i = 0; i < method->num_vars; i++) {
        stack[stack_pos + i].type = INTEGER;
        stack[stack_pos + i].u.val = 0;
    }
    stack_pos += method->num_vars;

    frame->caller_frame = cur_frame;
    cur_frame = frame;

    return NOT_AN_IDENT;
}

/*
// ---------------------------------------------------------------
*/
void frame_return(void) {
    int i;
    Frame *caller_frame = cur_frame->caller_frame;

    /* Free old data on stack. */
    for (i = cur_frame->stack_start; i < stack_pos; i++)
        data_discard(&stack[i]);
    stack_pos = cur_frame->stack_start;

    /* Let go of method and objects. */
    method_discard(cur_frame->method);
    cache_discard(cur_frame->object);
    cache_discard(cur_frame->method->object);

    /* Discard any error action specifiers. */
    while (cur_frame->specifiers)
        pop_error_action_specifier();

    /* Discard any handler information. */
    while (cur_frame->handler_info)
        pop_handler_info();

    /* Append frame to frame store for later reuse. */
    cur_frame->caller_frame = frame_store;
    frame_store = cur_frame;

    /* Return to the caller frame. */
    cur_frame = caller_frame;

    frame_depth--;
}

/*
// ---------------------------------------------------------------
*/
internal void execute(void) {
    int opcode;

    while (cur_frame) {
        tick++;
        if ((--(cur_frame->ticks)) == 0) {
            out_of_ticks_error();
        } else {
            opcode = cur_frame->opcodes[cur_frame->pc];

#if DEBUG_EXECUTE
            write_err("#%d #%d.%I %d %s",
                cur_frame->object->dbref, 
                cur_frame->method->object->dbref,
                ((cur_frame->method->name != NOT_AN_IDENT) ?
                    cur_frame->method->name :
                    opcode_id),
                line_number(cur_frame->method, cur_frame->pc),
                op_table[cur_frame->opcodes[cur_frame->pc]].name);
#endif

            cur_frame->last_opcode = opcode;
            cur_frame->pc++;
            (*op_table[opcode].func)();
        }
    }
}

/*
// ---------------------------------------------------------------
//
// Requires cur_frame->pc to be the current instruction.  Do NOT call this
// function if there is any possibility of the assignment failing before the
// current instruction finishes.
//
*/
void anticipate_assignment(void) {
    int opcode, ind;
    long id;
    data_t *dp, d;

    opcode = cur_frame->opcodes[cur_frame->pc];
    if (opcode == SET_LOCAL) {
        /* Zero out local variable value. */
        dp = &stack[cur_frame->var_start +
                    cur_frame->opcodes[cur_frame->pc + 1]];
        data_discard(dp);
        dp->type = INTEGER;
        dp->u.val = 0;
    } else if (opcode == SET_OBJ_VAR) {
        /* Zero out the object variable, if it exists. */
        ind = cur_frame->opcodes[cur_frame->pc + 1];
        id = object_get_ident(cur_frame->method->object, ind);
        d.type = INTEGER;
        d.u.val = 0;
        object_assign_var(cur_frame->object, cur_frame->method->object,
                          id, &d);
    }
}

/*
// ---------------------------------------------------------------
*/
Ident pass_message(int stack_start, int arg_start) {
    method_t *method;
    Ident result;

    if (cur_frame->method->name == -1)
        return methodnf_id;

    /* Find the next method to handle the message. */
    method = object_find_next_method(cur_frame->object->dbref,
                                     cur_frame->method->name,
                                     cur_frame->method->object->dbref);
    if (!method)
        return methodnf_id;

    /* Start the new frame. */
    result = frame_start(cur_frame->object, method, cur_frame->sender,
                         cur_frame->caller, stack_start, arg_start);
    cache_discard(method->object);
    return result;
}

/*
// ---------------------------------------------------------------
*/
Ident send_message(Dbref dbref, Ident message, int stack_start, int arg_start) {
    object_t *obj;
    method_t *method;
    Ident result;
    Dbref sender, caller;

    /* Get the target object from the cache. */
    obj = cache_retrieve(dbref);
    if (!obj)
        return objnf_id;

    /* Find the method to run. */
    /* object_find_method is now a front end that increments the
       cur_search variable and calls object_find_message_recurse,
       the recursive routine.  This is necessary to make single-
       inheritance lineage obejct re-entrant.  It fixes a bug in
       the circular-definition catching logic that caused it to
       fail when a parent's method sent a message to the child as
       a result of a message to teh child hanbdled by the parent
       (whew.) added 5/7/1995 Jeffrey P. kesselman */
    method = object_find_method(obj->dbref, message);
    if (!method) {
        cache_discard(obj);
        return methodnf_id;
    }

    /* private: caller has to be definer */
    if ((method->m_state == MS_PRIVATE) &&
        (cur_frame->method->object->dbref != method->object->dbref)) {
        return private_id;

    /* protected: sender has to be this */
    } else if ((method->m_state == MS_PROTECTED) &&
        (cur_frame->object->dbref != obj->dbref)) {
        return protected_id;

    /* root: caller has to be $root */
    } else if ((method->m_state == MS_ROOT) &&
        (cur_frame->method->object->dbref != ROOT_DBREF)) {
        return root_id;

    /* driver: only I can send to this method */
    } else if ((method->m_state == MS_DRIVER) && (cur_frame)) {
        return driver_id;
    }

    /* protected: sender has to be this */

    /* Start the new frame. */
    sender = (cur_frame) ? cur_frame->object->dbref : NOT_AN_IDENT;
    caller = (cur_frame) ? cur_frame->method->object->dbref : NOT_AN_IDENT;
    result = frame_start(obj, method, sender, caller, stack_start, arg_start);

    cache_discard(obj);
    cache_discard(method->object);

    return result;
}

/*
// ---------------------------------------------------------------
*/
void pop(int n) {

#ifdef DEBUG
    write_err("pop(%d)", n);
#endif

    while (n--)
        data_discard(&stack[--stack_pos]);
}

/*
// ---------------------------------------------------------------
*/
void check_stack(int n) {
    while (stack_pos + n > stack_size) {
        stack_size = stack_size * 2 + STACK_MALLOC_DELTA;
        stack = EREALLOC(stack, data_t, stack_size);
    }
}

/*
// ---------------------------------------------------------------
*/
void push_int(long n) {

#ifdef DEBUG
    write_err("push(%d)", n);
#endif

    check_stack(1);
    stack[stack_pos].type = INTEGER;
    stack[stack_pos].u.val = n;
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_float(float f) {

#ifdef DEBUG
    write_err("push(%f)", f);
#endif

    check_stack(1);
    stack[stack_pos].type = FLOAT;
    stack[stack_pos].u.fval = f;
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_string(string_t *str) {

#ifdef DEBUG
    write_err("push(\"%S\")", str);
#endif

    check_stack(1);
    stack[stack_pos].type = STRING;
    stack[stack_pos].u.str = string_dup(str);
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_dbref(Dbref dbref) {

#ifdef DEBUG
    write_err("push($%d)", dbref);
#endif

    check_stack(1);
    stack[stack_pos].type = DBREF;
    stack[stack_pos].u.dbref = dbref;
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_list(list_t * list) {
#ifdef DEBUG
    string_t *str = string_new(0);

    write_err("push(%S)", data_add_list_literal_to_str(str, list));

    string_discard(str);
#endif

    check_stack(1);
    stack[stack_pos].type = LIST;
    stack[stack_pos].u.list = list_dup(list);
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_dict(Dict *dict) {
#ifdef DEBUG
    string_t *str = string_new(0);

    write_err("push(%S)", dict_add_literal_to_str(str, dict));

    string_discard(str);
#endif

    check_stack(1);
    stack[stack_pos].type = DICT;
    stack[stack_pos].u.dict = dict_dup(dict);
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_symbol(Ident id) {

#ifdef DEBUG
    write_err("push(\'%s)", ident_name(id));
#endif

    check_stack(1);
    stack[stack_pos].type = SYMBOL;
    stack[stack_pos].u.symbol = ident_dup(id);
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_error(Ident id) {

#ifdef DEBUG
    write_err("push(\'%s)", ident_name(id));
#endif

    check_stack(1);
    stack[stack_pos].type = ERROR;
    stack[stack_pos].u.error = ident_dup(id);
    stack_pos++;
}

/*
// ---------------------------------------------------------------
*/
void push_buffer(Buffer *buf) {

#ifdef DEBUG
    write_err("push() buffer");
#endif

    check_stack(1);
    stack[stack_pos].type = BUFFER;
    stack[stack_pos].u.buffer = buffer_dup(buf);
    stack_pos++;
}

int func_init_0(void)
{
    int arg_start = arg_starts[--arg_pos];
    int num_args = stack_pos - arg_start;

    if (num_args)
        func_num_error(num_args, "none");
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref)
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
    else
        return 1;
    return 0;
}

int func_init_1(data_t **args, int type1)
{
    int arg_start = arg_starts[--arg_pos];
    int num_args = stack_pos - arg_start;

    *args = &stack[arg_start];
    if (num_args != 1)
        func_num_error(num_args, "one");
    else if (type1 && stack[arg_start].type != type1)
        func_type_error("first", &stack[arg_start], english_type(type1));
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref) {
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
       /* pop(1); */
    } else
        return 1;
    return 0;
}

int func_init_2(data_t **args, int type1, int type2)
{
    int arg_start = arg_starts[--arg_pos];
    int num_args = stack_pos - arg_start;

    *args = &stack[arg_start];
    if (num_args != 2)
        func_num_error(num_args, "two");
    else if (type1 && stack[arg_start].type != type1)
        func_type_error("first", &stack[arg_start], english_type(type1));
    else if (type2 && stack[arg_start + 1].type != type2)
        func_type_error("second", &stack[arg_start + 1], english_type(type2));
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref) {
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
       /* pop(2); */
    } else
        return 1;
    return 0;
}

int func_init_3(data_t **args, int type1, int type2, int type3)
{
    int arg_start = arg_starts[--arg_pos];
    int num_args = stack_pos - arg_start;

    *args = &stack[arg_start];
    if (num_args != 3)
        func_num_error(num_args, "three");
    else if (type1 && stack[arg_start].type != type1)
        func_type_error("first", &stack[arg_start], english_type(type1));
    else if (type2 && stack[arg_start + 1].type != type2)
        func_type_error("second", &stack[arg_start + 1], english_type(type2));
    else if (type3 && stack[arg_start + 2].type != type3)
        func_type_error("third", &stack[arg_start + 2], english_type(type3));
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref) {
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
        /* pop(3); */
    } else
        return 1;
    return 0;
}

int func_init_0_or_1(data_t **args, int *num_args, int type1)
{
    int arg_start = arg_starts[--arg_pos];

    *args = &stack[arg_start];
    *num_args = stack_pos - arg_start;
    if (*num_args > 1)
        func_num_error(*num_args, "at most one");
    else if (type1 && *num_args == 1 && stack[arg_start].type != type1)
        func_type_error("first", &stack[arg_start], english_type(type1));
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref) {
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
        /* pop(*num_args); */
    } else
        return 1;
    return 0;
}

int func_init_1_or_2(data_t **args, int *num_args, int type1, int type2)
{
    int arg_start = arg_starts[--arg_pos];

    *args = &stack[arg_start];
    *num_args = stack_pos - arg_start;
    if (*num_args < 1 || *num_args > 2)
        func_num_error(*num_args, "one or two");
    else if (type1 && stack[arg_start].type != type1)
        func_type_error("first", &stack[arg_start], english_type(type1));
    else if (type2 && *num_args == 2 && stack[arg_start + 1].type != type2)
        func_type_error("second", &stack[arg_start + 1], english_type(type2));
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref) {
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
        /* pop(*num_args); */
    } else
        return 1;
    return 0;
}

int func_init_2_or_3(data_t **args, int *num_args, int type1, int type2,
                     int type3)
{
    int arg_start = arg_starts[--arg_pos];

    *args = &stack[arg_start];
    *num_args = stack_pos - arg_start;
    if (*num_args < 2 || *num_args > 3)
        func_num_error(*num_args, "two or three");
    else if (type1 && stack[arg_start].type != type1)
        func_type_error("first", &stack[arg_start], english_type(type1));
    else if (type2 && stack[arg_start + 1].type != type2)
        func_type_error("second", &stack[arg_start + 1], english_type(type2));
    else if (type3 && *num_args == 3 && stack[arg_start + 2].type != type3)
        func_type_error("third", &stack[arg_start + 2], english_type(type3));
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref) {
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
        /* pop(*num_args); */
    } else
        return 1;
    return 0;
}

int func_init_1_to_3(data_t **args, int *num_args, int type1, int type2,
                     int type3)
{
    int arg_start = arg_starts[--arg_pos];

    *args = &stack[arg_start];
    *num_args = stack_pos - arg_start;
    if (*num_args < 1 || *num_args > 3)
        func_num_error(*num_args, "one to three");
    else if (type1 && stack[arg_start].type != type1)
        func_type_error("first", &stack[arg_start], english_type(type1));
    else if (type2 && *num_args >= 2 && stack[arg_start + 1].type != type2)
        func_type_error("second", &stack[arg_start + 1], english_type(type2));
    else if (type3 && *num_args == 3 && stack[arg_start + 2].type != type3)
        func_type_error("third", &stack[arg_start + 2], english_type(type3));
    else if (op_table[cur_frame->last_opcode].binding != INV_OBJNUM &&
             op_table[cur_frame->last_opcode].binding
             != cur_frame->method->object->dbref) {
        cthrow(perm_id,
               "Caller (#%l) is not the object bound to this function.",
               cur_frame->method->object->dbref);
        /* pop(*num_args); */
    } else
        return 1;
    return 0;
}

void func_num_error(int num_args, char *required)
{
    Number_buf nbuf;

    cthrow(numargs_id, "Called with %s argument%s, requires %s.",
          english_integer(num_args, nbuf),
          (num_args == 1) ? "" : "s", required);
}

void func_type_error(char *which, data_t *wrong, char *required)
{
    cthrow(type_id, "The %s argument (%D) is not %s.", which, wrong, required);
}

void cthrow(Ident error, char *fmt, ...)
{
    string_t *str;
    va_list arg;

    va_start(arg, fmt);
    str = vformat(fmt, arg);

    va_end(arg);
    interp_error(error, str);
    string_discard(str);
}

void interp_error(Ident error, string_t *explanation)
{
    list_t * location;
    Ident location_type;
    data_t *d;
    char *opname;

    /* Get the opcode name and decide whether it's a function or not. */
    opname = op_table[cur_frame->last_opcode].name;
    location_type = (islower(*opname)) ? function_id : opcode_id;

    /* Construct a two-element list giving the location. */
    location = list_new(2);
    d = list_empty_spaces(location, 2);

    /* The first element is 'function or 'opcode. */
    d->type = SYMBOL;
    d->u.symbol = ident_dup(location_type);
    d++;

    /* The second element is the symbol for the opcode. */
    d->type = SYMBOL;
    d->u.symbol = ident_dup(op_table[cur_frame->last_opcode].symbol);

    start_error(error, explanation, NULL, location);
    list_discard(location);
}

void user_error(Ident error, string_t *explanation, data_t *arg)
{
    list_t * location;
    data_t *d;

    /* Construct a list giving the location. */
    location = list_new(5);
    d = list_empty_spaces(location, 5);

    /* The first element is 'method. */
    d->type = SYMBOL;
    d->u.symbol = ident_dup(method_id);
    d++;

    /* The second through fifth elements are the current method info. */
    fill_in_method_info(d);

    /* Return from the current method, and propagate the error. */
    frame_return();
    start_error(error, explanation, arg, location);
    list_discard(location);
}

internal void out_of_ticks_error(void)
{
    internal string_t *explanation;
    list_t * location;
    data_t *d;

    /* Construct a list giving the location. */
    location = list_new(5);
    d = list_empty_spaces(location, 5);

    /* The first element is 'interpreter. */
    d->type = SYMBOL;
    d->u.symbol = ident_dup(interpreter_id);
    d++;

    /* The second through fifth elements are the current method info. */
    fill_in_method_info(d);

    /* Don't give the topmost frame a chance to return. */
    frame_return();

    if (!explanation)
        explanation = string_from_chars("Out of ticks", 12);
    start_error(methoderr_id, explanation, NULL, location);
    list_discard(location);
}

internal void start_error(Ident error, string_t *explanation, data_t *arg,
                        list_t * location)
{
    list_t * error_condition, *traceback;
    data_t *d;

    /* Construct a three-element list for the error condition. */
    error_condition = list_new(3);
    d = list_empty_spaces(error_condition, 3);

    /* The first element is the error code. */
    d->type = ERROR;
    d->u.error = ident_dup(error);
    d++;

    /* The second element is the explanation string. */
    d->type = STRING;
    d->u.str = string_dup(explanation);
    d++;

    /* The third element is the error arg, or 0 if there is none. */
    if (arg) {
        data_dup(d, arg);
    } else {
        d->type = INTEGER;
        d->u.val = 0;
    }

    /* Now construct a traceback, starting as a two-element list. */
    traceback = list_new(2);
    d = list_empty_spaces(traceback, 2);

    /* The first element is the error condition. */
    d->type = LIST;
    d->u.list = error_condition;
    d++;

    /* The second argument is the location. */
    d->type = LIST;
    d->u.list = list_dup(location);

    /* Start the error propagating.  This consumes traceback. */
    propagate_error(traceback, error);
}

/* Requires:        traceback is a list of strings containing the traceback
 *                        information to date.  THIS FUNCTION CONSUMES TRACEBACK.
 *                id is an error id.  This function accounts for an error id
 *                        which is "owned" by a data stack frame that we will
 *                        nuke in the course of unwinding the call stack.
 *                str is a string containing an explanation of the error. */
void propagate_error(list_t * traceback, Ident error)
{
    int i, ind, propagate = 0;
    Error_action_specifier *spec;
    Error_list *errors;
    Handler_info *hinfo;

    /* If there's no current frame, drop all this on the floor. */
    if (!cur_frame) {
        list_discard(traceback);
        return;
    }

    /* Add message to traceback. */
    traceback = traceback_add(traceback, error);

    /* Look for an appropriate specifier in this frame. */
    for (; cur_frame->specifiers; pop_error_action_specifier()) {

        spec = cur_frame->specifiers;
        switch (spec->type) {

          case CRITICAL:

            /* We're in a critical expression.  Make a copy of the error,
             * since it may currently be living in the region of the stack
             * we're about to nuke. */
            error = ident_dup(error);

            /* Nuke the stack back to where we were at the beginning of the
             * critical expression. */
            pop(stack_pos - spec->stack_pos);

            /* Jump to the end of the critical expression. */
            cur_frame->pc = spec->u.critical.end;

            /* Push the error on the stack, and discard our copy of it. */
            push_error(error);
            ident_discard(error);

            /* Pop this error spec, discard the traceback, and continue
             * processing. */
            pop_error_action_specifier();
            list_discard(traceback);
            return;

          case PROPAGATE:

            /* We're in a propagate expression.  Set the propagate flag and
             * keep going. */
            propagate = 1;
            break;

          case CATCH:

            /* We're in a catch statement.  Get the error list index. */
            ind = spec->u.ccatch.error_list;

            /* If the index is -1, this was a 'catch any' statement.
             * Otherwise, check if this error code is in the error list. */
            if (spec->u.ccatch.error_list != -1) {
                errors = &cur_frame->method->error_lists[ind];
                for (i = 0; i < errors->num_errors; i++) {
                    if (errors->error_ids[i] == error)
                        break;
                }

                /* Keep going if we didn't find the error. */
                if (i == errors->num_errors)
                    break;
            }

            /* We catch this error.  Make a handler info structure and push it
             * onto the stack. */
            hinfo = EMALLOC(Handler_info, 1);
            hinfo->traceback = traceback;
            hinfo->error = ident_dup(error);
            hinfo->next = cur_frame->handler_info;
            cur_frame->handler_info = hinfo;

            /* Pop the stack down to where we were at the beginning of the
             * catch statement.  This may nuke our copy of error, but we don't
             * need it any more. */
            pop(stack_pos - spec->stack_pos);

            /* Jump to the handler expression, pop this specifier, and continue
             * processing. */
            cur_frame->pc = spec->u.ccatch.handler;
            pop_error_action_specifier();
            return;

        }
    }

    /* There was no handler in the current frame. */
    frame_return();
    propagate_error(traceback, (propagate) ? error : methoderr_id);
}

internal list_t * traceback_add(list_t * traceback, Ident error)
{
    list_t * frame;
    data_t *d, frame_data;

    /* Construct a list giving information about this stack frame. */
    frame = list_new(5);
    d = list_empty_spaces(frame, 5);

    /* First element is the error code. */
    d->type = ERROR;
    d->u.error = ident_dup(error);
    d++;

    /* Second through fifth elements are the current method info. */
    fill_in_method_info(d);

    /* Add the frame to the list. */
    frame_data.type = LIST;
    frame_data.u.list = frame;
    traceback = list_add(traceback, &frame_data);
    list_discard(frame);

    return traceback;
}

void pop_error_action_specifier(void)
{ 
    Error_action_specifier *old;

    /* Pop the first error action specifier off that stack. */
    old = cur_frame->specifiers;
    cur_frame->specifiers = old->next;
    efree(old);
}

void pop_handler_info(void)
{
    Handler_info *old;

    /* Free the data in the first handler info specifier, and pop it off that
     * stack. */
    old = cur_frame->handler_info;
    list_discard(old->traceback);
    ident_discard(old->error);
    cur_frame->handler_info = old->next;
    efree(old);
}

internal void fill_in_method_info(data_t *d)
{
    Ident method_name;

    /* The method name, or 0 for eval. */
    method_name = cur_frame->method->name;
    if (method_name == NOT_AN_IDENT) {
        d->type = INTEGER;
        d->u.val = 0;
    } else {
        d->type = SYMBOL;
        d->u.val = ident_dup(method_name);
    }
    d++;

    /* The current object. */
    d->type = DBREF;
    d->u.dbref = cur_frame->object->dbref;
    d++;

    /* The defining object. */
    d->type = DBREF;
    d->u.dbref = cur_frame->method->object->dbref;
    d++;

    /* The line number. */
    d->type = INTEGER;
    d->u.val = line_number(cur_frame->method, cur_frame->pc);
}

void bind_opcode(int opcode, Dbref dbref) {
    op_table[opcode].binding = dbref;
}