ldmud-3.2.9/doc/
ldmud-3.2.9/doc/efun/
ldmud-3.2.9/mud/
ldmud-3.2.9/mud/heaven7/
ldmud-3.2.9/mud/heaven7/lib/
ldmud-3.2.9/mud/lp-245/
ldmud-3.2.9/mud/lp-245/banish/
ldmud-3.2.9/mud/lp-245/doc/
ldmud-3.2.9/mud/lp-245/doc/examples/
ldmud-3.2.9/mud/lp-245/doc/sefun/
ldmud-3.2.9/mud/lp-245/log/
ldmud-3.2.9/mud/lp-245/obj/Go/
ldmud-3.2.9/mud/lp-245/players/lars/
ldmud-3.2.9/mud/lp-245/room/death/
ldmud-3.2.9/mud/lp-245/room/maze1/
ldmud-3.2.9/mud/lp-245/room/sub/
ldmud-3.2.9/mud/lp-245/secure/
ldmud-3.2.9/mud/morgengrauen/
ldmud-3.2.9/mud/morgengrauen/lib/
ldmud-3.2.9/mud/sticklib/
ldmud-3.2.9/mud/sticklib/src/
ldmud-3.2.9/mudlib/uni-crasher/
ldmud-3.2.9/pkg/
ldmud-3.2.9/pkg/debugger/
ldmud-3.2.9/pkg/diff/
ldmud-3.2.9/pkg/misc/
ldmud-3.2.9/src/autoconf/
ldmud-3.2.9/src/bugs/
ldmud-3.2.9/src/bugs/MudCompress/
ldmud-3.2.9/src/bugs/b-020916-files/
ldmud-3.2.9/src/bugs/doomdark/
ldmud-3.2.9/src/bugs/ferrycode/ferry/
ldmud-3.2.9/src/bugs/ferrycode/obj/
ldmud-3.2.9/src/bugs/psql/
ldmud-3.2.9/src/done/
ldmud-3.2.9/src/done/order_alist/
ldmud-3.2.9/src/done/order_alist/obj/
ldmud-3.2.9/src/done/order_alist/room/
ldmud-3.2.9/src/gcc/
ldmud-3.2.9/src/gcc/2.7.0/
ldmud-3.2.9/src/gcc/2.7.1/
ldmud-3.2.9/src/hosts/
ldmud-3.2.9/src/hosts/GnuWin32/
ldmud-3.2.9/src/hosts/amiga/NetIncl/
ldmud-3.2.9/src/hosts/amiga/NetIncl/netinet/
ldmud-3.2.9/src/hosts/amiga/NetIncl/sys/
ldmud-3.2.9/src/hosts/i386/
ldmud-3.2.9/src/hosts/msdos/byacc/
ldmud-3.2.9/src/hosts/msdos/doc/
ldmud-3.2.9/src/hosts/os2/
ldmud-3.2.9/src/hosts/win32/
ldmud-3.2.9/src/util/
ldmud-3.2.9/src/util/erq/
ldmud-3.2.9/src/util/indent/hosts/next/
ldmud-3.2.9/src/util/xerq/
ldmud-3.2.9/src/util/xerq/lpc/
ldmud-3.2.9/src/util/xerq/lpc/www/
/*---------------------------------------------------------------------------
 * Gamedriver: Bytecode Interpreter
 *
 *---------------------------------------------------------------------------
 * This module implements the bytecode interpreter for the compiled LPC
 * programs. The machine is implemented as a stackmachine with separate
 * stacks for values and control.
 *
 * See also 'exec.h' for the details of program storage, and 'svalue.h'
 * for the details of value storage.
 *
 * --- Evaluator Stack ---
 *
 *    The evaluation stack is an array of 'svalue_t's (see datatypes.h
 *    for information about this type) with EVALUATOR_SIZE<<1 elements.
 *    <inter_sp> resp. <sp> points to the last (that is topmost) valid
 *    entry in the stack, the framepointer <inter_fp> resp. <fp> points
 *    to the bottom of the frame of one function. Single values in the
 *    frame are then accessed by indexing the frame pointer.
 *    A typical stack layout looks like this:
 *
 *                    ^
 *    (inter_)sp   -> |  Top stack value
 *                    |  ...
 *                    |  Temporary stack values
 *                    |  Break addresses for switch instructions
 *    break_sp     -> |   (forming a sub-stack growing _down_).
 *                    |  ...
 *                    |  Local variable number 1
 *                    |  Local variable number 0
 *                    |  ...
 *                    |  Argument number 1
 *    (inter_)fp   -> |  Argument number 0
 *                    |
 *                    |
 *    VALUE_STACK  -----
 *
 *    The interpreter assumes that there are no destructed objects
 *    on the stack - to aid in this, the functions remove_object_from_stack()
 *    and (in array.c) check_for_destr() replace destructed objects by
 *    value 0.
 *
 *
 * --- Control Stack ---
 *
 *    During nested function calls, the return information to the higher
 *    functions are stored on the control stack.
 *
 *    One particularity about the current implementation is that every
 *    inter-object call (ie. every 'extern_call') and every catch()
 *    constitutes in a recursive call to eval_instruction().
 *
 *
 * --- Error Recovery Stack --- (implemented in backend.c)
 *
 *    Error recovery in general is implemented using setjmp()/longjmp().
 *    The error recovery stack holds the (possibly nested) longjmp() contexts
 *    together with an indication where the jump will lead. Currently these
 *    context types are defined:
 *      ERROR_RECOVERY_NONE:     No error recovery available
 *                               (used by the top entry in the stack)
 *      ERROR_RECOVERY_BACKEND:  Errors fall back to the backend,
 *                               e.g. process_objects(), call_heart_beat()
 *                               and others.
 *      ERROR_RECOVERY_APPLY:    Errors fall back into the secure_apply()
 *                               function used for sensitive applies.
 *      ERROR_RECOVERY_CATCH:    Errors are caught by the catch() construct.
 *      ERROR_RECOVERY_CATCH_NOLOG: Errors are caught by the catch_nolog()
 *                               construct.
 *
 *    The _CATCH contexts differs from the others in that it allows the
 *    continuing execution after the error. In order to achieve this, the
 *    stack entry holds the necessary additional information to re-init
 *    the interpreter.
 *    TODO: Elaborate on the details.
 *
 *
 * --- Bytecode ---
 *
 *    The machine instructions are stored as unsigned characters and read
 *    sequentially. A single machine instruction consists of one or two
 *    bytes defining the instruction, optionally followed by more bytes
 *    with parameters (e.g. number of arguments on the stack, branch offsets,
 *    etc).
 *
 *    Apart from the usual machine instructions (branches, stack
 *    manipulation, summarily called 'codes'), the machine implements every
 *    efun by its own instruction code. Since this leads to more than
 *    256 instructions, the less often used instructions are grouped into
 *    'xefuns'+'xcodes', 'tefuns'+'tcodes'  and 'vefuns'+'tcodes', and the
 *    byte defining for the instruction within its group is prefixed by
 *    a byte defining the group: F_ESCAPE for xefuns, F_TEFUN for tefuns,
 *    and F_VEFUN for vefuns.
 *
 *    Like normal machine instructions and efuns, xefuns are implemented as
 *    cases in a giant switch() instruction, while the implementations of
 *    tefuns/vefuns are called via function tables (tefuns take a fixed
 *    number of arguments, vefuns a variable number).
 *
 *    Implementationwise, every machine instruction, efun or else, is assigned
 *    a unique number and a preprocessor symbol F_<name>. The relation between
 *    the assigned number and the bytecode is linear: normal instructions and
 *    efuns occupy the number range <offset>..<offset>+255, xefuns the range
 *    <offset>+256..<offset>+383, tefuns the range <offset>+384..<offset>+511,
 *    and vefuns the range from <offset>+512 . <offset> is currently 256, this
 *    allows the lexer to use the machine instruction numbers as an extension
 *    of the charset to encode references to efuns directly.
 *
 *    All existing machine instructions are defined in the file func_spec,
 *    which during the compilation of the driver is evaluated by make_func.y
 *    to create the LPC compiler lang.y from prolang.y, the symbolic
 *    instruction names and numbers in instrs.h, and the definition of the
 *    tables efuns in efun_defs.c .
 *
 *
 * --- Calling Convention ---
 *
 *    All arguments for a function are evaluated and pushed to the value
 *    stack. The last argument is the last pushed. It is important that
 *    the called function gets exactly as many arguments as it wants; for
 *    LPC functions ('lfuns') this means that the actual function call will
 *    remove excessive arguments or push '0's for missing arguments. The
 *    number of arguments will be stored in the control stack, so that
 *    the return instruction not needs to know it explicitely.
 *
 *    If the function called is an lfun (inherited or not), the number
 *    of arguments passed to the call is encoded in the bytecode stream,
 *    and the number of arguments expected can be determined from the
 *    functions 'struct function' entry.
 *
 *    Efuns, operators and internal bytecode usually operate on a fixed
 *    number of arguments and the compiler makes sure that the right
 *    number is given. If an efun takes a variable number of arguments,
 *    the actual number is stored in the byte following the efun's opcode.
 *
 *    The called function must ensure that exactly one value remains on the
 *    stack when returning. The caller is responsible of deallocating the
 *    returned value. This includes 'void' lfuns, which just push the
 *    value 0 as return value.
 *
 *    When a LPC function returns, it will use the instruction F_RETURN, which
 *    will deallocate all arguments and local variables, and only let the
 *    top of stack entry remain. The number of arguments and local variables
 *    are stored in the control stack, so that the evaluator knows how much
 *    to deallocate.
 *
 *    If flag 'extern_call' is set, then the evaluator should return from
 *    eval_instruction(). Otherwise, the evaluator will continue to execute
 *    the instruction at the returned address. In the current implementation,
 *    every inter-object call (call_other) receives its own (recursive)
 *    call to eval_instruction().
 *
 *---------------------------------------------------------------------------
 * TODO: The virtual machine should be reconsidered, using the DGD and MudOS
 * TODO:: machines for inspiration. This applies to implementation as well
 * TODO:: as to the instruction set.
 * TODO: Let all assign_ and transfer_ functions check for destruct objects.
 * TODO:: The speed difference to assign_checked_ and transfer_checked_ is
 * TODO:: not big enough to justify the extra set of functions.
 */

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

#include "driver.h"
#include "typedefs.h"

#include "my-alloca.h"
#include "my-rusage.h"
#include <fcntl.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <setjmp.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef MARK
#include <prof.h>
#endif

#ifdef HAVE_CRYPT_H
     /* solaris defines crypt() here */
#    include <crypt.h>
#endif
#if !defined(HAVE_CRYPT) && defined(HAVE__CRYPT)
#    define crypt(pass, salt) _crypt(pass, salt)
#endif

#define USES_SVALUE_STRLEN
#include "interpret.h"

#include "actions.h"
#include "array.h"
#include "backend.h"
#include "call_out.h"
#include "closure.h"
#include "comm.h"
#include "ed.h"
#include "efuns.h"
#include "exec.h"
#include "filestat.h"
#include "gcollect.h"
#include "heartbeat.h"
#include "instrs.h"
#include "lex.h"
#include "main.h"
#include "mapping.h"
#include "object.h"
#include "otable.h"
#include "parse.h"
#include "prolang.h"
#include "ptrtable.h"
#include "random.h"
#include "sent.h"
#include "simulate.h"
#include "simul_efun.h"
#include "stdstrings.h"
#include "stralloc.h"
#include "smalloc.h" /* USES_SVALUE_STRLEN, malloc_increment_size() */
#include "sprintf.h"
#include "svalue.h"
#include "swap.h"
#include "switch.h"
#include "wiz_list.h"
#include "xalloc.h"

#include "../mudlib/sys/driver_hook.h"
#include "../mudlib/sys/debug_info.h"
#include "../mudlib/sys/trace.h"

/*-------------------------------------------------------------------------*/
/* Types */

/* --- struct catch_context: error_recovery subclass for catch() ---
 *
 * This extension of the struct error_recovery_info (see backend.h)
 * stores the additional information needed to reinitialize the global
 * variables when bailing out of a catch(). The type is always
 * ERROR_RECOVERY_CATCH or ERROR_RECOVERY_CATCH_NOLOG.
 *
 * It is handled by the functions push_, pop_ and pull_error_context().
 */

struct catch_context
{
    struct error_recovery_info recovery_info;
      /* The subclassed recovery info.
       */
    struct control_stack * save_csp;
    object_t        * save_command_giver;
    svalue_t        * save_sp;
      /* The saved global values
       */
};

/* --- struct cache: one entry of the apply cache
 *
 * Every entry in the apply cache holds information about a function
 * call, both for functions found and not found.
 */

struct cache
{
    char *name;
      /* The name of the cached function, shared for existing functions,
       * allocated if the object does not have the function.
       */
    program_t *progp;
      /* The pointer to the program code of the function, or NULL if the
       * object does not implement the function.
       */
    int id;
      /* The id_number of the program. */

    funflag_t flags;
      /* Copy of the _MOD_STATIC and _MOD_PROTECTED flags of the function.
       */
    fun_hdr_p funstart;
      /* Pointer to the function.
       */
    int function_index_offset;
    int variable_index_offset;
      /* Function and variable index offset.
       */
};

/* --- struct mvf_info: structure used by m_values()/unmkmapping() ---
 *
 * This structure is passed by reference to the filter functions used
 * by the two efuns and passes information from one filter call to the
 * next.
 */
struct mvf_info
{
    svalue_t * svp;
      /* m_values_filter: Pointer to next result vector entry
       * m_unmake_filter: Pointer to result array of svalues
       */
    int             num;
      /* m_values_filter: Column to retrieve.
       * m_unmake_filter: Next row to retrieve
       */
    int             width;
      /* m_unmake_filter: width of the mapping
       */
};

/*-------------------------------------------------------------------------*/
/* Macros */

#define ERRORF(s) {inter_pc = pc; inter_sp = sp; error s ;}
#define ERROR(s) ERRORF((s))
  /* ERRORF((...)) acts like error(...), except that first the local pc and sp
   * are copied into the global variables.
   * ERROR() is an easier to type form of ERRORF() when your error message
   * is just one string. It will be redefined below for the tabled
   * efuns.
   */

#define FATALF(s) {inter_pc = pc; inter_sp = sp; fatal s ;}
#define FATAL(s) FATALF((s))
  /* Analogue.
   */

#if APPLY_CACHE_BITS < 1
#    error APPLY_CACHE_BITS must be at least 1.
#else
#    define CACHE_SIZE (1 << APPLY_CACHE_BITS)
#endif
  /* Number of entries in the apply cache.
   */


/*-------------------------------------------------------------------------*/
/* Tracing */

int tracedepth;
  /* Current depth of traced functions.
   */

int trace_level;
  /* Current set of active trace options.
   * This set can be different from interactive->trace_level if several
   * nested trace() calls occur.
   */

static int traceing_recursion = -1;
  /* Kind of mutex, used to turn off tracing while doing trace output.
   * Necessary because output with add_message() might result result
   * in further code to be executed.
   */

static Bool trace_exec_active = MY_FALSE;
  /* TRUE whenever TRACE_EXEC is not just requested, but actually
   * active. This distinction is necessary as tracing might be limited
   * to one object only, and testing the object name for every instruction
   * would be too expensive. Hence, the tracing condition is checked
   * only on object changes and this variable is updated accordingly.
   * See macros SET_TRACE_EXEC and TRACE_EXEC_P.
   */

#ifdef TRACE_CODE
/* The buffers for the traced code:
 */

static int              previous_instruction[TOTAL_TRACE_LENGTH];
static ptrdiff_t        stack_size[TOTAL_TRACE_LENGTH];
static ptrdiff_t        abs_stack_size[TOTAL_TRACE_LENGTH];
static bytecode_p       previous_pc[TOTAL_TRACE_LENGTH];
static program_t * previous_programs[TOTAL_TRACE_LENGTH];
static object_t  * previous_objects[TOTAL_TRACE_LENGTH];
  /* These arrays, organized as ring buffers, hold the vitals of the
   * last TOTAL_TRACE_LENGTH instructions executed. Yet unused entries
   * are 0 resp. NULL.
   */

static int              last = TOTAL_TRACE_LENGTH - 1;
  /* Index to the last used entry in the ringbuffers above.
   */

#endif

/* --- Macros --- */

#define TRACE_IS_INTERACTIVE() (command_giver && O_IS_INTERACTIVE(command_giver))

  /* Return TRUE if the current command_giver is interactive.
   * TODO: Instead of disabling all traceoutput whenever the command_giver
   * TODO:: turns non-interactive, output should be redirected (with a
   * TODO:: special mark) to the current_interactive.
   */

#define TRACETST(b) (TRACE_IS_INTERACTIVE() && (O_GET_INTERACTIVE(command_giver)->trace_level & (b)))

  /* Return TRUE if the any of the tracing options <b> are requested
   * by the interactive user.
   */

#define TRACEP(b) (trace_level & (b) && trace_test(b))
  /* Return TRUE if tracing options <b> are both active in trace_level
   * and requested by the interactive user.
   */

#define TRACEHB \
  ( current_heart_beat == NULL || TRACETST(TRACE_HEART_BEAT))

  /* Return TRUE if either the current execution is not caused
   * by a heart beat call, or if heartbeat tracing is allowed.
   */

#define SET_TRACE_EXEC MACRO( \
                              if (trace_level & TRACE_EXEC) \
                                  trace_exec_active = MY_TRUE;\
                            )

  /* If TRACE_EXEC is requested, (re)activate it.
   * See trace_exec_active for the background.
   */

#define TRACE_EXEC_P (   TRACEP(TRACE_EXEC) \
                      || (trace_exec_active = MY_FALSE, MY_FALSE))

  /* If TRACE_EXEC is still requested, return TRUE, otherwise deactivate
   * it and return FALSE.
   * See trace_exec_active for the background.
   */

/*-------------------------------------------------------------------------*/
/* Variables */

/* The virtual machine's registers.
 *
 * While the interpreter is in eval_instruction(), some of the values are
 * kept in local variables for greater speed, with the globals being updated
 * only when necessary.
 * The affected variables are: inter_pc, inter_sp, TODO: which else?
 */

svalue_t *inter_sp;
  /* Points to last valid value on the value stack.
   */
#ifndef MALLOC_LPC_TRACE
static
#endif
       bytecode_p inter_pc;
  /* Next bytecode to interpret.
   */

static svalue_t *inter_fp;
  /* Framepointer: pointer to first argument.
   */

static bytecode_p *break_sp;
  /* Points to address to branch to at next F_BREAK from within a switch().
   * This is actually a stack of addresses with break_sp pointing to the
   * bottom with the most recent entry. This break stack is stored on
   * the evaluator stack, one address per svalue_t (which incidentally
   * stored in the u.string field), between the functions temporary values
   * and its local variables.
   * TODO: Since this stores an opcode* in a svalue, it should get its
   * TODO:: own union type, and break_sp should be an svalue_t *.
   */

program_t *current_prog;
  /* The current program. This is usually current_object->prog, but can
   * differ when executing an inherited program.
   */

static svalue_t current_lambda;
  /* If the VM is executing a lambda, this variable holds a counted
   * reference to it to make sure that it isn't destructed while it is
   * still executing.
   */

static char **current_strings;
  /* Pointer to the string literal block of the current program for
   * faster access.
   */

int function_index_offset;
  /* Index of current program's function block within the functions of
   * the current objects program (needed for inheritance).
   */

static int variable_index_offset;
  /* Index of current program's variable block within the variables
   * of the current object (needed for inheritance).
   */

svalue_t *current_variables;
  /* Pointer to begin of the current variable block.
   * This is current_object->variables + variable_index_offset for
   * faster access.
   */


/* Other Variables */

int32 eval_cost;
  /* The amount of eval cost used in the current execution thread.
   */

int32 assigned_eval_cost;
  /* Auxiliary variable used to account eval costs to single objects and
   * their user's wizlist entry.
   * Whenver the execution thread enters a different object,
   * assigned_eval_cost is set to the current value of eval_cost. When the
   * thread leaves the object again, the difference between the actual
   * eval_cost value and the older assigned_eval_cost is accounted to
   * the current object.
   * The implementation combines both actions in one function
   * assign_eval_cost().
   */

svalue_t apply_return_value = { T_NUMBER };
  /* This variable holds the result from a call to apply(), transferred
   * properly from the interpreter stack where the called function
   * left it.
   * push_ and pop_apply_value() handle this particular transfer.
   * Note: The process_string() helper function process_value() takes
   * direct advantage of this variable.
   */

#define SIZEOF_STACK (EVALUATOR_STACK_SIZE<<1)

static svalue_t value_stack_array[SIZEOF_STACK+1];
#define VALUE_STACK (value_stack_array+1)

  /* The evaluator stack, sized with (hopefully) enough fudge to handle
   * function arguments and overflows.
   * The stack grows upwards, and <inter_sp> points to last valid entry.
   *
   * The first entry of value_stack_array[] is not used and serves as
   * dummy so that underflows can be detected in a portable way
   * (Standard C disallows indexing before an array). Instead, VALUE_STACK
   * is the real bottom of the stack.
   */

svalue_t catch_value = { T_INVALID } ;
  /* Holds the value throw()n from within a catch() while the throw
   * is executed.
   */

static struct control_stack control_stack_array[MAX_TRACE+2];
#define CONTROL_STACK (control_stack_array+2)
struct control_stack *csp;
  /* The control stack holds copies of the machine registers for previous
   * function call levels, with <csp> pointing to the last valid
   * entry, describing the last context.
   * This also means that CONTROL_STACK[0] (== control_stack_array[2]) will
   * have almost no interesting values as it will terminate execution.
   * Especially CONTROL_STACK[0].prog is NULL to mark the bottom.
   *
   * The first two entries of control_stack_array[] are not used and
   * serve as dummies so that underflows can be detected in a portable
   * way (Standard C disallows indexing before an array).
   */

#ifdef APPLY_CACHE_STAT
p_int apply_cache_hit  = 0;
p_int apply_cache_miss = 0;
  /* Number of hits and misses in the apply cache.
   */
#endif

static struct cache cache[CACHE_SIZE];
  /* The apply cache.
   */

static struct
  {
    svalue_t v;
      /* The target value:
       *   .v.type: T_CHAR_LVALUE
       *   .v.u.string: the char to modify
       * or
       *   .v.type: T_{POINTER,STRING}_RANGE_LVALUE
       *   .v.u.{vec,string}: the target value holding the range
       *   .index1, .index2, .size: see below
       */
    mp_int index1;  /* First index of the range */
    mp_int index2;  /* Last index of the range plus 1 */
    mp_int size;    /* Current(?) size of the value */
  }
special_lvalue;
  /* When assigning to vector and string ranges or elements, the
   * target information is stored in this structure.
   * TODO: Having one global structure counts as 'ugly'.
   * Used knowingly by: (r)index_lvalue(), transfer_pointer_range(),
   *                    assign_string_range().
   * Used unknowingly by: assign_svalue(), transfer_svalue(),
   *                    add_number_to_lvalue(), F_VOID_ASSIGN.
   */

static svalue_t indexing_quickfix = { T_NUMBER };
  /* When indexing arrays and mappings with just one ref, especially
   * for the purpose of getting a lvalue, the indexed item is copied
   * into this variable and indexed from here.
   * Used by operators: push_(r)indexed_lvalue, push_indexed_value,
   *                    push_indexed_map_lvalue.
   * TODO: Rename this variable, or better: devise a nice solution.
   * TODO:: Use the protected_lvalues instead?
   * TODO:: To quote Marion:
   * TODO::     marion says: but this is crude too
   * TODO::     marion blushes.
   * TODO: Is it made sure that this var will be vacated before the
   * TODO:: next use? Otoh, if not it's no problem as the value is
   * TODO:: by definition volatile.
   */

svalue_t last_indexing_protector = { T_NUMBER };
  /* When indexing a protected non-string-lvalue, this variable receives
   * the protecting svalue for the duration of the operation (actually
   * until the next indexing operation (TODO: not nice)).
   * This is necessary because the indexing operation necessarily destroys
   * the protector structure, even though the protection is still needed.
   * Used by: protected_index_lvalue().
   */

#ifdef OPCPROF

#define MAXOPC 0x280
  /* Number of different instructions to trace.
   */

static int opcount[MAXOPC];
  /* Counter array for instruction profiling: each entry counts the
   * usages of one instruction. The full instruction code (not the
   * opcode) is used as index.
   */

#endif

#ifdef DEBUG

static program_t *check_a_lot_ref_counts_search_prog;
  /* Program you developer are especially interested in.
   */

static struct pointer_table *ptable;
  /* check_a_lot_of_ref_counts: the table of structures already
   * visited.
   */

#endif

/*-------------------------------------------------------------------------*/
/* Forward declarations */

static Bool apply_low(char *, object_t *, int, Bool);
static void call_simul_efun(int code, object_t *ob, int num_arg);
#ifdef DEBUG
static void check_extra_ref_in_vector(svalue_t *svp, size_t num);
#endif

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

/* Assign the evaluation cost elapsed since the last call to the
 * current_object and it's user's wizlist entry. Then set assigned_eval_cost
 * to the current eval_cost so that later calls can do the same.
 *
 * This function must be called at least whenever the execution leaves
 * one object for another one.
 */

#define ASSIGN_EVAL_COST \
    if (current_object->user)\
        current_object->user->cost += eval_cost - assigned_eval_cost;\
    current_object->ticks += eval_cost - assigned_eval_cost;\
    {\
        unsigned long carry = current_object->ticks / 1000000000;\
        if (carry)\
        {\
            current_object->gigaticks += carry;\
            current_object->ticks %= 1000000000;\
        }\
    }\
    assigned_eval_cost = eval_cost;

void assign_eval_cost(void) { ASSIGN_EVAL_COST }

/*-------------------------------------------------------------------------*/
void
init_interpret (void)

/* Initialize the interpreter data structures, especially the apply cache.
 */

{
    struct cache invalid_entry;
    int i;

    /* The cache is inited to hold entries for 'functions' in a non-existing
     * program (id 0). The first real apply calls will thus see a (virtual)
     * collision with 'older' cache entries.
     */

    invalid_entry.id = 0;
    invalid_entry.progp = (program_t *)1;

    for (i = 0; i < CACHE_SIZE; i++)
        cache[i] = invalid_entry;
}

/*=========================================================================*/

/*                         S V A L U E S                                   */

/*-------------------------------------------------------------------------*/
/* The following functions handle svalues, ie. the data referenced
 * by the svalue_ts. 'Freeing' in this context therefore never means
 * a svalue_t, only the data referenced by it.
 *
 * destructed_object_ref(v): test if <v> references a destructed object.
 * get_object_ref(v): return the object referenced by <v>, if any.
 * free_string_svalue(v): free string svalue <v>.
 * free_object_svalue(v): free object svalue <v>.
 * zero_object_svalue(v): replace the object in svalue <v> by number 0.
 * free_svalue(v):        free the svalue <v>.
 * assign_svalue_no_free(to,from): put a copy of <from> into <to>; <to>
 *                        is considered empty.
 * copy_svalue_no_free(to,from): put a shallow value copy of <from> into <to>;
 *                        <to> is considered empty.
 * assign_checked_svalue_no_free(to,from,sp,pc): put a copy of <from> into <to>;
 *                        <to> is considered empty, <from> may be destructed
 *                        object.
 * assign_local_svalue_no_free(to,from,sp,pc): put a copy of local var <from>
 *                        into <to>; <to> is considered empty, <from> may
 *                        be destructed object.
 * static assign_lrvalue_no_free(to,from): like assign_svalue_no_free(),
 *                        but lvalues and strings are handled differently.
 * assign_svalue(dest,v): assign <v> to <dest>, freeing <dest> first.
 *                        Also handles assigns to lvalues.
 * transfer_svalue_no_free(dest,v): move <v> into <dest>; <dest> is
 *                        considered empty.
 * transfer_svalue(dest,v): move <v> into <dest>; freeing <dest> first.
 *                        Also handles transfers to lvalues.
 * static add_number_to_lvalue(dest,i,pre,post): add <i> to lvalue <dest>.
 *
 * In addition there are some helper functions.
 *
 * TODO: All these functions and vars should go into a separate module.
 */

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

/* --- Protector structures ---
 *
 * Whenever an assignment is made to a single value, or to a range in
 * a string, vector or mapping, the interpreter generates protector
 * structures in place of the usual LVALUE-svalues, which hold:
 *  - a svalue structure referring to the svalue into which the assignment
 *    is done (this structure is always first so that the protector
 *    structures can be used instead of normal svalues),
 *  - the necessary information to store the assigned svalue into its
 *    place in the target holding the value,
 *  - a protective reference to the holding value.
 *
 * All this just to keep LPC statements like 'a = ({ 1 }); a[0] = (a = 0);'
 * from crashing.
 *
 * TODO: A simpler way would be to compute the lhs of an assignment
 * TODO:: after evaluating the rhs - not vice versa as it is now.
 * TODO:: However, passing lvalues and ranges as ref-parameters to functions
 * TODO:: would still be a potential problem.
 */

/* --- struct protected_lvalue: protect a single value
 */
struct protected_lvalue
{
    svalue_t v;
      /* .v.type: T_PROTECTED_LVALUE
       * .v.u.lvalue: the protected value
       */
    svalue_t protector; /* protects .v.u.lvalue (or its holder) */
};

/* --- struct protected_char_lvalue: protect a char in a string
 */
struct protected_char_lvalue
{
    svalue_t v;
      /* .v.type: T_PROTECTED_CHAR_LVALUE
       * .v.u.string: points to the char to access
       */
    svalue_t protector; /* protects .lvalue */
    svalue_t *lvalue;   /* the string containing the char */
    char *start;
      /* must be == lvalue->u.string, otherwise the string has been
       * changed and this lvalue is invalid
       */
};

/* --- struct protected_range_lvalue: protect a range in a string or vector
 */
struct protected_range_lvalue {
    svalue_t v;
      /* .v.type: T_PROTECTED_{POINTER,STRING}_RANGE_LVALUE
       * .v.u.{string,vec}: the target value holding the range
       */
    svalue_t protector; /* protects .lvalue */
    svalue_t *lvalue;   /* the value holding the range */
    int index1, index2;      /* first and last index of the range */
    int size;                /* original size of .lvalue */

    /* .v.u.{vec,string} must be == .lvalue->u.{vec,string}, otherwise
     * the target has been changed and the range information (index, size)
     * is no longer valid.
     */
};

/*-------------------------------------------------------------------------*/
/* Forward declarations */

static void transfer_pointer_range(svalue_t *source);
static void transfer_protected_pointer_range(
    struct protected_range_lvalue *dest, svalue_t *source);
static void assign_string_range(svalue_t *source, Bool do_free);
static void assign_protected_string_range(
    struct protected_range_lvalue *dest,svalue_t *source, Bool do_free);

/*-------------------------------------------------------------------------*/
static INLINE Bool
_destructed_object_ref (svalue_t *svp)

/* Return TRUE if the svalue in <svp> references a destructed object.
 */

{
    lambda_t *l;
    int type;

    if (svp->type != T_OBJECT && svp->type != T_CLOSURE)
        return MY_FALSE;

    if (svp->type == T_OBJECT || !CLOSURE_MALLOCED(type = svp->x.closure_type))
        return (svp->u.ob->flags & O_DESTRUCTED) ? MY_TRUE : MY_FALSE;

    /* Lambda closure */

    l = svp->u.lambda;

    if (CLOSURE_HAS_CODE(type) && type == CLOSURE_UNBOUND_LAMBDA)
        return MY_FALSE;

    if (type == CLOSURE_ALIEN_LFUN
     && (l->function.alien.ob->flags & O_DESTRUCTED))
        return MY_TRUE;

    return (l->ob->flags & O_DESTRUCTED) ? MY_TRUE : MY_FALSE;

} /* _destructed_object_ref() */

Bool destructed_object_ref (svalue_t *v) { return _destructed_object_ref(v); }

#define destructed_object_ref(v) _destructed_object_ref(v)

/*-------------------------------------------------------------------------*/
static INLINE object_t *
_get_object_ref (svalue_t *svp)

/* If <svp> references an object (destructed or alive), return the object.
 * Return NULL otherwise.
 */

{
    lambda_t *l;
    int type;

    if (svp->type != T_OBJECT && svp->type != T_CLOSURE)
        return NULL;

    if (svp->type == T_OBJECT || !CLOSURE_MALLOCED(type = svp->x.closure_type))
        return svp->u.ob;

    /* Lambda closure */

    l = svp->u.lambda;

    if (CLOSURE_HAS_CODE(type) && type == CLOSURE_UNBOUND_LAMBDA)
        return NULL;

    if (type == CLOSURE_ALIEN_LFUN)
        return l->function.alien.ob;

    return l->ob;

} /* _get_object_ref() */

object_t * get_object_ref (svalue_t *v) { return _get_object_ref(v); }

#define get_object_ref(v) _get_object_ref(v)

/*-------------------------------------------------------------------------*/
static INLINE void
_free_string_svalue (svalue_t *v)

/* Free the string svalue <v>; <v> must be of type T_STRING.
 */

{
#ifdef DEBUG
    if (v->type != T_STRING)
    {
        fatal("free_string_svalue(): Expected string, "
              "received svalue type (%d:%hd)\n"
            , v->type, v->x.generic);
        /* NOTREACHED */
        return;
    }
#endif

    switch(v->x.string_type)
    {
    case STRING_MALLOC:
        xfree(v->u.string);
        break;
    case STRING_SHARED:
        free_string(v->u.string);
        break;
    }
}

void free_string_svalue (svalue_t *v) { _free_string_svalue(v); }

#define free_string_svalue(v) _free_string_svalue(v)

/*-------------------------------------------------------------------------*/
void
free_object_svalue (svalue_t *v)

/* Free the object svalue <v>; <v> must be of type T_OBJECT.
 */

{
    object_t *ob = v->u.ob;

#ifdef DEBUG
    if (v->type != T_OBJECT)
    {
        fatal("free_object_svalue(): Expected object, "
              "received svalue type (%d:%hd)\n"
            , v->type, v->x.generic);
        /* NOTREACHED */
        return;
    }
#endif

    free_object(ob, "free_object_svalue");
}

/*-------------------------------------------------------------------------*/
void
zero_object_svalue (svalue_t *v)

/* Change <v> from an object svalue to the svalue-number 0.
 */

{
    object_t *ob = v->u.ob;

    free_object(ob, "zero_object_svalue");
    put_number(v, 0);
}

/*-------------------------------------------------------------------------*/
static void
free_protector_svalue (svalue_t *v)

/* Free the svalue <v> which contains a protective reference to a vector
 * or to a mapping.
 */

{
    switch (v->type)
    {
      case T_POINTER:
        free_array(v->u.vec);
        break;
      case T_MAPPING:
        free_mapping(v->u.map);
        break;
      case T_PROTECTOR_MAPPING:
        free_protector_mapping(v->u.map);
        break;
    }
}

/*-------------------------------------------------------------------------*/
void
free_svalue (svalue_t *v)

/* Free the svalue <v>, which may be of any type.
 * Afterwards, the content of <v> is undefined.
 */

{
    ph_int type = v->type;

    v->type = T_INVALID;
      /* If freeing the value throws an error, it is most likely that
       * we run out of stack. To avoid the error handling running
       * out of stack on the same value, we mask it before we free
       * it - at the risk of leaking memory.
       */

    assert_stack_gap();

    switch (type)
    {
    case T_STRING:
        switch(v->x.string_type)
        {
        case STRING_MALLOC:
            xfree(v->u.string);
            break;
        case STRING_SHARED:
            free_string(v->u.string);
            break;
        }
        break;

    case T_OBJECT:
      {
        object_t *ob = v->u.ob;
        free_object(ob, "free_svalue");
        break;
      }

    case T_QUOTED_ARRAY:
    case T_POINTER:
        free_array(v->u.vec);
        break;

    case T_MAPPING:
        free_mapping(v->u.map);
        break;

    case T_SYMBOL:
        free_string(v->u.string);
        break;

    case T_CLOSURE:
        free_closure(v);
        break;

    case T_CALLBACK:
        free_callback(v->u.cb);
        break;

    case T_LVALUE:
        switch (v->u.lvalue->type)
        {
        case T_PROTECTED_LVALUE:
          {
              struct protected_lvalue *p;

              p = v->u.protected_lvalue;
              free_protector_svalue(&p->protector);
              xfree(p);
              break;
          }

        case T_PROTECTED_CHAR_LVALUE:
          {
              struct protected_char_lvalue *p;

              p = v->u.protected_char_lvalue;
              if (p->lvalue->type == T_STRING
               && p->lvalue->u.string == p->start)
              {
                  p->lvalue->x.string_type = STRING_MALLOC;
              }
              else
              {
                  xfree(p->start);
              }
              free_protector_svalue(&p->protector);
              xfree(p);
              break;
          }

        case T_PROTECTED_STRING_RANGE_LVALUE:
          {
              struct protected_range_lvalue *p;

              p = v->u.protected_range_lvalue;
              if (p->lvalue->type == T_STRING
               && p->lvalue->u.string == p->v.u.string)
              {
                  p->lvalue->x.string_type = STRING_MALLOC;
              }
              else
              {
                  xfree(p->v.u.string);
              }
              free_protector_svalue(&p->protector);
              xfree(p);
              break;
          }

        case T_PROTECTED_POINTER_RANGE_LVALUE:
          {
              struct protected_range_lvalue *p;

              p = v->u.protected_range_lvalue;
              free_array(p->v.u.vec);
              free_protector_svalue(&p->protector);
              xfree(p);
              break;
          }

        case T_ERROR_HANDLER:
          {
              svalue_t *p;

              p = v->u.lvalue;
              (*p->u.error_handler)(p);
              break;
          }
        } /* switch (v->u.lvalue->type) */
        break; /* case T_LVALUE */

    }
} /* free_svalue() */

/*-------------------------------------------------------------------------*/
static INLINE void
check_for_ref_loop (svalue_t * dest)

/* <dest> has just been assigned to - check if this created a reference loop.
 * If yes, free <dest> and throw an error.
 */

{
    if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
    {
        /* rover1 will scan the lvalue chain in two-steps, rover2 will
         * scan it step by step. If there is a loop, the two will eventually
         * meet again.
         */
        svalue_t * rover1, * rover2;

        rover1 = rover2 = dest;
        do {
            if (rover1->type == T_LVALUE || rover1->type == T_PROTECTED_LVALUE)
                rover1 = rover1->u.lvalue;
            else
                break;
            if (rover1->type == T_LVALUE || rover1->type == T_PROTECTED_LVALUE)
                rover1 = rover1->u.lvalue;
            else
                break;
            if (rover2->type == T_LVALUE || rover2->type == T_PROTECTED_LVALUE)
                rover2 = rover2->u.lvalue;
            else
                break;
            if (rover1 == rover2)
            {
                free_svalue(dest);
                error("Assignment would create reference loop.\n");
            }
        } while (rover1
             && (rover1->type == T_LVALUE || rover1->type == T_PROTECTED_LVALUE)
             && rover2
             && (rover2->type == T_LVALUE || rover2->type == T_PROTECTED_LVALUE)
                );
    }

} /* check_for_ref_loop() */

/*-------------------------------------------------------------------------*/
static INLINE void
_assign_svalue_no_free (svalue_t *to, svalue_t *from)

/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
 * value is either copied when appropriate, or its refcount is increased.
 * <to> is considered empty at the time of call.
 *
 * If <from> is a destructed object, <to> is set to the number 0 but
 * <from> is left unchanged.
 */

{
#ifdef DEBUG
    if (from == 0)
        fatal("Null pointer to assign_svalue().\n");
#endif

    /* Copy all the data */
    *to = *from;

    /* Now create duplicates resp. increment refcounts where necessary */

    switch(from->type)
    {
    case T_STRING:
        switch(from->x.string_type)
        {
        case STRING_MALLOC:        /* No idea to make the string shared */
          {
              char *p, *str;

              str = from->u.string;
              p = xalloc(malloced_strlen(str));
              if (!p)
              {
                  put_number(to, 0);
                  error("Out of memory\n");
              }
              strcpy(p, str);
              to->u.string = p;
              break;
          }
        case STRING_VOLATILE:      /* Good idea to make it shared */
            put_string(to, make_shared_string(from->u.string));
            if (!to->u.string)
            {
                put_number(to, 0);
                error("Out of memory\n");
            }
            break;
        case STRING_SHARED:        /* It already is shared */
            ref_string(from->u.string);
            break;
#ifdef DEBUG
        default:
            fatal("assign_svalue_no_free(): bad string type %d\n", from->x.string_type);
#endif
        }
        break;

    case T_OBJECT:
        {
          object_t *ob = to->u.ob;
          if ( !(ob->flags & O_DESTRUCTED) )
              (void)ref_object(ob, "ass to var");
          else
              put_number(to, 0);

          break;
        }
        break;

    case T_QUOTED_ARRAY:
    case T_POINTER:
        (void)ref_array(to->u.vec);
        break;

    case T_SYMBOL:
        ref_string(to->u.string);
        break;

    case T_CLOSURE:
        if (!destructed_object_ref(to))
            addref_closure(to, "ass to var");
        else
            put_number(to, 0);
        break;

    case T_MAPPING:
        (void)ref_mapping(to->u.map);
        break;
    }

    /* Protection against endless reference loops */
    if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
    {
        check_for_ref_loop(to);
    }
} /* _assign_svalue_no_free() */

void assign_svalue_no_free (svalue_t *to, svalue_t *from)
{ _assign_svalue_no_free(to,from); }

#define assign_svalue_no_free(to,from) _assign_svalue_no_free(to,from)

/*-------------------------------------------------------------------------*/
static INLINE void
copy_svalue_no_free (svalue_t *to, svalue_t *from)

/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
 * value is either copied when appropriate, or its refcount is increased.
 * In particular, if <from> is a mapping (which must not contain destructed
 * objects!) or array, a shallow copy is created.
 * <to> is considered empty at the time of call.
 *
 * If <from> is a destructed object, <to> is set to the number 0 but
 * <from> is left unchanged.
 */

{
    assign_svalue_no_free(to, from);

    /* For arrays and mappings, create a shallow copy */
    if (from->type == T_MAPPING)
    {
        mapping_t *old, *new;

        old = to->u.map;
        if (old->ref != 1)
        {
            DYN_MAPPING_COST(MAP_SIZE(old));
            new = copy_mapping(old);
            if (!new)
                error("Out of memory: mapping[%lu] for copy.\n"
                     , MAP_SIZE(old));
            free_mapping(old);
            to->u.map = new;
        }
    }
    else if (from->type == T_POINTER
          || from->type == T_QUOTED_ARRAY)
    {
        vector_t *old, *new;
        size_t size, i;

        old = to->u.vec;
        size = VEC_SIZE(old);
        if (old->ref != 1 && old != &null_vector)
        {
            DYN_ARRAY_COST(size);
            new = allocate_uninit_array((int)size);
            if (!new)
                error("Out of memory: array[%lu] for copy.\n"
                     , (unsigned long) size);
            for (i = 0; i < size; i++)
                assign_svalue_no_free( &new->item[i]
                                     , &old->item[i]);
            free_array(old);
            to->u.vec = new;
        }
    }
} /* copy_svalue_no_free() */

/*-------------------------------------------------------------------------*/
static INLINE void
assign_checked_svalue_no_free (svalue_t *to, svalue_t *from
                              , svalue_t *sp, bytecode_p pc
                              )

/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
 * value is either copied when appropriate, or its refcount is increased.
 * <to> is considered empty at the time of call.
 * <from> may point to a variable or vector element, so it might contain
 * a destructed object. In that case, <from> and <to> are set to
 * svalue-number 0.
 *
 * <sp> and <pc> are the current stackpointer and program counter and are
 * needed to update <inter_xx> in case of errors.
 */

{
    switch (from->type)
    {
    case T_STRING:
        switch(from->x.string_type)
        {
        case STRING_MALLOC:        /* No idea to make the string shared */
        case STRING_VOLATILE:
          {
            char *p;
            char *str;

            if (from->x.string_type == STRING_MALLOC)
                p = xalloc(malloced_strlen(str = from->u.string));
            else
                p = xalloc(strlen(str = from->u.string)+1);
            if (!p) {
                put_number(to, 0);
                inter_sp = sp;
                inter_pc = pc;
                error("Out of memory\n");
            }
            (void)strcpy(p, str);
            put_malloced_string(to, p);
            return;
          }

        case STRING_SHARED:        /* It already is shared */
            put_ref_string(to, from->u.string);
            return;
        }
        case STRING_VOLATILE:
        {
          char *str;

          str = make_shared_string(from->u.string);
          if ( !str ) {
              put_ref_string(to, STR_DEFAULT);
              inter_sp = sp;
              inter_pc = pc;
              error("Out of memory\n");
          }
          put_string(to, str);
          break;
        }
#ifdef DEBUG
        fatal("assign_checked_svalue_no_free(): bad string type %d\n", from->x.string_type);
#endif
        break;

    case T_OBJECT:
      {
        object_t *ob = from->u.ob;
        if ( !(ob->flags & O_DESTRUCTED) ) {
            ref_object(ob, "ass to var");
            break;
        }
        zero_object_svalue(from);
        break;
      }

    case T_QUOTED_ARRAY:
    case T_POINTER:
        (void)ref_array(from->u.vec);
        break;

    case T_SYMBOL:
        ref_string(from->u.string);
        break;

    case T_CLOSURE:
        if (!destructed_object_ref(from))
            addref_closure(from, "ass to var");
        else
            assign_svalue(from, &const0);
        break;

    case T_MAPPING:
        (void)ref_mapping(from->u.map);
        break;
    }
    *to = *from;

    /* Protection against endless reference loops */
    if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
    {
        check_for_ref_loop(to);
    }
} /* assign_checked_svalue_no_free() */

/*-------------------------------------------------------------------------*/
static INLINE void
assign_local_svalue_no_free ( svalue_t *to, svalue_t *from
                            , svalue_t *sp, bytecode_p pc
                            )

/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
 * value is either copied when appropriate, or its refcount is increased.
 * <to> is considered empty at the time of call.
 *
 * <from> is meant to point to a local variable, which might be an arg
 * to the current lfun and may thus contain a VOLATILE string. If that is
 * the case, the string is made shared before assignment.
 * If <from> is a lvalue, the chain is unraveled and the final non-lvalue
 * is assigned. If that value is a destructed object, 0 is assigned.
 *
 * <sp> and <pc> are the current stackpointer and program counter and are
 * needed to update <inter_xx> in case of errors.
 */

{
assign_from_lvalue:
    switch (from->type) {
      case T_STRING:
        switch(from->x.string_type) {
          case STRING_MALLOC:        /* No idea to make the string shared */
          {
            char *p;
            char *str;

            p = xalloc(malloced_strlen(str = from->u.string));
            if (!p) {
                put_number(to, 0);
                inter_sp = sp;
                inter_pc = pc;
                error("Out of memory\n");
            }
            (void)strcpy(p, str);
            put_malloced_string(sp, p);
            return;
          }
          case STRING_SHARED:        /* It already is shared */
            put_ref_string(to, from->u.string);
            break;
          case STRING_VOLATILE:
          {
            char *str;

            str = make_shared_string(from->u.string);
            if ( !str ) {
                put_ref_string(to, STR_DEFAULT);
                inter_sp = sp;
                inter_pc = pc;
                error("Out of memory\n");
            }
            put_string(to, str);
            break;
          }
#ifdef DEBUG
          default:
            fatal("assign_local_svalue_no_free(): bad string type %d\n", from->x.string_type);
#endif
        }
        return;
      case T_OBJECT:
        (void)ref_object(from->u.ob, "assign_local_lvalue_no_free");
        break;
      case T_QUOTED_ARRAY:
      case T_POINTER:
        (void)ref_array(from->u.vec);
        break;
      case T_SYMBOL:
        ref_string(from->u.string);
        break;
      case T_CLOSURE:
        addref_closure(from, "ass to var");
        break;
      case T_MAPPING:
        (void)ref_mapping(from->u.map);
        break;
      case T_LVALUE:
      case T_PROTECTED_LVALUE:
        from = from->u.lvalue;
        if (destructed_object_ref(from)) {
            assign_svalue(from, &const0);
            break;
        }
        goto assign_from_lvalue;
      case T_PROTECTED_CHAR_LVALUE:
        put_number(to, *from->u.string);
        return;
    }
    *to = *from;

    /* Protection against endless reference loops */
    if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
    {
        inter_sp = sp;
        inter_pc = pc;
        check_for_ref_loop(to);
    }
} /* assign_local_svalue_no_free() */

/*-------------------------------------------------------------------------*/
static INLINE
void assign_lrvalue_no_free (svalue_t *to, svalue_t *from)

/* Put a duplicate of svalue <from> into svalue <to>, meaning that the original
 * value is either copied when appropriate, or its refcount is increased.
 * <to> is considered empty at the time of call.
 *
 * This function differs from assign_svalue_no_free() in the handling of
 * two types:
 *  - if <from> is an unshared string, the string is made shared and
 *    both <to> and <from> are changed to use the shared string.
 *  - if <from> is a lvalue, <to>.u.lvalue is set to point to <from>.
 *    This is necessary when pushing references onto the stack - if
 *    assign_svalue_no_free() were used, the first free_svalue() would undo
 *    the whole lvalue indirection, even though there were still other lvalue
 *    entries in the stack for the same svalue.
 *    TODO: An alternative would be use a special struct lvalue {} with a
 *    refcount.
 */

{
#ifdef DEBUG
    if (from == 0)
        fatal("Null pointer to assign_lrvalue_no_free().\n");
#endif

    /* Copy the data */
    *to = *from;

    /* Now adapt the refcounts or similar */

    switch(from->type)
    {
    case T_STRING:
        if (to->x.string_type != STRING_SHARED)
        {
            to->x.string_type = STRING_SHARED;
            to->u.string = make_shared_string(from->u.string);
            if (from->x.string_type == STRING_MALLOC)
            {
                xfree(from->u.string);
            }
            *from = *to;
        }
        ref_string(from->u.string);
        break;

    case T_OBJECT:
        (void)ref_object(to->u.ob, "ass to var");
        break;

    case T_QUOTED_ARRAY:
    case T_POINTER:
        (void)ref_array(to->u.vec);
        break;

    case T_SYMBOL:
        ref_string(to->u.string);
        break;

    case T_CLOSURE:
        addref_closure(to, "ass to var");
        break;

    case T_MAPPING:
        (void)ref_mapping(to->u.map);
        break;

    case T_LVALUE:
        to->u.lvalue = from;
        break;
    }

    /* Protection against endless reference loops */
    if (to->type == T_LVALUE || to->type == T_PROTECTED_LVALUE)
    {
        check_for_ref_loop(to);
    }
} /* assign_lrvalue_no_free() */

/*-------------------------------------------------------------------------*/
void
assign_svalue (svalue_t *dest, svalue_t *v)

/* Put a duplicate of svalue <v> into svalue <dest>, meaning that the
 * original value is either copied when appropriate, or its refcount is
 * increased.
 *
 * <dest> is considered a valid svalue and therefore freed before the
 * assignment. Structured values will necessiate doing the assignment before
 * the actual deallocation, otherwise recursive structures could cause crashs.
 * One nasty example is
 *    a = ( ({((a=({0})),(a[0]=a)),(a=0)})[0] = query_verb() );
 * which used to corrupt the shared string table, namely the entry for
 * the verb in variable a if its length uses a memory block of
 * the same length as an array of size 2.
 *
 * If <dest> is a lvalue, <v> will be assigned to the svalue referenced
 * to by <dest>.
 */

{
    /* Free the <dest> svalue.
     * If <dest> is a lvalue, the loop will traverse the lvalue chain until
     * the actual svalue is found.
     * If a T_xxx_LVALUE is found, the assignment will be done here
     * immediately.
     */

    for (;;) {
        switch(dest->type)
        {
        case T_LVALUE:
        case T_PROTECTED_LVALUE:
            dest = dest->u.lvalue;
            continue;

        case T_STRING:
            switch(dest->x.string_type)
            {
            case STRING_MALLOC:
                xfree(dest->u.string);
                break;
            case STRING_SHARED:
                free_string(dest->u.string);
                break;
            }
            break;

        case T_OBJECT:
          {
            object_t *ob = dest->u.ob;
            free_object(ob, "assign_svalue");
            break;
          }

        case T_QUOTED_ARRAY:
        case T_POINTER:
          {
            vector_t *vec = dest->u.vec;
            assign_svalue_no_free(dest, v);
              /* TODO: leaks vec if out of memory */
            free_array(vec);
            return;
          }

        case T_MAPPING:
          {
            mapping_t *map = dest->u.map;
            assign_svalue_no_free(dest, v); /* leaks map if out of memory */
            free_mapping(map);
            return;
          }

        case T_SYMBOL:
            free_string(dest->u.string);
            break;

        case T_CLOSURE:
            free_closure(dest);
            break;

        /* If the final svalue in dest is one of these lvalues,
         * the assignment is done right here and now.
         * Note that 'dest' in some cases points to a protector structure.
         */

        case T_CHAR_LVALUE:
            if (v->type == T_NUMBER)
            {
                if (!v->u.number)
                    error("Can't assign 0 to a string character.\n");
                else
                    *dest->u.string = (char)v->u.number;
            }
            return;

        case T_PROTECTED_CHAR_LVALUE:
          {
            struct protected_char_lvalue *p;

            p = (struct protected_char_lvalue *)dest;
            if (p->lvalue->type == T_STRING
             && p->lvalue->u.string == p->start)
            {
                if (v->type == T_NUMBER)
                {
                    if (!v->u.number)
                        error("Can't assign 0 to a string character.\n");
                    else
                        *p->v.u.string = (char)v->u.number;
                }
            }
            return;
          }

        case T_POINTER_RANGE_LVALUE:
            if (v->type == T_POINTER)
            {
                (void)ref_array(v->u.vec); /* transfer_...() will free it once */
                transfer_pointer_range(v);
            }
            return;

        case T_PROTECTED_POINTER_RANGE_LVALUE:
            if (v->type == T_POINTER)
            {
                (void)ref_array(v->u.vec); /* transfer_...() will free it once */
                transfer_protected_pointer_range(
                  (struct protected_range_lvalue *)dest, v
                );
            }
            return;

        case T_STRING_RANGE_LVALUE:
            assign_string_range(v, MY_FALSE);
            return;

        case T_PROTECTED_STRING_RANGE_LVALUE:
            assign_protected_string_range(
                  (struct protected_range_lvalue *)dest, v, MY_FALSE
            );
            return;

        } /* switch() */

        /* No more lvalues to follow, old value freed: do the assign next */
        break;
    } /* end for */

    /* Now assign the value to the now-invalid <dest> */
    assign_svalue_no_free(dest, v);
} /* assign_svalue() */

/*-------------------------------------------------------------------------*/
static INLINE void
_transfer_svalue_no_free (svalue_t *dest, svalue_t *v)

/* Move the value <v> into <dest>. If <v> is an unshared string, it
 * is made shared.
 *
 * <dest> is assumed to be invalid before the call, <v> is invalid after.
 */

{
    /* If <v> is a string, share it */
    if (v->type == T_STRING && v->x.string_type == STRING_VOLATILE)
    {
        put_string(dest, make_shared_string(v->u.string));
        if ( !dest->u.string )
        {
            put_number(dest, 0);
            error("Out of memory\n");
        }
    }
    else /* just copy the data */
    {
        *dest = *v;
    }

    /* Protection against endless reference loops */
    if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
    {
        v->type = T_INVALID;
        check_for_ref_loop(dest);
    }
} /* _transfer_svalue_no_free() */

void transfer_svalue_no_free (svalue_t *dest, svalue_t *v)
{  _transfer_svalue_no_free(dest,v); }

#define transfer_svalue_no_free(dest,v) _transfer_svalue_no_free(dest,v)

/*-------------------------------------------------------------------------*/
static INLINE void
transfer_svalue_no_free_spc ( svalue_t *dest, svalue_t *v
                            , svalue_t *sp, bytecode_p pc)

/* Move the value <v> into <dest>. If <v> is a volatile string, it
 * is made shared.
 *
 * <dest> is assumed to be invalid before the call, <v> is invalid after.
 *
 * This function may be called from eval_instruction() and needs to receive
 * the current stackpointer <sp> and the current programcounter <pc>.
 */

{
    if (v->type == T_STRING && v->x.string_type == STRING_VOLATILE)
    {
        put_string(dest, make_shared_string(v->u.string));
        if ( !dest->u.string )
        {
            put_number(dest, 0);
            inter_sp = sp;
            inter_pc = pc;
            error("Out of memory\n");
        }
    }
    else
    {
        *dest = *v;
    }

    /* Protection against endless reference loops */
    if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
    {
        v->type = T_INVALID;
        inter_sp = sp;
        inter_pc = pc;
        check_for_ref_loop(dest);
    }

} /* transfer_svalue_no_free_spc() */

/*-------------------------------------------------------------------------*/
void
transfer_svalue (svalue_t *dest, svalue_t *v)

/* Move svalue <v> into svalue <dest>.
 *
 * <dest> is considered a valid svalue and therefore freed before the
 * assignment. <v> will be invalid after the call.
 *
 * If <dest> is a lvalue, <v> will be moved into the svalue referenced
 * to by <dest>. If <v> is a volatile string, it is made shared.
 *
 * F_VOID_ASSIGN uses a modified copy of this code (for speed reasons).
 */

{
    /* Unravel the T_LVALUE chain, if any. */
    while (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
        dest = dest->u.lvalue;

    /* Free the <dest> svalue.
     * If a T_xxx_LVALUE is found, the transfer will be done here
     * immediately.
     */

    for(;;)
    {
        switch (dest->type)
        {
        case T_STRING:
            switch(dest->x.string_type)
            {
            case STRING_MALLOC:
                xfree(dest->u.string);
                break;
            case STRING_SHARED:
                free_string(dest->u.string);
                break;
            }
            break;

        case T_OBJECT:
          {
            object_t *ob = dest->u.ob;
            free_object(ob, "transfer_svalue");
            break;
          }

        case T_QUOTED_ARRAY:
        case T_POINTER:
            free_array(dest->u.vec);
            break;

        case T_SYMBOL:
            free_string(dest->u.string);
            break;

        case T_CLOSURE:
            free_closure(dest);
            break;

        case T_MAPPING:
            free_mapping(dest->u.map);
            break;

        /* If the final svalue in dest is one of these lvalues,
         * the assignment is done right here and now.
         * Note that 'dest' in some cases points to a protector structure.
         */

        case T_CHAR_LVALUE:
            if (v->type == T_NUMBER)
            {
                if (!v->u.number)
                    error("Can't assign 0 to a string character.\n");
                else
                    *dest->u.string = (char)v->u.number;
            }
            else
                free_svalue(v);
            return;

        case T_PROTECTED_CHAR_LVALUE:
          {
            struct protected_char_lvalue *p;

            p = (struct protected_char_lvalue *)dest;
            if (p->lvalue->type == T_STRING
             && p->lvalue->u.string == p->start)
            {
                if (v->type == T_NUMBER)
                {
                    if (!v->u.number)
                        error("Can't assign 0 to a string character.\n");
                    else
                        *p->v.u.string = (char)v->u.number;
                    return;
                }
            }
            free_svalue(v);
            return;
          }

        case T_POINTER_RANGE_LVALUE:
            transfer_pointer_range(v);
            return;

        case T_PROTECTED_POINTER_RANGE_LVALUE:
            transfer_protected_pointer_range(
              (struct protected_range_lvalue *)dest, v
            );
            return;

        case T_STRING_RANGE_LVALUE:
            assign_string_range(v, MY_TRUE);
            return;

        case T_PROTECTED_STRING_RANGE_LVALUE:
            assign_protected_string_range(
              (struct protected_range_lvalue *)dest, v, MY_TRUE
            );
            return;
        } /* end switch */

        /* No more lvalues to follow, old value freed: do the assign next */
        break;
    } /* end for */

    /* Transfer the value, making volatile strings shared */
    if (v->type == T_STRING && v->x.string_type == STRING_VOLATILE)
    {
        put_string(dest, make_shared_string(v->u.string));
        /* TODO: Nanue? No check for dest->u.string != NULL?
         * TODO:: Like in transfer_svalue_no_free_spc()?
         */
    }
    else
    {
        *dest = *v;
    }

    /* Protection against endless reference loops */
    if (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE)
    {
        v->type = T_INVALID;
        check_for_ref_loop(dest);
    }

} /* transfer_svalue() */

/*-------------------------------------------------------------------------*/
static void
transfer_pointer_range (svalue_t *source)

/* Transfer the vector <source> to the vector range defined by
 * <special_lvalue>, modifying the target vector in special_lvalue
 * accordingly. <source> is freed once in the call.
 *
 * If <source> is not a vector, it is just freed.
 */

{
    if (source->type == T_POINTER)
    {
        vector_t *sv;      /* Source vector (from source) */
        vector_t *dv;      /* Destination vector (from special_lvalue) */
        vector_t *rv;      /* Result vector */
        mp_int dsize;           /* Size of destination vector */
        mp_int ssize;           /* Size of source vector */
        mp_int index1, index2;  /* First and last index of destination range */
        mp_int i;

        /* Setup the variables */
        dsize = special_lvalue.size;
        index1 = special_lvalue.index1;
        index2 = special_lvalue.index2;
        dv = special_lvalue.v.u.lvalue->u.vec;
        sv = source->u.vec;
        ssize = (mp_int)VEC_SIZE(sv);

#ifdef NO_NEGATIVE_RANGES
        if (index1 > index2)
            error("Illegal range [%ld..%ld] for assignment.\n"
                 , index1, index2-1
                 );
#endif /* NO_NEGATIVE_RANGES */

        if (ssize + index1 - index2 == 0)
        {
            /* <source> fits exactly into the target range */

            svalue_t *s, *d;  /* Copy source and destination */

            s = sv->item;
            d = dv->item + index1;

            ref_array(dv); /* protect against recursive refs during the copy */

            /* If there is just one ref to the source, use the faster
             * transfer instead of the slow assign for the copy.
             */
            if (sv->ref == 1)
            {
                for (i = ssize; --i >= 0; )
                {
                    transfer_svalue(d++, s++);
                }
                free_empty_vector(sv);
            }
            else /* sv->ref > 1 */
            {
                for (i = ssize; --i >= 0; )
                {
                    assign_svalue(d++, s++);
                }

                free_array(sv);
                  /* deref_array() is not enough, because in situations
                   * where one d == sv, eg
                   *    arr = ({ ({ 1 }) });
                   *    arr[0..0] = arr[0];
                   * sv would be left behind with 0 refs but unfreed.
                   */
            }

            free_array(dv); /* Undo the ref_array() above */
        }
        else
        {
            /* Create a new vector */

            svalue_t *s, *d; /* Copy source and destination */

            rv = allocate_array(dsize + ssize + index1 - index2);
            special_lvalue.v.u.lvalue->u.vec = rv;
            s = dv->item;
            d = rv->item;

            for (i = index1; --i >= 0; )
            {
                assign_svalue_no_free(d++, s++);
            }

            s = sv->item;
            for (i = ssize; --i >= 0; )
            {
                assign_svalue_no_free(d++, s++);
            }
            free_array(sv);

            s = dv->item + index2;
            for (i = dsize - index2; --i >= 0; )
            {
                assign_svalue_no_free(d++, s++);
            }

            free_array(dv); /* this can make the lvalue invalid to use */
        }
    }
    else
        /* Not a pointer: just free it */
        free_svalue(source);

} /* transfer_pointer_range() */

/*-------------------------------------------------------------------------*/
static void
transfer_protected_pointer_range ( struct protected_range_lvalue *dest
                                 , svalue_t *source)

/* Transfer the vector <source> to the vector range defined by
 * <dest>, modifying the target vector in <dest>
 * accordingly. <source> is freed once in the call.
 *
 * If <source> is not a vector, it is just freed.
 */

{
    if (source->type == T_POINTER && dest->v.u.vec == dest->lvalue->u.vec)
    {
        vector_t *sv;      /* Source vector (from source) */
        vector_t *dv;      /* Dest vector (from dest) */
        vector_t *rv;      /* Result vector */
        mp_int dsize;           /* Size of the dest vector */
        mp_int ssize;           /* Size of the source vector */
        mp_int index1, index2;  /* Target range indices */
        mp_int i;

        /* Setup the variables */
        dsize = dest->size;
        index1 = dest->index1;
        index2 = dest->index2;
        dv = dest->v.u.vec;
        sv = source->u.vec;
        ssize = (mp_int)VEC_SIZE(sv);

#ifdef NO_NEGATIVE_RANGES
        if (index1 > index2)
            error("Illegal range [%ld..%ld] for assignment.\n"
                 , index1, index2-1
                 );
#endif /* NO_NEGATIVE_RANGES */

        if (ssize + index1 - index2 == 0)
        {
            /* <source> fits exactly into the target range */

            svalue_t *s, *d; /* Copy source and destination */

            s = sv->item;
            d = dv->item + index1;

            /* If there is just one ref to the source, use the faster
             * transfer instead of the slow assign for the copy.
             */
            if (sv->ref == 1)
            {
                for (i = ssize; --i >= 0; )
                {
                    transfer_svalue(d++, s++);
                }
                free_empty_vector(sv);
            }
            else /* sv->ref > 1 */
            {
                for (i = ssize; --i >= 0; )
                {
                    assign_svalue(d++, s++);
                }

                deref_array(sv);
                /* The if() above effectively did the 'free_svalue(source)' */
            }
        }
        else
        {
            /* Create a new vector */

            svalue_t *s, *d;  /* Copy source and destination */

            rv = allocate_array(dsize + ssize + index1 - index2);
            dest->lvalue->u.vec = rv;

            s = dv->item;
            d = rv->item;
            for (i = index1; --i >= 0; )
            {
                assign_svalue_no_free(d++, s++);
            }

            s = sv->item;
            for (i = ssize; --i >= 0; ) {
                assign_svalue_no_free(d++, s++);
            }
            free_array(sv);

            s = dv->item + index2;
            for (i = dsize - index2; --i >= 0; )
            {
                assign_svalue_no_free(d++, s++);
            }

            free_array(dv); /* this can make the lvalue invalid to use */
        }
    }
    else
        /* Not a pointer: just free it */
        free_svalue(source);

} /* transfer_protected_pointer_range() */

/*-------------------------------------------------------------------------*/
static void
assign_string_range (svalue_t *source, Bool do_free)

/* Transfer the string <source> to the string range defined by
 * <special_lvalue>, modifying the target string in special_lvalue
 * accordingly. If <do_free> is TRUE, <source> is freed once in the call.
 *
 * If <source> is not a string, it is just freed resp. ignored.
 */

{
    if (source->type == T_STRING)
    {
        svalue_t *dsvp;     /* destination svalue (from special_lvalue) */
        char *ds;                /* destination string (from dsvp) */
        char *ss;                /* source string (from source) */
        char *rs;                /* result string */
        mp_int dsize;            /* size of destination string */
        mp_int ssize;            /* size of source string */
        mp_int index1, index2;   /* range indices */

        /* Set variables */
        dsize = special_lvalue.size;
        index1 = special_lvalue.index1;
        index2 = special_lvalue.index2;
        dsvp = special_lvalue.v.u.lvalue;
        ds = dsvp->u.string;
        ss = source->u.string;
        ssize = (mp_int)svalue_strlen(source);

#ifdef NO_NEGATIVE_RANGES
        if (index1 > index2)
            error("Illegal range [%ld..%ld] for assignment.\n"
                 , index1, index2-1
                 );
#endif /* NO_NEGATIVE_RANGES */

        /* Create the new string */
        rs = xalloc((size_t)(dsize + ssize + index1 - index2 + 1));
        if (!rs)
        {
            /* We don't pop the stack here --> don't free source */
            error("Out of memory\n");
        }

        if (index1)
            memcpy(rs, ds, (size_t)index1);
        if (ssize)
            memcpy(rs + index1, ss, (size_t)ssize);
        strcpy(rs + index1 + ssize, ds + index2);

        /* Assign the new string in place of the old */
        free_string_svalue(dsvp);
        dsvp->x.string_type = STRING_MALLOC;
        dsvp->u.string = rs;

        if (do_free)
            free_string_svalue(source);
    }
    else
    {
        /* Not a string: just free it */
        if (do_free)
            free_svalue(source);
    }
} /* assign_string_range() */

/*-------------------------------------------------------------------------*/
static void
assign_protected_string_range ( struct protected_range_lvalue *dest
                              , svalue_t *source
                              , Bool do_free
                              )

/* Transfer the string <source> to the string range defined by
 * <dest>, modifying the target string in dest
 * accordingly.
 *
 * If <do_free> is TRUE, <source> and the protector <dest> are freed once
 * in the call.
 *
 * If <source> is not a string, it is just freed resp. ignored.
 */

{
    if (source->type == T_STRING)
    {
        svalue_t *dsvp;     /* destination value (from dest) */
        char *ss;                /* source string (from source) */
        char *ds;                /* destination string (from dsvp) */
        char *rs;                /* result string */
        mp_int dsize;            /* size of destination string */
        mp_int ssize;            /* size of source string */
        mp_int index1, index2;   /* range indices */

        /* Set variables */
        dsize = dest->size;
        index1 = dest->index1;
        index2 = dest->index2;
        dsvp = dest->lvalue;
        ds = dest->v.u.string;

#ifdef NO_NEGATIVE_RANGES
        if (index1 > index2)
            error("Illegal range [%ld..%ld] for assignment.\n"
                 , index1, index2-1
                 );
#endif /* NO_NEGATIVE_RANGES */

        /* If the lvalue is no longer valid, free it */
        if (dsvp->u.string != ds)
        {
            if (do_free)
            {
                free_svalue(source);
                xfree(dest->v.u.string);
                xfree(dest);
            }
            return;
        }

        /* Create a new string */
        ss = source->u.string;
        ssize = (mp_int)svalue_strlen(source);
        rs = xalloc((size_t)(dsize + ssize + index1 - index2 + 1));
        if (!rs)
        {
            error("Out of memory\n");
        }

        if (index1)
            memcpy(rs, ds, (size_t)index1);
        if (ssize)
            memcpy(rs + index1, ss, (size_t)ssize);
        strcpy(rs + (dest->index2 = (int)(index1 + ssize)), ds + index2);
        xfree(ds);

        dest->v.u.string = dsvp->u.string = rs;
        if (do_free)
        {
            free_string_svalue(source);
            dest->v.x.string_type = STRING_MALLOC;
            free_protector_svalue(&dest->protector);
            xfree(dest);
        }
    }
    else
    {
        /* Not a string: just free it */
        if (do_free)
        {
            free_svalue(source);
            dest->v.x.string_type = STRING_MALLOC;
            free_protector_svalue(&dest->protector);
            xfree(dest);
        }
    }
} /* transfer_protected_string_range() */

/*-------------------------------------------------------------------------*/
static void
add_number_to_lvalue (svalue_t *dest, int i, svalue_t *pre, svalue_t *post)

/* Add the number <i> to the (PROTECTED_)LVALUE <dest>.
 * If <pre> is not null, the <dest> value before the addition is copied
 * into it.
 * If <post> is not null, the <dest> value after the addition is copied
 * into it.
 * Both <pre> and <post> are supposed to be empty svalues when given.
 *
 * If <dest> is of the wrong type, an error is generated.
 */

{
    /* Deref the T_(PROTECTED_)LVALUES */
    do
        dest = dest->u.lvalue;
    while (dest->type == T_LVALUE || dest->type == T_PROTECTED_LVALUE);

    /* Now increment the non-LVALUE */
    switch (dest->type)
    {
    default:
        error("Reference to bad type to ++/--\n");
        break;

    case T_NUMBER:
        if (pre) put_number(pre, dest->u.number);
        dest->u.number += i;
        if (post) put_number(post, dest->u.number);
        break;

    case T_FLOAT:
      {
        STORE_DOUBLE_USED
        double d;

        d = READ_DOUBLE(dest);

        if (pre)
        {
            pre->type = T_FLOAT;
            STORE_DOUBLE(pre, d);
        }

        d += (double)i;
        STORE_DOUBLE(dest, d);

        if (post)
        {
            post->type = T_FLOAT;
            STORE_DOUBLE(post, d);
        }
        break;
      }

    case T_PROTECTED_LVALUE:
        add_number_to_lvalue(dest, i, pre, post);
        break;

    case T_CHAR_LVALUE:
        if (((*dest->u.string + i) & 0xff) == 0)
            error("Can't set string character to 0.\n");
        if (pre) put_number(pre, (*dest->u.string));
        (*dest->u.string) += i;
        if (post) put_number(post, (*dest->u.string));
        break;

    case T_PROTECTED_CHAR_LVALUE:
      {
        struct protected_char_lvalue *p;

        p = (struct protected_char_lvalue *)dest;
        if (p->lvalue->type == T_STRING
         && p->lvalue->u.string == p->start)
        {
            if (((*p->v.u.string + i) & 0xff) == 0)
                error("Can't set string character to 0.\n");
            if (pre) put_number(pre, *p->v.u.string);
            i = *p->v.u.string += i;
            if (post) put_number(post, i);
        }
        break;
      }
    } /* switch() */
} /* add_number_to_lvalue() */

/*-------------------------------------------------------------------------*/
static vector_t *
inter_add_array (vector_t *q, vector_t **vpp)

/* Append array <q> to array *<vpp>. Both <q> and *<vpp> are freed,
 * the result vector (just one ref) is assigned to *<vpp> and also returned.
 *
 * <inter_sp> is supposed to point at the two vectors and will be decremented
 * by 2.
 */

{
    vector_t *p;       /* The second summand vector */
    mp_int cnt;
    vector_t *r;       /* Result vector */
    svalue_t *s, *d;   /* Pointers for copying: src and dest */
    size_t p_size, q_size;  /* Sizes of p and q */

    p = *vpp;

    inter_sp -= 2;

    /* Out of memory might result in some memory leaks. Better that freeing
     * arrays with 0 ref count, or indigestion in garbage_collection() .
     * It will simply give some more debugging output...
     */

    /* *vpp could be in the summands, thus don't free p / q before
     * assigning.
     * On the other hand, with an uninitialized array, we musn't assign
     * before the copying is done.
     */

    p_size = VEC_SIZE(p);
    q_size = VEC_SIZE(q);
    s = p->item;

    /* Allocate the result vector and copy p into it.
     */
    if (!(p->ref-1))
    {
        /* p will be deallocated completely - try to optimize a bit */

#ifdef MALLOC_smalloc
        /* We try to expand the existing memory for p (without moving)
         * instead of allocating a completely new vector.
         */
        d = malloc_increment_size(p, q_size * sizeof(svalue_t));
        if ( NULL != d)
        {
            /* We got the additional memory */
            r = p;
            r->ref = 1;
            r->size = p_size + q_size;

            r->user->size_array -= p_size;
            r->user = current_object->user;
            r->user->size_array += p_size + q_size;

            if (max_array_size && p_size + q_size > max_array_size)
            {
                /* Oops, overflow - invalidate everything */
                *vpp = allocate_array(0);
                d = r->item + p_size;
                for (cnt = (mp_int)q_size; --cnt >=0; )
                {
                    d[cnt].type = T_INVALID;
                }
                free_array(r);
                free_array(q);
                error("Illegal array size: %ld.\n", (long)(p_size + q_size));
            }
        } else
#endif
        /* Just allocate a new vector and memcopy p into it. */
        {
            r = allocate_uninit_array((p_int)(p_size + q_size));
            deref_array(p);
            d = r->item;
            for (cnt = (mp_int)p_size; --cnt >= 0; )
            {
                *d++ = *s++;
            }
        }
    }
    else
    {
        /* p must survive: allocate a new vector and assign the values
         * from p.
         */
        r = allocate_uninit_array((p_int)(p_size + q_size));
        deref_array(p);
        d = r->item;
        for (cnt = (mp_int)p_size; --cnt >= 0; ) {
            assign_checked_svalue_no_free (d++, s++, inter_sp, inter_pc);
        }
    }

    /* Add the values from q. Again, try to optimize */
    s = q->item;
    if (q->ref == 1)
    {
        for (cnt = (mp_int)q_size; --cnt >= 0; )
        {
            if (destructed_object_ref(s))
                assign_svalue(s, &const0);
            *d++ = *s++;
        }
        *vpp = r;
        free_empty_vector(q);
    }
    else /* q->ref > 1 */
    {
        for (cnt = (mp_int)q_size; --cnt >= 0; ) {
            assign_checked_svalue_no_free (d++, s++, inter_sp, inter_pc);
        }
        *vpp = r;

        deref_array(q);
    }

    if (!p->ref && p != q)
        free_empty_vector(p);

    return r;
} /* inter_add_array() */

/*=========================================================================*/

/*                           S T A C K                                     */

/*-------------------------------------------------------------------------*/
/* The following functions handle the pushing and popping of the
 * interpreter stack. Often functions appear in two versions: one version
 * using the global variable <inter_sp>, the other version receiving and
 * returning the old/new stack pointer as argument and result.
 *
 * Obviously, the former version can be easily called from outside the
 * interpreter, while the latter allows better optimization.
 *
 * To make things even more complicated, some of the 'slower' functions
 * are redefined with preprocessor macros to use the faster function - this
 * is meant to make the code in this module faster, but relies on certain
 * naming conventions (e.g. that 'sp' is always the local copy of the
 * stack pointer).
 *
 * TODO: Streamline the functions, given them macros as fast alternative
 * TODO:: publish them all in interpret.h and enforce their use.
 *-------------------------------------------------------------------------
 * The functions are:
 *
 * _push_object(ob, sp), push_object(ob):
 *     Push a non-destructed object onto the stack.
 * _push_valid_object(ob, sp), push_valid_object(ob):
 *     Push an object onto the stack.
 * _push_number(n, sp), push_number(n):
 *     Push a number onto the stack.
 * _push_shared_string(p, sp), push_shared_string(p),
 * push_referenced_shared_string(p):
 *     Push a shared string onto the stack.
 * push_string_shared(p):
 *     Share a string and push it onto the stack.
 * _push_string_malloced(p, sp), push_string_malloced(p):
 *     Malloc a copy of a string and push it onto the stack.
 * _push_malloced_string(p, sp), push_malloced_string(p):
 *     Push a malloced string onto the stack.
 * put_malloced_string(p, sp):
 *     Put a malloced string onto the stack.
 * _push_volatile_string(p, sp), push_volatile_string(p):
 *     Push a volatile string onto the stack.
 * push_svalue(v), push_svalue_block(num,v):
 *     Push one or more svalues onto the stack.
 * pop_stack(), _drop_n_elems(n,sp):
 *     Pop (free) elements from the stack.
 * stack_overflow(sp,fp,pc):
 *     Handle a stack overflow.
 * push_vector(v), push_referenced_vector(v):
 *     Push a vector onto the stack.
 * push_referenced_mapping(m):
 *     Push a mapping onto the stack.
 *
 * and as macros only:
 *
 * push_mapping(m,sp):
 *     Push a mapping onto the stack.
 */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_object (object_t *ob, svalue_t *sp)

/* Push the object <ob> onto the stack, currently ending at <sp>, and
 * return the new stackpointer.
 * <ob> must not be destructed.
 * The ref to <ob> is incremented.
 */

{
    sp++;
    put_ref_object(sp, ob, "push_object");
    return sp;
}

/*-------------------------------------------------------------------------*/
void
push_object (object_t *ob)

/* Push the object <ob> onto the stack defined by <inter_sp>.
 * <ob> must not be destructed.
 * The ref to <ob> is incremented.
 */

{
    inter_sp++;
    put_ref_object(inter_sp, ob, "push_object");
}

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_valid_ob (object_t *ob, svalue_t *sp)

/* Push the object <ob> onto the stack, currently ending at <sp>, and
 * return the new stackpointer.
 * If <ob> is destructed, the number 0 is pushed.
 * The ref to <ob> is incremented if necessary.
 */

{
    sp++;
    if (ob->flags & O_DESTRUCTED)
        put_number(sp, 0);
    else
        put_ref_object(sp, ob, "push_valid_ob");
    return sp;
}

/*-------------------------------------------------------------------------*/
void
push_valid_ob (object_t *ob)

/* Push the object <ob> onto the stack defined by <inter_sp>.
 * If <ob> is destructed, the number 0 is pushed.
 * The ref to <ob> is incremented if necessary.
 */

{
    inter_sp++;
    if (ob->flags & O_DESTRUCTED)
        put_number(inter_sp, 0);
    else
        put_ref_object(inter_sp, ob, "push_valid_ob");
}

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_number (p_int n, svalue_t *sp)

/* Push the number <n> onto the stack, currently ending at <sp>, and return
 * the new stackpointer.
 */

{
    sp++;
    put_number(sp, n);
    return sp;
}

/*-------------------------------------------------------------------------*/
void
push_number (p_int n)

/* Push the number <n> onto the stack as defined by <inter_sp>.
 */

{
    inter_sp++;
    put_number(inter_sp, n);
}

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_shared_string (char *p, svalue_t *sp)

/* Push the shared string <p> onto the stack, currently ending at <sp>,
 * and return the new stackpointer.
 * The refs of <p> are incremented.
 */

{
    sp++;
    put_ref_string(sp, p);
    return sp;
}

/*-------------------------------------------------------------------------*/
void
push_shared_string (char *p)

/* Push the shared string <p> onto the stack as defined by <inter_sp>.
 * The refs of <p> are incremented.
 */

{
    inter_sp = _push_shared_string(p, inter_sp);
}

/*-------------------------------------------------------------------------*/
void
push_referenced_shared_string (char *p)

/* Push the shared string <p> onto the stack as defined by <inter_sp>.
 * The refs of <p> are _not_ incremented.
 */

{
    svalue_t *sp = inter_sp;

    sp++;
    put_string(sp, p);
    inter_sp = sp;
}

/*-------------------------------------------------------------------------*/
void
push_string_shared (char *p)

/* Make a shared string of <p> and push it onto the stack as defined
 * by <inter_sp>.
 */

{
    inter_sp++;
    put_string(inter_sp, make_shared_string(p));
}

/*-------------------------------------------------------------------------*/
static svalue_t *
_push_string_malloced (char *p, svalue_t *sp)

/* Malloc a copy of string <p>, push it onto the stack currently ending
 * at <sp>, and return the new stackpointer.
 */

{
    char *s;

    s = xalloc(strlen(p)+1);
    strcpy(s, p);
    sp++;
    put_malloced_string(sp, s);
    return sp;
}

/*-------------------------------------------------------------------------*/
void
push_string_malloced (char *p)

/* Malloc a copy of string <p> and push it onto the stack as defined
 * by <inter_sp>.
 */

{
    svalue_t *sp;
    char *s;

    s = xalloc(strlen(p)+1);
    strcpy(s, p);
    sp = ++inter_sp;
    put_malloced_string(sp, s);
}

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_malloced_string (char *p, svalue_t *sp)

/* Push malloced string <p> onto the stack, currently ending at <sp>,
 * and return the new stackpointer.
 * <p> is not copied!
 */

{
    sp++;
    put_malloced_string(sp, p);
    return sp;
}

/*-------------------------------------------------------------------------*/
void push_malloced_string (char *p)

/* Push malloced string <p> onto the stack as defined by <inter_sp>.
 * <p> is not copied!
 */

{
    inter_sp++;
    put_malloced_string(inter_sp, p);
}

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_push_volatile_string (char *p, svalue_t *sp)

/* Push the volatile string <p> onto the stack, currently ending at <sp>,
 * and return the new stackpointer.
 */

{
    sp++;
    put_volatile_string(sp, p);
    return sp;
}

/*-------------------------------------------------------------------------*/
void
push_volatile_string (char *p)

/* Push the volatile string <p> onto the stack as defined by <inter_sp>.
 */

{
    inter_sp++;
    put_volatile_string(inter_sp, p);
}

/*-------------------------------------------------------------------------*/
void
push_svalue (svalue_t *v)

/* Push the svalue <v> onto the stack as defined by <inter_sp>.
 * Same semantic as assign_svalue_no_free().
 */

{
    assign_svalue_no_free(++inter_sp, v);
}

/*-------------------------------------------------------------------------*/
void
push_svalue_block (int num, svalue_t *v)

/* Push all <num> svalues starting at <v> onto the stack as defined by
 * <inter_sp>. Same semantic as assign_svalue_no_free().
 */

{
    svalue_t *w;

    for (w = inter_sp; --num >= 0; v++)
    {
        w++;
        assign_lrvalue_no_free(w, v);
    }
    inter_sp = w;
}

/*-------------------------------------------------------------------------*/
static INLINE void
_pop_stack (void)

/* Pop the topmost element from the stack as defined by <inter_sp>,
 * using free_svalue().
 */

{
#ifdef DEBUG
    if (inter_sp < VALUE_STACK)
        fatal("VM Stack underflow: %ld too low.\n", (long)(VALUE_STACK - inter_sp));
#endif
    free_svalue(inter_sp--);
}

void pop_stack (void) { _pop_stack(); }

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
_pop_n_elems (int n, svalue_t *sp)

/* Pop the <n> topmost elements from the stack, currently ending at <sp>,
 * and return the new stackpointer.
 * The elements are freed using free_svalue().
 */

{
#ifdef DEBUG
    if (n < 0)
        fatal("pop_n_elems: %d elements.\n", n);
#endif
    for (; --n >= 0; )
    {
        free_svalue(sp--);
    }
    return sp;
}

svalue_t * pop_n_elems (int n, svalue_t *sp)
{ return _pop_n_elems(n, sp); }

/*-------------------------------------------------------------------------*/
static void
stack_overflow (svalue_t *sp, svalue_t *fp, bytecode_p pc)

/* Recover from a stack overflow by popping all the elements between the
 * current stack end <sp> and the begin of the frame <fp>.
 * The function then assigns the new <sp> == <fp> and the <pc> to the
 * corresponding inter_xx variables and generates an error.
 */

{
    if (sp >= &VALUE_STACK[SIZEOF_STACK])
        fatal("Fatal stack overflow: %ld too high.\n"
             , (long)(sp - &VALUE_STACK[SIZEOF_STACK])
             );
    sp = _pop_n_elems(sp-fp, sp);
    ERROR("stack overflow\n")
}

/*-------------------------------------------------------------------------*/
void
push_vector (vector_t *v)

/* Push vector <v> onto the stack as defined by <inter_sp>.
 * The refs of <v> are incremented.
 */

{
    inter_sp++;
    put_ref_array(inter_sp, v);
}

/*-------------------------------------------------------------------------*/
void
push_referenced_vector (vector_t *v)

/* Push vector <v> onto the stack as defined by <inter_sp>.
 * The refs of <v> are _not_ incremented.
 */

{
    inter_sp++;
    put_array(inter_sp, v);
}

/*-------------------------------------------------------------------------*/
void
push_referenced_mapping (mapping_t *m)

/* Push mapping <m> onto the stack as defined by <inter_sp>.
 * The refs of <m> are _not_ incremented.
 */

{
    inter_sp++;
    put_mapping(inter_sp, m);
}

/*-------------------------------------------------------------------------*/
/* Macro-only functions:
 */

#define push_mapping(m) ( \
    sp++,\
    sp->type = T_MAPPING,\
    sp->u.map = ref_mapping(m)\
)

/*-------------------------------------------------------------------------*/
/* Fast version of several functions, must come last so to not disturb
 * the actual definitions:
 */

#define push_object(ob)         (sp = _push_object((ob), sp))
#define push_valid_ob(ob)       (sp = _push_valid_ob((ob), sp))
#define push_number(n)          (sp = _push_number(n, sp))
#define push_shared_string(p)   (sp = _push_shared_string((p), sp))
#define push_string_malloced(p) (sp = _push_string_malloced(p, sp))
#define push_malloced_string(s) (sp = _push_malloced_string((s), sp))
#define push_volatile_string(s) (sp = _push_volatile_string((s), sp))
#define pop_stack()             free_svalue(sp--)
#define pop_n_elems(n)          (sp = _pop_n_elems((n), sp))
#define push_vector(v) ( \
        sp++,\
        sp->type = T_POINTER,\
        sp->u.vec = ref_array(v)\
)

#define push_referenced_vector(v) ( \
        sp++,\
        sp->type = T_POINTER,\
        sp->u.vec = (v)\
)

/*=========================================================================*/

/*                          I N D E X I N G                                */

/*-------------------------------------------------------------------------*/
/* The following functions are concerned with the indexing of single
 * elements and ranges of strings, vectors and mappings, both as rvalue
 * and lvalue.
 *
 * Most of the functions are just the implementations of the corresponding
 * machine operators and are called just from the interpreter switch().
 * The actual arguments are pulled from the vm stack and the results pushed;
 * the functions receive the current stackpointer and programcounter as
 * function call parameters. The program counter is usally only used to
 * update <inter_pc> in case of errors. Result of the call is the new
 * stackpointer pointing to the result on the machine stack.
 *
 * TODO: A lot of the functions differ only in minute details - test how
 * TODO:: much time merging the functions (and adding if()s for the
 * TODO:: differences) really costs.
 *-------------------------------------------------------------------------
 * The functions (in a LPCish notation) are:
 *
 *   push_indexed_lvalue(vector|mapping v, int|mixed i)
 *     Return &(v[i]), unprotected.
 *   push_rindexed_lvalue(vector v, int i)
 *     Return &(v[<i]), unprotected.
 *   push_protected_indexed_lvalue(vector|mapping v, int|mixed i)
 *     Return &(v[i]), protected.
 *   push_protected_rindexed_lvalue(vector v, int i)
 *     Return &(v[<i]), protected.
 *   push_protected_indexed_map_lvalue(mapping m, mixed i, int j)
 *     Return &(m[i:j]), protected.
 *   index_lvalue(vector|mapping|string & v, int|mixed i)
 *     Return &(*v[i]), unprotected, using special_lvalue.
 *   rindex_lvalue(vector|string & v, int i)
 *     Return &(*v[<i]), unprotected, using special_lvalue.
 *   protected_index_lvalue(vector|mapping|string & v, int|mixed i)
 *     Return &(*v[i]), protected.
 *   protected_rindex_lvalue(vector|string & v, int i)
 *     Return &(*v[<i]), protected.
 *   range_lvalue(vector|string & v, int i2, int i1)
 *     Return &(*v[i1..i2]), unprotected, using special_lvalue.
 *   protected_range_lvalue(vector|string & v, int i2, int i1)
 *     Return &(*v[i1..i2]), protected.
 *   push_indexed_value(string|vector|mapping v, int|mixed i)
 *     Return v[i].
 *   push_rindexed_value(string|vector v, int i)
 *     Return v[<i].
 */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_indexed_lvalue (svalue_t *sp, bytecode_p pc)

/* Operator F_PUSH_INDEXED_LVALUE(vector  v=sp[-1], int   i=sp[0])
 * Operator F_PUSH_INDEXED_LVALUE(mapping v=sp[-1], mixed i=sp[0])
 *
 * Compute the lvalue &(v[i]) and push it into the stack. If v has just
 * one ref left, the indexed item is stored in indexing_quickfix and the
 * lvalue refers to that variable.
 */

{
    svalue_t *i;     /* the index value */
    svalue_t *vec;   /* the indexed vector or mapping */
    svalue_t *item;  /* the indexed element vec[i] */
    int ind;              /* numeric value of *i */

    /* Get the arguments */
    i = sp;
    vec = sp - 1;

    /* Index a vector.
     */
    if (vec->type == T_POINTER)
    {
        if (i->type != T_NUMBER || (ind = i->u.number) < 0)
        {
            ERROR("Illegal index for []: not a (positive) number.\n")
            /* TODO: Print type and value of i */
            return NULL;
        }
        if ((size_t)ind >= VEC_SIZE(vec->u.vec))
        {
            ERRORF(("Index out of bounds for []: %ld, vector size: %lu.\n"
                   , (long)ind, VEC_SIZE(vec->u.vec)))
            return NULL;
        }

        /* Compute the indexed element */
        item = &vec->u.vec->item[ind];

        if (vec->u.vec->ref == 1)
        {
            /* Rescue the indexed item as vec will go away */
            assign_svalue (&indexing_quickfix, item);
            item = &indexing_quickfix;
        }

        /* Remove the arguments from the stack */
        sp = vec;
        free_array(vec->u.vec);

        /* Return the result */
        vec->type = T_LVALUE;
        vec->u.lvalue = item;
        return sp;
    }

    /* Index a mapping
     */
    if (vec->type == T_MAPPING)
    {
        mapping_t *m;

        m = vec->u.map;

        if (!m->num_values)
        {
            ERROR("Indexing a mapping of width 0.\n")
            return NULL;
        }

        /* Compute the indexed element */
        item = get_map_lvalue(m, i);
        if (!item)
        {
            outofmemory("indexed lvalue");
            /* NOTREACHED */
            return NULL;
        }

        if (m->ref == 1)
        {
            /* Rescue the indexed item as vec will go away */
            assign_svalue (&indexing_quickfix, item);
            item = &indexing_quickfix;
        }

        /* Remove the arguments from the stack */
        free_svalue(sp--);
        free_mapping(m);

        /* Return the result */
        vec->type = T_LVALUE;
        vec->u.lvalue = item;
        return sp;
    }

    /* Illegal type to index */
    inter_sp = sp;
    inter_pc = pc;
    error("(lvalue)Indexing on illegal type.\n");
    /* TODO: Print the type */
    return sp;
} /* push_indexed_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_rindexed_lvalue (svalue_t *sp, bytecode_p pc)

/* Operator F_PUSH_RINDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
 *
 * Compute the lvalue &(v[<i]) and push it into the stack. If v has just
 * one ref left, the indexed item is stored in indexing_quickfix and the
 * lvalue refers to that variable.
 */

{
    svalue_t *i;     /* the index value */
    svalue_t *vec;   /* the vector */
    svalue_t *item;  /* the indexed item */
    mp_int ind;           /* the numeric value of *i */

    /* Get the arguments */
    i = sp;
    vec = sp - 1;

    /* Index a vector.
     */
    if (vec->type == T_POINTER)
    {
        if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
        {
            ERROR("Illegal index for [<]: not a (positive) number.\n")
            /* TODO: Print type and value of i */
            return NULL;
        }
        if ( (ind = (mp_int)VEC_SIZE(vec->u.vec) - ind) < 0) {
            ERRORF(("Index out of bounds for [<]: %ld, vector size: %lu.\n"
                   , (long)(i->u.number), VEC_SIZE(vec->u.vec)))
            return NULL;
        }

        /* Compute the indexed item */
        item = &vec->u.vec->item[ind];

        if (vec->u.vec->ref == 1)
        {
            /* Rescue the indexed item as vec will go away */
            assign_svalue (&indexing_quickfix, item);
            item = &indexing_quickfix;
        }

        /* Remove the arguments from the stack */
        sp = vec;
        free_array(vec->u.vec);

        /* Return the result */
        vec->type = T_LVALUE;
        vec->u.lvalue = item;
        return sp;
    }

    /* Indexing on illegal type */
    inter_sp = sp;
    inter_pc = pc;
    error("(lvalue)Indexing on illegal type.\n");
    /* TODO: Print type */
    return NULL;
} /* push_rindexed_lvalue() */

/*-------------------------------------------------------------------------*/
/* void BUILD_MAP_PROTECTOR(svalue_t *dest, mapping_t *m)
 *
 * Init svalue <dest> to protectively hold mapping <m> in which one entry
 * is about to be used as target for a lvalue.
 *
 * If mapping <m> is dirty, protect its hash_mapping part by incrementing
 * its refcount (and if this is the first call, also initialize the .deleted
 * entry), and by making the svalue a T_PROTECTOR_MAPPING.
 *
 * If <m> is not dirty, not protection is necessary.
 */
#define BUILD_MAP_PROTECTOR(dest, m)     \
{                                        \
    struct hash_mapping *hm;             \
                                         \
    if ( NULL != (hm = (m)->hash) ) {    \
        if (!hm->ref++)                  \
            hm->deleted = NULL;          \
        dest.type = T_PROTECTOR_MAPPING; \
    } else {                             \
        dest.type = T_MAPPING;           \
    }                                    \
    dest.u.map = m;                      \
}

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_protected_indexed_lvalue (svalue_t *sp, bytecode_p pc)

/* Op. F_PUSH_PROTECTED_INDEXED_LVALUE(vector  v=sp[-1], int   i=sp[0])
 * Op. F_PUSH_PROTECTED_INDEXED_LVALUE(mapping v=sp[-1], mixed i=sp[0])
 *
 * Compute the lvalue &(v[i]), store it in a struct protected_lvalue, and
 * push the protector as PROTECTED_LVALUE into the stack.
 */

{
    svalue_t           * i;       /* the index */
    svalue_t           * vec;     /* the vector */
    svalue_t           * item;    /* the indexed element */
    struct protected_lvalue * lvalue;  /* the protector */
    int                       ind;     /* numeric value of *i */

    /* Get the arguments */
    i = sp;
    vec = sp - 1;

    /* Index a vector.
     */
    if (vec->type == T_POINTER)
    {
        if (i->type != T_NUMBER || (ind = i->u.number) < 0)
        {
            ERROR("Illegal index for []: not a (positive) number.\n")
            /* TODO: Print type and value */
            return NULL;
        }

        if ((size_t)ind >= VEC_SIZE(vec->u.vec))
        {
            ERRORF(("Index out of bounds for []: %ld, vector size: %lu.\n"
                   , (long)ind, VEC_SIZE(vec->u.vec)))
            return NULL;
        }

        /* Compute the indexed item and set up the protector */

        item = &vec->u.vec->item[ind];
        lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
        lvalue->v.type = T_PROTECTED_LVALUE;
        lvalue->v.u.lvalue = item;
        put_array(&(lvalue->protector), vec->u.vec);
          /* The one ref to vec is transferred from *vec */

        /* Remove the arguments and return the result */
        sp = vec;
        vec->type = T_LVALUE;
        vec->u.lvalue = &lvalue->v;
        return sp;
    }

    /* Index a mapping
     */
    if (vec->type == T_MAPPING)
    {
        mapping_t *m;

        m = vec->u.map;

        if (!m->num_values)
        {
            ERROR("Indexing a mapping of width 0.\n");
            return NULL;
        }

        /* Compute the indexed item and set up the protector */

        item = get_map_lvalue(m, i);
        if (!item)
        {
            outofmemory("indexed lvalue");
            /* NOTREACHED */
            return NULL;
        }

        lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
        lvalue->v.type = T_PROTECTED_LVALUE;
        lvalue->v.u.lvalue = item;
        BUILD_MAP_PROTECTOR(lvalue->protector, m)
          /* The one ref is transferred from the stack */

        /* Remove the arguments and return the result */
        pop_stack();
        vec->type = T_LVALUE;
        vec->u.lvalue = &lvalue->v;
        return sp;
    }

    /* Indexing on illegal type. */

    inter_sp = sp;
    inter_pc = pc;
    error("(lvalue)Indexing on illegal type.\n");
    /* TODO: Print type */
    return NULL;
} /* push_protected_indexed_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_protected_rindexed_lvalue (svalue_t *sp, bytecode_p pc)

/* Op. F_PUSH_PROTECTED_RINDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
 *
 * Compute the lvalue &(v[<i]), store it in a struct protected_lvalue, and
 * push the protector as PROTECTED_LVALUE into the stack.
 */

{
    svalue_t           * i;       /* the index */
    svalue_t           * vec;     /* the vector */
    svalue_t           * item;    /* the indexed element */
    struct protected_lvalue * lvalue;  /* the protector */
    mp_int                    ind;     /* numeric value of *i */

    /* Get the arguments */

    i = sp;
    vec = sp - 1;

    /* Index a vector
     */
    if (vec->type == T_POINTER)
    {
        if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
        {
            ERROR("Illegal index for [<]: not a (positive) number.\n")
            /* TODO: Print type and value of i */
            return NULL;
        }

        if ( (ind = (mp_int)VEC_SIZE(vec->u.vec) - ind) < 0)
        {
            ERRORF(("Index out of bounds for [<]: %ld, vector size: %lu\n"
                   , (long) i->u.number, VEC_SIZE(vec->u.vec)))
            return NULL;
        }

        /* Compute the indexed element and setup the protector */

        item = &vec->u.vec->item[ind];

        lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
        lvalue->v.type = T_PROTECTED_LVALUE;
        lvalue->v.u.lvalue = item;
        put_array(&(lvalue->protector), vec->u.vec);
          /* The one ref is transferred from the stack */

        /* Remove arguments and return result */
        sp = vec;
        vec->type = T_LVALUE;
        vec->u.lvalue = &lvalue->v;
        return sp;
    }

    /* Indexing in illegal type */

    inter_sp = sp;
    inter_pc = pc;
    error("(lvalue)Indexing on illegal type.\n");
    /* TODO: Print type */
    return NULL;
} /* push_protected_rindexed_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_protected_indexed_map_lvalue (svalue_t *sp, bytecode_p pc)

/* Op. F_PUSH_PROTECTED_INDEXED_MAP_LVALUE(mapping m=sp[-2], mixed i=sp[-1]
 *                                                         , int   j=sp[0])
 *
 * Compute the lvalue &(m[i:j]), store it in a struct protected_lvalue, and
 * push the protector as PROTECTED_LVALUE into the stack.
 */

{
    svalue_t           * i;       /* the index */
    svalue_t           * vec;     /* the vector */
    svalue_t           * item;    /* the indexed element */
    struct protected_lvalue * lvalue;  /* the protector */

    /* Get the arguments */
    i = sp - 1;
    vec = sp - 2;

    /* Index a mapping.
     */
    if (vec->type == T_MAPPING)
    {
        mapping_t *m;

        m = vec->u.map;
        if (sp->u.number != T_NUMBER
         || (p_uint)sp->u.number >= (p_uint)m->num_values
            /* using uints automagically checks for negative indices */
           )
        {
            ERROR("Illegal subindex for []: not a number or out of bounds.\n")
            /* TODO: This error message should be done nicer. */
            return NULL;
        }

        /* Compute the indexed element and setup the protector */

        item = get_map_lvalue(m, i);
        if (!item)
        {
            outofmemory("indexed lvalue");
            /* NOTREACHED */
            return NULL;
        }
        item += sp->u.number;

        lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
        lvalue->v.type = T_PROTECTED_LVALUE;
        lvalue->v.u.lvalue = item;
        BUILD_MAP_PROTECTOR(lvalue->protector, m)
          /* The one ref is transferred from the stack */

        /* Remove the arguments and return the result */
        sp--;
        pop_stack();
        vec->type = T_LVALUE;
        vec->u.lvalue = &lvalue->v;
        return sp;
    }

    /* Indexing on illegal type */

    inter_sp = sp;
    inter_pc = pc;
    error("(lvalue)Indexing on illegal type.\n");
    /* TODO: Print type */
    return NULL;
} /* push_protected_indexed_map_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
index_lvalue (svalue_t *sp, bytecode_p pc)

/* Operator F_INDEX_LVALUE (string|vector &v=sp[0], int   i=sp[-1])
 *          F_INDEX_LVALUE (mapping       &v=sp[0], mixed i=sp[-1])
 *
 * Compute the index &(v[i]) of lvalue <v> and push it into the stack. The
 * computed index is a lvalue itself.
 * If <v> is a string-lvalue, it is made a malloced string if necessary,
 * and the pushed result will be a lvalue pointing to a CHAR_LVALUE stored
 * in <special_lvalue>.
 */

{
    svalue_t *vec;   /* the vector/mapping */
    svalue_t *i;     /* the index */
    int            ind;   /* numeric value of <i> */
    short          type;  /* type of <vec> */

    /* get the arguments */
    vec = sp;
    i = sp -1;

    /* Dereference the initial (and possibly more) lvalue-indirection
     */
    do {
        vec = vec->u.lvalue;
        type = vec->type;
    } while (type == T_LVALUE || type == T_PROTECTED_LVALUE);

    /* Index a vector.
     */
    if (type == T_POINTER)
    {
        vector_t *v = vec->u.vec;

        if (i->type != T_NUMBER || (ind = i->u.number) < 0)
        {
            ERROR("Illegal index for []: not a (positive) number.\n")
            /* TODO: Print type and value of i */
            return NULL;
        }

        if ((size_t)ind >= VEC_SIZE(v))
        {
            ERRORF(("Index for [] out of bounds: %ld, vector size: %lu\n"
                   , (long)ind, VEC_SIZE(v)))
            return NULL;
        }

        /* Remove the arguments and push the result */

        sp = i;

        sp->type = T_LVALUE;
        sp->u.lvalue = &v->item[ind];
        return sp;
    }

    /* Index a string.
     */
    if (type == T_STRING)
    {
        if (i->type != T_NUMBER || (ind = i->u.number) < 0)
        {
            ERROR("Illegal index for []: not a (positive) number.\n")
            /* TODO: Print type and value of i */
            return NULL;
        }

        if (ind >= _svalue_strlen(vec) )
        {
            ERRORF(("Index out for [] of bounds: %ld, string length: %ld.\n"
                   , (long)ind, (long)_svalue_strlen(vec)))
            return NULL;
        }

        /* If the string is not malloced, i.e. changeable, allocate
         * a new copy which can be changed.
         */
        if (vec->x.string_type != STRING_MALLOC)
        {
            char *p = string_copy(vec->u.string);

            if (vec->x.string_type == STRING_SHARED)
                free_string(vec->u.string);
            vec->x.string_type = STRING_MALLOC;
            vec->u.string = p;
        }

        /* Remove the arguments and create and push the result. */

        sp = i;

        sp->type = T_LVALUE;
        sp->u.lvalue = &special_lvalue.v;
        special_lvalue.v.type = T_CHAR_LVALUE;
        special_lvalue.v.u.string = &vec->u.string[ind];
        return sp;
    }

    /* Index a mapping.
     */
    if (type == T_MAPPING)
    {
        svalue_t *item;
        mapping_t *m;

        m = vec->u.map;
        if (!m->num_values)
        {
            ERROR("Indexing a mapping of width 0.\n");
            return NULL;
        }

        /* Compute the element */
        item = get_map_lvalue(m, i);
        if (!item)
        {
            outofmemory("indexed lvalue");
            /* NOTREACHED */
            return NULL;
        }

        /* Remove the arguments and push the result. */

        sp = i;
        free_svalue(i);
        sp->type = T_LVALUE;
        sp->u.lvalue = item;
        return sp;
    }

    /* Illegal type to index. */

    inter_sp = sp;
    inter_pc = pc;
    error("(lvalue)Indexing on illegal type.\n");
    /* TODO: Print type */
    return NULL;
} /* index_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
rindex_lvalue (svalue_t *sp, bytecode_p pc)

/* Operator F_RINDEX_LVALUE (string|vector &v=sp[0], int   i=sp[-1])
 *
 * Compute the index &(v[<i]) of lvalue <v> and push it into the stack. The
 * computed index is a lvalue itself.
 * If <v> is a string-lvalue, it is made a malloced string if necessary,
 * and the pushed result will be a lvalue pointing to a CHAR_LVALUE stored
 * in <special_lvalue>.
 */

{
    svalue_t *vec;   /* the vector/string */
    svalue_t *i;     /* the index */
    mp_int         ind;   /* numeric value of <i> */
    short          type;  /* type of <vec> */

    /* get the arguments */
    vec = sp;
    i = sp -1;

    if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
    {
        ERROR("Illegal index for [<]: not a (positive) number.\n")
        /* TODO: Print type and value of i */
        return NULL;
    }

    /* Dereference the initial (and possibly more) lvalue-indirection
     */
    do {
        vec = vec->u.lvalue;
        type = vec->type;
    } while (type == T_LVALUE || type == T_PROTECTED_LVALUE);

    /* Index a vector
     */
    if (type == T_POINTER)
    {
        vector_t *v = vec->u.vec;


        if ( (ind = (mp_int)VEC_SIZE(v) - ind) < 0)
        {
            ERRORF(("Index out of bounds for [<]: %ld, vector size: %lu\n"
                   , (long) i->u.number, VEC_SIZE(vec->u.vec)))
            return NULL;
        }

        /* Remove the arguments and return the result */

        sp = i;
        sp->type = T_LVALUE;
        sp->u.lvalue = &v->item[ind];
        return sp;
    }

    /* Index a string
     */
    if (type == T_STRING)
    {
        if ( (ind = (mp_int)_svalue_strlen(vec) - ind) < 0)
        {
            ERRORF(("Index out of bounds for [<]: %ld, string length: %ld\n"
                   , (long) i->u.number, (long)_svalue_strlen(vec)))
            return NULL;
        }

        /* If the string is not malloced, i.e. changeable, allocate a
         * copy in vec which can be changed.
         */
        if (vec->x.string_type != STRING_MALLOC)
        {
            char *p = string_copy(vec->u.string);

            if (vec->x.string_type == STRING_SHARED)
                free_string(vec->u.string);
            vec->x.string_type = STRING_MALLOC;
            vec->u.string = p;
        }

        /* Remove the argument and return the result */

        sp = i;
        sp->type = T_LVALUE;
        sp->u.lvalue = &special_lvalue.v;
        special_lvalue.v.type = T_CHAR_LVALUE;
        special_lvalue.v.u.string = &vec->u.string[ind];
        return sp;
    }

    /* Indexing on illegal type */

    inter_sp = sp;
    inter_pc = pc;
    error("(lvalue)Indexing on illegal type.\n");
    /* TODO: Print the type */
    return NULL;
} /* rindex_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
protected_index_lvalue (svalue_t *sp, bytecode_p pc)

/* Operator F_PROTECTED_INDEX_LVALUE (string|vector &v=sp[0], int   i=sp[-1])
 *          F_PROTECTED_INDEX_LVALUE (mapping       &v=sp[0], mixed i=sp[-1])
 *
 * Compute the index &(*v[i]) of lvalue <v>, wrap it into a protector, and push
 * the reference to the protector as PROTECTED_LVALUE onto the stack.
 *
 * If <v> is a protected non-string-lvalue, the protected_lvalue referenced
 * by <v>.u.lvalue will be deallocated, and the protector itself will be
 * stored in <last_indexing_protector> for the time being.
 *
 * If <v> is a string-lvalue, it is made a malloced string if necessary.
 */

{
    svalue_t *vec;   /* the indexed value */
    svalue_t *i;     /* the index */
    int            ind;   /* numeric value of <i> */
    short          type;  /* type of <vec> */

    /* Get arguments */
    vec = sp->u.lvalue;
    i = sp -1;

    /* The loop unravels the (possible) lvalue chain starting at vec.
     * When a non-lvalue is encountered, the indexing takes place
     * the function returns.
     */
    for (;;)
    {
        type = vec->type;

        /* Index a vector.
         */
        if (type == T_POINTER)
        {
            vector_t *v = vec->u.vec;
            struct protected_lvalue *lvalue;

            if (i->type != T_NUMBER || (ind = i->u.number) < 0)
            {
                ERROR("Illegal index for []: not a (positive) number.\n")
                /* TODO: Print type and value of i */
                return NULL;
            }

            if ((size_t)ind >= VEC_SIZE(v))
            {
                ERRORF(("Index for [] out of bounds: %ld, vector size: %lu.\n"
                       , (long)ind, VEC_SIZE(v)))
                return NULL;
            }

            /* Drop the arguments */
            sp = i;

            /* Compute and return the result */

            lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
            lvalue->v.type = T_PROTECTED_LVALUE;
            lvalue->v.u.lvalue = &v->item[ind];
            put_ref_array(&(lvalue->protector), v);

            sp->type = T_LVALUE;
            sp->u.lvalue = &lvalue->v;

            return sp;
        }

        /* Index a string.
         */
        if (type == T_STRING)
        {
            struct protected_char_lvalue *val;

            if (i->type != T_NUMBER || (ind = i->u.number) < 0)
            {
                ERROR("Illegal index for []: not a (positive) number.\n")
                /* TODO: Print type and value of i */
                return NULL;
            }

            if (ind >= svalue_strlen(vec) )
            {
                ERRORF(("Index for [] out of bounds: %ld, string length: %ld.\n"
                       , (long)ind, (long)_svalue_strlen(vec)))
                return NULL;
            }

            /* If the string is not allocated, ie. changeable, allocate
             * a new changeable copy.
             */
            if (vec->x.string_type != STRING_MALLOC)
            {
                char *p = string_copy(vec->u.string);

                if (vec->x.string_type == STRING_SHARED)
                    free_string(vec->u.string);
                vec->u.string = p;
                /* string_type is used by svalue_strlen */
                vec->x.string_type = STRING_MALLOC;
            }

            /* Make the string 'VOLATILE' so that we have full control
             * over its deallocation.
             */
            vec->x.string_type = STRING_VOLATILE;

            /* Drop the arguments */
            sp = i;

            /* Compute and return the result */

            val = (struct protected_char_lvalue *)xalloc(sizeof *val);
            val->v.type = T_PROTECTED_CHAR_LVALUE;
            val->v.u.string = &vec->u.string[ind];
            val->lvalue = vec;
            val->start = vec->u.string;
            val->protector.type = T_INVALID;

            sp->type = T_LVALUE;
            sp->u.protected_char_lvalue = val;

            return sp;
        }

        /* Index a mapping.
         */
        if (type == T_MAPPING)
        {
            svalue_t *item;
            struct protected_lvalue *lvalue;
            mapping_t *m;

            m = vec->u.map;
            if (!m->num_values)
            {
                ERROR("Indexing a mapping of width 0.\n");
                return NULL;
            }

            /* Compute the indexed element */
            item = get_map_lvalue(m, i);
            if (!item)
            {
                outofmemory("indexed lvalue");
                /* NOTREACHED */
                return NULL;
            }

            /* Build the protector */
            ref_mapping(m);
            lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
            lvalue->v.type = T_PROTECTED_LVALUE;
            lvalue->v.u.lvalue = item;
            BUILD_MAP_PROTECTOR(lvalue->protector, m)

            /* Drop the arguments and return the result */
            sp = i;
            free_svalue(i);

            sp->type = T_LVALUE;
            sp->u.lvalue = &lvalue->v;

            return sp;
        }

        /* lvalues are just dereferenced.
         */
        if (type == T_LVALUE)
        {
            vec = vec->u.lvalue;
            continue;
        }

        /* Non-string protected lvalues are dereferenced, a protected
         * string lvalue is indexed immediately.
         */
        if (type == T_PROTECTED_LVALUE)
        {
            struct protected_lvalue *lvalue;
            struct protected_char_lvalue *val;

            lvalue = (struct protected_lvalue *)vec;

            if (lvalue->v.u.lvalue->type != T_STRING)
            {
                /* Deref a non-string protected lvalue.
                 * If this is the lvalue passed to the operator, also free
                 * the protector structure (since its stack space will be
                 * used for the result), but keep the protector itself
                 * in a global variable.
                 */
                if (vec == sp->u.lvalue)
                {
                    free_protector_svalue(&last_indexing_protector);
                    last_indexing_protector = lvalue->protector;
                    vec = lvalue->v.u.lvalue;
                    xfree(lvalue);
                    continue;
                }

                vec = lvalue->v.u.lvalue;
                continue;
            }

            vec = lvalue->v.u.lvalue; /* it's a string... */

            if (i->type != T_NUMBER || (ind = i->u.number) < 0)
            {
                ERROR("Illegal index for []: not a (positive) number.\n")
                /* TODO: Print type and value of i */
                return NULL;
            }

            if (ind >= svalue_strlen(vec) )
            {
                ERRORF(("Index for [] out of bounds: %ld, string length: %ld\n"
                       , (long)ind, (long)_svalue_strlen(vec)))
                return NULL;
            }

            /* If the string is not allocated, ie changeable, allocate
             * a copy we can change.
             */
            if (vec->x.string_type != STRING_MALLOC)
            {
                char *p = string_copy(vec->u.string);

                if (vec->x.string_type == STRING_SHARED)
                    free_string(vec->u.string);
                vec->u.string = p;
            }

            /* Make the string 'VOLATILE' so that we have full control
             * over its deallocation.
             */
            vec->x.string_type = STRING_VOLATILE;

            /* Build the protector */
            val = (struct protected_char_lvalue *)xalloc(sizeof *val);
            val->v.type = T_PROTECTED_CHAR_LVALUE;
            val->v.u.string = &vec->u.string[ind];
            val->lvalue = vec;
            val->start = vec->u.string;

            /* Drop the arguments and return the result.
             * If this was the lvalue passed to the operator in the
             * first place, adopt the protecting value and free the old
             * operator structure. If not, just don't assign a protecting
             * value.
             */
            if (lvalue == sp->u.protected_lvalue)
            {
                val->protector = lvalue->protector;
                xfree(lvalue);
            }
            else
            {
                val->protector.type = T_INVALID;
            }

            sp = i;
            sp->type = T_LVALUE;
            sp->u.protected_char_lvalue = val;

            return sp;
        }

        /* Indexing on illegal type */
        inter_sp = sp;
        inter_pc = pc;
        error("(lvalue)Indexing on illegal type.\n");
        /* TODO: Print type */
        return NULL;
    } /* for(ever) */

    /* NOTREACHED */
    return NULL;
} /* protected_index_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
protected_rindex_lvalue (svalue_t *sp, bytecode_p pc)

/* Operator F_PROTECTED_RINDEX_LVALUE (string|vector &v=sp[0], int   i=sp[-1])
 *
 * Compute the index &(*v[<i]) of lvalue <v>, wrap it into a protector, and
 * push the reference to the protector as PROTECTED_LVALUE onto the stack.
 *
 * If <v> is a protected non-string-lvalue, the protected_lvalue referenced
 * by <v>.u.lvalue will be deallocated, and the protector itself will be
 * stored in <last_indexing_protector> for the time being.
 *
 * If <v> is a string-lvalue, it is made a malloced string if necessary.
 */

{
    svalue_t *vec;   /* the indexed value */
    svalue_t *i;     /* the index */
    mp_int         ind;   /* numeric value of <i> */
    short          type;  /* type of <vec> */

    /* Get arguments */
    vec = sp->u.lvalue;
    i = sp -1;

    if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
    {
        ERROR("Illegal indexi for [<]: not a (positive) number.\n")
        /* TODO: Print type and value of i */
        return NULL;
    }

    /* The loop unravels the (possible) lvalue chain starting at vec.
     * When a non-lvalue is encountered, the indexing takes place
     * the function returns.
     */
    for (;;)
    {
        type = vec->type;

        /* Index a vector.
         */
        if (type == T_POINTER)
        {
            vector_t *v = vec->u.vec;
            struct protected_lvalue *lvalue;

            if ( (ind = (mp_int)VEC_SIZE(v) - ind) < 0)
            {
                ERRORF(("Index for [<] out of bounds: %ld, vector size: %lu\n"
                       , (long)i->u.number, VEC_SIZE(v)))
                return NULL;
            }

            /* Create the protector for the result */

            lvalue = (struct protected_lvalue *)xalloc(sizeof *lvalue);
            lvalue->v.type = T_PROTECTED_LVALUE;
            lvalue->v.u.lvalue = &v->item[ind];
            put_ref_array(&(lvalue->protector), v);

            /* Drop the arguments and return the result */

            sp = i;

            sp->type = T_LVALUE;
            sp->u.lvalue = &lvalue->v;
            return sp;
        }

        /* Index a string.
         */
        if (type == T_STRING)
        {
            struct protected_char_lvalue *val;

            if ( (ind = (mp_int)svalue_strlen(vec)  - ind) < 0)
            {
                ERRORF(("Index for [<] out of bounds: %ld, string length: %ld.\n"
                       , (long)i->u.number, (long)svalue_strlen(vec)))
                return NULL;
            }

            /* If the string is not allocated, ie. changeable, allocate
             * a new changeable copy.
             */
            if (vec->x.string_type != STRING_MALLOC)
            {
                char *p = string_copy(vec->u.string);

                if (vec->x.string_type == STRING_SHARED)
                    free_string(vec->u.string);
                vec->u.string = p;
            }

            /* Make the string 'VOLATILE' so that we have full control
             * over its deallocation.
             */
            vec->x.string_type = STRING_VOLATILE;

            /* Build the protector */
            val = (struct protected_char_lvalue *)xalloc(sizeof *val);
            val->v.type = T_PROTECTED_CHAR_LVALUE;
            val->v.u.string = &vec->u.string[ind];
            val->lvalue = vec;
            val->start = vec->u.string;
            val->protector.type = T_INVALID;

            /* Drop the arguments and return the result */
            sp = i;

            sp->type = T_LVALUE;
            sp->u.protected_char_lvalue = val;

            return sp;
        }

        /* lvalues are just dereferenced.
         */
        if (type == T_LVALUE)
        {
            vec = vec->u.lvalue;
            continue;
        }

        /* Non-string protected lvalues are dereferenced, a protected
         * string lvalue is indexed immediately.
         */
        if (type == T_PROTECTED_LVALUE)
        {
            struct protected_lvalue *lvalue;
            struct protected_char_lvalue *val;

            lvalue = (struct protected_lvalue *)vec;

            if (lvalue->v.u.lvalue->type != T_STRING)
            {
                /* Deref a non-string protected lvalue.
                 * If this is the lvalue passed to the operator, also free
                 * the protector structure (since its stack space will be
                 * used for the result), but keep the protector itself
                 * in a global variable.
                 */
                if (vec == sp->u.lvalue)
                {
                    free_protector_svalue(&last_indexing_protector);
                    last_indexing_protector = lvalue->protector;
                    vec = lvalue->v.u.lvalue;
                    xfree(lvalue);
                    continue;
                }
                vec = lvalue->v.u.lvalue;
                continue;
            }

            vec = lvalue->v.u.lvalue; /* it's a string... */

            if ( (ind = (mp_int)svalue_strlen(vec)  - ind) < 0)
            {
                ERRORF(("Index for [<] out of bounds: %ld, string length: %ld.\n"
                       , (long)i->u.number, (long)svalue_strlen(vec)))
                return NULL;
            }

            /* If the string is not allocated, ie changeable, allocate
             * a copy we can change.
             */
            if (vec->x.string_type != STRING_MALLOC)
            {
                char *p = string_copy(vec->u.string);
                if (vec->x.string_type == STRING_SHARED)
                    free_string(vec->u.string);
                vec->u.string = p;
            }

            /* Make the string 'VOLATILE' so that we have full control
             * over its deallocation.
             */
            vec->x.string_type = STRING_VOLATILE;

            /* Build the protector */
            val = (struct protected_char_lvalue *)xalloc(sizeof *val);
            val->v.type = T_PROTECTED_CHAR_LVALUE;
            val->v.u.string = &vec->u.string[ind];
            val->lvalue = vec;
            val->start = vec->u.string;

            /* Drop the arguments and return the result.
             * If this was the lvalue passed to the operator in the
             * first place, adopt the protecting value and free the old
             * operator structure. If not, just don't assign a protecting
             * value.
             */
            if (lvalue == sp->u.protected_lvalue)
            {
                val->protector = lvalue->protector;
                xfree(lvalue);
            }
            else
            {
                val->protector.type = T_INVALID;
            }

            sp = i;
            sp->type = T_LVALUE;
            sp->u.protected_char_lvalue = val;

            return sp;
        }

        /* Indexing on illegal type */
        inter_sp = sp;
        inter_pc = pc;
        error("(lvalue)Indexing on illegal type.\n");
        /* TODO: Print the type */
        return NULL;
    } /* for(ever) */

    /* NOTREACHED */
    return NULL;
} /* protected_rindex_lvalue() */

/*-------------------------------------------------------------------------*/
static svalue_t *
range_lvalue (int code, svalue_t *sp)

/* Operator F_RANGE_LVALUE (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
 * and the operators F_{NR,RN,RR}_RANGE_LVALUE.
 *
 * Compute the range &(v[i1..i2]) of lvalue <v> and push it into the stack.
 * The value pushed is a lvalue pointint to <special_lvalue>. <special_lvalue>
 * then is the POINTER_RANGE_- resp. STRING_RANGE_LVALUE.
 *
 * <code> is a two-byte flag determining whether the indexes are counted
 * from the beginning ('[i1..' and '..i2]') or the the end of the vector
 * or string ('[<i1..' and '..<i2]'). If <code>&0xff00 is TRUE, the lower
 * index is counted from the end, if <code>&0x00ff is TRUE, the upper
 * index is counted from the end.
 * TODO: This code thingy is not really nice.
 */

{
    svalue_t *vec;         /* the indexed vector or string */
    svalue_t *i;           /* the index */
    int            ind1, ind2;  /* Lower and upper range index */
    short          type;        /* type of <vec> */
    mp_int         size;        /* size of <vec> in elements */

    /* Get the arguments */
    vec = sp;
    i = sp-1;

#ifdef DEBUG
    if (sp->type != T_LVALUE) {
        inter_sp = sp;
        error("wrong type to range_lvalue\n");
        /* TODO: Print type */
        return NULL;
    }
#endif

    /* Deref the initial, and possibly more, lvalues.
     */
    do {
        vec = vec->u.lvalue;
        type = vec->type;
    } while (type == T_LVALUE || type == T_PROTECTED_LVALUE);

    /* Determine the type of the result, and the input's size.
     */
    switch(type)
    {
    case T_POINTER:
        special_lvalue.v.type = T_POINTER_RANGE_LVALUE;
        size = (mp_int)VEC_SIZE(vec->u.vec);
        break;
    case T_STRING:
        special_lvalue.v.type = T_STRING_RANGE_LVALUE;
        size = (mp_int)svalue_strlen(vec);
        break;
    default:
        inter_sp = sp;
        error("(lvalue)Range index on illegal type.\n");
        /* TODO: Print type */
        return NULL;
    }

    /* Get and check the upper bound i2 */

    if (i->type != T_NUMBER)
    {
        inter_sp = sp;
        error("Illegal upper range index: not a number.\n");
        /* TODO: Print type */
        return NULL;
    }

    if (code & 0xff)
    {
        ind2 = size - i->u.number;
    }
    else
    {
        ind2 = i->u.number;
    }


    if (++ind2 < 0 || ind2 > size)
    {
        inter_sp = sp;
        error("Upper range index out of bounds: %ld, size: %ld.\n"
             , (long)i->u.number, (long)size);
        return NULL;
    }

    /* Get and check the lower bound i1 */

    if ((--i)->type != T_NUMBER)
    {
        inter_sp = sp;
        error("Illegal lower range index: not a number.\n");
        /* TODO: Print the type */
        return NULL;
    }

    if (code & 0xff00)
    {
        ind1 = size - i->u.number;
    }
    else
    {
        ind1 = i->u.number;
    }

    if (ind1 < 0 || ind1 > size)
    {   /* Appending (ind1 == size) is allowed */
        inter_sp = sp;
        error("Lower range index out of bounds: %ld, size: %ld.\n"
             , (long)i->u.number, (long)size);
        return NULL;
    }

    /* Finish the special_lvalue structure
     */
    special_lvalue.v.u.lvalue = vec;
    special_lvalue.size = size;
    special_lvalue.index1 = ind1;
    special_lvalue.index2 = ind2;

    /* Drop the arguments and return the result. */

    sp = i;

    sp->type = T_LVALUE;
    sp->u.lvalue = &special_lvalue.v;

    return sp;
} /* range_lvalue() */

/*-------------------------------------------------------------------------*/
static svalue_t *
protected_range_lvalue (int code, svalue_t *sp)

/* X-Operator F_PROTECTED_RANGE_LVALUE
 *                       (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
 * and the x-operators F_PROTECTED_{NR,RN,RR}_RANGE_LVALUE and
 * F_PROTECTED_LVALUE.
 *
 * Compute the range &(v[i1..i2]) of lvalue <v>, wrap it into a protector,
 * and push the reference to the protector onto the stack.
 *
 * If <v> is a protected lvalue itself, its protecting svalue will be used
 * in the result protector.
 *
 * If <v> is a string-lvalue, it is made a malloced string if necessary.
 *
 * <code> is a two-byte flag determining whether the indexes are counted
 * from the beginning ('[i1..' and '..i2]') or the the end of the vector
 * or string ('[<i1..' and '..<i2]'). If <code>&0xff00 is TRUE, the lower
 * index is counted from the end, if <code>&0x00ff is TRUE, the upper
 * index is counted from the end.
 * TODO: This code thingy is not really nice.
 */

{
    svalue_t *vec;         /* the indexed vector or string */
    svalue_t *i;           /* the index */
    int            ind1, ind2;  /* Lower and upper range index */
    short          type;        /* type of <vec> */
    mp_int         size;        /* size of <vec> in elements */
    short          lvalue_type; /* Result type */
    svalue_t  protector;   /* Protecting svalue saved from v */
    struct protected_range_lvalue *new_lvalue;
                                /* Result protector structure */

#ifdef DEBUG
    if (sp->type != T_LVALUE)
    {
        inter_sp = sp;
        error("wrong type to protected_range_lvalue\n");
        /* TODO: Print type */
        return NULL;
    }
#endif
    /* Get the arguments, and also remember the protector in case v
     * is a protected lvalue.
     */
    vec = sp->u.lvalue; /* deref initial lvalue */
    i = sp - 1;

    type = vec->type;

    if (type != T_PROTECTED_LVALUE)
        protector.type = T_INVALID;
    else
        protector = ((struct protected_lvalue*)vec)->protector;

    /* Deref any possibly following lvalues
     */
    while (type == T_LVALUE || type == T_PROTECTED_LVALUE)
    {
        vec = vec->u.lvalue;
        type = vec->type;
    }

    /* Determine the type of the result, and the input's size.
     * Also massage the input value a bit.
     */
    switch(type)
    {
    case T_POINTER:
        (void)ref_array(vec->u.vec); /* Count the coming protector */
        lvalue_type = T_PROTECTED_POINTER_RANGE_LVALUE;
        size = (mp_int)VEC_SIZE(vec->u.vec);
        break;

    case T_STRING:
        /* If the string is not allocated, ie changeable, allocate
         * a copy we can change.
         */
        if (vec->x.string_type != STRING_MALLOC)
        {
            char *p = string_copy(vec->u.string);

            if (vec->x.string_type == STRING_SHARED)
                free_string(vec->u.string);
            vec->u.string = p;
        }
        /* Make the string 'VOLATILE' so that we have full control
         * over its deallocation.
         */
        vec->x.string_type = STRING_VOLATILE;

        lvalue_type = T_PROTECTED_STRING_RANGE_LVALUE;
        size = (mp_int)svalue_strlen(vec);
        break;

    default:
        inter_sp = sp;
        error("(lvalue)Range index on illegal type.\n");
        /* TODO: Print type */
        return NULL;
    }

    /* Get and check the upper index i2 */

    if (i->type != T_NUMBER)
    {
        inter_sp = sp;
        error("Illegal upper range index: not a number.\n");
        /* TODO: Print type. */
        return NULL;
    }

    if (code & 0xff)
    {
        ind2 = size - i->u.number;
    }
    else
    {
        ind2 = i->u.number;
    }

    if (++ind2 < 0 || ind2 > size) {
        inter_sp = sp;
        error("Upper range index out of bounds: %ld, size: %ld.\n"
             , (long)i->u.number, (long)size);
        return NULL;
    }

    /* Get and check the lower index i1 */

    if ((--i)->type != T_NUMBER)
    {
        inter_sp = sp;
        error("Illegal lower range index: not a number.\n");
        /* TODO: Print type. */
        return NULL;
    }

    if (code & 0xff00)
    {
        ind1 = size - i->u.number;
    }
    else
    {
        ind1 = i->u.number;
    }

    if (ind1 < 0 || ind1 > size)
    {
        /* Appending (ind1 == size) is allowed */
        inter_sp = sp;
        error("Lower range index out of bounds: %ld, size: %ld.\n"
             , (long)i->u.number, (long)size);
        return NULL;
    }

    /* Build the protector */
    new_lvalue = (struct protected_range_lvalue *)xalloc(sizeof *new_lvalue);
    new_lvalue->v.type = lvalue_type;
    new_lvalue->v.u = vec->u;
    new_lvalue->protector = protector;
    new_lvalue->lvalue = vec;
    new_lvalue->index2 = ind2;
    new_lvalue->index1 = ind1;
    new_lvalue->size = size;

    /* Drop the arguments and return the result */

    sp = i;

    sp->type = T_LVALUE;
    sp->u.protected_range_lvalue = new_lvalue;

    return sp;
} /* protected_range_lvalue() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_indexed_value (svalue_t *sp, bytecode_p pc)

/* Operator F_INDEX (string|vector v=sp[0], int   i=sp[-1])
 *          F_INDEX (mapping       v=sp[0], mixed i=sp[-1])
 *
 * Compute the value (v[i]) and push it onto the stack.
 * If the value would be a destructed object, 0 is pushed onto the stack
 * and the ref to the object is removed from the vector/mapping.
 *
 * Mapping indices may use <indexing_quickfix> for temporary storage.
 */

{
    svalue_t *vec;  /* the indexed value */
    svalue_t *i;    /* the index */
    int            ind;  /* numeric value of <i> */

    /* Get arguments */
    i = sp;
    vec = sp - 1;

    switch (vec->type)
    {
    case T_STRING:
      {
        if (i->type != T_NUMBER || (ind = i->u.number) < 0)
        {
            ERROR("Illegal index for []: not a (positive) number.\n")
            /* TODO: Print type and value */
            return NULL;
        }

        /* Index the string */
        if (ind >= _svalue_strlen(vec))
            ind = 0;
        else
            ind = vec->u.string[ind];

        /* Drop the args and return the result */

        free_string_svalue(vec);

        sp = vec;
        put_number(sp, ind);

        return sp;
      }

    case T_POINTER:
        if (i->type != T_NUMBER || (ind = i->u.number) < 0)
        {
            ERROR("Illegal index for []: not a (positive) number.\n")
            /* TODO: Print type and value */
            return NULL;
        }

        /* Drop the arguments */
        sp = vec;

        if (ind < 0 || (size_t)ind >= VEC_SIZE(vec->u.vec))
        {
            ERRORF(("Index for [] out of bounds: %ld, vector size: %lu\n"
                   , (long)ind, VEC_SIZE(vec->u.vec)))
            return NULL;
        }

        /* Assign the indexed element to the sp entry holding vec.
         * Decrement the vector ref manually to optimize the case that
         * this is the last ref to the vector.
         */
        if (vec->u.vec->ref == 1)
        {
            svalue_t *p, tmp;

            /* Copy the indexed element into <tmp>
             */
#if !defined(NO_INLINES) && defined(__GNUC__)
            tmp = const0;
            /* gcc complains about tmp being clobbered */
#endif
            p = &vec->u.vec->item[ind];
            if (destructed_object_ref(p))
            {
                free_svalue(p);
                put_number(&tmp, 0);
            }
            else
            {
                tmp = *p;
            }

            /* Invalidate the old space of the result value and free
             * the vector.
             */
            p->type = T_INVALID;
            free_array(vec->u.vec);

            /* Return the result */
            *sp = tmp;
            return sp;
        }

        deref_array(vec->u.vec);

        /* The vector continues to live: keep the refcount as it is
         * and just assign the indexed element for the result.
         */
        assign_checked_svalue_no_free(sp, &vec->u.vec->item[ind], sp, pc);
        return sp;

    case T_MAPPING:
      {
        svalue_t *item;
        mapping_t *m;

        m = vec->u.map;
        if (!m->num_values)
        {
            inter_sp = sp;
            inter_pc = pc;
            error("(value)Indexing a mapping of width 0.\n");
            return NULL;
        }

        /* Get the item */
        item = get_map_value(m, i);

        /* Drop the arguments */

        free_svalue(i); /* must come before the free(m) in case i and m are
                         * identical: if not done, the following test for
                         * m->ref == 1 would not be possible.
                         */

        if (m->ref == 1)
        {
            /* Only one ref left to the mapping: rescue the indexed
             * item in indexing_quickfix before the free(m) will deallocate
             * it.
             */
            assign_svalue (&indexing_quickfix, item);
            item = &indexing_quickfix;
        }
        free_mapping(m);

        /* Return the result */
        sp = vec;
        assign_checked_svalue_no_free(sp, item, sp, pc);
        return sp;
      }

    default:
        inter_sp = sp;
        inter_pc = pc;
        error("(value)Indexing on illegal type.\n");
        /* TODO: Print type */
        return NULL;
    }

    /* NOTREACHED */
    return NULL;
} /* push_indexed_value() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
push_rindexed_value (svalue_t *sp, bytecode_p pc)

/* Operator F_RINDEX (string|vector v=sp[0], int   i=sp[-1])
 *
 * Compute the value (v[<i]) and push it onto the stack.
 * If the value would be a destructed object, 0 is pushed onto the stack
 * and the ref to the object is removed from the vector/mapping.
 */

{
    svalue_t *vec;  /* the indexed value */
    svalue_t *i;    /* the index */
    mp_int         ind;  /* numeric value of <i> */

    /* Get arguments */
    i = sp;
    vec = sp - 1;

    if (i->type != T_NUMBER || (ind = i->u.number) <= 0)
    {
        ERROR("Illegal index for [<]: not a (positive) number.\n")
        /* TODO: Print type and value */
        return NULL;
    }

    switch (vec->type)
    {
    case T_STRING:
      {
        /* Index the string */

        if ( (ind = (mp_int)_svalue_strlen(vec) - ind) < 0 )
            ind = 0;
        else
            ind = vec->u.string[ind];

        /* Drop the args and return the result */

        free_string_svalue(vec);

        sp = vec;
        put_number(sp, ind);
        return sp;
      }

    case T_POINTER:
        /* Drop the arguments */
        sp = vec;
        if ( (ind = (mp_int)VEC_SIZE(vec->u.vec) - ind) < 0) {
            ERRORF(("Index for [<] out of bounds: %ld, vector size: %lu\n"
                   , (long)i->u.number, VEC_SIZE(vec->u.vec)))
            return NULL;
        }

        /* Assign the indexed element to the sp entry holding vec.
         * Decrement the vector ref manually to optimize the case that
         * this is the last ref to the vector.
         */
        if (vec->u.vec->ref == 1)
        {
            svalue_t *p, tmp;

            /* Copy the indexed element into <tmp>
             */
#if !defined(NO_INLINES) && defined(__GNUC__)
            tmp = const0;
            /* gcc complains about tmp being clobbered */
#endif
            p = &vec->u.vec->item[ind];
            if (destructed_object_ref(p))
            {
                free_svalue(p);
                put_number(&tmp, 0);
            }
            else
            {
                tmp = *p;
            }

            /* Invalidate the old space of the result value and free
             * the vector.
             */
            p->type = T_INVALID;
            free_array(vec->u.vec);

            /* Return the result */
            *sp = tmp;
            return sp;
        }

        deref_array(vec->u.vec);

        /* The vector continues to live: keep the refcount as it is
         * and just assign the indexed element for the result.
         */
        assign_checked_svalue_no_free(sp, &vec->u.vec->item[ind], sp, pc);
        return sp;

    default:
        inter_sp = sp;
        inter_pc = pc;
        error("(value)Indexing on illegal type.\n");
        /* TODO: Print type */
        return NULL;
    }

    /* NOTREACHED */
    return NULL;
} /* push_rindexed_value() */

/*=========================================================================*/
/*-------------------------------------------------------------------------*/
void m_indices_filter ( svalue_t *key
                      , svalue_t *data UNUSED
                      , void *extra /* is a svalue_t ** */ )

/* Filter function used by mapping:m_indices() to implement the
 * m_indices() efun. It is here take advantage of the inline expansion
 * of assign_svalue_no_free().
 *
 * <key> points to a key in a mapping, <extra> points to a svalue_t*
 * pointing to a storage place. *key is assigned to **extra, *extra is
 * incremented afterwards.
 */

{
#ifdef __MWERKS__
#    pragma unused(data)
#endif
    svalue_t **svpp = (svalue_t **)extra;

    assign_svalue_no_free( (*svpp)++, key );
}

/*-------------------------------------------------------------------------*/
static void m_values_filter ( svalue_t *key UNUSED
                            , svalue_t *data
                            , void *extra /* is a struct mvf_info * */ )

/* Filter function used by efun m_values().
 *
 * <data> points to a data entry in a mapping, <extra> points to
 * a struct mvf_info describing the amount of data to copy, and the
 * target place. The <data> is copied to where <extra> points and <*extra>
 * is updated.
 */

{
#ifdef __MWERKS__
#    pragma unused(key)
#endif
    struct mvf_info * vip = (struct mvf_info *)extra;

    assign_svalue_no_free( vip->svp++, data + vip->num);
}

/*-------------------------------------------------------------------------*/
static void m_unmake_filter ( svalue_t *key
                            , svalue_t *data
                            , void *extra)

/* Filter function used by efun unmkmapping().
 *
 * <key>/<data> point to key and data entry in a mapping, <extra> points to
 * a struct mvf_info describing the amount of data to copy, and the
 * target place. The <keu> and <data> is copied to where <extra> points
 * and <*extra> is updated.
 */

{
    struct mvf_info * vip = (struct mvf_info *)extra;
    int i;

    assign_svalue_no_free(vip->svp->u.vec->item + vip->num, key);
    for (i = 0; i < vip->width; i++)
        assign_svalue_no_free(vip->svp[i+1].u.vec->item + vip->num, data+i);
    vip->num++;
}

/*-------------------------------------------------------------------------*/
static program_t *
search_inherited (char *str, program_t *prg, int *offpnt)

/* Auxiliary function to efun replace_program(): check if program <str>
 * is inherited by <prg>. If yes, return the originating program and
 * store the (accumulated) variable and function offsets in offpnt[0]
 * and offpnt[1] resp.
 *
 * If the program is not found, return NULL.
 *
 * Nested inherits are handled in a depth search, the function recurses
 * for this.
 */

{
    program_t *tmp;
    int i;
#ifdef DEBUG
    char *ts;
#endif

#ifdef DEBUG
    ts = NULL;
    if (d_flag)
    {
        ts = time_stamp();
        debug_message("%s search_inherited started\n", ts);
        debug_message("%s searching for PRG(%s) in PRG(%s)\n"
                     , ts, str, prg->name);
        debug_message("%s num_inherited=%d\n", ts, prg->num_inherited);
    }
#endif

    /* Loop through all inherited programs, returning directly when
     * the name program was found.
     */
    for ( i = 0; i < prg->num_inherited; i++)
    {
#ifdef DEBUG
        if (d_flag)
        {
            debug_message("%s index %d:\n", ts, i);
            debug_message("%s checking PRG(%s)\n"
                         , ts, prg->inherit[i].prog->name);
        }
#endif
        /* Duplicate virtual inherits don't count */
        if ( prg->inherit[i].inherit_type & INHERIT_TYPE_DUPLICATE )
            continue;

        if ( strcmp(str, prg->inherit[i].prog->name ) == 0 )
        {
#ifdef DEBUG
            if (d_flag)
                debug_message("%s match found\n", ts);
#endif
            offpnt[0] = prg->inherit[i].variable_index_offset;
            offpnt[1] = prg->inherit[i].function_index_offset;
            return prg->inherit[i].prog;
        }
        else if ( NULL != (tmp = search_inherited(str, prg->inherit[i].prog,offpnt)) )
        {
#ifdef DEBUG
            if (d_flag)
                debug_message("%s deferred match found\n", ts);
#endif
            offpnt[0] += prg->inherit[i].variable_index_offset;
            offpnt[1] += prg->inherit[i].function_index_offset;
            return tmp;
        }
    }

#ifdef DEBUG
    if (d_flag)
        debug_message("%s search_inherited failed\n", ts);
#endif

    return NULL;
} /* search_inherited() */

/*-------------------------------------------------------------------------*/
static replace_ob_t *
retrieve_replace_program_entry (void)

/* Auxiliary function to efun replace_program(): test if a program
 * replacement is already scheduled for the current object. If yes,
 * return the pointer to the replace_ob struct, else return NULL.
 */

{
    replace_ob_t *r_ob;

    for (r_ob = obj_list_replace; r_ob; r_ob = r_ob->next)
    {
        if (r_ob->ob == current_object)
            return r_ob;
    }
    return NULL;
}

/*-------------------------------------------------------------------------*/
#ifdef DEBUG
static INLINE svalue_t *
find_value (int num)

/* Return the address of object-global variable number <num> in the
 * current variable block.
 *
 * <num> is the index of the variable in the current object's variable
 * array.
 */

{
    if (num >= current_object->prog->num_variables) {
        fatal("Illegal variable access %d(%d).\n",
            num, current_object->prog->num_variables);
    }
    return &current_variables[num];
}

#else

#define find_value(num) (&current_variables[(num)])

#endif

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
find_virtual_value (int num)

/* For the virtually inherited variable <num> (given as index within
 * the current object's variable block) return the address of the actual
 * variable.
 *
 * If the program for this variable was inherited more than one time,
 * this function returns the address of the corresponding variable svalue
 * of the very first inheritance. If the program was inherited just once,
 * this function is identical to find_value().
 *
 * TODO: It would be nicer if the driver would 'know' here which inherit
 * TODO:: to use, either by giving the inherit index in the code, or
 * TODO:: by putting a reference to the base instance in the struct
 * TODO:: inherit.
 */

{
    inherit_t *inheritp;
    program_t *progp;
    char *progpp; /* actually a program_t **, but some compilers... */

    /* Set inheritp to the inherited program which defines variable <num>
     */
    inheritp = current_prog->inherit;
    while
      (   inheritp->variable_index_offset + inheritp->prog->num_variables <= num
       || inheritp->variable_index_offset > num)
    {
        inheritp++;
    }

    /* Get the index of the variable within the inherited program.
     */
    num -= inheritp->variable_index_offset;

    /* Set inheritp to the first instance of this inherited program.
     * A cleaner, but slighly slower way to write the following segment
     * is: for (inheritp = current_object->prog_inherit
     *         ; inheritp->prog != progp
     *         ; inheritp++) NOOP;
     */
    progp = inheritp->prog;
    progpp = (char *)&current_object->prog->inherit->prog;
    while (*(program_t **)progpp != progp)
        progpp += sizeof(inherit_t);
    inheritp = (inherit_t *)
                 (((PTRTYPE)(progpp)) - offsetof(inherit_t, prog));

    /* Compute the actual variable address */

    num += inheritp->variable_index_offset;

#ifdef DEBUG
    if (!current_object->variables)
    {
        if (num)
            fatal("%s Fatal: find_virtual_value() on object %p '%s' "
                  "w/o variables, num %d\n"
                 , time_stamp(), current_object, current_object->name, num);
        else
            error("%s Error: find_virtual_value() on object %p '%s' "
                  "w/o variables, num %d\n"
                 , time_stamp(), current_object, current_object->name, num);
    }
#endif

    return &current_object->variables[num];
      /* TODO: Why not '&current_variables[num]'? */
} /* find_virtual_value() */

/*-------------------------------------------------------------------------*/
char *
add_slash (char *str)

/* Prepend a slash ('/') in front of string <str> and return an allocated
 * copy of the result.
 */

{
    char *tmp;

    tmp = xalloc(strlen(str)+2);
    if (tmp) {
        *tmp = '/';
        strcpy(tmp+1,str);
    }
    return tmp;
} /* add_slash() */

/*-------------------------------------------------------------------------*/
static void
bad_arg_pc (int arg, int instr, svalue_t *sp, bytecode_p pc)
  NORETURN;

static void
bad_arg_pc (int arg, int instr, svalue_t *sp, bytecode_p pc)

/* Argument number <arg> to instruction <instr> was "bad".
 * Set inter_sp/pc to <sp>/<pc> and raise a runtime error with
 * a descriptive message.
 *
 * TODO: It would be nice if the function would say why the argument was bad.
 * TODO:: In general, the should be a generic error function which is used
 * TODO:: even within The Large Switch - not this 'goto bad_arg_1;' crap.
 */

{
    ERRORF(("Bad argument %d to %s()\n", arg, get_f_name(instr)))
    /* NOTREACHED */
}

/*-------------------------------------------------------------------------*/
void
bad_efun_arg (int arg, int instr, svalue_t *sp)

/* Argument number <arg> to instruction <instr> was "bad".
 *
 * inter_sp is updated to <sp>, inter_pc is supposed to be correct (and
 * pointing just after the opcode).
 *
 * If <instr> is negative, it is used as an offset to inter_pc to find
 * the actual instruction code at <inter_pc>+<instr>.
 *
 * TODO: It would be nice if the function would say why the argument was bad.
 * TODO: This function depends on the order and values of F_ESCAPE,
 * TODO:: F_TEFUN and F_VEFUN.
 */

{
    inter_sp = sp;
    if (instr < 0)
    {
        /* Find and decode the actual instruction at the given offset */
        bytecode_p pc = inter_pc + instr;

        instr = *pc;
        if (instr <= F_VEFUN)
            instr = pc[1] | (instr << F_ESCAPE_BITS);
    }
    error("Bad argument %d to %s()\n", arg, get_f_name(instr));
}

/*-------------------------------------------------------------------------*/
void
bad_xefun_arg (int arg, svalue_t *sp)

/* Argument number <arg> to a xefun (fixed args) or tefun was "bad".
 *
 * inter_sp is updated to <sp>, inter_pc is supposed to be correct and
 * pointing just after the opcode.
 *
 * This function is also defined to bad_efun_vararg().
 */

{
    bad_efun_arg(arg, -2, sp);
}

/*-------------------------------------------------------------------------*/
void
bad_xefun_vararg (int arg, svalue_t *sp)

/* Argument number <arg> to a xefun (var. args) or vefun was "bad".
 *
 * inter_sp is updated to <sp>, inter_pc is supposed to be correct and
 * pointing just after the opcode.
 */

{
    bad_efun_arg(arg, -3, sp);
}

/*-------------------------------------------------------------------------*/
Bool
_privilege_violation (char *what, svalue_t *where, svalue_t *sp)

/* Call the mudlib to check for a privilege violation:
 *
 *   master->privilege_violation(what, current_object, where)
 *
 * where <what> describes the type of the violation, and <where> is the
 * data used in the violation. <sp> is the current stack setting.
 *
 * If the apply returns a positive number, the privilege is granted and
 * the function returns TRUE.
 * If the apply returns 0, the privilege is gently denied and the function
 * returns FALSE.
 * If the apply returns something else, or if the lfun doesn't exist,
 * an error is raised.
 *
 * If the current_object is the master or simul_efun object, this function
 * immediately returns TRUE.
 *
 * <inter_sp> is updated to <sp>, <inter_pc> is assumed to be correct.
 */

{
    svalue_t *svp;

    /* Trusted objects */
    if (current_object == master_ob) return MY_TRUE;
    if (current_object == simul_efun_object) return MY_TRUE;

    /* Setup and call the lfun */
    push_volatile_string(what);
    push_valid_ob(current_object);
    sp++;
    assign_svalue_no_free(sp, where);
    inter_sp = sp;
    svp = apply_master(STR_PRIVILEGE, 3);

    /* Is there a lfun to call? */
    if (!svp || svp->type != T_NUMBER || svp->u.number < 0)
    {
        inter_sp = sp-3;
        error("privilege violation: %s\n", what);
        /* TODO: Print full args and types */
    }

    /* Return the result */
    return svp->u.number > 0;
} /* _privilege_violation() */

/*-------------------------------------------------------------------------*/
Bool
privilege_violation4 ( char *what,    object_t *whom
                     , char *how_str, int how_num
                     , svalue_t *sp)

/* Call the mudlib to check for a privilege violation:
 *
 *   !whom:
 *       master->privilege_violation(what, current_object, how_str, how_num)
 *   whom && how_str:
 *       master->privilege_violation(what, current_object, whom, how_str)
 *   whom && !how_str:
 *       master->privilege_violation(what, current_object, whom, how_num)
 *
 * where <what> describes the type of the violation, and <whom>/<how_str>/
 * <how_num> are data used in the violation. <sp> is the current stack setting.
 *
 * If the apply returns a positive number, the privilege is granted and
 * the function returns TRUE.
 * If the apply returns 0, the privilege is gently denied and the function
 * returns FALSE.
 * If the apply returns something else, or if the lfun doesn't exist,
 * an error is raised.
 *
 * If the current_object is the master or simul_efun object, this function
 * immediately returns TRUE.
 *
 * If the lfun doesn't exist, or returns anything else but a positive
 * number, an error is raised.
 *
 * <inter_sp> is updated to <sp>, <inter_pc> is assumed to be correct.
 */

{
    svalue_t *svp;

    /* Trust these objects */
    if (current_object == master_ob) return MY_TRUE;
    if (current_object == simul_efun_object) return MY_TRUE;

    /* Set up the lfun call */

    push_volatile_string(what);
    push_valid_ob(current_object);
    if (!whom)
    {
        if (how_str)
            push_volatile_string(how_str);
        else
            push_number(0);
        push_number(how_num);
    }
    else
    {
        push_object(whom);
        if (how_str)
            push_volatile_string(how_str);
        else
            push_number(how_num);
    }
    inter_sp = sp;
    svp = apply_master(STR_PRIVILEGE, 4);

    /* Was it the proper lfun to call? */
    if (!svp || svp->type != T_NUMBER || svp->u.number < 0)
    {
        inter_sp = sp-4;
        error("privilege violation : %s\n", what);
        /* TODO: Print full args and types */
    }

    /* Return the result */
    return svp->u.number > 0;
}

/*-------------------------------------------------------------------------*/
#define privilege_violation(what, where) (\
        inter_pc = pc,\
        _privilege_violation(what, where, sp)\
)
  /* Just as _privilege_violation(), just that it automatically uses
   * local <sp> and <pc> and updates the inter_ variables from them.
   */

/*-------------------------------------------------------------------------*/
static Bool
strpref (const char *p, const char *s)

/* Return TRUE if string <s> begins with string <p>, FALSE if not.
 * Used by the function trace_test().
 */

{
    while (*p)
        if (*p++ != *s++)
            return MY_FALSE;
    return MY_TRUE;
}

/*-------------------------------------------------------------------------*/
static Bool
trace_test (int b)

/* Test if tracing of the given option(s) <b> is allowed right now.
 * The function tests the options <b> against what the current interactive
 * requested, and if a trace_prefix is given, if the prefix matches the
 * name of the current object.
 */

{
    interactive_t *ip;

    return current_interactive
        && O_SET_INTERACTIVE(ip, current_interactive)
        && (ip->trace_level & b)
        && (ip->trace_prefix == NULL
            || (current_object
                && strpref(ip->trace_prefix, current_object->name)))
    ;
} /* trace_test() */

/*-------------------------------------------------------------------------*/
static void
do_trace (char *msg, char *fname, char *post)

/* If not in a heartbeat, or if heartbeat tracing is allowed, generate
 * a tracemessage of the form '<tracedepth> <msg> <objname> <fname> <post>'
 * and print it to the player using add_message().
 *
 * Don't do anything if the current command_giver is not interactive.
 *
 * <obj_name> is filled in only if TRACE_OBJNAME is requested, else
 * the empty string is used.
 */

{
    char buf[10000];
    char *objname;

    if (!TRACEHB)
        return;
    objname = TRACETST(TRACE_OBJNAME)
              ? (current_object && current_object->name ? current_object->name
                                                        : "?")
              : "";
    sprintf(buf, "*** %d %*s %s %s %s%s", tracedepth, tracedepth, ""
               , msg, objname, fname, post);
    add_message(buf);
#ifdef DEBUG
    add_message(message_flush);
#endif
} /* do_trace() */

/*-------------------------------------------------------------------------*/
static void
do_trace_call (fun_hdr_p funstart, Bool is_lambda)

/* Trace a call to the function starting at <funstart>.
 */

{

    if (!++traceing_recursion || !TRACE_IS_INTERACTIVE()) /* Do not recurse! */
    {
        int save_var_ix_offset = variable_index_offset;
          /* TODO: Might be clobbered, but where? */

        /* Trace the function itself */
        if (is_lambda)
            do_trace("Call direct ", "lambda-closure", " ");
        else
        {
            char *name;

            memcpy(&name, FUNCTION_NAMEP(funstart), sizeof name);
            do_trace("Call direct ", name, " ");
        }

        /* If requested, also trace the arguments */
        if (TRACEHB)
        {
            if (TRACETST(TRACE_ARGS))
            {
                int i;
                svalue_t *svp;

                add_message(" with %d arguments: "
                           , FUNCTION_NUM_ARGS(funstart) & 0x7f);
                svp = inter_fp;
                for (i = (FUNCTION_NUM_ARGS(funstart) & 0x7f); --i >= 0; )
                {
                    print_svalue(svp++);
                    add_message(" ");
                }
            }
            add_message("\n");
        }
        variable_index_offset = save_var_ix_offset;
    }
    traceing_recursion--;
} /* do_trace_call() */

/*-------------------------------------------------------------------------*/
static void
do_trace_return (svalue_t *sp)

/* Trace the return from a function call; <sp> is the current stack pointer,
 * pointing to the result.
 */

{
    tracedepth--; /* We leave this level */

    if (!++traceing_recursion || !TRACE_IS_INTERACTIVE())
    {
        if (trace_test(TRACE_RETURN))
        {
            inter_sp = sp;
            do_trace("Return", "", "");
            if (TRACEHB) {
                if (TRACETST(TRACE_ARGS)) {
                    add_message(" with value: ");
                    print_svalue(sp);
                }
                add_message("\n");
            }
        }
    }
    traceing_recursion--;

    /* If requested, (re)activate TRACE_EXEC */
    SET_TRACE_EXEC;
}

/*-------------------------------------------------------------------------*/
struct longjump_s *
push_error_context (svalue_t *sp, bytecode_t catch_inst)

/* Create a catch recovery context, using <sp> as the stackpointer to save,
 * link it into the recovery stack and return the longjmp context struct.
 * The actual type of the catch context is chosen on the actual
 * <catch_inst>ruction (CATCH or CATCH_NO_LOG).
 */

{
    struct catch_context *p;

    p = xalloc (sizeof *p);
    p->save_sp = sp;
    p->save_csp = csp;
    p->save_command_giver = command_giver;
    p->recovery_info.rt.last = rt_context;
    p->recovery_info.rt.type
      = (catch_inst == F_CATCH) ? ERROR_RECOVERY_CATCH
                                : ERROR_RECOVERY_CATCH_NOLOG;
    rt_context = (rt_context_t *)&p->recovery_info;
    return &p->recovery_info.con;
} /* push_error_context() */

/*-------------------------------------------------------------------------*/
void
pop_error_context (void)

/* Pop and discard the top entry in the error recovery stack, assuming
 * that it's a catch recovery entry.
 *
 * This function is called when the catch() completed normally.
 */

{
    struct catch_context *p;

    p = (struct catch_context *)rt_context;

#ifdef DEBUG
    if (!ERROR_RECOVERY_CAUGHT(p->recovery_info.rt.type))
        fatal("Catch: runtime stack underflow");
    if (csp != p->save_csp-1)
        fatal("Catch: Lost track of csp");
    /* Note: the command_giver might have changed (with the exec() efun),
     * so testing it is of no use.
     */
#endif
    rt_context = p->recovery_info.rt.last;
    xfree(p);
} /* pop_error_context() */

/*-------------------------------------------------------------------------*/
svalue_t *
pull_error_context (svalue_t *sp)

/* Restore the context saved by a catch() after a throw() or runtime error
 * occured. <sp> is the current stackpointer and is used to pop the elements
 * pushed since the catch().
 *
 * The function pops the topmost recovery entry, which must be the catch
 * recovery entry, restores the important global variables and returns
 * the saved stack pointer.
 */

{
    struct catch_context *p;
    struct control_stack *csp2;

    p = (struct catch_context *)rt_context;

    if (!ERROR_RECOVERY_CAUGHT(p->recovery_info.rt.type))
        fatal("Catch: runtime stack underflow");

    /* If there was a call_other() or similar, previous_ob and current_object
     * must be restored. For this, find the control frame where the call
     * occured and get the proper values from there.
     */
    csp2 = p->save_csp;
    while (++csp2 <= csp)
    {
        if (csp2->extern_call)
        {
            previous_ob = csp2->prev_ob;
            current_object = csp2->ob;
            break;
        }
    }

    /* Restore the global variables and the evaluator stack */
    csp = p->save_csp;
    pop_n_elems(sp - p->save_sp);
    command_giver = p->save_command_giver;

    /* Remove the context from the context stack */
    rt_context = p->recovery_info.rt.last;
    xfree(p);

    return sp;
} /* pull_error_context() */

/*-------------------------------------------------------------------------*/
void
push_control_stack ( svalue_t *sp
                   , bytecode_p     pc
                   , svalue_t *fp
                   )

/* Push the current execution context onto the control stack.
 * On stack overflow, raise a 'too deep recursion' error.
 */

{

    /* Check for overflow */
    if (csp >= &CONTROL_STACK[MAX_USER_TRACE-1])
    {
        if (!num_error || csp == &CONTROL_STACK[MAX_TRACE-1])
        {
            ERROR("Too deep recursion.\n")
        }
    }

    /* Move csp to the next entry and fill it with the current context
     */
    csp++;

    /* csp->funstart  has to be set later, it is used only for tracebacks. */
    csp->fp = fp;
    csp->prog = current_prog;
    csp->lambda = current_lambda; put_number(&current_lambda, 0);
    /* csp->extern_call = MY_FALSE; It is set by eval_instruction() */
    csp->catch_call = MY_FALSE;
    csp->pc = pc;
    csp->function_index_offset = function_index_offset;
    csp->current_variables = current_variables;
    csp->break_sp = break_sp;
} /* push_control_stack() */

/*-------------------------------------------------------------------------*/
void
pop_control_stack (void)

/* Pop the last entry from the control stack and restore the execution
 * context from it - except for extern_call of which the old value will
 * be used immediately after the pop.
 */

{
#ifdef DEBUG
    if (csp < CONTROL_STACK)
        fatal("Popped out of the control stack");
#endif

    if ( NULL != (current_prog = csp->prog) ) /* is 0 when we reach the bottom */
        current_strings = current_prog->strings;
    if (current_lambda.type == T_CLOSURE)
        free_closure(&current_lambda);
    current_lambda = csp->lambda;
    inter_pc = csp->pc;
    inter_fp = csp->fp;
    function_index_offset = csp->function_index_offset;
    current_variables     = csp->current_variables;
    break_sp = csp->break_sp;
    csp--;
} /* pop_control_stack() */


/*-------------------------------------------------------------------------*/
static INLINE funflag_t
setup_new_frame1 (int fx, int fun_ix_offs, int var_ix_offs)

/* Setup current_prog, function_ and variable_index_offset for a call
 * to function index <fx> in the current program.
 *
 * <fun_ix_offs> and <var_ix_offs> are offsets to be added to the
 * functions given offsets - this is necessary when <fx> is given relative
 * to some inherited program and needs to be adjusted for the topmost
 * program.
 *
 * Return the 'flags' for the function.
 */

{
    program_t *progp;
    funflag_t flags;

    progp = current_prog;
    flags = progp->functions[fx];

    /* Handle a cross-define.
     * This is a rather rare occasion and usually happens only with functions
     * like heart_beat() which are called by function index and not by name.
     * This index, determined at compile time, might point to the
     * cross-defined function entry.
     */
    if (flags & NAME_CROSS_DEFINED)
    {
        fx += CROSSDEF_NAME_OFFSET(flags);
        flags = progp->functions[fx];
    }

    /* If the function is inherited, find the real function definition
     * and adjust the offsets to point to its code and variables.
     * This is an iteration walking along the inherit chain.
     */
    fun_ix_offs += fx;
    while(flags & NAME_INHERITED)
    {
        inherit_t *inheritp;

        inheritp = &progp->inherit[flags & INHERIT_MASK];
        progp = inheritp->prog;
        fx -= inheritp->function_index_offset;
        var_ix_offs += inheritp->variable_index_offset;
          /* Remember here that function offset is relative to current_prog,
           * but variable_offset is relative to current_object.
           */
        flags = progp->functions[fx];
    }
    /* fx is now the 'pure' function index without any offsets */

    /* Setup the variables and return */
    current_prog = progp;
    function_index_offset = fun_ix_offs - fx;
    variable_index_offset = var_ix_offs;

    return flags;
} /* setup_new_frame1() */

/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
setup_new_frame2 (fun_hdr_p funstart, svalue_t *sp
                 , Bool allowRefs, Bool is_lambda)

/* Before calling the function at <funstart>, massage the data on the
 * stack ending at <sp> to match the formal argumentlist of the function
 * (excessive args are removed, missing args are provided as 0),
 * and allocate the local variables on the stack.
 *
 * <is_lambda> has to be TRUE if the function is a lambda closure.
 * This information is needed for proper tracing.
 *
 * If <allowRefs> is TRUE, references may be passed as extended varargs
 * ('(varargs mixed *)'). Currently this is used only for simul efuns.
 * TODO: Investigate if holding references in arrays is really such a
 * TODO:: a bad thing. Maybe it's just an implementation issue.
 *
 * csp->num_local_variables is supposed to hold the number of actual
 * arguments on the stack.
 *
 * Result is the new stackpointer, the framepointer <inter_fp>,
 * csp->num_local_variables and <break_sp> are set up.
 */

{
    int i;        /* Difference between number of formal and actual args;
                   * Number of (uninitialized) local variables
                   */
    int num_arg;  /* Number of formal args */

    /* Setup the frame pointer */
    inter_fp = sp - csp->num_local_variables + 1;

    /* (Re)move excessive arguments.
     * TODO: This code uses that bit7 makes num_arg negative.
     */
    num_arg = FUNCTION_NUM_ARGS(funstart);
    if ((i = csp->num_local_variables - num_arg) > 0)
    {
        /* More actual than formal args, or the function has
         * a 'varargs' argument.
         */

        if (num_arg < 0)
        {
            /* Function has a 'varargs' argument */

            num_arg &= 0x7f;

            if ((i = csp->num_local_variables - num_arg + 1) < 0)
            {
                /* More formal than actual parameters. */

                csp->num_local_variables = num_arg;

                /* First, fill in zero for the rest... */
                do {
                    *++sp = const0;
                } while (++i);

                /* ...and an empty array for the varargs portion */
                ++sp;
                put_array(sp, allocate_uninit_array(0));
            }
            else
            {
                /* More actual than formal parameters */

                vector_t *v;

                csp->num_local_variables = num_arg;

                /* Move the extra args into an array and put that
                 * onto the stack
                 */
                v = allocate_uninit_array(i);
                while (--i >= 0)
                {
                    if (!allowRefs && sp->type == T_LVALUE)
                        num_arg = -1; /* mark error condition */
                    v->item[i] = *sp--;
                }

                ++sp;
                put_array(sp, v);

                if (num_arg < 0)
                {
                    bytecode_p pc = funstart; /* for the ERROR() macro */

                    ERROR("Varargs argument passed by reference.\n");
                }
            }
        }
        else
        {
            /* Function takes a fixed number of arguments */

            /* Pop the extraneous args */
            do {
                free_svalue(sp--);
                csp->num_local_variables--;
            } while(--i);

        } /* if(varargs or fixedargs) */

        /* Clear the local variables */

        if ( 0 != (i = FUNCTION_NUM_VARS(funstart)) )
        {
            csp->num_local_variables += i;
            do {
                *++sp = const0;
            } while (--i);
        }
    }
    else
    {
        /* Enough or too little arguments supplied to a fixed-args
         * function: initialize the missing args and the locals
         * in one swoop.
         */

        if ( 0 != (i = FUNCTION_NUM_VARS(funstart) - i) )
        {
            csp->num_local_variables += i;
            do {
                *++sp = const0;
            } while (--i);
        }
    }

    /* Check for stack overflow. Since the actual stack size is
     * larger than EVALUATOR_STACK_SIZE, this check at the
     * end should be sufficient. If not, stack_overflow() will
     * generate a fatal error and we have to resize.
     */
    if ( sp >= &VALUE_STACK[EVALUATOR_STACK_SIZE] )
        stack_overflow(sp, csp->fp, funstart);

    /* Count the call depth for traces and handle tracing */
    tracedepth++;
    if (TRACEP(TRACE_CALL) && TRACE_IS_INTERACTIVE())
    {
      inter_sp = sp;
      do_trace_call(funstart, is_lambda);
    }


    /* Initialize the break stack, pointing to the entry above
     * the first available svalue.
     */
    break_sp = (bytecode_p *)&sp[1].u.string;

    return sp;
} /* setup_new_frame2() */

/*-------------------------------------------------------------------------*/
static funflag_t
setup_new_frame (int fx)

/* Setup a call for function <fx> in the current program.
 * Result are the flags for the function.
 */

{
    funflag_t flags;

    flags = setup_new_frame1(fx, 0, 0);
    inter_sp = setup_new_frame2(
      current_prog->program + (flags & FUNSTART_MASK), inter_sp
      , MY_FALSE, MY_FALSE
    );
#ifdef DEBUG
    if (!current_object->variables && variable_index_offset)
        fatal("%s Fatal: new frame for object %p '%s' w/o variables, "
              "but offset %d\n"
             , time_stamp(), current_object, current_object->name
             , variable_index_offset);
#endif
    current_variables = current_object->variables;
    if (current_variables)
        current_variables += variable_index_offset;
    current_strings = current_prog->strings;
    return flags;
}

/*-------------------------------------------------------------------------*/
void
reset_machine (Bool first)

/* Reset the virtual machine. <first> is true on the very first call
 * (the cold boot, so to speak). Subsequent calls pass <first> as false
 * and this way make sure that all values currently on the stack
 * are properly removed.
 */

{
    traceing_recursion = -1;
    if (first)
    {
        csp = CONTROL_STACK - 1;
        inter_sp = VALUE_STACK - 1;
        tracedepth = 0;
        put_number(&current_lambda, 0);
    }
    else
    {
        inter_sp = _pop_n_elems(inter_sp - VALUE_STACK + 1, inter_sp);
        if (current_lambda.type == T_CLOSURE)
            free_closure(&current_lambda);
        put_number(&current_lambda, 0);
        while (csp >= CONTROL_STACK)
        {
            if (csp->lambda.type == T_CLOSURE)
                free_closure(&csp->lambda);
            csp--;
        }
    }
} /* reset_machine() */

/*-------------------------------------------------------------------------*/
#ifdef DEBUG
int
check_state (void)

/* Check the virtual machine for consistency. Return 0 when it is, else
 * print a debug message and return an error code.
 *
 * As this function can be costly, it is by default not called from
 * the backend loop.
 */

{
    int rc;

    rc = 0;

    if (rt_context->type != ERROR_RECOVERY_BACKEND) {
        debug_message("%s rt_context stack inconsistent: type %d instead of %d\n"
                     , time_stamp(), rt_context->type, ERROR_RECOVERY_BACKEND);
        printf("%s rt_context stack inconsistent: type %d instead of %d\n"
              , time_stamp(), rt_context->type, ERROR_RECOVERY_BACKEND);
        if (!rc) rc = 1;
    }
    if (csp != CONTROL_STACK - 1) {
        debug_message("%s csp inconsistent: %p instead of %p\n"
                     , time_stamp(), csp, CONTROL_STACK-1);
        printf("%s csp inconsistent: %p instead of %p\n"
              , time_stamp(), csp, CONTROL_STACK-1);
        if (!rc) rc = 2;
    }
    if (inter_sp != VALUE_STACK - 1) {
        debug_message("%s sp inconsistent: %p instead of %p\n"
                     , time_stamp(), inter_sp, VALUE_STACK - 1);
        printf("%s sp inconsistent: %p instead of %p\n"
              , time_stamp(), inter_sp, VALUE_STACK - 1);
        if (!rc) rc = 3;
    }

    return rc;
}
#endif

/*-------------------------------------------------------------------------*/
void
free_interpreter_temporaries (void)

/* Free all svalue the interpreter holds in global variables.
 * Usually the values are freed whenever a new value is stored, but
 * this function allows e.g. the garbage collector to free them all
 * at once.
#ifdef TRACE_CODE
 * The function also cleans out all destructed objects from the
 * instruction trace.
#endif
 */

{
    free_protector_svalue(&last_indexing_protector);
    last_indexing_protector.type = T_NUMBER;
    free_svalue(&indexing_quickfix);
    indexing_quickfix.type = T_NUMBER;
    free_svalue(&apply_return_value);
    apply_return_value.type = T_NUMBER;

#ifdef TRACE_CODE
    {
        int i;

        for (i = TOTAL_TRACE_LENGTH; --i >= 0; )
        {
            object_t *ob;

            if (NULL != (ob = previous_objects[i])
             && ob->flags & O_DESTRUCTED
               )
            {
                free_object(ob, "free_interpreter_temporaries");
                previous_objects[i] = NULL;
                previous_instruction[i] = 0;
            }
        }
    }
#endif

} /* free_interpreter_temporaries() */

/*-------------------------------------------------------------------------*/
void
remove_object_from_stack (object_t *ob)

/* Object <ob> was/will be destructed, so remove all references from
 * to it from the stack, including references through closures.
 */

{
    svalue_t *svp;

    for (svp = VALUE_STACK; svp <= inter_sp; svp++)
    {
        if (get_object_ref(svp) == ob)
        {
            free_svalue(svp);
            put_number(svp, 0);
        }
    } /* foreach svp in stack */
} /* remove_object_from_stack() */

/*-------------------------------------------------------------------------*/
Bool
eval_instruction (bytecode_p first_instruction
                 , svalue_t *initial_sp)

/* Evaluate the code starting at <first_instruction>, using <inital_sp>
 * as the stack pointer. All other variables like current_prog must be
 * setup before the call. The function will return upon encountering
 * a F_RETURN instruction for which .extern_call or .catch_call is true,
 * or upon encountering a F_END_CATCH instruction.
 *
 * The result will state the reason for returning: FALSE for F_RETURN,
 * and TRUE for F_END_CATCH.
 *
 * This also means that for every intra-object call eval_instruction()
 * is called recursively.
 *
 * There must not be destructed objects on the stack. The destruct_object()
 * function will automatically remove all occurences. The effect is that
 * all called efuns know that they won't have destructed objects as
 * arguments.
 *
 * All instructions/functions callable from LPC must return a value or be
 * declared void. This does not apply to internal control codes like F_JUMP.
 */

{
    register bytecode_p     pc;  /* Current program pointer */
    register svalue_t *fp;  /* Current frame pointer */
    register svalue_t *sp;  /* Current stack pointer */
      /* For speed reasons, these variables shadow their global counterparts,
       * allowing more optimisations.
       * gcc feels better about setjmp() when variables are declared register.
       * Still we might get 'variable foo might be clobbered' warnings, but
       * declaring them as volatile would degrade optimization, so we don't.
       */
    int num_arg;      /* Number of arguments given to the current instr */
    int instruction;  /* The current instruction code */
#ifdef DEBUG
    svalue_t *expected_stack; /* Expected stack at the instr end */
#endif

    /* Handy macros. Some of these are redefined later for multi-
     * byte instructions.
     */
#   ifdef DEBUG
#       define GET_NUM_ARG \
            if (num_arg != GET_UINT8(pc-1)) {\
                fatal("Argument count error for %s: %d vs. %d.\n", get_f_name(instruction), num_arg, GET_UINT8(pc-1));}
        /* The macro catches two faults: getting num_arg for instructions
         * which don't take arguments, and getting num_arg after incrementing
         * the pc too far.
         */
#   else /* DEBUG */
#       define GET_NUM_ARG num_arg = GET_UINT8(pc); inter_pc = ++pc;
#   endif /* DEBUG */
      /* Get and/or test the number of arguments.
       */

#   define TYPE_TEST1(arg, t) if ( (arg)->type != t ) goto bad_arg_1;
#   define TYPE_TEST2(arg, t) if ( (arg)->type != t ) goto bad_arg_2;
#   define TYPE_TEST3(arg, t) if ( (arg)->type != t ) goto bad_arg_3;
#   define TYPE_TEST4(arg, t) if ( (arg)->type != t ) goto bad_arg_4;
      /* Test the type of a certain argument.
       */

#   ifdef MARK
#        define CASE(x) case (x): MARK(x);
#   else
#        define CASE(x) case (x):
#   endif
      /* Macro to build the case: labels for the evaluator switch.
       * 'MARK' adds profiling support.
       */

    /* Setup the variables.
     * The next F_RETURN at this level will return out of eval_instruction().
     */
    if (!csp->catch_call)
        csp->extern_call = MY_TRUE;
    sp = initial_sp;
    pc = first_instruction;
    fp = inter_fp;
    SET_TRACE_EXEC;

    /* ------ The evaluation loop ------ */

again:
    /* Get the next instruction and increment the pc */

    instruction = LOAD_CODE(pc);
      /* If this a xcode, the second byte will be added later */

#if 0
    printf("DEBUG: %p (%p): %d %s\n", pc-1, sp, instruction, get_f_name(instruction));
#endif

#   ifdef TRACE_CODE
        /* Store some vitals in the trace buffer */

#       if TOTAL_TRACE_LENGTH & TOTAL_TRACE_LENGTH-1
            if (++last == TOTAL_TRACE_LENGTH)
                last = 0;
#       else
            last = (last+1) & (TOTAL_TRACE_LENGTH-1);
#       endif
        previous_instruction[last] = instruction;
        previous_pc[last] = pc-1;
        stack_size[last] = sp - fp - csp->num_local_variables;
        abs_stack_size[last] = sp - VALUE_STACK;
        if (previous_objects[last])
        {
            /* Need to free the previously stored object */
            free_object(previous_objects[last], "TRACE_CODE");
        }
        previous_objects[last] = ref_object(current_object, "TRACE_CODE");
        previous_programs[last] = current_prog;
#   endif  /* ifdef TRACE_CODE */

#   ifdef MALLOC_LPC_TRACE
        inter_pc = pc;
#   endif

#   ifdef OPCPROF
        opcount[instruction]++;
#   endif

    /* If requested, trace the instruction.
     * Print the name of the instruction, but guard against recursions.
     */
    if (trace_exec_active && TRACE_EXEC_P && TRACE_IS_INTERACTIVE())
    {
        if (!++traceing_recursion)
        {
            inter_sp = sp;
            do_trace("Exec ", get_f_name(instruction), "\n");
            instruction = EXTRACT_UCHAR(pc-1);
        }
        traceing_recursion--;
    }

    /* Test the evaluation cost.
     * eval_cost < 0 signify a wrap-around - unlikely, but with these crazy
     * wizards everything is possible.
     */
    ++eval_cost;
    if (max_eval_cost && (eval_cost >= max_eval_cost || eval_cost < 0))
    {
        rt_context_t * context;

        /* Evaluation too long. Restore some globals and throw
         * an error.
         */

        printf("%s eval_cost too big %ld\n", time_stamp(), (long)eval_cost);

        assign_eval_cost();

        /* If the error isn't caught, reset the eval costs */
        for (context = rt_context
            ; !ERROR_RECOVERY_CONTEXT(context->type)
            ; context = context->last
            ) NOOP;
        if (context->type <= ERROR_RECOVERY_BACKEND)
        {
            CLEAR_EVAL_COST;
            RESET_LIMITS;
        }

        inter_pc = pc;
        inter_fp = fp;
        ERROR("Too long evaluation. Execution aborted.\n")
    }

#if defined(DEBUG)

    /* Get the expected number of arguments and determined the expected
     * stack setting.
     */
    if (instrs[instruction].min_arg != instrs[instruction].max_arg)
    {
        num_arg = GET_UINT8(pc);
        pc++;
    }
    else
    {
        /* Safety measure. It is supposed that the evaluator knows
         * the number of arguments.
         */
        num_arg = -1;
    }

    if (num_arg != -1)
    {
        expected_stack = sp - num_arg +
            ( instrs[instruction].ret_type == TYPE_VOID ? 0 : 1 );
    }
    else
    {
        expected_stack = NULL;
    }
#endif /* DEBUG */

    /* The monster switch to execute the instruction.
     * The order of the cases is held (mostly) in the order
     * the instructions appear in func_spec.
     */
    inter_sp = sp;
    inter_pc = pc;
      /* TODO: This continual update is crude, but circumvents a lot
       * TODO:: of situations where an error is thrown but inter_sp
       * TODO:: is invalid (heck, every assign_svalue() could cause that). In
       * TODO:: the long run, we should do this only for efuns (which are by
       * TODO:: then hopefully all tabled).
       */
    switch(instruction)
    {
    default:
        fatal("Undefined instruction %s (%d)\n", get_f_name(instruction),
              instruction);
        /* NOTREACHED */
bad_arg_1: bad_arg_pc(1, instruction, sp, pc);
bad_arg_2: bad_arg_pc(2, instruction, sp, pc);
bad_arg_3: bad_arg_pc(3, instruction, sp, pc);
bad_arg_4: bad_arg_pc(4, instruction, sp, pc);
bad_left:  ERRORF(("Bad left type to %s.\n",  get_f_name(instruction)))
bad_right: ERRORF(("Bad right type to %s.\n", get_f_name(instruction)))
        /* NOTREACHED */
        return MY_FALSE; /* hint for data flow analysis */

#ifdef F_ILLEGAL
    case 255:
    CASE(F_ILLEGAL);                /* --- illegal             --- */
        inter_pc = pc;
        fatal("Illegal instruction\n");
        /* NOTREACHED */
#endif /* F_ILLEGAL */

    CASE(F_TEFUN);                  /* --- tefun <code>        --- */
    {
        /* Call the tabled efun 0x100 + <code>, where <code> is
         * a uint8 in the range 0x80..0xff.
         * The efun takes a fixed number of arguments which are
         * on the stack.
         */

        int code;

        code = LOAD_UINT8(pc);
#ifdef TRACE_CODE
        previous_instruction[last] = code + 0x100;
#endif
#ifdef OPCPROF
        opcount[code+0x100]++;
#endif
        inter_sp = sp;
        inter_pc = pc;
        ASSIGN_EVAL_COST
        sp = (*efun_table[code-128])(sp);
        break;
    }

    CASE(F_VEFUN);                 /* --- vefun <code> <nargs> --- */
    {
        /* Call the tabled efun 0x200 + <code>, where <code> is
         * a uint8 in the range 0x00..0x7f, with uint8 <nargs>
         * arguments on the stack.
         */
        int code;
        int numarg;

        code = LOAD_UINT8(pc);
        numarg = LOAD_UINT8(pc);
#ifdef TRACE_CODE
        previous_instruction[last] = code + 0x200;
#endif
#ifdef OPCPROF
        opcount[code+0x200]++;
#endif
        inter_sp = sp;
        inter_pc = pc;
        ASSIGN_EVAL_COST
        sp = (*vefun_table[code])(sp, numarg);
        break;
    }

    /* F_ESCAPE contains a sub-switch() and is handled at the
     * end.
     */

    /* --- Predefined functions with counterparts in LPC --- */

    CASE(F_IDENTIFIER);             /* --- identifier <var_ix> --- */
    {
        /* Push value of object variable <var_ix>.
         * It is possible that it is a variable that points to
         * a destructed object. In that case, it has to be replaced by 0.
         *
         * <var_ix> is a uint8.
         */
        svalue_t * val = find_value((int)(LOAD_UINT8(pc)));
        sp++;
        assign_checked_svalue_no_free(sp, val, sp, pc);
        break;
    }

    CASE(F_STRING);                /* --- string <ix>          --- */
    {
        /* Push the string current_strings[<ix>] onto the stack,
         * <ix> being a (16-Bit) ushort, stored low byte first.
         * See also the F_CSTRINGx functions.
         */
        unsigned short string_number;

        LOAD_SHORT(string_number, pc);
        push_shared_string(current_strings[string_number]);
        break;
    }

    CASE(F_CSTRING3);               /* --- cstring3 <ix>       --- */
    {
        /* Push the string current_strings[0x3<ix>] onto the stack.
         * <ix> is a 8-Bit uint.
         */
        push_shared_string(current_strings[LOAD_UINT8(pc)+0x300]);
        break;
    }

    CASE(F_CSTRING2);               /* --- cstring2 <ix>       --- */
    {
        /* Push the string current_strings[0x2<ix>] onto the stack.
         * <ix> is a 8-Bit uint.
         */
        push_shared_string(current_strings[LOAD_UINT8(pc)+0x200]);
        break;
    }

    CASE(F_CSTRING1);               /* --- cstring1 <ix>       --- */
    {
        /* Push the string current_strings[0x1<ix>] onto the stack.
         * <ix> is a 8-Bit uint.
         */
        push_shared_string(current_strings[LOAD_UINT8(pc)+0x100]);
        break;
    }

    CASE(F_CSTRING0);               /* --- cstring0 <ix>       --- */
    {
        /* Push the string current_strings[0x0<ix>] onto the stack.
         * <ix> is a 8-Bit uint.
         */
        push_shared_string(current_strings[LOAD_UINT8(pc)]);
        break;
    }

    CASE(F_NUMBER);                 /* --- number <num>        --- */
    {
        /* Push the number <num> onto the stack.
         * <num> is a p_int stored in the host format.
         * See also the F_CONSTx functions.
         * TODO: It should be rewritten to use the LOAD_ macros (but
         * TODO:: then the compiler needs to use them, too.
         */
        sp++;
        sp->type = T_NUMBER;
        memcpy(&sp->u.number, pc, sizeof sp->u.number);
        pc += sizeof sp->u.number;
        break;
    }

    CASE(F_NNUMBER);                /* --- nnumber <num>       --- */
    {
        /* Push the number -<num> onto the stack.
         * <num> is a p_int stored in the host format.
         * See also the F_CONSTx functions.
         * TODO: It should be rewritten to use the LOAD_ macros (but
         * TODO:: then the compiler needs to use them, too.
         */
        sp++;
        sp->type = T_NUMBER;
        memcpy(&sp->u.number, pc, sizeof sp->u.number);
        pc += sizeof sp->u.number;
        sp->u.number = - sp->u.number;
        break;
    }

    CASE(F_CONST0);                 /* --- const0              --- */
        /* Push the number 0 onto the stack.
         */
        push_number(0);
        break;

    CASE(F_CONST1);                 /* --- const1              --- */
        /* Push the number 1 onto the stack.
         */
        push_number(1);
        break;

    CASE(F_NCONST1);                /* --- nconst1             --- */
        /* Push the number -1 onto the stack.
         */
        push_number(-1);
        break;

    CASE(F_CLIT);                   /* --- clit <num>          --- */
    {
        /* Push the number <num> onto the stack.
         * <num> is a 8-Bit uint.
         */
        push_number((p_int)LOAD_UINT8(pc));
        break;
    }

    CASE(F_NCLIT);                  /* --- nclit <num>         --- */
    {
        /* Push the number -<num> onto the stack.
         * <num> is a 8-Bit uint.
         */
        push_number(-(p_int)LOAD_UINT8(pc));
        break;
    }

    CASE(F_FLOAT);                  /* --- float <mant> <exp>  --- */
    {
        /* Push the float build from <mant> (4 bytes) and <exp> (2 bytes)
         * onto the stack. The binary format is the one determined
         * by STORE_DOUBLE in datatypes.h
         * TODO: This code makes heavy assumptions about data sizes and
         * TODO:: layout. E.g. there need not be a 16-Bit integral type
         * TODO:: available.
         * TODO: It should be rewritten to use the LOAD_ macros (but
         * TODO:: then the compiler needs to use them, too.
         */

#if SIZEOF_CHAR_P == 4
        sp++;
        sp->type = T_FLOAT;

        memcpy((char *)&sp->u.mantissa, pc, sizeof(sp->u.mantissa));
        memcpy((char *)&sp->x.exponent, pc + sizeof(sp->u.mantissa), sizeof(sp->x.exponent));
        pc += sizeof(sp->u.mantissa)+sizeof(sp->x.exponent);
#else
        int32 mantissa;
        /* TODO: int16 */ short exponent;

        sp++;
        sp->type = T_FLOAT;

        memcpy((char *)&mantissa, pc, sizeof(mantissa));
        sp->u.mantissa = mantissa;

        memcpy((char *)&exponent, pc + sizeof(mantissa), sizeof(exponent));
        sp->x.exponent = exponent;
        pc += sizeof(mantissa)+sizeof(exponent);
#endif
        break;
    }

    CASE(F_CLOSURE);                /* --- closure <ix>        --- */
    {
        /* Push the closure value <ix> onto the stack.
         * <ix> is a uint16, stored low byte first.
         * Values 0xf000..0xffff are efun and simul-efun symbols, the others
         * are operators and literals.
         * Simul-efun symbols (0xf800..0xffff) and true efun symbolx (0xf000..
         * 0xf7ff for which instrs[].Default >= 0) are made signed and stored
         * as they are.
         * Operator symbols (0xf000..0xf7ff for which instrs[].Default == -1)
         * are moved into their 0xe800..0xefff range, then made signed and
         * stored.
         */

        /* TODO: uint16 */ unsigned short tmp_ushort;
        /* TODO: int32 */ int ix;

        LOAD_SHORT(tmp_ushort, pc);

        ix = tmp_ushort;
        if (ix < 0xf000)
        {
            sp++;
            inter_sp = sp;
            inter_pc = pc;
            closure_literal(sp, ix);
            /* If out of memory, this will set sp to svalue-0 and
             * throw an error.
             */
        }
        else
        {
            sp++;
            sp->type = T_CLOSURE;
            sp->u.ob = ref_object(current_object, "closure");
            if (ix >= CLOSURE_SIMUL_EFUN_OFFS)
            {
                /* Sefun closure */
                sp->x.closure_type = (short)ix;
            }
            else
            {
                /* Efun or operator closure */
                if (pragma_warn_deprecated
                 && instrs[ix - CLOSURE_EFUN_OFFS].deprecated != NULL)
                    warnf("Warning: %s() is deprecated: %s\n"
                         , instrs[ix - CLOSURE_EFUN_OFFS].name
                         , instrs[ix - CLOSURE_EFUN_OFFS].deprecated
                         );

                sp->x.closure_type
                  = (short)(  instrs[ix - CLOSURE_EFUN_OFFS].Default == -1
                            ? ix + CLOSURE_OPERATOR-CLOSURE_EFUN
                            : ix);
            }
        }
        break;
    }

    CASE(F_SYMBOL);                 /* --- symbol <ix> <num>   --- */
    {
        /* Push a symbol of current_strings[<ix>] with <num> quotes
         * onto the stack.
         * <ix> is a uint16, stored low byte first. <num> is a uint8.
         */

        char *str;
        /* TODO: uint16 */ unsigned short string_number;

        LOAD_SHORT(string_number, pc);

        sp++;
        sp->type = T_SYMBOL;
        sp->x.quotes = LOAD_UINT8(pc);
        sp->u.string = str = ref_string(current_strings[string_number]);
        break;
    }

    CASE(F_RETURN0);                /* --- return0             --- */
        /* Return from the function with result value 0.
         */
        push_number(0);
        /* FALLTHROUGH */

    CASE(F_RETURN);                 /* --- return              --- */
    {
        /* Return from the function with the result topmost on the stack.
         * If this is an .extern_call, eval_instruction()
         * is left here.
         */

        svalue_t *svp; /* Save of current sp */

        svp = sp;

        /* Remove any intermediate error contexts */
        while (csp->catch_call)
        {
            pop_control_stack();
            pop_error_context();
        }

        /* Deallocate frame, but not the result value.
         */
#ifdef DEBUG
        if (fp + csp->num_local_variables > sp)
            fatal("Bad stack at F_RETURN, %ld values too low\n"
                 , (long)((fp + csp->num_local_variables) - sp));
        else if (fp + csp->num_local_variables < sp)
            fatal("Bad stack at F_RETURN, %ld values too high\n"
                 , (long)(sp - (fp + csp->num_local_variables)));
#endif
        while (sp != fp)
        {
            free_svalue(--sp);
        }
        *sp = *svp;

        /* Restore the previous execution context */
        if ( NULL != (current_prog = csp->prog) ) /* is 0 when we reach the bottom */
            current_strings = current_prog->strings;

        function_index_offset = csp->function_index_offset;
        current_variables     = csp->current_variables;
        break_sp = csp->break_sp;
        if (current_lambda.type == T_CLOSURE)
            free_closure(&current_lambda);
        current_lambda = csp->lambda;

        if (csp->extern_call)
        {
            /* eval_instruction() must be left - setup the globals */

            ASSIGN_EVAL_COST
            current_object = csp->ob;
            previous_ob = csp->prev_ob;
            inter_pc = csp->pc;
            inter_fp = csp->fp;
            if (trace_level)
            {
                do_trace_return(sp);
                if (csp == CONTROL_STACK - 2)
                    /* TODO: This can't be legal according to ISO C */
                    traceing_recursion = -1;
            }
            csp--;
            inter_sp = sp;
            return MY_FALSE;
        }

        /* We stay in eval_instruction() */

        if (trace_level)
            do_trace_return(sp);
        pc = csp->pc;
        fp = csp->fp;
        csp--;
        break;
    }

    CASE(F_BREAK);                  /* --- break               --- */
    {
        /* Break out of a switch() by pulling the continuation address
         * from the break stack.
         */

        pc = *break_sp;
        break_sp += sizeof(svalue_t)/sizeof(*break_sp);
        break;
    }

    CASE(F_SWITCH);            /* --- switch <lots of data...> --- */
    {
        /* The switch()-Statement: pop the topmost value from the stack,
         * search it in the given case values and set the pc to the
         * associated code. Also push the address of the next instruction
         * as break address onto the break stack.
         *
         * The compiler makes sure that there is always a 'default' case
         * and that all execution paths eventually execute a F_BREAK.
         *
         * The layout created by the LPC compiler is this:
         *
         *     switch b1 a2 b2 [b3 [b4] ]
         *            instructions (sans the first byte 'i0')...
         *            l[]
         *            [c0 [c1]]
         *            a0 a1 i0
         *            v*n
         *            o*n
         *            [d0]
         *
         * b1 & 0x03 is 0, marking this switch statement as unaligned.
         * Since for an efficient search the tables v*n and o*n must be
         * 4-Byte aligned (TODO: on some machines 8-Byte), the interpreter
         * will on first execution of such a switch align it (using
         * closure:align_switch()) by arranging the bytes a0..a2 around
         * the tables. The aligned layout is this:
         *
         *     switch b1 b2 [b3 [b4] ]
         *            instructions...
         *            l[]
         *            [c0 [c1]]            <-- p0 = pc + offset
         *            a0..
         *            v[]                  <-- tabstart
         *            o[]                  <-- end_tab = pc + offset + tablen
         *            ..a2                 <-- p1
         *            [d0]
         *
         *  b1 (bits 1..0) = len: the length in bytes needed to store
         *        'offset', 'tablen', 'default offset', 'o*n' and the
         *        length of lookup tables for table ranges.
         *  b1 (bits 7..2) = tablen lo
         *  c0 = tablen mid (optional)
         *  c1 = tablen hi  (optional)
         *  b2 = offset lo
         *  b3 = offset med (optional)
         *  b4 = offset hi  (optional)
         *  a0, a1 = default-case offset lo and med in host byte order
         *  d0     = default-case offset hi (optional)
         *  a2 'type' (bits 0..4): start position for search (used to index
         *                         a table with the real offsets)
         *            (bit  5)   : 0: numeric switch , 1: string switch
         *            (bits 6..7): in an unaligned switch, the true value
         *                         of <len> (b1, bits 1..0).
         *  l[]: range lookup table: each <len> bytes, network byte order
         *       (numeric switch only)
         *  v[]: case values, char* or p_int, host byte order
         *  o[]: case offsets : each <len> bytes, network byte order
         *
         * The case value table v[] holds (sorted numerically) all values
         * appearing in the case statements, both singular values and range
         * bounds. Range bound values (which are inclusive) always appear
         * next to each other.
         *
         * The offset table o[] holds the associated offset with
         * this interpretation:
         *
         *   singular cases: jump destination offsets relative to pc.
         *
         *   range cases:    the 'offset' for the lower bound is 1, the
         *                   offset for the upper bound gives the jump
         *                   destination relative to pc.
         *
         *   lookup ranges:  the 'offset' for the lower bound is 0, the
         *                   offset for the upper bound is an offset
         *                   pointing into the lookup table.
         *                   The real jump offset is then
         *                     l[o[i] + <value> - lower-bound].
         *
         *   The lookup ranges are used for an efficient implementation of
         *   sparse ranges like 'case 0: case 2: case 5: ...'.
         *
         *   TODO: This code still makes too many un-macro'ed mem accesses.
         */

        Bool useDefault; /* TRUE: Immediately jump to the default case */
        mp_int offset;  /* Length of instruction and range-table area */
        mp_int def_offs;  /* Offset to code for the 'default' case */
        int tablen; /* Number of single case entries, multiplied by 4 */
        int len;    /* Number of bytes per offset/length value (1..3) */
        int type;   /* Start position for search */
        static int32 off_tab[] = {
                0*sizeof(char*), 0x00001*sizeof(char*), 0x00003*sizeof(char*),
          0x00007*sizeof(char*), 0x0000f*sizeof(char*), 0x0001f*sizeof(char*),
          0x0003f*sizeof(char*), 0x0007f*sizeof(char*), 0x000ff*sizeof(char*),
          0x001ff*sizeof(char*), 0x003ff*sizeof(char*), 0x007ff*sizeof(char*),
          0x00fff*sizeof(char*), 0x01fff*sizeof(char*), 0x03fff*sizeof(char*),
          0x07fff*sizeof(char*), 0x0ffff*sizeof(char*), 0x1ffff*sizeof(char*),
          0x3ffff*sizeof(char*), 0x7ffff*sizeof(char*)
        };
          /* Start offsets for the binary search for different table sizes.
           * This table is indexed by <type> & 0x1f, and the compiler choses
           * the start position to be the first power of 2 which is at least
           * half the table size. This way the search algorithm only needs
           * to check for the upper table end.
           * TODO: Is the choice really so?
           */
        bytecode_p p0;
          /* Points after the range lookup tables (initially). */
        bytecode_p p1;
          /* Points to the table of offsets. */
        bytecode_p tabstart;
          /* Points to the 'v*n' table of cases */
        bytecode_p end_tab;
          /* Points to the 'o*n' table of offsets for the cases */
        bytecode_p break_addr;
          /* Address of the first bytecode after the switch, will be pushed
           * onto the break stack.
           */
        mp_int s;
          /* Search value for the lookup, derived from the stack value.
           * It is either u.number or the numeric value of u.string.
           */
        /* TODO: opcode? */ unsigned char *l;
          /* Current search pointer into the value table v[] */
        mp_int r;
          /* Current value retrieved from *<l> */
        mp_int d;
          /* Half the distance between <l> and the current upper resp. lower
           * bound of the search partition
           */
        /* TODO: opcode? */ unsigned char *p2;
          /* For a found case, the pointer into o[] */
        mp_int o0, o1;
          /* The offsets read from *(p2-1) and *p2, resp. *p2 and *(p2+1) */
        int i; /* Temporary */

        /* Extract the basic tablen and len */
        tablen = EXTRACT_UCHAR(pc);
        if ( !(len = tablen & SWITCH_VALUELEN) )
        {
            /* Oops, first lets align the switch */
            align_switch(pc);
            tablen = EXTRACT_UCHAR(pc);
            len = tablen & SWITCH_VALUELEN;
        }
        tablen &= ~SWITCH_VALUELEN;
        /* SWITCH_TABLEN_SHIFT is 2, so don't need to do
         *   tablen = (tablen >> SWITCH_TABLEN_SHIFT) * 4
         */

        /* Get the offset, aka the length of instruction and range table
         * part, and let p0 point after them.
         */
        offset = EXTRACT_UCHAR(pc+1);
        if (len > 1)
        {
            offset += EXTRACT_UCHAR(pc+2) << 8;
            if (len > 2)
            {
                offset += EXTRACT_UCHAR(pc+3) << 16;
            }
        }
        p0 = pc + offset;

        /* Get the full tablen, aka the number of single case entries,
         * and set p1 to point _after_ the offset table 'o*n'.
         * The computed formula is
         *
         *   p1 = p0 + tablen * sizeof(char*) + tablen * len * sizeof(char)
         *               (length of v*n)           (length of o*n)
         *
         * with the code taking into account that the _variable_ tablen
         * already comes as 'tablen * sizeof(char*)'.
         *
         * TODO: This code assumes sizeof(char*) == 4.
         */
        if (len > 1)
        {
            tablen += *(unsigned char *)(p0++) << 8;
            if (len > 2)
            {
                tablen += *(unsigned char *)(p0++) << 16;
#if SIZEOF_CHAR_P == 4
                p1 = (unsigned char *)(p0 + (tablen << 1) - (tablen >> 2));
            }
            else
            {
                p1 = (unsigned char *)(p0 + tablen + (tablen >> 1));
#else
                p1 = (unsigned char *)(p0 + tablen + tablen*3/sizeof(p_int) );
            }
            else
            {
                p1 = (unsigned char *)(p0 + tablen + tablen*2/sizeof(p_int) );
#endif
            }
        }
        else
        {
            p1 = (unsigned char *)(p0 + tablen + tablen / sizeof(p_int) );
        }

        /* Gather the 'default offset' and the 'type' from the alignment
         * bytes before v[] (pointer to by p0) and the bytes after
         * o[] (pointed to by p1).
         * Set 'tabstart' to the real start of 'v*n'.
         * Set 'break_addr' to the first instruction after the switch.
         */

        {
            int a, b;
            union { unsigned char b[sizeof(p_int)-1]; short s; } abuf;
              /* TODO: Assumes sizeof(p_int)-1 >= sizeof(short) */
              /* TODO: Assumes sizeof(p_int) == 4 */
              /* TODO: Assumes sizeof(short) == 2 */

            /* Gather the bytes a0..a2 into abuf.b[] */
            b = (int)(((p_int)p0-1) & sizeof abuf.b);
              /* The number of a-bytes after 'o*n' */
            memcpy((char *)abuf.b, p0, sizeof abuf.b);
            a = (int)(sizeof abuf.b - b);
              /* The number of remaining bytes */
            memcpy((char *)(abuf.b + a), (char *)(p1 + a), (size_t)b);
            def_offs = abuf.s;
            type = abuf.b[2];
            if (len > 2)
            {
                def_offs += p1[3] << 16;
                break_addr = p1 + sizeof(p_int);
            }
            else
            {
                break_addr = p1 + sizeof(p_int)-1;
            }
            tabstart = p0 + a;
        }

        /* Set 'end_tab' to point to the 'o*n' table,
         * push the break address onto the break stack.
         */
        end_tab  = tabstart + tablen;
        break_sp -= sizeof(svalue_t)/sizeof(*break_sp);
        *break_sp = break_addr;

        /* Get the search value from the argument passed on the
         * stack. This also does the type checking.
         */
        useDefault = MY_FALSE;
        if (type & SWITCH_TYPE)
        {
            /* String switch */

            if ( sp->type == T_NUMBER && !sp->u.number )
            {
                /* Special case: uninitialized string '0'.
                 * Use a magic value for this one.
                 */
                s = (mp_int)ZERO_AS_STR_CASE_LABEL;
            }
            else if ( sp->type == T_STRING )
            {
                /* The case strings in the program shared, so whatever
                 * string we get on the stack, it must at least have
                 * a shared twin to be sensible. Get the address of
                 * that twin.
                 */
                switch(sp->x.string_type)
                {
                case STRING_SHARED:
                    s = (mp_int)sp->u.string;
                    break;
                default:
                    s = (mp_int)findstring(sp->u.string);
                    break;
                }
            }
            else
            {
                /* Non-string value for string switch: use default */
                useDefault = MY_TRUE;
                s = 0;
            }
        }
        else if (sp->type == T_NUMBER)
        {
            /* Numeric switch and number given */
            s = sp->u.number;
        }
        else
        {
            /* Non-number value for numeric switch: use default */
            useDefault = MY_TRUE;
            s = 0;
        }
        pop_stack();

        if (useDefault)
        {
            o1 = def_offs;
        }
        else
        {
            /* Setup the binary search:
             *   l points roughly into the middle of the table,
             *   d is 1/4 of the (assumed) total size of the table
             */
            i = type & SWITCH_START;
            l = tabstart + off_tab[i];
            d = (mp_int)((off_tab[i]+sizeof(p_int)) >> 1 & ~(sizeof(p_int)-1));
              /* '+sizeof()' to make the off_tab[] value even and non-0 */

            /* Binary search for the value <s> in the table, starting at
             * position <l> and first subdivision size <d>.
             * The algorithm runs until <d> falls below the size of a case value
             * (sizeof(p_int)).
             *
             * After the loop terminates, o1 will be the jump offset relative
             * to the pc, which might be the 'default' offset if the value <s>
             * was not found.
             */
            for(;;)
            {
                r = *(p_int*)l; /* Get the case value */

                if (s < r)
                {

                    /* --- s < r --- */

                    if (d < (mp_int)sizeof(p_int))
                    {
                        if (!d)
                        {
                            /* End of search: s not found.
                             *
                             * Set p2 to the offset matching <l> and retrieve
                             * o0 and o1 from there.
                             *
                             * s might still be in a range, then <l>/<p2> point to
                             * the entries for the upper bound.
                             */
                            p2 =   tabstart + tablen
                                 + ((p_int*)l - (p_int*)tabstart)*len;
                            o0 = EXTRACT_UCHAR(p2-1);
                            o1 = EXTRACT_UCHAR(p2);
                            if (len > 1)
                            {
                                o0 += EXTRACT_UCHAR(p2-2) << 8;
                                o1 = EXTRACT_UCHAR(p2+1) + (o1 << 8);
                                if (len > 2)
                                {
                                    o0 += EXTRACT_UCHAR(p2-3) << 16;
                                    o1 = EXTRACT_UCHAR(p2+2) + (o1 << 8);
                                }
                            }
                            /* Because the pre-table alignment area is in the
                             * indexing underflow memory region, we can't make
                             * useful predictions on the peeked o0 value in case
                             * of underflow.
                             */

                            /* Test for a range */

                            if (o0 <= 1 && l > tabstart)
                            {
                                /* No indexing underflow: test if s is in range */

                                r = ((p_int*)l)[-1]; /* the lower bound */
                                if (s >= r)
                                {
                                    /* s is in the range */
                                    if (!o0)
                                    {
                                        /* Look up the real jump offset */
                                        l = pc + o1 + (s-r) * len;
                                        o1 = 0;
                                        i = len;
                                        do {
                                            o1 = (o1 << 8) + *l++;
                                        } while (--i);
                                        break;
                                    }
                                    /* o1 holds jump destination */
                                    break;
                                }
                                /* s is not in the range */
                            }

                            /* <s> not found at all: use 'default' address */
                            o1 = def_offs;

                            /* o1 holds jump destination */
                            break;
                        } /* if (!d) */

                        /* Here is 0 < d < sizeof(p_int).
                         * Set d = 0 and finish the loop in the next
                         * iteration.
                         * TODO: Why the delay?
                         */
                        d = 0;
                    }
                    else
                    {
                        /* Move <l> down and half the partition size <d>. */
                        l -= d;
                        d >>= 1;
                    }
                }
                else if (s > r)
                {

                    /* --- s > r --- */

                    if (d < (mp_int)sizeof(p_int))
                    {
                        if (!d)
                        {
                            /* End of search: s not found.
                             *
                             * Set p2 to the offset matching <l> and retrieve
                             * o0 and o1 from there.
                             *
                             * s might still be in a range, then <l> points to
                             * the entry of the lower bound, and <p2> is set to
                             * the entry for the upper bound.
                             */
                            p2 = tabstart + tablen
                                 + (((p_int*)l - (p_int*)tabstart) + 1)*len;
                            o0 = EXTRACT_UCHAR(p2-1);
                            o1 = EXTRACT_UCHAR(p2);
                            if (len > 1)
                            {
                                o0 += EXTRACT_UCHAR(p2-2) << 8;
                                o1 = EXTRACT_UCHAR(p2+1) + (o1 << 8);
                                if (len > 2)
                                {
                                    o0 += EXTRACT_UCHAR(p2-3) << 16;
                                    o1 = EXTRACT_UCHAR(p2+2) + (o1 << 8);
                                }
                            }

                            /* Test for a range */

                            if (o0 <= 1)
                            {
                                /* It is a range. */

                                if (s <= ((p_int*)l)[1])
                                {
                                    /* s is in the range, and r is already correct
                                     * (ie the upper bound)
                                     */
                                    if (!o0)
                                    {
                                        /* Lookup the real jump offset */
                                        l = pc + o1 + (s-r) * len;
                                        o1 = 0;
                                        i = len;
                                        do {
                                            o1 = (o1 << 8) + *l++;
                                        } while (--i);
                                        break;
                                    }
                                    /* o1 holds jump destination */
                                    break;
                                }
                                /* s is not in the range */
                            }

                            /* <s> not found at all: use 'default' address */
                            o1 = def_offs;

                            /* o1 holds jump destination */
                            break;
                        } /* !d */

                        /* Here is 0 < d < sizeof(p_int).
                         * Set d = 0 and finish the loop in the next
                         * iteration.
                         * TODO: Why the delay?
                         */
                        d = 0;
                    }
                    else
                    {
                        /* Move <l> up, and half the partition size <d>
                         * If this would push l beyond the table, repeat the
                         * steps 'move <l> down and half the partition size'
                         * until <l> is within the table again.
                         */

                        l += d;
                        while (l >= end_tab)
                        {
                            d >>= 1;
                            if (d <= (mp_int)sizeof(p_int)/2)
                            {
                                /* We can't move l further - finish the loop */
                                l -= sizeof(p_int);
                                d = 0;
                                break;
                            }
                            l -= d;
                        }
                        d >>= 1;
                    }
                }
                else
                {
                    /* --- s == r --- */

                    /* End of search: s found.
                     *
                     * Set p2 to the offset matching <l> and retrieve
                     * o0 and o1 from there.
                     *
                     * We don't distinguish between a singular case match
                     * and a match with an upper range bound, but we have
                     * to take extra steps in case <s> matched a lower range
                     * bound. In that light, o0 need not be an exact value.
                     */
                    p2 = tabstart + tablen + ((p_int*)l - (p_int*)tabstart)*len;
                    o0 = EXTRACT_UCHAR(p2-1);
                    o1 = EXTRACT_UCHAR(p2);
                    if (len > 1)
                    {
                        o0 |= EXTRACT_UCHAR(p2-2);
                        o1 = EXTRACT_UCHAR(p2+1) + (o1 << 8);
                        if (len > 2)
                        {
                            o0 |= EXTRACT_UCHAR(p2-3);
                            o1 = EXTRACT_UCHAR(p2+2) + (o1 << 8);
                        }
                    }

                    /* Test if <s> matched the end of a range with a lookup table.
                     */
                    /* TODO: Does this mean that the compiler never creates
                     * TODO:: an ordinary range at the beginning of v[]?
                     */
                    if (!o0 && l > tabstart)
                    {
                        r = ((p_int*)l)[-1]; /* the lower bound */
                        l = pc + o1 + (s-r) * len;
                        o1 = 0;
                        i = len;
                        do
                        {
                            o1 = (o1 << 8) + *l++;
                        } while (--i);
                        /* o1 holds jump destination */
                        break;
                    }

                    /* Test if <s> matched the start of a range */
                    if (o1 <= 1)
                    {
                        /* Yup. Realign p2 and reget o1 */
                        p2 += len;

                        /* Set l to point to the jump offset */
                        if (o1)
                        {
                            /* start of ordinary range */
                            l = p2;
                        }
                        else
                        {
                            /* start of range with lookup table */
                            i = len;
                            do {
                                o1 = (o1 << 8) + *p2++;
                            } while (--i);
                            l = pc + o1;
                        }

                        /* Get the jump offset from where <l> points */
                        o1 = 0;
                        i = len;
                        do {
                            o1 = (o1 << 8) + *l++;
                        } while (--i);

                        /* o1 holds jump destination */
                        break;
                    }

                    /* At this point, s was a match with a singular case, and
                     * o1 already holds the jump destination.
                     */
                    break;
                }
            } /* binary search */
        } /* if (useDefault) */

        /* o1 is now the offset to jump to. */
        pc += o1;
        break;
    }

    CASE(F_SSCANF);                 /* --- sscanf <numarg>     --- */
    {
        /* EFUN sscanf()
         *
         *   int sscanf(string str, string fmt, mixed var1, mixed var2, ...)
         *
         * Scanf <str> according to <fmt> and store the resultes in var1...
         * The compiler knows that var1... have to be passed as lvalues.
         *
         * Result is the number of variables assigned.
         */
        int i;

        num_arg = LOAD_UINT8(pc);
          /* GET_NUM_ARG doesn't work here. Trust me. */
        inter_sp = sp;
        inter_pc = pc;
        i = e_sscanf(num_arg, sp);
        pop_n_elems(num_arg-1);
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

#ifdef F_PARSE_COMMAND
    CASE(F_PARSE_COMMAND);      /* --- parse_command <numargs> --- */
    {
        /* EFUN parse_command()
         *
         *   int parse_command(string cmd, object|object* objs
         *                    , string fmt, mixed var1, mixed var2...)
         *
         * Parse the command <cmd> against <objs> and the format <fmt>
         * and assign the parsed values to variables var1....
         * The compiler knows that var1... have to be passed as lvalues.
         *
         * Result is TRUE if the pattern matches, and FALSE if not.
         */
        int i;
        svalue_t *arg;

        assign_eval_cost();
        num_arg = LOAD_UINT8(pc);
          /* GET_NUM_ARG doesn't work here either. */
        arg = sp - num_arg + 1;
        if (arg[0].type != T_STRING)
            goto bad_arg_1;
        if (arg[1].type != T_OBJECT && arg[1].type != T_POINTER)
            goto bad_arg_2;
        if (arg[2].type != T_STRING)
            goto bad_arg_3;
        if (arg[1].type == T_POINTER)
            check_for_destr(arg[1].u.vec);

        inter_sp = sp;
        inter_pc = pc;
        if (compat_mode)
            i = e_old_parse_command(arg[0].u.string, &arg[1], arg[2].u.string
                                   , &arg[3], num_arg-3);
        else
            i = e_parse_command(arg[0].u.string, &arg[1], arg[2].u.string
                               , &arg[3], num_arg-3);
        pop_n_elems(num_arg);        /* Get rid of all arguments */
        push_number(i ? 1 : 0);      /* Push the result value */
        break;
    }
#endif /* PARSE_COMMAND */

    CASE(F_LOCAL);                  /* --- local <ix>          --- */
    {
        /* Fetch the value of local variable <ix> and push it
         * onto the stack.
         */
        svalue_t * new_fp = fp + LOAD_UINT8(pc);
        sp++;
        assign_local_svalue_no_free(sp, new_fp, sp, pc);
        break;
    }

    CASE(F_CATCH);        /* --- catch <offset> <guarded code> --- */
    CASE(F_CATCH_NO_LOG); /* --- catch_no_log <offset> <guarded code> --- */
    {
        /* catch(...instructions...)
         * catch_nolog(...instructions...)
         *
         * Execute the instructions (max. uint8 <offset> bytes) following the
         * catch statement. If an error occurs, or a throw() is executed,
         * catch that exception, push the <catch_value> (a global var)
         * onto the stack and continue execution at instruction
         * <pc>+1+<offset>.
         *
         * The implementation is such that a control-stack entry is created
         * as if the instructions following catch are called as a subroutine
         * from <pc>+1+<offset>. Additionally an appropriate error context
         * is pushed. This way the error handling will have the VM 'return'
         * to the right place automatically.
         *
         * The last instruction of the guarded code is F_END_CATCH which
         * will clean up the control and error stack.
         *
         * If the actual guarded code is longer than 256 Bytes, the compiler
         * will generate appropriate branches:
         *
         *                  catch 2
         *                  branch guarded_code
         *                  branch continuation
         *    guarded_code: ...
         */

        uint offset;

        /* Get the offset to the next instruction after the CATCH statement.
         */
        offset = LOAD_UINT8(pc);

        /* Save the important variables in their global locations */
        inter_pc = pc;
        inter_sp = sp;
        inter_fp = fp;

        /* Perform the catch() */
        if (!catch_instruction(instruction, offset
#ifdef __INTEL_COMPILER
                              , (svalue_t ** volatile) &inter_sp
#else
                              , (volatile svalue_t ** volatile) &inter_sp
#endif
                              , inter_pc, inter_fp))
        {
            return MY_FALSE; /* Guarded code terminated with 'return' itself */
        }

        /* Restore the important variables */
        pc = inter_pc;
        sp = inter_sp;
        fp = inter_fp;

        /* Not really necessary, but tells gcc to complain less */
        instruction = F_CATCH;
        num_arg = -1;
#ifdef DEBUG
        expected_stack = NULL;
#endif
        break;
    }

    CASE(F_INC);                    /* --- inc                 --- */
    {
        /* void inc (mixed & sp[0])
         *
         * Increment the (numeric) value designed by the lvalue on top
         * of the stack, then remove the lvalue from the
         * stack (not free()!, this lvalue is just a copy).
         */

        svalue_t *svp;

        /* Get the designated value */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            ERROR("Bad argument to ++: not a lvalue.\n")
            /* TODO: Give value and type */
#endif
        svp = sp->u.lvalue;

        /* Now increment where we can */
        if (svp->type == T_NUMBER)
        {
            if (svp->u.number == PINT_MAX)
            {
                ERRORF(("Numeric overflow: (%ld)++\n", (long)svp->u.number));
                /* NOTREACHED */
                break;
            }
            svp->u.number++;
            sp--;
            break;
        }
        else if (svp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            d = READ_DOUBLE(svp) + 1.0;
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: (%g)++\n", READ_DOUBLE(svp)));
            sp->type = T_FLOAT;
            STORE_DOUBLE(svp, d);
            sp--;
            break;
        }
        else if (svp->type == T_CHAR_LVALUE)
        {
            if ((unsigned char)(*svp->u.string) == 0xff /* TODO: MAX_CHAR */ )
                ERROR("Can't set string character to 0.\n")
            else
                (*svp->u.string)++;
            sp--;
            break;
        }
        else if (svp->type == T_LVALUE
              || svp->type == T_PROTECTED_LVALUE)
        {
            inter_sp = sp;
            add_number_to_lvalue(svp, 1, NULL, NULL);
            sp--;
            break;
        }

        ERROR("++ of non-numeric argument\n")
        /* TODO: Give type and value */
        break;
    }

    CASE(F_DEC);                    /* --- dec                 --- */
    {
        /* void dec (mixed & sp[0])
         *
         * Decrement the (numeric) value designed by the lvalue on top
         * of the stack, then remove the lvalue from the
         * stack (not free()!, this lvalue is just a copy).
         */

        svalue_t *svp;

        /* Get the designated value */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            ERROR("Bad argument to --: not a lvalue.\n")
            /* TODO: Give type and value */
#endif
        svp = sp->u.lvalue;

        /* Now decrement where we can */
        if (svp->type == T_NUMBER)
        {
            if (svp->u.number == PINT_MIN)
            {
                ERRORF(("Numeric overflow: (%ld)--\n", (long)svp->u.number));
                /* NOTREACHED */
                break;
            }
            svp->u.number--;
            sp--;
            break;
        }
        else if (svp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            d = READ_DOUBLE(svp) - 1.0;
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: (%g)--\n", READ_DOUBLE(svp)));
            sp->type = T_FLOAT;
            STORE_DOUBLE(svp, d);
            sp--;
            break;
        }
        else if (svp->type == T_CHAR_LVALUE)
        {
            if ((unsigned char)(*svp->u.string) == 0x01)
                ERROR("Can't set string character to 0.\n")
            else
                (*svp->u.string)--;
            sp--;
            break;
        }
        else if (svp->type == T_LVALUE
              || svp->type == T_PROTECTED_LVALUE)
        {
            inter_sp = sp;
            add_number_to_lvalue(svp, -1,  NULL, NULL);
            sp--;
            break;
        }

        ERROR("-- of non-numeric argument\n")
        /* TODO: Give type and value */
        break;
    }

    CASE(F_POST_INC);               /* --- post_inc            --- */
    {
        /* mixed post_inc (mixed & sp[0])
         *
         * Increment the numeric value designated by the lvalue on top
         * of the stack, and replace the stack entry with the value
         * before the increment. The lvalue itself is simply removed, not
         * free()d.
         */

        svalue_t *svp;

        /* Get the designated value */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            ERROR("Bad argument to ++: not a lvalue.\n")
            /* TODO: Give the type and value */
#endif
        svp = sp->u.lvalue;

        /* Do the push and increment */
        if (svp->type == T_NUMBER)
        {
            if (svp->u.number == PINT_MAX)
            {
                ERRORF(("Numeric overflow: (%ld)++\n", (long)svp->u.number));
                /* NOTREACHED */
                break;
            }
            put_number(sp,  svp->u.number++ );
            break;
        }
        else if (svp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            d = READ_DOUBLE(svp);
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            d += 1.0;
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: (%g)++\n", READ_DOUBLE(svp)));
            STORE_DOUBLE(svp, d);
            break;
        }
        else if (svp->type == T_CHAR_LVALUE)
        {
            if ((unsigned char)(*svp->u.string) == 0xff /* TODO: MAX_CHAR */ )
                ERROR("Can't set string character to 0.\n")
            else
                put_number(sp, (*svp->u.string)++ );
            break;
        }
        else if (svp->type == T_LVALUE
              || svp->type == T_PROTECTED_LVALUE)
        {
            inter_sp = sp;
            add_number_to_lvalue(svp, 1, sp, NULL);
            break;
        }

        ERROR("++ of non-numeric argument\n")
        /* TODO: Give type and value */
        break;
    }

    CASE(F_POST_DEC);               /* --- post_dec            --- */
    {
        /* mixed post_dec (mixed & sp[0])
         *
         * Decrement the numeric value designated by the lvalue on top
         * of the stack, and replace the stack entry with the value
         * before the decrement. The lvalue itself is simply removed, not
         * free()d.
         */

        svalue_t *svp;

        /* Get the designated value */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            ERROR("Bad argument to --: not a lvalue.\n")
            /* TODO: Give the type and value */
#endif
        svp = sp->u.lvalue;

        /* Do the push and decrement */
        if (svp->type == T_NUMBER)
        {
            if (svp->u.number == PINT_MIN)
            {
                ERRORF(("Numeric overflow: (%ld)--\n", (long)svp->u.number));
                /* NOTREACHED */
                break;
            }
            put_number(sp,  svp->u.number-- );
            break;
        }
        else if (svp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            d = READ_DOUBLE(svp);
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            d -= 1.0;
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: (%g)--\n", READ_DOUBLE(svp)));
            STORE_DOUBLE(svp, d);
            break;
        }
        else if (svp->type == T_CHAR_LVALUE)
        {
            if ((unsigned char)(*svp->u.string) == 0x01)
                ERROR("Can't set string character to 0.\n")
            else
                put_number(sp, (*svp->u.string)-- );
            break;
        }
        else if (svp->type == T_LVALUE
              || svp->type == T_PROTECTED_LVALUE)
        {
            inter_sp = sp;
            add_number_to_lvalue(svp, -1, sp, NULL);
            break;
        }

        ERROR("-- of non-numeric argument\n")
        /* TODO: Give the type and value */
        break;
    }

    CASE(F_PRE_INC);                /* --- pre_inc             --- */
    {
        /* mixed pre_inc (mixed & sp[0])
         *
         * Increment the numeric value designated by the lvalue on top
         * of the stack, and replace the stack entry with the incremented
         * value. The lvalue itself is simply removed, not free()d.
         */

        svalue_t *svp;

        /* Get the designated value */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            ERROR("Bad argument to ++: not a lvalue\n")
            /* TODO: Give type and value */
#endif
        svp = sp->u.lvalue;

        /* Do the increment and push */
        if (svp->type == T_NUMBER)
        {
            if (svp->u.number == PINT_MAX)
            {
                ERRORF(("Numeric overflow: ++(%ld)\n", (long)svp->u.number));
                /* NOTREACHED */
                break;
            }
            put_number(sp,  ++(svp->u.number) );
            break;
        }
        else if (svp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            d = READ_DOUBLE(svp) + 1.0;
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: ++(%g)\n", READ_DOUBLE(svp)));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            STORE_DOUBLE(svp, d);
            break;
        }
        else if (svp->type == T_CHAR_LVALUE)
        {
            if ((unsigned char)(*svp->u.string) == 0xff /* TODO: MAX_CHAR */ )
                ERROR("Can't set string character to 0.\n")
            else
                put_number(sp, ++(*svp->u.string) );
            break;
        }
        else if (svp->type == T_LVALUE
              || svp->type == T_PROTECTED_LVALUE)
        {
            inter_sp = sp;
            add_number_to_lvalue(svp, 1, NULL, sp);
            break;
        }

        ERROR("++ of non-numeric argument\n")
        /* TODO: Give type and value */
        break;
    }

    CASE(F_PRE_DEC);                /* --- pre_dec             --- */
    {
        /* mixed pre_dec (mixed & sp[0])
         *
         * Decrement the numeric value designated by the lvalue on top
         * of the stack, and replace the stack entry with the decremented
         * value. The lvalue itself is simply removed, not free()d.
         */

        svalue_t *svp;

        /* Get the designated value */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            ERROR("Bad argument to --: not a lvalue\n")
            /* TODO: Give the type and value */
#endif
        svp = sp->u.lvalue;

        /* Do the decrement and push */
        if (svp->type == T_NUMBER)
        {
            if (svp->u.number == PINT_MIN)
            {
                ERRORF(("Numeric overflow: --(%ld)\n", (long)svp->u.number));
                /* NOTREACHED */
                break;
            }
            put_number(sp,  --(svp->u.number) );
            break;
        }
        else if (svp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            d = READ_DOUBLE(svp) - 1.0;
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: --(%g)\n", READ_DOUBLE(svp)));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            STORE_DOUBLE(svp, d);
            break;
        }
        else if (svp->type == T_CHAR_LVALUE)
        {
            if ((unsigned char)(*svp->u.string) == 0x01)
                ERROR("Can't set string character to 0.\n")
            else
                put_number(sp, --(*svp->u.string) );
            break;
        }
        else if (svp->type == T_LVALUE
              || svp->type == T_PROTECTED_LVALUE)
        {
            inter_sp = sp;
            add_number_to_lvalue(svp, -1, NULL, sp);
            break;
        }

        ERROR("-- of non-numeric argument\n")
        /* TODO: Give the type and value */
        break;
    }

    CASE(F_LAND);                   /* --- land <offset>       --- */
    {
        /* If sp[0] is the number 0, leave it on the stack (as result)
         * and branch by <offset>.
         * Otherwise, pop the value and just continue.
         */

        if (sp->type == T_NUMBER)
        {
            if (sp->u.number == 0)
            {
                uint offset = LOAD_UINT8(pc);
                pc += offset;
                break;
            }
            /* No need to explicitely free_svalue(), it's just a number */
        }
        else
        {
            free_svalue(sp);
        }
        sp--;
        pc++;
        break;
    }

    CASE(F_LOR);                    /* --- lor <offset>        --- */
    {
        /* If sp[0] is not the number 0, leave it on the stack (as result)
         * and branch by <offset>.
         * Otherwise, pop the value and just continue.
         */

        if (sp->type == T_NUMBER && sp->u.number == 0)
            sp--; /* think 'free_svalue(sp--)' here... */
        else
            pc += GET_UINT8(pc);
        pc++;
        break;
    }

    CASE(F_ASSIGN);                 /* --- assign              --- */
    {
        /* Assign the value sp[-1] to the value designated by lvalue sp[0].
         * The assigned value sp[-1] remains on the stack as result
         * (ie. the assign yields a rvalue).
         *
         * Make sure that complex destinations like arrays are not freed
         * before the assignment is complete - see the comments to
         * assign_svalue().
         */

        svalue_t *dest;

        /* Get the designated lvalue */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            fatal("Bad argument to F_ASSIGN: not a lvalue\n");
            /* TODO: Give type and value */
#endif
        dest = sp->u.lvalue;
        assign_svalue(dest, sp-1);
        sp--;
        break;
    }

    CASE(F_VOID_ASSIGN);            /* --- void_assign         --- */
    {
        /* Assign the value sp[-1] to the value designated by lvalue sp[0],
         * then remove both values from the stack.
         *
         * VOID_ASSIGNs occur pretty often, so the implementation uses an
         * adopted copy of the transfer_svalue() code.
         *
         * Make sure that complex destinations like arrays are not freed
         * before the assignment is complete - see the comments to
         * assign_svalue().
         */

        svalue_t *dest;

        /* Get the designated value */
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            fatal("Bad argument to F_VOID_ASSIGN: not a lvalue\n");
            /* TODO: Give type and value */
#endif
        dest = sp->u.lvalue;

        /* Free the destination svalue so that transfer_svalue_no_free_spc()
         * can be used. However, if the dest is a lvalue, a pointer or
         * mapping, the assignment takes place right here and the next
         * instruction will be executed by means of a 'goto again'.
         */
        switch(dest->type)
        {
        case T_STRING:
            switch(dest->x.string_type)
            {
            case STRING_MALLOC:
                xfree(dest->u.string);
                break;
            case STRING_SHARED:
                free_string(dest->u.string);
                break;
            }
            break;

        case T_OBJECT:
          {
            object_t *ob = dest->u.ob;
            free_object(ob, "void_assign");
            break;
          }

        case T_SYMBOL:
            free_string(dest->u.string);
            break;

        case T_CLOSURE:
            free_closure(dest);
            break;


        /* The cases below all assign right in the case block */

        case T_QUOTED_ARRAY:
        case T_POINTER:
          {
            vector_t *v = dest->u.vec;

            transfer_svalue_no_free_spc(dest, sp-1, sp, pc);
            sp -= 2;
            free_array(v);
            goto again;
          }

        case T_MAPPING:
          {
            mapping_t *m = dest->u.map;

            transfer_svalue_no_free_spc(dest, sp-1, sp, pc);
            sp -= 2;
            free_mapping(m);
            goto again;
          }

        case T_CHAR_LVALUE:
          {
            if (sp[-1].type == T_NUMBER)
            {
                if (sp[-1].u.number == 0)
                    ERROR("Can't assign 0 to string character.\n");
                *dest->u.string = (char)sp[-1].u.number;
            }
            else
            {
                free_svalue(sp-1);
            }
            sp -= 2;
            goto again;
          }

        /* the assignment class of operators always gets 'fresh' lvalues.
         * Thus, if we encounter a protected lvalue of any flavour, this is
         * due to a dereference of a reference stored in the original
         * lvalue, and the protected lvalue must not be freed.
         */

        case T_PROTECTED_CHAR_LVALUE:
          {
            struct protected_char_lvalue *p;

            p = (struct protected_char_lvalue *)dest;
            if (p->lvalue->type == T_STRING
             && p->lvalue->u.string == p->start)
            {
                if (sp[-1].type == T_NUMBER)
                {
                    if (sp[-1].u.number == 0)
                        ERROR("Can't assign 0 to string character.\n");
                    *p->v.u.string = (char)sp[-1].u.number;
                    sp -= 2;
                    goto again;
                }
            }
            sp--;
            pop_stack();
            goto again;
          }

        case T_POINTER_RANGE_LVALUE:
            transfer_pointer_range(sp-1);
            sp -= 2;
            goto again;

        case T_PROTECTED_POINTER_RANGE_LVALUE:
            transfer_protected_pointer_range(
              (struct protected_range_lvalue *)dest, sp-1
            );
            sp -= 2;
            goto again;

        case T_STRING_RANGE_LVALUE:
            inter_sp = sp;
            assign_string_range(sp-1, MY_TRUE);
            sp -= 2;
            goto again;

        case T_PROTECTED_STRING_RANGE_LVALUE:
            inter_sp = sp;
            assign_protected_string_range(
              (struct protected_range_lvalue *)dest, sp-1, MY_TRUE
            );
            sp -= 2;
            goto again;

        case T_LVALUE:
        case T_PROTECTED_LVALUE:
          {
            /* This may be a chain of LVALUEs - we might as well
             * call transfer_svalue() to deal with it
             */
            transfer_svalue(dest->u.lvalue, sp-1);
            sp -= 2;
            goto again;
          }
        }

        /* Nothing complicated: dest was just freed, now transfer
         * the data.
         */
        transfer_svalue_no_free_spc(dest, sp-1, sp, pc);
        sp -= 2;
        break;
    }

    CASE(F_ADD);                    /* --- add                 --- */
        /* Add sp[0] to sp[-1] (the order is important), pop both
         * summands from the stack and push the result.
         *
         * Possible type combinations:
         *   string      + (string,int,float) -> string
         *   (int,float) + string             -> string
         *   int         + int                -> int
         *   float       + (int,float)        -> float
         *   int         + float              -> float
         *   vector      + vector             -> vector
         *   mapping     + mapping            -> mapping
         */

        switch ( sp[-1].type )
        {

        case T_STRING:
            inter_pc = pc;
            inter_sp = sp;
            switch ( sp->type )
            {
            case T_STRING:
              {
                char *res;
                size_t l = _svalue_strlen(sp-1);
                size_t l2 = _svalue_strlen(sp);

                DYN_STRING_COST(l+l2)
                res = xalloc(l + l2 + 1);
                if (!res)
                    ERROR("Out of memory\n")
                strcpy(res, (sp-1)->u.string);
                strcpy(res+l, sp->u.string);
                free_string_svalue(sp);
                sp--;
                free_string_svalue(sp);
                put_malloced_string(sp, res);
                break;
              }

            case T_NUMBER:
              {
                char buff[80];
                char *res;
                size_t len1;

                buff[sizeof(buff)-1] = '\0';
                sprintf(buff, "%ld", sp->u.number);
                if (buff[sizeof(buff)-1] != '\0')
                    FATAL("Buffer overflow in F_ADD: int number too big.\n")
                res = xalloc((len1 = svalue_strlen(sp-1)) + strlen(buff) + 1);
                if (!res)
                    ERROR("Out of memory\n")
                strcpy(res, (sp-1)->u.string);
                strcpy(res+len1, buff);
                pop_n_elems(2);
                push_malloced_string(res);
                DYN_STRING_COST(len1)
                break;
              }

            case T_FLOAT:
              {
                char buff[160];
                char *res;
                size_t len1;

                buff[sizeof(buff)-1] = '\0';
                sprintf(buff, "%g", READ_DOUBLE( sp ) );
                if (buff[sizeof(buff)-1] != '\0')
                    FATAL("Buffer overflow in F_ADD: float number too big.\n")
                res = xalloc((len1 = svalue_strlen(sp-1)) + strlen(buff) + 1);
                if (!res)
                    ERROR("Out of memory\n")
                strcpy(res, (sp-1)->u.string);
                strcpy(res+len1, buff);
                sp--;
                free_string_svalue(sp);
                put_malloced_string(sp, res);
                DYN_STRING_COST(len1)
                break;
              }

            default:
                goto bad_add;
            }
            break;
            /* End of case T_STRING */

          case T_NUMBER:
            switch ( sp->type )
            {
            case T_STRING:
              {
                char buff[80], *res;
                size_t len1;

                buff[sizeof(buff)-1] = '\0';
                sprintf(buff, "%ld", (sp-1)->u.number);
                if (buff[sizeof(buff)-1] != '\0')
                    FATAL("Buffer overflow in F_ADD: int number too big.\n")
                inter_pc = pc;
                inter_sp = sp;
                res = xalloc(svalue_strlen(sp) + (len1 = strlen(buff)) + 1);
                if (!res)
                    ERROR("Out of memory\n")
                strcpy(res, buff);
                strcpy(res+len1, sp->u.string);
                free_string_svalue(sp);
                sp--;
                /* Overwrite the number at sp */
                put_malloced_string(sp, res);
                break;
              }

            case T_NUMBER:
              {
                p_int i;
                p_int right = sp->u.number;
                p_int left = (sp-1)->u.number;

                if ((left >= 0 && right >= 0 && PINT_MAX - left < right)
                 || (left < 0 && right < 0 && PINT_MIN - left > right)
                   )
                {
                    ERRORF(("Numeric overflow: %ld + %ld\n"
                           , (long)left, (long)right));
                    /* NOTREACHED */
                    break;
                }
                i = left + right;
                sp--;
                sp->u.number = i;
                break;
              }

            case T_FLOAT:
              {
                STORE_DOUBLE_USED
                double sum;

                sum = (double)((sp-1)->u.number) + READ_DOUBLE(sp);
                if (sum < (-DBL_MAX) || sum > DBL_MAX)
                    ERRORF(("Numeric overflow: %ld + %g\n"
                           , (long)(sp-1)->u.number, READ_DOUBLE(sp)));
                STORE_DOUBLE(sp-1, sum);
                sp--;
                sp->type = T_FLOAT;
                break;
              }

            default:
                goto bad_add;
            }
            break;
            /* End of case T_NUMBER */

        case T_FLOAT:
          {
            STORE_DOUBLE_USED
            double sum;

            if (sp->type == T_FLOAT)
            {
                sum = READ_DOUBLE(sp-1) + READ_DOUBLE(sp);
                if (sum < (-DBL_MAX) || sum > DBL_MAX)
                    ERRORF(("Numeric overflow: %g + %g\n"
                           , READ_DOUBLE(sp-1), READ_DOUBLE(sp)));
                STORE_DOUBLE(sp-1, sum);
                sp--;
                break;
            }
            if (sp->type == T_NUMBER)
            {
                sum = READ_DOUBLE(sp-1) + (double)(sp->u.number);
                if (sum < (-DBL_MAX) || sum > DBL_MAX)
                    ERRORF(("Numeric overflow: %g + %ld\n"
                           , READ_DOUBLE(sp-1), (long)sp->u.number));
                STORE_DOUBLE(sp-1, sum);
                sp--;
                break;
            }
            if (sp->type == T_STRING)
            {
                char buff[160];
                char *res;
                size_t len1;

                buff[sizeof(buff)-1] = '\0';
                sprintf(buff, "%g", READ_DOUBLE(sp-1) );
                if (buff[sizeof(buff)-1] != '\0')
                    FATAL("Buffer overflow in F_ADD: float number too big.\n")
                res = xalloc(svalue_strlen(sp) + (len1 = strlen(buff)) + 1);
                if (!res)
                    error("Out of memory\n");
                strcpy(res, buff);
                strcpy(res+len1, (sp)->u.string);
                free_string_svalue(sp);
                sp--;
                put_malloced_string(sp, res);
                break;
            }
            goto bad_add;
          }
          /* End of case T_FLOAT */

        case T_POINTER:
          {
            if (sp->type != T_POINTER) goto bad_add;
            inter_sp = sp;
            inter_pc = pc;
            inter_add_array(sp->u.vec, &(sp-1)->u.vec);
            sp--;
            break;
          }

        case T_MAPPING:
          {
            mapping_t *m;

            if (sp->type != T_MAPPING) goto bad_add;
            check_map_for_destr((sp-1)->u.map);
            check_map_for_destr(sp->u.map);
            inter_pc = pc;
            inter_sp = sp;
            m = add_mapping((sp-1)->u.map,sp->u.map);
            if (!m) {
                ERROR("Out of memory.\n")
            }
            pop_n_elems(2);
            push_mapping(m); /* This will make ref count == 2 */
            deref_mapping(m);
            if (max_mapping_size && MAP_SIZE(m) > max_mapping_size)
            {
                check_map_for_destr(m);
                if (max_mapping_size && MAP_SIZE(m) > max_mapping_size)
                    ERRORF(("Illegal mapping size: %ld\n", MAP_SIZE(m)));
            }
            break;
          }

        default:
        bad_add:
            ERROR("Bad type of arg to '+'\n")
            /* TODO: Give type, value and position. */
            /* NOTREACHED */
        }

        break;

    CASE(F_SUBTRACT);               /* --- subtract            --- */
    {
        /* Subtract sp[0] from sp[-1] (the order is important), pop both
         * arguments from the stack and push the result.
         *
         * Possible type combinations:
         *   int         - int                -> int
         *   float       - (int,float)        -> float
         *   int         - float              -> float
         *   string      - string             -> string
         *   vector      - vector             -> vector
         *   mapping     - mapping            -> mapping
         */

        p_int i;

        if ((sp-1)->type == T_NUMBER)
        {
            if (sp->type == T_NUMBER)
            {
                p_int left = (sp-1)->u.number;
                p_int right = sp->u.number;

                if ((left >= 0 && right < 0 && PINT_MAX + right < left)
                 || (left < 0 && right >= 0 && PINT_MIN + right > left)
                   )
                {
                    ERRORF(("Numeric overflow: %ld - %ld\n"
                           , (long)left, (long)right));
                    /* NOTREACHED */
                    break;
                }

                i = left - right;
                sp--;
                sp->u.number = i;
                break;
            }
            if (sp->type == T_FLOAT)
            {
                STORE_DOUBLE_USED
                double diff;

                diff = (double)((sp-1)->u.number) - READ_DOUBLE(sp);
                if (diff < (-DBL_MAX) || diff > DBL_MAX)
                    ERRORF(("Numeric overflow: %ld - %g\n"
                           , (long)(sp-1)->u.number, READ_DOUBLE(sp)));
                sp--;
                STORE_DOUBLE(sp, diff);
                sp->type = T_FLOAT;
                break;
            }
        }
        else if ((sp-1)->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double diff;

            if (sp->type == T_FLOAT)
            {
                diff = READ_DOUBLE(sp-1) - READ_DOUBLE(sp);
                if (diff < (-DBL_MAX) || diff > DBL_MAX)
                    ERRORF(("Numeric overflow: %g - %g\n"
                           , READ_DOUBLE(sp-1), READ_DOUBLE(sp)));
                sp--;
                STORE_DOUBLE(sp, diff);
                break;
            }
            if (sp->type == T_NUMBER)
            {
                diff = READ_DOUBLE(sp-1) - (double)(sp->u.number);
                if (diff < (-DBL_MAX) || diff > DBL_MAX)
                    ERRORF(("Numeric overflow: %g - %ld\n"
                           , READ_DOUBLE(sp-1), (long)sp->u.number));
                sp--;
                STORE_DOUBLE(sp, diff);
                break;
            }
        }
        else if ((sp-1)->type == T_POINTER && sp->type == T_POINTER)
        {
            vector_t *v;

            v = sp->u.vec;
            if (v->ref > 1)
            {
                deref_array(v);
                v = slice_array(v, 0, (mp_int)VEC_SIZE(v) - 1 );
            }
            sp--;
            /* subtract_array already takes care of destructed objects */
            sp->u.vec = subtract_array(sp->u.vec, v);
            break;
        }
        else if ((sp-1)->type == T_MAPPING && sp->type == T_MAPPING)
        {
            mapping_t *m;

            m = subtract_mapping(sp[-1].u.map, sp->u.map);
            free_mapping(sp->u.map);
            sp--;
            free_mapping(sp->u.map);
            sp->u.map = m;
            break;
        }
        else if ((sp-1)->type == T_STRING && sp->type == T_STRING)
        {
            char * result;

            inter_sp = sp;
            result = intersect_strings((sp-1)->u.string, sp->u.string, MY_TRUE);
            free_string_svalue(sp);
            sp--;
            free_string_svalue(sp);
            put_malloced_string(sp, result);
            break;
        }

        else
            goto bad_arg_1;

        goto bad_arg_2;
    }

    CASE(F_MULTIPLY);               /* --- multiply            --- */
    {
        /* Multiply sp[-1] by sp[0] pop both arguments from the stack
         * and push the result.
         * TODO: Could be extended to cover mappings.
         * TODO:: array/string multiplied by element === implode.
         *
         * Possible type combinations:
         *   int         * int                -> int
         *   float       * (int,float)        -> float
         *   int         * float              -> float
         *   string      * int                -> string
         *   int         * string             -> string
         *   array       * int                -> array
         *   int         * array              -> array
         */

        p_int i;

        switch ( sp[-1].type )
        {
        case T_NUMBER:
            if (sp->type == T_NUMBER)
            {
                p_int left = (sp-1)->u.number;
                p_int right = sp->u.number;

                if (left > 0 && right > 0)
                {
                    if ((left != 0 && PINT_MAX / left < right)
                     || (right != 0 && PINT_MAX / right < left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld * %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
                else if (left < 0 && right < 0)
                {
                    if ((left != 0 && PINT_MAX / left > right)
                     || (right != 0 && PINT_MAX / right > left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld * %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
                else if (left != 0 && right != 0)
                {
                    if ((left > 0 && PINT_MIN / left > right)
                     || (right > 0 && PINT_MIN / right > left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld * %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
                i = left * right;
                sp--;
                sp->u.number = i;
                break;
            }
            if (sp->type == T_FLOAT)
            {
                STORE_DOUBLE_USED
                double product;

                product = (sp-1)->u.number * READ_DOUBLE(sp);
                if (product < (-DBL_MAX) || product > DBL_MAX)
                    ERRORF(("Numeric overflow: %ld * %g\n"
                           , (long)(sp-1)->u.number, READ_DOUBLE(sp)));
                sp--;
                STORE_DOUBLE(sp, product);
                sp->type = T_FLOAT;
                break;
            }
            if (sp->type == T_STRING)
            {
                char * result;
                size_t reslen;
                size_t len;

                if (sp[-1].u.number < 0)
                    goto bad_arg_1;

                len = svalue_strlen(sp);

                if (len > (size_t)PINT_MAX
                 || (len != 0             && PINT_MAX / len < sp[-1].u.number)
                 || (sp[-1].u.number != 0 && PINT_MAX / sp[-1].u.number < len)
                   )
                    ERROR("Result string too long.\n");

                reslen = (size_t)sp[-1].u.number * len;
                result = xalloc(reslen+1);
                if (!result)
                    ERROR("Out of memory.\n");

                if (sp[-1].u.number > 0 && len)
                {
                    size_t curlen;

                    /* Seed result[] with one copy of the string */
                    memcpy(result, sp->u.string, len);

                    /* Repeatedly double the string in result */
                    curlen = len;
                    while (2*curlen < reslen)
                    {
                        memcpy(result+curlen, result, curlen);
                        curlen *= 2;
                    }

                    /* Fill up result to the full length */
                    if (reslen > curlen)
                        memcpy(result+curlen, result, reslen-curlen);
                }

                result[reslen] = '\0';
                DYN_STRING_COST(reslen)
                free_svalue(sp);
                sp--;
                /* No free_svalue(sp): it's just a number */
                put_malloced_string(sp, result);
                break;
            }
            if (sp->type == T_POINTER)
            {
                vector_t *result;
                mp_int reslen;
                size_t len;

                if (sp[-1].u.number < 0)
                    goto bad_arg_1;

                inter_sp = sp;
                inter_pc = pc;
                len = VEC_SIZE(sp->u.vec);
                reslen = sp[-1].u.number * (mp_int)len;
                result = allocate_uninit_array(reslen);

                if (sp[-1].u.number > 0 && len)
                {
                    size_t left;
                    svalue_t *from, *to;

                    /* Seed result[] with one copy of the array.
                     * To save memory we make sure that all strings
                     * are made shared.
                     */
                    for ( from = sp->u.vec->item, to = result->item, left = len
                        ; left
                        ; from++, to++, left--)
                    {
                        if (from->type == T_STRING
                         && from->x.string_type == STRING_MALLOC
                           )
                        {
                            put_string(to, make_shared_string(from->u.string));
                            if (!to->u.string)
                                ERROR("Out of memory.\n");
                        }
                        else
                            assign_svalue_no_free(to, from);
                    } /* for() seed */

                    /* Now fill the remainder of the vector with
                     * the values already copied in there.
                     */
                    for (from = result->item, left = reslen - len
                        ; left
                        ; to++, from++, left--
                        )
                        assign_svalue_no_free(to, from);
                } /* if (len) */

                free_svalue(sp);
                sp--;
                /* No free_svalue(sp): it's just a number */
                put_array(sp, result);
                break;
            }
            goto bad_arg_2;
        case T_FLOAT:
          {
            STORE_DOUBLE_USED
            double product;

            if (sp->type == T_FLOAT)
            {
                product = READ_DOUBLE(sp-1) * READ_DOUBLE(sp);
                if (product < (-DBL_MAX) || product > DBL_MAX)
                    ERRORF(("Numeric overflow: %g * %g\n"
                           , READ_DOUBLE(sp-1), READ_DOUBLE(sp)));
                STORE_DOUBLE(sp-1, product);
                sp--;
                break;
            }
            if (sp->type == T_NUMBER)
            {
                product = READ_DOUBLE(sp-1) * sp->u.number;
                if (product < (-DBL_MAX) || product > DBL_MAX)
                    ERRORF(("Numeric overflow: %g * %ld\n"
                           , READ_DOUBLE(sp-1), (long)sp->u.number));
                STORE_DOUBLE(sp-1, product);
                sp--;
                break;
            }
            goto bad_arg_2;
          }
        case T_STRING:
          {
            if (sp->type == T_NUMBER)
            {
                char * result;
                size_t reslen;
                size_t len;
                size_t curlen;

                if (sp->u.number < 0)
                    goto bad_arg_2;

                len = svalue_strlen(sp-1);

                if (len > (size_t)PINT_MAX
                 || (len != 0          && PINT_MAX / len < sp->u.number)
                 || (sp->u.number != 0 && PINT_MAX / sp->u.number < len)
                   )
                    ERROR("Result string too long.\n");

                reslen = (size_t)sp->u.number * len;
                result = xalloc(reslen+1);
                if (!result)
                    ERROR("Out of memory.\n");

                if (sp->u.number > 0 && len)
                {
                    /* Seed result[] with one copy of the string */
                    memcpy(result, sp[-1].u.string, len);

                    /* Repeatedly double the string in result */
                    curlen = len;
                    while (2*curlen < reslen)
                    {
                        memcpy(result+curlen, result, curlen);
                        curlen *= 2;
                    }

                    /* Fill up result to the full length */
                    if (reslen > curlen)
                        memcpy(result+curlen, result, reslen-curlen);
                }

                result[reslen] = '\0';
                DYN_STRING_COST(reslen)
                /* No free_svalue(sp): it's just a number */
                sp--;
                free_string_svalue(sp);
                put_malloced_string(sp, result);
                break;
            }
            goto bad_arg_2;
          }
        case T_POINTER:
          {
            if (sp->type == T_NUMBER)
            {
                vector_t *result;
                mp_int reslen;
                size_t len;

                if (sp->u.number < 0)
                    goto bad_arg_2;

                inter_sp = sp;
                inter_pc = pc;
                len = VEC_SIZE(sp[-1].u.vec);
                reslen = sp->u.number * (mp_int)len;
                result = allocate_uninit_array(reslen);

                if (sp->u.number > 0 && len)
                {
                    size_t left;
                    svalue_t *from, *to;

                    /* Seed result[] with one copy of the array.
                     * To save memory we make sure that all strings
                     * are made shared.
                     */
                    for ( from = sp[-1].u.vec->item, to = result->item, left = len
                        ; left
                        ; from++, to++, left--)
                    {
                        if (from->type == T_STRING
                         && from->x.string_type == STRING_MALLOC
                           )
                        {
                            put_string(to, make_shared_string(from->u.string));
                            if (!to->u.string)
                                ERROR("Out of memory.\n");
                        }
                        else
                            assign_svalue_no_free(to, from);
                    } /* for() seed */

                    /* Now fill the remainder of the vector with
                     * the values already copied in there.
                     */
                    for (from = result->item, left = reslen - len
                        ; left
                        ; to++, from++, left--
                        )
                        assign_svalue_no_free(to, from);
                } /* if (len) */

                /* No free_svalue(sp): it's just a number */
                sp--;
                free_svalue(sp);
                put_array(sp, result);
                break;
              }
            goto bad_arg_2;
          }
        default:
            goto bad_arg_1;
        }
        break;
    }

    CASE(F_DIVIDE);                 /* --- divide              --- */
    {
        /* Divide sp[-1] by sp[0] pop both arguments from the stack
         * and push the result.
         * TODO: Could be extended to cover arrays and mappings.
         * TODO:: array/string divided by element === explode.
         *
         * Possible type combinations:
         *   int         / int                -> int
         *   float       / (int,float)        -> float
         *   int         / float              -> float
         */

        int i;

        if ((sp-1)->type == T_NUMBER)
        {
            if (sp->type == T_NUMBER) {
                if (sp->u.number == 0)
                    ERROR("Division by zero\n")
                if ((sp-1)->u.number == PINT_MIN && sp->u.number == -1)
                    ERRORF(("Numeric overflow: %ld / -1\n"
                           , (long)(sp-1)->u.number
                           ));
                i = (sp-1)->u.number / sp->u.number;
                sp--;
                sp->u.number = i;
                break;
            }
            if (sp->type == T_FLOAT)
            {
                double dtmp;
                STORE_DOUBLE_USED

                dtmp = READ_DOUBLE( sp );
                if (dtmp == 0.)
                    ERROR("Division by zero\n")
                sp--;
                dtmp = (double)sp->u.number / dtmp;
                if (dtmp < (-DBL_MAX) || dtmp > DBL_MAX)
                    ERRORF(("Numeric overflow: %ld / %g\n"
                           , (long)(sp)->u.number, READ_DOUBLE(sp+1)));
                STORE_DOUBLE(sp, dtmp);
                sp->type = T_FLOAT;
                break;
            }
            goto bad_arg_2;
        }
        else if ((sp-1)->type == T_FLOAT)
        {
            double dtmp;
            STORE_DOUBLE_USED

            if (sp->type == T_FLOAT)
            {
                dtmp = READ_DOUBLE( sp );
                if (dtmp == 0.) {
                    ERROR("Division by zero\n")
                    return MY_FALSE;
                }
                sp--;
                dtmp = READ_DOUBLE(sp) / dtmp;
                if (dtmp < (-DBL_MAX) || dtmp > DBL_MAX)
                    ERRORF(("Numeric overflow: %g / %g\n"
                           , READ_DOUBLE(sp), READ_DOUBLE(sp+1)));
                STORE_DOUBLE(sp, dtmp);
                break;
            }
            if (sp->type == T_NUMBER)
            {
                if (sp->u.number == 0) {
                    ERROR("Division by zero\n")
                    return MY_FALSE;
                }
                dtmp = (double)sp->u.number;
                sp--;
                dtmp = READ_DOUBLE(sp) / dtmp;
                if (dtmp < (-DBL_MAX) || dtmp > DBL_MAX)
                    ERRORF(("Numeric overflow: %g / %ld\n"
                           , READ_DOUBLE(sp), (long)(sp+1)->u.number));
                STORE_DOUBLE(sp, dtmp);
                break;
            }
            goto bad_arg_2;
        }
        goto bad_arg_1;
        break;
    }

    CASE(F_MOD);                    /* --- mod                 --- */
    {
        /* Compute sp[-1] modulus sp[0] pop both arguments from the stack
         * and push the result.
         * TODO: Could be extended to cover floats(!), arrays and mappings.
         * TODO: Define properly and add the rem operation.
         *
         * Possible type combinations:
         *   int         % int                -> int
         */

        int i;

        if ((sp-1)->type != T_NUMBER)
            goto bad_arg_1;
        if (sp->type != T_NUMBER)
            goto bad_arg_2;
        if (sp->u.number == 0)
        {
            ERROR("Modulus by zero.\n")
            return MY_FALSE;
        }
        else if (sp->u.number == 1
              || sp->u.number == -1
                )
            i = 0;
              /* gcc 2.91 on Linux/x86 generates buggy code
               * for MIN_INT % -1. Might as well catch it all.
               */
        else
            i = (sp-1)->u.number % sp->u.number;
        sp--;
        sp->u.number = i;
        break;
    }

    CASE(F_GT);                     /* --- gt                  --- */
    {
        /* Test if sp[-1] > sp[0]. If yes, push 1 onto the stack,
         * else 0 (of course after popping both arguments).
         *
         * Comparable types are int, string and float, each only
         * to its own type.
         */

        int i;

        if ((sp-1)->type == T_STRING && sp->type == T_STRING)
        {
            i = strcmp((sp-1)->u.string, sp->u.string) > 0;
            free_string_svalue(sp);
            sp--;
            free_string_svalue(sp);
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
        {
            i = (sp-1)->u.number > sp->u.number;
            sp--;
            sp->u.number = i;
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
        {
            i = READ_DOUBLE( sp-1 ) > READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
        {
            i = (double)((sp-1)->u.number) > READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
        {
            i = READ_DOUBLE( sp-1 ) > (double)(sp->u.number);
            sp--;
            put_number(sp, i);
            break;
        }

        if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_1;
        if (!(  sp   ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_2;
        ERROR("Arguments to > don't match\n")
        /* TODO: Give type and value */
    }

    CASE(F_GE);                     /* --- ge                  --- */
    {
        /* Test if sp[-1] >= sp[0]. If yes, push 1 onto the stack,
         * else 0 (of course after popping both arguments).
         *
         * Comparable types are int, string and float, each only
         * to its own type.
         */

        int i;

        if ((sp-1)->type == T_STRING && sp->type == T_STRING)
        {
            i = strcmp((sp-1)->u.string, sp->u.string) >= 0;
            free_string_svalue(sp);
            sp--;
            free_string_svalue(sp);
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
        {
            i = (sp-1)->u.number >= sp->u.number;
            sp--;
            sp->u.number = i;
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
        {
            i = READ_DOUBLE( sp-1 ) >= READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
        {
            i = (double)((sp-1)->u.number) >= READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
        {
            i = READ_DOUBLE( sp-1 ) >= (double)(sp->u.number);
            sp--;
            put_number(sp, i);
            break;
        }

        if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_1;
        if (!(  sp   ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_2;
        ERROR("Arguments to >= don't match\n")
        /* TODO: Give type and value */
    }

    CASE(F_LT);                     /* --- lt                  --- */
    {
        /* Test if sp[-1] < sp[0]. If yes, push 1 onto the stack,
         * else 0 (of course after popping both arguments).
         *
         * Comparable types are int, string and float, each only
         * to its own type.
         */

        int i;

        if ((sp-1)->type == T_STRING && sp->type == T_STRING)
        {
            i = strcmp((sp-1)->u.string, sp->u.string) < 0;
            free_string_svalue(sp);
            sp--;
            free_string_svalue(sp);
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
        {
            i = (sp-1)->u.number < sp->u.number;
            sp--;
            sp->u.number = i;
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
        {
            i = READ_DOUBLE( sp-1 ) < READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
        {
            i = (double)((sp-1)->u.number) < READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
        {
            i = READ_DOUBLE( sp-1 ) < (double)(sp->u.number);
            sp--;
            put_number(sp, i);
            break;
        }

        if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_1;
        if (!(  sp   ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_2;
        ERROR("Arguments to < don't match\n")
        /* TODO: Give error and type */
    }

    CASE(F_LE);                     /* --- le                  --- */
    {
        /* Test if sp[-1] <= sp[0]. If yes, push 1 onto the stack,
         * else 0 (of course after popping both arguments).
         *
         * Comparable types are int, string and float, each only
         * to its own type.
         */

        int i;

        if ((sp-1)->type == T_STRING && sp->type == T_STRING)
        {
            i = strcmp((sp-1)->u.string, sp->u.string) <= 0;
            free_string_svalue(sp);
            sp--;
            free_string_svalue(sp);
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_NUMBER)
        {
            i = (sp-1)->u.number <= sp->u.number;
            sp--;
            sp->u.number = i;
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_FLOAT)
        {
            i = READ_DOUBLE( sp-1 ) <= READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
        {
            i = (double)((sp-1)->u.number) <= READ_DOUBLE( sp );
            sp--;
            put_number(sp, i);
            break;
        }

        if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
        {
            i = READ_DOUBLE( sp-1 ) <= (double)(sp->u.number);
            sp--;
            put_number(sp, i);
            break;
        }

        if (!( (sp-1)->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_1;
        if (!(  sp   ->type & (T_NUMBER|T_STRING|T_FLOAT) ))
            goto bad_arg_2;

        ERROR("Arguments to <= don't match\n")
        /* TODO: Give type and value */
    }

    CASE(F_EQ);                     /* --- eq                  --- */
    {
        /* Test if sp[-1] == sp[0]. If yes, push 1 onto the stack,
         * else 0 (of course after popping both arguments).
         *
         * Comparable types are all types, each to its own. Comparisons
         * between distinct types (with the exception of int vs float)
         * always yield 'unequal'.
         * Vectors and mappings are compared by ref only.
         */

        int i;

        if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
        {
            i = READ_DOUBLE( sp-1 ) == (double)(sp->u.number);
        }
        else if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
        {
            i = (double)((sp-1)->u.number) == READ_DOUBLE( sp );
        }
        else if ((sp-1)->type != sp->type)
        {
            i = 0;
        }
        else /* types are equal */
        {
            switch(sp->type)
            {
            case T_NUMBER:
                i = (sp-1)->u.number == sp->u.number;
                break;
            case T_POINTER:
                i = (sp-1)->u.vec == sp->u.vec;
                break;
            case T_STRING:
                i = strcmp((sp-1)->u.string, sp->u.string) == 0;
                break;
            case T_OBJECT:
                i = (sp-1)->u.ob == sp->u.ob;
                break;
            case T_FLOAT:
                i = READ_DOUBLE( sp-1 ) == READ_DOUBLE( sp );
                break;

            case T_CLOSURE:
                i = closure_eq(sp-1, sp);
                break;

            case T_SYMBOL:
            case T_QUOTED_ARRAY:
                i = (sp-1)->u.string  == sp->u.string &&
                    (sp-1)->x.generic == sp->x.generic;
                break;
            case T_MAPPING:
                i = (sp-1)->u.map == sp->u.map;
                break;
            default:
                if (sp->type == T_LVALUE)
                    error("Reference passed to !=\n");
                fatal("Illegal type to !=\n");
                  /* TODO: Give type and value */
                /* NOTREACHED */
                return MY_FALSE;
            }
        }

        pop_stack();
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_NE);                     /* --- ne                  --- */
    {
        /* Test if sp[-1] != sp[0]. If yes, push 1 onto the stack,
         * else 0 (of course after popping both arguments).
         *
         * Comparable types are all types, each to its own. Comparisons
         * between distinct types always yield 'unequal'.
         * Vectors and mappings are compared by ref only.
         */

        int i;

        if ((sp-1)->type == T_FLOAT && sp->type == T_NUMBER)
        {
            i = READ_DOUBLE( sp-1 ) != (double)(sp->u.number);
        }
        else if ((sp-1)->type == T_NUMBER && sp->type == T_FLOAT)
        {
            i = (double)((sp-1)->u.number) != READ_DOUBLE( sp );
        }
        else if ((sp-1)->type != sp->type)
        {
            i = 1;
        }
        else /* types are equal */
        {
            switch(sp->type)
            {
            case T_NUMBER:
                i = (sp-1)->u.number != sp->u.number;
                break;
            case T_STRING:
                i = strcmp((sp-1)->u.string, sp->u.string);
                break;
            case T_POINTER:
                i = (sp-1)->u.vec != sp->u.vec;
                break;
            case T_OBJECT:
                i = (sp-1)->u.ob != sp->u.ob;
                break;
            case T_FLOAT:
                i = READ_DOUBLE( sp-1 ) != READ_DOUBLE( sp );
                break;

            case T_CLOSURE:
                i = !closure_eq(sp-1, sp);
                break;

            case T_SYMBOL:
            case T_QUOTED_ARRAY:
                i = (sp-1)->u.string  != sp->u.string ||
                    (sp-1)->x.generic != sp->x.generic;
                break;
            case T_MAPPING:
                i = (sp-1)->u.map != sp->u.map;
                break;
            default:
                if (sp->type == T_LVALUE)
                    error("Reference passed to !=\n");
                fatal("Illegal type to !=\n");
                /* NOTREACHED */
                return MY_FALSE;
            }
        }

        pop_stack();
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_COMPL);                  /* --- compl               --- */
        /* Compute the binary complement of number sp[0] and leave
         * that on the stack.
         */
        if (sp->type != T_NUMBER)
            ERROR("Bad argument to ~\n")
            /* TODO: Give type and value */
        sp->u.number = ~ sp->u.number;
        break;

    CASE(F_AND);                    /* --- and                 --- */
    {
        /* Compute the intersection of sp[-1] and sp[0] and leave
         * the result on the stack.
         *
         * Possible type combinations:
         *   int    & int    -> int
         *   string & string -> string
         *   vector & vector -> vector
         *
         * TODO: Extend this to mappings.
         */

        int i;

        if (sp->type == T_POINTER && (sp-1)->type == T_POINTER)
        {
            inter_sp = sp - 2;
            (sp-1)->u.vec = intersect_array(sp->u.vec, (sp-1)->u.vec);
            sp--;
            break;
        }

        if (sp->type == T_STRING && (sp-1)->type == T_STRING)
        {
            char * result;

            inter_sp = sp;
            result = intersect_strings(sp[-1].u.string, sp->u.string, MY_FALSE);
            free_string_svalue(sp-1);
            free_string_svalue(sp);
            put_malloced_string(sp-1, result);
            sp--;
            break;
        }

        if ((sp-1)->type != T_NUMBER)
            goto bad_arg_1;
        if (sp->type != T_NUMBER)
            goto bad_arg_2;

        i = (sp-1)->u.number & sp->u.number;
        sp--;
        sp->u.number = i;
        break;
    }

    CASE(F_OR);                     /* --- or                  --- */
    {
        /* Compute the binary-or of sp[-1] and sp[0] and leave
         * the result on the stack.
         *
         * Possible type combinations:
         *   int    | int    -> int
         *
         * TODO: Extend this to vectors and mappings.
         */

        int i;

        if ((sp-1)->type != T_NUMBER)
            goto bad_arg_1;
        if (sp->type != T_NUMBER)
            goto bad_arg_2;
        i = (sp-1)->u.number | sp->u.number;
        sp--;
        sp->u.number = i;
        break;
    }

    CASE(F_XOR);                    /* --- xor                 --- */
    {
        /* Compute the binary-xor of sp[-1] and sp[0] and leave
         * the result on the stack.
         *
         * Possible type combinations:
         *   int ^ int    -> int
         *
         * TODO: Extend this to vectors and mappings.
         */

        int i;

        if ((sp-1)->type != T_NUMBER)
            goto bad_arg_1;
        if (sp->type != T_NUMBER)
            goto bad_arg_2;

        i = (sp-1)->u.number ^ sp->u.number;
        sp--;
        sp->u.number = i;
        break;
    }

    CASE(F_LSH);                    /* --- lsh                 --- */
    {
        /* Shift number sp[-1] left by sp[0] bits and leave
         * the result on the stack.
         *
         * Possible type combinations:
         *   int << int    -> int
         *
         * TODO: Extend this to vectors and mappings.
         * TODO: Implement an arithmetic shift.
         */

        int i;

        if ((sp-1)->type != T_NUMBER)
            goto bad_arg_1;
        if (sp->type != T_NUMBER)
            goto bad_arg_2;

        i = sp->u.number;
        sp--;
        sp->u.number = (uint)i > MAX_SHIFT ? 0 : sp->u.number << i;
        break;
    }

    CASE(F_RSH);                    /* --- rsh                 --- */
    {
        /* Arithmetically shift number sp[-1] right by sp[0] bits and leave
         * the result on the stack.
         *
         * Possible type combinations:
         *   int >> int    -> int
         *
         * TODO: Extend this to vectors and mappings.
         */

        int i;

        if ((sp-1)->type != T_NUMBER)
            goto bad_arg_1;
        if (sp->type != T_NUMBER)
            goto bad_arg_2;

        i = sp->u.number;
        sp--;
        if ((uint)i <= MAX_SHIFT)
            sp->u.number >>= i;
        else if (sp->u.number >= 0)
            sp->u.number = 0;
        else
            sp->u.number = -1;
        break;
    }

    CASE(F_RSHL);                   /* --- rshl                --- */
    {
        /* Logically shift number sp[-1] right by sp[0] bits and leave
         * the result on the stack.
         *
         * Possible type combinations:
         *   int >> int    -> int
         *
         * TODO: Extend this to vectors and mappings.
         */

        int i;

        if ((sp-1)->type != T_NUMBER)
            goto bad_arg_1;
        if (sp->type != T_NUMBER)
            goto bad_arg_2;

        i = sp->u.number;
        sp--;
        if ((uint)i > MAX_SHIFT)
            sp->u.number = 0;
        else
            sp->u.number = (p_uint)sp->u.number >> i;
        break;
    }

    CASE(F_NOT);                    /* --- not                 --- */
        /* Compute the logical negation of sp[0] and put it onto the stack.
         * Every value != 0 is replaced by 0, just number 0 is replaced by 1.
         */

        if (sp->type == T_NUMBER)
        {
            if (sp->u.number == 0)
            {
                sp->u.number = 1;
                break;
            }
        } else
            free_svalue(sp);
        put_number(sp, 0);
        break;

    CASE(F_NX_RANGE);               /* --- nx_range            --- */
    CASE(F_RX_RANGE);               /* --- rx_range            --- */
        /* Push '1' onto the stack to make up for the missing
         * upper range bound, then fall through to the normal
         * range handling.
         */
        sp++;
        put_number(sp, 1);
        /* FALLTHROUGH */

    CASE(F_RANGE);                  /* --- range               --- */
    CASE(F_NR_RANGE);               /* --- nr_range            --- */
    CASE(F_RN_RANGE);               /* --- rn_range            --- */
    CASE(F_RR_RANGE);               /* --- rr_range            --- */
      {
        /* Compute the range sp[-1]..sp[0] from string/array sp[-2]
         * and leave it on the stack.
         * This code also handles the NX/RX_RANGE, pretending that
         * they are NR/RR_RANGEs.
         */

        if (sp[-1].type != T_NUMBER)
            ERROR("Bad type of start interval to [..] range.\n")
            /* TODO: Give type and value */
        if (sp[0].type != T_NUMBER)
            ERROR("Bad type of end interval to [..] range.\n")
            /* TODO: Give type and value */

        if (sp[-2].type == T_POINTER)
        {
            /* Slice a range from an array */

            vector_t *v;
            int size, i1, i2;

            size = (int)VEC_SIZE(sp[-2].u.vec);

            if (instruction == F_RANGE
             || instruction == F_NR_RANGE
             || instruction == F_NX_RANGE)
                i1 = sp[-1].u.number;
            else
                i1 = size - sp[-1].u.number;

            if (instruction == F_RANGE
             || instruction == F_RN_RANGE)
                i2 = sp[0].u.number;
            else
                i2 = size - sp[0].u.number;
            if (i2 >= size)
                i2 = size - 1;

            pop_stack();
            pop_stack();

            v = slice_array(sp->u.vec, i1, i2);

            free_array(sp->u.vec);
            if (v)
            {
                sp->u.vec = v;
            }
            else
            {
                put_number(sp, 0);
            }
        }
        else if (sp[-2].type == T_STRING)
        {
            /* Slice a range from string */

            int len, from, to;
            char *res;

            len = (int)svalue_strlen(&sp[-2]);
            if (instruction == F_RANGE
             || instruction == F_NR_RANGE
             || instruction == F_NX_RANGE)
                from = sp[-1].u.number;
            else
                from = len - sp[-1].u.number;
            if (from < 0)
            {
                from = 0;
            }

            if (instruction == F_RANGE
             || instruction == F_RN_RANGE)
                to = sp[0].u.number;
            else
                to = len - sp[0].u.number;
            if (to >= len)
                to = len-1;

            if (to < from)
            {
                pop_n_elems(3);
                push_volatile_string("");
                break;
            }

            if (to == len-1)
            {
                res = string_copy(sp[-2].u.string + from);
                pop_n_elems(3);
                push_malloced_string(res);
                break;
            }

            res = xalloc((size_t)(to - from + 2));
            strncpy(res, sp[-2].u.string + from, (size_t)(to - from + 1));
            res[to - from + 1] = '\0';
            pop_n_elems(3);
            push_malloced_string(res);
        }
        else
        {
            ERROR("Bad argument to [..] range operand: neither string nor array.\n")
            /* TODO: Give type and value */
        }
        break;
      }

    CASE(F_ADD_EQ);                 /* --- add_eq              --- */
    CASE(F_VOID_ADD_EQ);            /* --- void_add_eq         --- */
    {
        /* Add sp[-1] to the value designated by lvalue sp[0] (the order
         * is important) and assign the result to sp[0].
         * For F_ADD_EQ, the result is also left on the stack.
         *
         * Possible type combinations:
         *   string       + (string,int,float) -> string
         *   int          + string             -> string
         *   int          + int                -> int
         *   int          + float              -> float
         *   float        + (float,int)        -> float
         *   vector       + vector             -> vector
         *   mapping      + mapping            -> mapping
         * TODO: This type mapping should be documented in 2-dim-arrays,
         * TODO:: one each for F_ADD_EQ, F_MULT_EQ, etc. This would
         * TODO:: also make the checks in the compiler simpler.
         */

        short type2;         /* type and value of sp[-1] */
        union u u2;
        svalue_t *argp; /* the actual value of sp[0] */

        type2 = sp[-1].type;
        u2 = sp[-1].u;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        switch(argp->type)
        {

        case T_STRING:  /* Adding to a string */
          {
            char *new_string;

            /* Perform the addition, creating new_string */
            /* TODO: Make this use memcpy() instead of strcpy() */
            if (type2 == T_STRING)
            {
                size_t l = _svalue_strlen(argp);
                size_t l2 = _svalue_strlen(sp-1);

                DYN_STRING_COST(l+l2)
                inter_pc = pc;
                if ( !(new_string = xalloc(l + l2 + 1)) )
                    ERROR("Out of memory\n");
                memcpy(new_string, argp->u.string, l);
                memcpy(new_string+l, u2.string, l2+1);
                free_string_svalue(sp-1);
                sp -= 2;
            }
            else if (type2 == T_NUMBER)
            {
                char buff[80];
                size_t l = _svalue_strlen(argp);

                DYN_STRING_COST(l)
                buff[sizeof(buff)-1] = '\0';
                sprintf(buff, "%ld", (long)u2.number);
                if (buff[sizeof(buff)-1] != '\0')
                    FATAL("Buffer overflow in F_ADD_EQ: int number too big.\n")
                inter_pc = pc;
                if ( !(new_string =
                       xalloc(l + strlen(buff) + 1)) )
                    ERROR("Out of memory\n");
                strcpy(new_string, argp->u.string);
                strcat(new_string, buff);
                sp -= 2;
            }
            else if (type2 == T_FLOAT)
            {
                char buff[160];
                size_t l = _svalue_strlen(argp);

                DYN_STRING_COST(l)
                buff[sizeof(buff)-1] = '\0';
                sprintf(buff, "%g", READ_DOUBLE(sp-1) );
                if (buff[sizeof(buff)-1] != '\0')
                    FATAL("Buffer overflow in F_ADD_EQ: float number too big.\n")
                inter_pc = pc;
                if ( !(new_string =
                       xalloc(l + strlen(buff) + 1)) )
                    ERROR("Out of memory\n");
                strcpy(new_string, argp->u.string);
                strcat(new_string, buff);
                sp -= 2;
            }
            else
            {
                goto bad_arg_2;
            }

            /* Replace *argp by the new string */
            free_string_svalue(argp);
            argp->x.string_type = STRING_MALLOC;
            argp->u.string = new_string;
            break;
          }

        case T_NUMBER:  /* Add to a number */
            if (type2 == T_NUMBER)
            {
                p_int left = argp->u.number;
                p_int right = u2.number;

                if ((left >= 0 && right >= 0 && PINT_MAX - left < right)
                 || (left < 0 && right < 0 && PINT_MIN - left > right)
                   )
                {
                    ERRORF(("Numeric overflow: %ld += %ld\n"
                           , (long)left, (long)right));
                    /* NOTREACHED */
                    break;
                }

                if (instruction == F_VOID_ADD_EQ)
                {
                    argp->u.number += u2.number;
                    sp -= 2;
                    goto again;
                }
                (--sp)->u.number = argp->u.number += u2.number;
                goto again;
            }
            else if (type2 == T_FLOAT)
            {
                STORE_DOUBLE_USED
                double sum;

                sum = (double)(argp->u.number) + READ_DOUBLE(sp-1);
                if (sum < (-DBL_MAX) || sum > DBL_MAX)
                    ERRORF(("Numeric overflow: %ld + %g\n"
                           , (long)argp->u.number, READ_DOUBLE(sp-1)));
                argp->type = T_FLOAT;
                STORE_DOUBLE(argp, sum);
                if (instruction == F_VOID_ADD_EQ)
                {
                    sp -= 2;
                    goto again;
                }

                --sp;
                sp->type = T_FLOAT;
                STORE_DOUBLE(sp, sum);
                goto again;
            }
            else if (type2 == T_STRING)
            {
                char buff[80], *res;
                size_t len1;

                buff[sizeof(buff)-1] = '\0';
                sprintf(buff, "%ld", argp->u.number);
                if (buff[sizeof(buff)-1] != '\0')
                    FATAL("Buffer overflow in F_ADD_EQ: int number too big.\n")
                inter_pc = pc;
                inter_sp = sp;
                res = xalloc(svalue_strlen(sp-1) + (len1 = strlen(buff)) + 1);
                if (!res)
                    ERROR("Out of memory\n")
                strcpy(res, buff);
                strcpy(res+len1, u2.string);
                free_string_svalue(sp-1);

                /* Overwrite the number in argp */
                put_malloced_string(argp, res);

                if (instruction == F_VOID_ADD_EQ)
                {
                    sp -= 2;
                    goto again;
                }

                --sp;
                res = string_copy(res);
                if (!res)
                    ERROR("Out of memory\n")
                put_malloced_string(sp, res);

                goto again;
            }
            else
            {
                ERROR("Bad type number to rhs +=.\n")
                /* TODO: Give type and value */
            }
            break;

        case T_CHAR_LVALUE:  /* Add to a character in a string */
            if (type2 == T_NUMBER)
            {
                p_int left = *argp->u.string;
                p_int right = u2.number;

                if ((left >= 0 && right >= 0 && PINT_MAX - left < right)
                 || (left < 0 && right < 0 && PINT_MIN - left > right)
                   )
                {
                    ERRORF(("Numeric overflow: %ld += %ld\n"
                           , (long)left, (long)right));
                    /* NOTREACHED */
                    break;
                }

                if (((left + right) & 0xff) == 0)
                    ERROR("Can't set string character to 0.\n");
                if (instruction == F_VOID_ADD_EQ)
                {
                    *argp->u.string += u2.number;
                    sp -= 2;
                    goto again;
                }
                (--sp)->u.number = *argp->u.string += u2.number;
                goto again;
            }
            else
            {
                ERROR("Bad type number to rhs +=.\n")
                /* TODO: Give type and value */
            }
            break;

        case T_MAPPING:  /* Add to a mapping */
            if (type2 != T_MAPPING)
            {
                ERROR("Bad type to rhs +=.\n")
                /* TODO: Give type and value */
            }
            else
            {
                check_map_for_destr(u2.map);
                add_to_mapping(argp->u.map, u2.map);
                sp -= 2;
                free_mapping(u2.map);
                if (max_mapping_size && MAP_SIZE(argp->u.map) > max_mapping_size)
                {
                    check_map_for_destr(argp->u.map);
                    if (max_mapping_size && MAP_SIZE(argp->u.map) > max_mapping_size)
                        ERRORF(("Illegal mapping size: %ld\n", MAP_SIZE(argp->u.map)));
                }
            }
            break;

        case T_POINTER:  /* Add to an array */
            if (type2 != T_POINTER)
            {
                ERROR("Bad type to rhs +=.\n")
                /* TODO: Give type and value */
            }
            else
            {
                vector_t *v;

                inter_sp = sp;
                inter_pc = pc;
                v = inter_add_array(u2.vec, &argp->u.vec);
                if (instruction == F_VOID_ADD_EQ)
                {
                    sp -= 2;
                    goto again;
                }
                sp--;
                sp->u.vec = ref_array(v);
                goto again;
                }
            break;

        case T_FLOAT:  /* Add to a float */
            if (type2 == T_FLOAT)
            {
                STORE_DOUBLE_USED
                double d;

                   /* don't use the address of u2, this would prevent putting
                    * it in a register
                    */
                d = READ_DOUBLE(argp) + READ_DOUBLE(sp-1);
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g + %g\n"
                           , READ_DOUBLE(argp), READ_DOUBLE(sp-1)));
                STORE_DOUBLE(argp, d);
                sp -= 2;
            }
            else if (type2 == T_NUMBER)
            {
                STORE_DOUBLE_USED
                double d;

                d = READ_DOUBLE(argp) + (double)sp[-1].u.number;
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g + %ld\n"
                           , READ_DOUBLE(argp), (long)(sp-1)->u.number));
                STORE_DOUBLE(argp, d);
                sp -= 2;
            }
            else
            {
                goto bad_right;
            }
            break;

        default:
            ERROR("Bad type to lhs +=\n")
            /* TODO: Give type and value */
        } /* end of switch */

        /* If the instruction is F_ADD_EQ, leave the result on the stack */
        if (instruction != F_VOID_ADD_EQ)
        {
            sp++;
            assign_svalue_no_free(sp, argp);
        }
        break;
    }

    CASE(F_SUB_EQ);                 /* --- sub_eq              --- */
    {
        /* Subtract sp[-1] from the value designated by lvalue sp[0] (the
         * order is important), assign the result to sp[0] and also leave
         * it on the stack.
         *
         * Possible type combinations:
         *   int         - int                -> int
         *   float       - (float,int)        -> float
         *   string      - string             -> string
         *   vector      - vector             -> vector
         *   mapping     - mapping            -> mapping
         */

        short type2;         /* type and value of sp[-1] */
        union u u2;
        svalue_t *argp; /* the actual value of sp[0] */

        type2 = sp[-1].type;
        u2 = sp[-1].u;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        switch (argp->type)
        {
        case T_NUMBER:  /* Subtract from a number */
            if (type2 != T_NUMBER)
                goto bad_right;
            sp--;

            {
                p_int left = argp->u.number;
                p_int right = u2.number;

                if ((left >= 0 && right < 0 && PINT_MAX + right < left)
                 || (left < 0 && right >= 0 && PINT_MIN + right > left)
                   )
                {
                    ERRORF(("Numeric overflow: %ld -= %ld\n"
                           , (long)left, (long)right));
                    /* NOTREACHED */
                    break;
                }
            }

            sp->u.number = argp->u.number -= u2.number;
            break;

        case T_CHAR_LVALUE:  /* Subtract from a char in a string */
            if (type2 != T_NUMBER)
                goto bad_right;

            {
                p_int left = *argp->u.string;
                p_int right = u2.number;

                if ((left >= 0 && right < 0 && PINT_MAX + right < left)
                 || (left < 0 && right >= 0 && PINT_MIN + right > left)
                   )
                {
                    ERRORF(("Numeric overflow: %ld -= %ld\n"
                           , (long)left, (long)right));
                    /* NOTREACHED */
                    break;
                }
            }

            if (((*argp->u.string - u2.number) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            sp--;
            sp->u.number = *argp->u.string -= u2.number;
            break;

        case T_STRING:   /* Subtract from a string */
        {
            char * result;

            if (type2 != T_STRING)
                goto bad_right;

            inter_sp = sp;
            result = intersect_strings(argp->u.string, (sp-1)->u.string, MY_TRUE);
            free_string_svalue(argp);
            put_malloced_string(argp, result);
            free_svalue(sp);
            sp--;
            free_string_svalue(sp);
            put_malloced_string(sp, string_copy(result));
            break;
        }

        case T_POINTER:  /* Subtract from an array */
          {
            vector_t *v, *v_old;

            if (type2 != T_POINTER)
                goto bad_right;

            v = u2.vec;

            /* Duplicate the minuend array if necessary, as
             * the subtraction will change and free it
             */
            if (v->ref > 1)
            {
                deref_array(v);
                v = slice_array(v, 0, (mp_int)VEC_SIZE(v)-1 );
            }
            sp--;
            v_old = argp->u.vec;
            v = subtract_array(v_old, v);
            argp->u.vec = v;
            put_ref_array(sp, v);
            break;
          }

        case T_FLOAT:  /* Subtract from a float */
            if (type2 == T_FLOAT)
            {
                STORE_DOUBLE_USED
                double d;

                /* don't use the address of u2, this would prevent putting it
                 * in a register
                 */
                sp--;
                d = READ_DOUBLE(argp) - READ_DOUBLE(sp);
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g + %g\n"
                           , READ_DOUBLE(argp), READ_DOUBLE(sp)));
                STORE_DOUBLE(argp, d);
                *sp = *argp;
            }
            else if (type2 == T_NUMBER)
            {
                STORE_DOUBLE_USED
                double d;

                sp--;
                d = READ_DOUBLE(argp) - (double)sp->u.number;
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g + %ld\n"
                           , READ_DOUBLE(argp), (long)sp->u.number));
                STORE_DOUBLE(argp, d);
                *sp = *argp;
            }
            else
            {
                goto bad_right;
            }
            break;

        case T_MAPPING:  /* Subtract from a mapping */
            if (type2 == T_MAPPING)
            {
                mapping_t *m;

                sp--;
                m = sp->u.map;
                check_map_for_destr(m);

                /* Test for the special case 'm - m' */
                if (m == argp->u.map)
                {
                    /* m->ref is > 1, because the content of the lvalue is
                     * associated with a ref
                     */
                    deref_mapping(m);
                    m = copy_mapping(m);
                }

                walk_mapping(m, sub_from_mapping_filter, argp->u.map);
                free_mapping(m);
                sp->u.map = ref_mapping(argp->u.map);
            }
            else
            {
                goto bad_right;
            }
            break;

        default:
            goto bad_left;
        } /* end of switch */
        break;
    }

    CASE(F_MULT_EQ);                /* --- mult_eq             --- */
    {
        /* Multiply sp[-1] to the value designated by lvalue sp[0],
         * assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int         * int                -> int
         *   float       * (float,int)        -> float
         *   string      * int                -> string
         *   array       * int                -> array
         *
         * TODO: Extend this to mappings.
         */

        svalue_t *argp;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;

            {
                p_int left = argp->u.number;
                p_int right = sp->u.number;

                if (left > 0 && right > 0)
                {
                    if ((left != 0 && PINT_MAX / left < right)
                     || (right != 0 && PINT_MAX / right < left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld *= %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
                else if (left < 0 && right < 0)
                {
                    if ((left != 0 && PINT_MAX / left > right)
                     || (right != 0 && PINT_MAX / right > left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld *= %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
                else if (left != 0 && right != 0)
                {
                    if ((left > 0 && PINT_MIN / left > right)
                     || (right > 0 && PINT_MIN / right > left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld *= %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
            }

            sp->u.number = argp->u.number *= sp->u.number;
            break;
        }

        if (argp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            sp--;
            if (sp->type == T_FLOAT)
            {
                d = READ_DOUBLE(argp) * READ_DOUBLE(sp);
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g * %g\n"
                           , READ_DOUBLE(argp), READ_DOUBLE(sp)));
                STORE_DOUBLE(argp, d);
                *sp = *argp;
            }
            else if (sp->type == T_NUMBER)
            {
                d = READ_DOUBLE(argp) * (double)sp->u.number;
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g * %ld\n"
                           , READ_DOUBLE(argp), (long)sp->u.number));
                STORE_DOUBLE(argp, d);
                *sp = *argp;
            }
            else
                goto bad_right;
            break;
        }

        if (argp->type == T_STRING)
        {
            char * result;
            size_t reslen;
            size_t len;

            sp--;
            if (sp->type != T_NUMBER || sp->u.number < 0)
                goto bad_right;

            len = svalue_strlen(argp);

            if (len > (size_t)PINT_MAX
             || (len != 0          && PINT_MAX / len < sp->u.number)
             || (sp->u.number != 0 && PINT_MAX / sp->u.number < len)
               )
                ERROR("Result string too long.\n");

            reslen = (size_t)sp->u.number * len;
            result = xalloc(reslen+1);
            if (!result)
                ERROR("Out of memory.\n");

            if (sp->u.number > 0 && len)
            {
                size_t curlen;

                /* Seed result[] with one copy of the string */
                memcpy(result, argp->u.string, len);

                /* Repeatedly double the string in result */
                curlen = len;
                while (2*curlen < reslen)
                {
                    memcpy(result+curlen, result, curlen);
                    curlen *= 2;
                }

                /* Fill up result to the full length */
                if (reslen > curlen)
                    memcpy(result+curlen, result, reslen-curlen);
            }

            result[reslen] = '\0';
            DYN_STRING_COST(reslen)

            free_string_svalue(argp);
            put_malloced_string(argp, result);
            assign_svalue_no_free(sp, argp);
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;

            {
                p_int left = *argp->u.string;
                p_int right = sp->u.number;

                if (left > 0 && right > 0)
                {
                    if ((left != 0 && PINT_MAX / left < right)
                     || (right != 0 && PINT_MAX / right < left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld *= %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
                else if (left < 0 && right < 0)
                {
                    if ((left != 0 && PINT_MAX / left > right)
                     || (right != 0 && PINT_MAX / right > left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld *= %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
                else if (left != 0 && right != 0)
                {
                    if ((left > 0 && PINT_MIN / left > right)
                     || (right > 0 && PINT_MIN / right > left)
                       )
                    {
                        ERRORF(("Numeric overflow: %ld *= %ld\n"
                               , (long)left, (long)right));
                        /* NOTREACHED */
                        break;
                    }
                }
            }

            if (((*argp->u.string * sp->u.number) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            sp->u.number = *argp->u.string *= sp->u.number;
            break;
        }

        if (argp->type == T_POINTER)
        {
            vector_t *result;
            mp_int reslen;
            size_t len;

            sp--;
            if (sp->type != T_NUMBER || sp->u.number < 0)
                goto bad_right;

            inter_sp = sp;
            inter_pc = pc;
            len = VEC_SIZE(argp->u.vec);
            reslen = sp->u.number * (mp_int)len;
            result = allocate_uninit_array(reslen);

            if (sp->u.number > 0 && len)
            {
                size_t left;
                svalue_t *from, *to;

                /* Seed result[] with one copy of the array.
                 * To save memory we make sure that all strings
                 * are made shared.
                 */
                for ( from = argp->u.vec->item, to = result->item, left = len
                    ; left
                    ; from++, to++, left--)
                {
                    if (from->type == T_STRING
                     && from->x.string_type == STRING_MALLOC
                       )
                    {
                        put_string(to, make_shared_string(from->u.string));
                        if (!to->u.string)
                            ERROR("Out of memory.\n");
                    }
                    else
                        assign_svalue_no_free(to, from);
                } /* for() seed */

                /* Now fill the remainder of the vector with
                 * the values already copied in there.
                 */
                for (from = result->item, left = reslen - len
                    ; left
                    ; to++, from++, left--
                    )
                    assign_svalue_no_free(to, from);
            } /* if (len) */

            free_svalue(argp);
            put_array(argp, result);
            assign_svalue_no_free(sp, argp);
            break;
        }

        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_DIV_EQ);                 /* --- div_eq              --- */
    {
        /* Divide the value designated by lvalue sp[0] by sp[-1],
         * assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int         / int                -> int
         *   float       / (float,int)        -> float
         *
         * TODO: Extend this to arrays and mappings.
         */

        svalue_t *argp;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            if (sp->u.number == 0)
                ERROR("Division by 0\n")
            if (argp->u.number == PINT_MIN && sp->u.number == -1)
                ERRORF(("Numeric overflow: %ld / -1\n"
                       , (long)argp->u.number
                       ));
            sp->u.number = argp->u.number /= sp->u.number;
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            if (sp->u.number == 0)
                ERROR("Division by 0\n")
            if (((*argp->u.string / sp->u.number) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            sp->u.number = *argp->u.string /= sp->u.number;
            break;
        }

        if (argp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            sp--;
            if (sp->type == T_FLOAT)
            {
                d = READ_DOUBLE(sp);
                if (d == 0.0)
                    ERROR("Division by 0\n")
                d = READ_DOUBLE(argp) / d;
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g / %g\n"
                           , READ_DOUBLE(argp), READ_DOUBLE(sp)));
                STORE_DOUBLE(argp, d);
                *sp = *argp;
            }
            else if (sp->type == T_NUMBER)
            {
                p_int i;
                i = sp->u.number;
                if (i == 0)
                    ERROR("Division by 0\n")
                d = READ_DOUBLE(argp) / (double)i;
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: %g / %ld\n"
                           , READ_DOUBLE(argp), (long)sp->u.number));
                STORE_DOUBLE(argp, d);
                *sp = *argp;
            }
            else
                goto bad_right;
            break;
        }
        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_MOD_EQ);                 /* --- mod_eq              --- */
    {
        /* Compute the modulus of the value designated by lvalue sp[0]
         * divided by sp[-1], assign the result to sp[0] and also
         * leave it on the stack.
         *
         * Possible type combinations:
         *   int         % int                -> int
         *
         * TODO: Extend this to arrays and mappings.
         * TODO: Implement the other remainder function.
         */

        svalue_t *argp;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            if (sp->u.number == 0)
                ERROR("Division by 0\n")
            sp->u.number = argp->u.number %= sp->u.number;
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            if (sp->u.number == 0)
                ERROR("Division by 0\n")
            if (((*argp->u.string % sp->u.number) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            sp->u.number = *argp->u.string %= sp->u.number;
            break;
        }

        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_AND_EQ);                 /* --- and_eq              --- */
    {
        /* Intersect the value designated by lvalue sp[0] with sp[-1],
         * assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int    & int    -> int
         *   string & string -> string
         *   array  & array  -> array
         *
         * TODO: Extend this to mappings.
         */

        svalue_t *argp;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)  /* Intersect a number */
        {
            if (sp[-1].type != T_NUMBER)
                goto bad_right;
            sp--;
            sp->u.number = argp->u.number &= sp->u.number;
            break;
        }

        if (argp->type == T_POINTER && sp[-1].type == T_POINTER)
        {
            /* Intersect an array */

            vector_t *vec1, *vec2;

            inter_sp = sp - 2;
            vec1 = argp->u.vec;
            vec2 = sp[-1].u.vec;
            argp->type = T_NUMBER;
            vec1 = intersect_array(vec1, vec2);
            put_ref_array(argp, vec1);
            sp--;
            sp->u.vec = argp->u.vec;
            free_svalue(sp+1);
            break;
        }

        if (argp->type == T_STRING && (sp-1)->type == T_STRING)
        {
            char * result;

            inter_sp = sp;
            result = intersect_strings(argp->u.string, (sp-1)->u.string, MY_FALSE);
            free_string_svalue(argp);
            put_malloced_string(argp, result);
            free_svalue(sp);
            sp--;
            free_string_svalue(sp);
            put_malloced_string(sp, string_copy(result));
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            if (sp[-1].type != T_NUMBER)
                goto bad_right;
            sp--;
            if (((*argp->u.string & sp->u.number) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            sp->u.number = *argp->u.string &= sp->u.number;
            break;
        }

        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_OR_EQ);                  /* --- or_eq               --- */
    {
        /* Binary-Or the value designated by lvalue sp[0] with sp[-1],
         * assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int         & int                -> int
         *
         * TODO: Extend this to mappings and arrays.
         */

        svalue_t *argp;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            sp->u.number = argp->u.number |= sp->u.number;
            break;
        }
        if (argp->type == T_CHAR_LVALUE)
        {
            if (sp[-1].type != T_NUMBER)
                goto bad_right;
            sp--;
            if (((*argp->u.string | sp->u.number) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            sp->u.number = *argp->u.string |= sp->u.number;
            break;
        }

        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_XOR_EQ);                 /* --- xor_eq              --- */
    {
        /* Binary-XOr the value designated by lvalue sp[0] with sp[-1],
         * assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int         ^ int                -> int
         *
         * TODO: Extend this to mappings and arrays.
         */

        svalue_t *argp;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            sp->u.number = argp->u.number ^= sp->u.number;
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            if (sp[-1].type != T_NUMBER)
                goto bad_right;
            sp--;
            if (((*argp->u.string ^ sp->u.number) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            sp->u.number = *argp->u.string ^= sp->u.number;
            break;
        }
        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_LSH_EQ);                 /* --- lsh_eq              --- */
    {
        /* Shift the value designated by lvalue sp[0] left by sp[-1],
         * assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int        << int                -> int
         *
         * TODO: Implement an arithmetic shift.
         */

        int i;
        svalue_t *argp;

#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            i = sp->u.number;
            argp->u.number <<= (uint)i > MAX_SHIFT ? MAX_SHIFT : i;
            sp->u.number = argp->u.number;
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            if (sp[-1].type != T_NUMBER)
                goto bad_right;
            sp--;
            i = sp->u.number;
            if (((*argp->u.string << ((uint)i > MAX_SHIFT ? MAX_SHIFT : i))
                 & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            *argp->u.string <<= (uint)i > MAX_SHIFT ? MAX_SHIFT : i;
            sp->u.number = *argp->u.string;
            break;
        }
        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_RSH_EQ);                 /* --- rsh_eq              --- */
    {
        /* Arithmetically shift the value designated by lvalue sp[0] right by
         * sp[-1], assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int        << int                -> int
         */

        int i;
        svalue_t *argp;
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            i = sp->u.number;
            argp->u.number >>= (uint)i > MAX_SHIFT ? (MAX_SHIFT+1) : i;
            sp->u.number = argp->u.number;
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            if (sp[-1].type != T_NUMBER)
                goto bad_right;
            sp--;
            i = sp->u.number;
            if (((*argp->u.string >> ((uint)i > MAX_SHIFT ? MAX_SHIFT : i))
                 & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            *argp->u.string >>= (uint)i > MAX_SHIFT ? MAX_SHIFT : i;
            sp->u.number = *argp->u.string;
            break;
        }
        goto bad_left;
        /* NOTREACHED */ break;
    }

    CASE(F_RSHL_EQ);               /* --- rshl_eq              --- */
    {
        /* Logically shift the value designated by lvalue sp[0] right by
         * sp[-1], assign the result to sp[0] and also leave it on the stack.
         *
         * Possible type combinations:
         *   int        << int                -> int
         */

        int i;
        svalue_t *argp;
#ifdef DEBUG
        if (sp->type != T_LVALUE)
            goto bad_arg_1;
#endif

        /* Set argp to the actual value designated by sp[0] */
        for ( argp = sp->u.lvalue
            ; T_LVALUE == argp->type || T_PROTECTED_LVALUE == argp->type
            ; argp = argp->u.lvalue)
            NOOP;

        /* Now do it */
        if (argp->type == T_NUMBER)
        {
            sp--;
            if (sp->type != T_NUMBER)
                goto bad_right;
            i = sp->u.number;
            if ((uint)i > MAX_SHIFT)
                argp->u.number = 0;
            else
                argp->u.number = (p_uint)argp->u.number >> i;
            sp->u.number = argp->u.number;
            break;
        }

        if (argp->type == T_CHAR_LVALUE)
        {
            if (sp[-1].type != T_NUMBER)
                goto bad_right;
            sp--;
            i = sp->u.number;
            if ((uint)i > MAX_SHIFT
             || ((*argp->u.string >> i) & 0xff) == 0)
                ERROR("Can't set string character to 0.\n");
            *argp->u.string = (p_uint)*argp->u.string >> i;
            sp->u.number = *argp->u.string;
            break;
        }
        goto bad_left;
        /* NOTREACHED */ break;
    }

    /* --- Machine internal instructions --- */

    CASE(F_POP_VALUE);              /* --- pop_value           --- */
        /* Pop the topmost value from the stack (freeing it).
         * Simple, huh?
         */
        pop_stack();
        break;

    CASE(F_DUP);                    /* --- dup                 --- */
        /* Push a duplicate of sp[0] onto the stack.
         */
        sp++;
        assign_svalue_no_free(sp, sp-1);
        break;

    CASE(F_LDUP);                   /* --- ldup                --- */
      {
        /* Push a duplicate of sp[0] onto the stack.
         * If sp[0] is an lvalue, it is derefenced first.
         */
        svalue_t * svp = sp;
        sp++;
        while (svp->type == T_LVALUE || svp->type == T_PROTECTED_LVALUE)
            svp = svp->u.lvalue;
        assign_svalue_no_free(sp, svp);
        break;
      }

    CASE(F_SWAP_VALUES);            /* --- swap_values         --- */
      {
        /* Swap sp[0] and sp[-1] on the stack.
         */
        svalue_t sv = sp[0];
        sp[0] = sp[-1];
        sp[-1] = sv;
        break;
      }

    CASE(F_CLEAR_LOCALS);    /* --- clear_locals <first> <num> --- */
      {
        /* Set the local variables <first> .. <first>+<num>-1 back
         * to svalue-0. This is used to initalize local variables
         * of nested scopes.
         */
        int first, num;
        svalue_t *plocal;

        first = LOAD_UINT8(pc);
        num = LOAD_UINT8(pc);

        for (plocal = fp+first; num > 0; num--, plocal++)
        {
            free_svalue(plocal);
            *plocal = const0;
        }
        break;
      }

    CASE(F_LBRANCH);                /* --- lbranch <offset>    --- */
    {
        /* Jump by (16-Bit) short <offset> bytes.
         * The <offset> is counted from its first byte (TODO: Ugh).
         */

        short offset;

        GET_SHORT(offset, pc);
        pc += offset;
        break;
    }

    CASE(F_LBRANCH_WHEN_ZERO); /* --- lbranch_when_zero <offset> --- */
    {
        /* Jump by (16-Bit) short <offset> bytes if sp[0] is number 0.
         * The <offset> is counted from its first byte (TODO: Ugh).
         * sp[0] is popped from the stack.
         */

        short offset;

        if (sp->type == T_NUMBER && sp->u.number == 0)
        {
            GET_SHORT(offset, pc);
            pc += offset;
            sp--;
            break;
        }
        pc += 2;
        pop_stack();
        break;
    }

    CASE(F_LBRANCH_WHEN_NON_ZERO); /* --- lbranch_when_non_zero <offset> --- */
    {
        /* Jump by (16-Bit) short <offset> bytes if sp[0] is not number 0.
         * The <offset> is counted from its first byte (TODO: Ugh).
         * sp[0] is popped from the stack.
         */

        short offset;

        if (sp->type != T_NUMBER || sp->u.number != 0)
        {
            GET_SHORT(offset, pc);
            pc += offset;
            pop_stack();
            break;
        }
        pc += 2;
        sp--;
        break;
    }

    CASE(F_BRANCH);                 /* --- branch <offset>     --- */
    {
        /* Jump forward by uint8 <offset> bytes.
         * The <offset> is counted from the next instruction.
         */

        pc += GET_UINT8(pc)+1;
        break;
    }

    CASE(F_BRANCH_WHEN_ZERO); /* --- branch_when_zero <offset> --- */
    {
        /* Jump forward by uint8 <offset> bytes if sp[0] is number 0.
         * The <offset> is counted from the next instruction.
         * sp[0] is popped from the stack.
         */

        if (sp->type == T_NUMBER)
        {
            if (sp->u.number == 0)
            {
                sp--;
                pc += GET_UINT8(pc) + 1;
                break;
            }
            sp--;
            pc++;
            break;
        }
        else
        {
            free_svalue(sp);
            sp--;
            pc++;
            break;
        }
    }

    CASE(F_BRANCH_WHEN_NON_ZERO); /* --- branch_when_non_zero <offset> --- */
    {
        /* Jump forward by uint8 <offset> bytes if sp[0] is not number 0.
         * The <offset> is counted from the next instruction.
         * sp[0] is popped from the stack.
         */

        if (sp->type == T_NUMBER)
        {
            if (sp->u.number == 0)
            {
                sp--;
                pc++;
                break;
            }
        }
        else
        {
            free_svalue(sp);
        }
        sp--;
        pc += GET_UINT8(pc) + 1;
        break;
    }

    CASE(F_BBRANCH_WHEN_ZERO);  /* --- bbranch_when_zero <offset> --- */
    {
        /* Jump backward by uint8 <offset> bytes if sp[0] is number 0.
         * The <offset> is counted from its first byte (TODO: Ugh).
         * sp[0] is popped from the stack.
         */

        if (sp->type == T_NUMBER && sp->u.number == 0)
        {
            sp--;
            pc -= GET_UINT8(pc);
            break;
        }
        pc += 1;
        pop_stack();
        break;
    }
    CASE(F_BBRANCH_WHEN_NON_ZERO); /* --- branch_when_non_zero <offset> --- */
    {
        /* Jump backward by uint8 <offset> bytes if sp[0] is not number 0.
         * The <offset> is counted from its first byte (TODO: Ugh).
         * sp[0] is popped from the stack.
         */

        if (sp->type == T_NUMBER)
        {
            if (sp->u.number == 0)
            {
                pc += 1;
                sp--;
                break;
            }
        }
        else
            free_svalue(sp);
        sp--;
        pc -= GET_UINT8(pc);
        break;
    }

                 /* --- call_function_by_address <index> <num> --- */
    CASE(F_CALL_FUNCTION_BY_ADDRESS);
    {
        /* Call the function <index> with <num> args on the stack.
         * <index> is a (16-Bit) unsigned short, giving the index within
         * the programs function table. <num> a uint8.
         *
         * Since the function may be redefined through inheritance, the
         * function must be searched in the current_objects program, which
         * might not be the current_program.
         *
         * The code is used to implement calls to non-private functions.
         */

        unsigned short func_index;   /* function index within program */
        unsigned short func_offset;
          /* function index within the current object's program.
           * This way local function may be redefined through inheritance.
           */
        funflag_t  flags;     /* the function flags */
        fun_hdr_p  funstart;  /* the actual function (code) */

        /* Get the function's index */
        LOAD_SHORT(func_index, pc);
        func_offset = (unsigned short)(func_index + function_index_offset);

        /* Find the function in the function table. As the function may have
         * been redefined by inheritance, we must look in the last table,
         * which is pointed to by current_object.
         */
#ifdef DEBUG
        if (func_offset >= current_object->prog->num_functions)
            fatal("call_function_by_address: "
                  "Illegal function index: offset %hu (index %hu), %d functions\n"
                 , func_offset, func_index
                 , current_object->prog->num_functions);
#endif

        /* NOT current_prog, which can be an inherited object. */
        flags = current_object->prog->functions[func_offset];

        /* If the function was cross-defined, get the real offset */
        if (flags & NAME_CROSS_DEFINED)
        {
            func_offset += CROSSDEF_NAME_OFFSET(flags);
        }

        /* Save all important global stack machine registers */
        push_control_stack(sp, pc+1, fp);

        /* Set the current program back to the objects program _after_
         * the control stack push, since here is where we search for
         * the function.
         */
        current_prog = current_object->prog;

        /* Search for the function definition and determine the offsets.
         */
        csp->num_local_variables = GET_UINT8(pc);
        flags = setup_new_frame1(func_offset, 0, 0);
        funstart = (fun_hdr_p)(current_prog->program + (flags & FUNSTART_MASK));
        csp->funstart = funstart;

        /* Setup the stack, arguments and local vars */
        sp = setup_new_frame2(funstart, sp, MY_FALSE, MY_FALSE);

        /* Finish the setup */

#ifdef DEBUG
        if (!current_object->variables && variable_index_offset)
            fatal("%s Fatal: call function for object %p '%s' w/o variables, "
                  "but offset %d\n"
                 , time_stamp(), current_object, current_object->name
                 , variable_index_offset);
#endif
        current_variables = current_object->variables;
        if (current_variables)
            current_variables += variable_index_offset;
        current_strings = current_prog->strings;
        fp = inter_fp;
        pc = FUNCTION_CODE(funstart);
        csp->extern_call = MY_FALSE;
        break;
    }

           /* --- call_explicit_inherited <prog> <index> <num> --- */
    CASE(F_CALL_EXPLICIT_INHERITED);
    {
        /* Call the (inherited) function <index> in program <prog> with
         * <num> arguments on the stack.
         *
         * <index> is a (16-Bit) unsigned short, giving the index within
         * the programs function table.
         * <prog> is a (16-Bit) unsigned short, giving the index within
         * the current programs inherit table.
         * <num> a uint8.
         */

        unsigned short prog_index;  /* Index within the inherit table */
        unsigned short func_index;  /* Index within the function table */
        funflag_t flags;            /* the functions flags */
        fun_hdr_p funstart;         /* the actual function (code) */
        inherit_t *inheritp;        /* the inheritance descriptor */

        /* Get the program and function index, and determine the
         * inheritance descriptor
         */
        LOAD_SHORT(prog_index, pc);
        LOAD_SHORT(func_index, pc);

        inheritp = &current_prog->inherit[prog_index];

#ifdef DEBUG
        if (func_index >= inheritp->prog->num_functions)
        {
            fatal("call_explicit_inherited: Illegal function index: "
                  "program %d, func %d, %d functions\n"
                 , prog_index, func_index, inheritp->prog->num_functions);
        }
#endif

        /* Save all important global stack machine registers */
        push_control_stack(sp, pc+1, fp);

        /* If we do an explicit call into a virtually inherited base class we
         * have to find the first instance of the inherited variables.
         * This cannot be done at compile time because it depends on the
         * _object_ (i.e. the runtime environment) in which current_prog
         * is running.
         * TODO: A better compiler might do some backpatching and at least
         * TODO:: leave hints where the variables are, so that we can omit
         * TODO:: the explicite search. Or some load-time patching.
         */
        if (current_prog != current_object->prog
         && inheritp->prog->num_variables
         && (current_prog->variable_names[inheritp->variable_index_offset
                                          +inheritp->prog->num_variables-1
                                         ].flags & TYPE_MOD_VIRTUAL)
         && !(inheritp->prog->variable_names[inheritp->prog->num_variables-1
                                            ].flags & TYPE_MOD_VIRTUAL)
           )
        {
            /* Now search for the first virtual inheritance of the program
             * in the inherit list of the topmost program.
             * Don't get confused by normal inherits, though.
             */

            int i = current_object->prog->num_inherited;
            inherit_t *inh = current_object->prog->inherit;

            while (i)
            {
                if (inh->prog == inheritp->prog
                 && current_object->prog
                                  ->variable_names[inh->variable_index_offset
                                                  +inh->prog->num_variables-1
                                                  ].flags&TYPE_MOD_VIRTUAL
                   )
                    break;
                inh++;
                i--;
            }

            if (i)
            {
                /* found, so adjust the inheritp and the offsets
                 * to start with
                 */
                inheritp = inh;
                current_variables = current_object->variables;
                function_index_offset = 0;
            }
#ifdef DEBUG
            else { /* this shouldn't happen! */
                char *ts;
                ts = time_stamp();
                fprintf(stderr,
                        "%s Adjusting variable offsets because of virtual "
                            "inheritance for call\n"
                        "%s from %s into %s (topmost program %s) FAILED.\n"
                        "%s Please check the inherit tree and report it.\n"
                       , ts, ts
                       , current_prog->name, inheritp->prog->name
                       , current_object->prog->name
                       , ts);
            }
#endif
        }

        /* Set the current program to the inherited program _after_
         * the control stack push, since there is where we search for
         * the function.
         */
        current_prog = inheritp->prog;

        /* Search for the function definition and determine the offsets.
         */
        csp->num_local_variables = EXTRACT_UCHAR(pc);
        flags = setup_new_frame1(
          func_index,
          function_index_offset + inheritp->function_index_offset,
          inheritp->variable_index_offset
        );
        funstart = (fun_hdr_p)(current_prog->program + (flags & FUNSTART_MASK));
        csp->funstart = funstart;

        /* Setup the stack, arguments and local vars */
        sp = setup_new_frame2(funstart, sp, MY_FALSE, MY_FALSE);

        /* Finish the setup */
        fp = inter_fp;
        pc = FUNCTION_CODE(funstart);
        current_variables += variable_index_offset;
        current_strings = current_prog->strings;
        csp->extern_call = MY_FALSE;
        break;
    }

    CASE(F_PUSH_IDENTIFIER_LVALUE);  /* --- push_identifier_lvalue <num> --- */
        /* Push an lvalue onto the stack pointing to object-global variable
         * <num>.
         *
         * <num> is an uint8 and used as index in the current objects
         * variable table.
         */
        sp++;
        sp->type = T_LVALUE;
        sp->u.lvalue = find_value((int)(LOAD_UINT8(pc) ));
        break;

    CASE(F_VIRTUAL_VARIABLE);    /* --- virtual_variable <num> --- */
    {
        /* Push the virtual object-global variable <num> onto the stack.
         * It is possible that it is a variable that points to
         * a destructed object. In that case, it has to be replaced by 0.
         *
         * <num> is an uint8 and used as index in the current objects
         * variable table.
         */
        svalue_t * val = find_virtual_value((int)(LOAD_UINT8(pc)));
        sp++;
        assign_checked_svalue_no_free(sp, val, sp, pc);
        break;
    }

                          /* --- push_virtual_variable_lvalue <num> --- */
    CASE(F_PUSH_VIRTUAL_VARIABLE_LVALUE);
        /* Push an lvalue onto the stack pointing to virtual object-global
         * variable <num>.
         *
         * <num> is an uint8 and used as index in the current objects
         * variable table.
         */
        sp++;
        sp->type = T_LVALUE;
        sp->u.lvalue = find_virtual_value((int)(LOAD_UINT8(pc) ));
        break;

#ifdef F_IDENTIFIER16

    CASE(F_IDENTIFIER16);         /* --- identifier16 <var_ix> --- */
    {
        /* Push value of object variable <var_ix>.
         * It is possible that it is a variable that points to
         * a destructed object. In that case, it has to be replaced by 0.
         *
         * <var_ix> is a (16-Bit) unsigned short.
         */
        unsigned short var_index;

        LOAD_SHORT(var_index, pc);
        sp++;
        assign_checked_svalue_no_free(sp, find_value((int)var_index), sp, pc);
        break;
    }

                       /* --- push_identifier16_lvalue <var_ix> --- */
    CASE(F_PUSH_IDENTIFIER16_LVALUE);
    {
        /* Push an lvalue onto the stack pointing to object-global variable
         * <num>.
         *
         * <num> is an uint8 and used as index in the current objects
         * variable table.
         */
        unsigned short var_index;

        LOAD_SHORT(var_index, pc);
        sp++;
        sp->type = T_LVALUE;
        sp->u.lvalue = find_value((int)var_index);
        break;
    }

#endif /* F_IDENTIFIER16 */

                         /* --- push_local_variable_lvalue <num> --- */
    CASE(F_PUSH_LOCAL_VARIABLE_LVALUE);
        /* Push an lvalue onto the stack pointing to local variable <num>.
         *
         * <num> is an uint8 and used as index onto the framepointer.
         */
        sp++;
        sp->type = T_LVALUE;
        sp->u.lvalue = fp + LOAD_UINT8(pc);
        break;

    CASE(F_PUSH_INDEXED_LVALUE);    /* --- push_indexed_lvalue --- */
        /* Operator F_PUSH_INDEXED_LVALUE(vector  v=sp[-1], int   i=sp[0])
         * Operator F_PUSH_INDEXED_LVALUE(mapping v=sp[-1], mixed i=sp[0])
         *
         * Compute the lvalue &(v[i]) and push it into the stack. If v has
         * just one ref left, the indexed item is stored in indexing_quickfix
         * and the lvalue refers to that variable.
         */

        sp = push_indexed_lvalue(sp, pc);
        break;

    CASE(F_PUSH_RINDEXED_LVALUE);   /* --- push_rindexed_lvalue --- */
        /* Operator F_PUSH_RINDEXED_LVALUE(vector v=sp[-1], int i=sp[0])
         *
         * Compute the lvalue &(v[<i]) and push it into the stack. If v has
         * just one ref left, the indexed item is stored in indexing_quickfix
         * and the lvalue refers to that variable.
         */

        sp = push_rindexed_lvalue(sp, pc);
        break;

    CASE(F_INDEX_LVALUE);           /* --- index_lvalue       --- */
        /* Operator F_INDEX_LVALUE (string|vector &v=sp[0], int   i=sp[-1])
         *          F_INDEX_LVALUE (mapping       &v=sp[0], mixed i=sp[-1])
         *
         * Compute the index &(v[i]) of lvalue <v> and push it into the stack.
         * The computed index is a lvalue itself.  If <v> is a string-lvalue,
         * it is made a malloced string if necessary, and the pushed result
         * will be a lvalue pointing to a CHAR_LVALUE stored in
         * <special_lvalue>.
         */

        sp = index_lvalue(sp, pc);
        break;

    CASE(F_RINDEX_LVALUE);          /* --- rindex_lvalue      --- */
        /* Operator F_RINDEX_LVALUE (string|vector &v=sp[0], int   i=sp[-1])
         *
         * Compute the index &(v[<i]) of lvalue <v> and push it into the
         * stack. The computed index is a lvalue itself.
         * If <v> is a string-lvalue, it is made a malloced string if
         * necessary, and the pushed result will be a lvalue pointing to a
         * CHAR_LVALUE stored in <special_lvalue>.
         */

        sp = rindex_lvalue(sp, pc);
        break;

    CASE(F_INDEX);                  /* --- index              --- */
        /* Operator F_INDEX (string|vector v=sp[0], int   i=sp[-1])
         *          F_INDEX (mapping       v=sp[0], mixed i=sp[-1])
         *
         * Compute the value (v[i]) and push it onto the stack.  If the value
         * would be a destructed object, 0 is pushed onto the stack and the
         * ref to the object is removed from the vector/mapping.
         *
         * Mapping indices may use <indexing_quickfix> for temporary storage.
         */

        sp = push_indexed_value(sp, pc);
        break;

    CASE(F_RINDEX);                 /* --- rindex              --- */
        /* Operator F_RINDEX (string|vector v=sp[0], int   i=sp[-1])
         *
         * Compute the value (v[<i]) and push it onto the stack.  If the value
         * would be a destructed object, 0 is pushed onto the stack and the
         * ref to the object is removed from the vector/mapping.
         */

        sp = push_rindexed_value(sp, pc);
        break;

    CASE(F_RANGE_LVALUE);           /* --- range_lvalue        --- */
        /* Operator F_RANGE_LVALUE (string|vector &v=sp[0]
         *                         , int i2=sp[-1], i1=sp[-2])
         *
         * Compute the range &(v[i1..i2]) of lvalue <v> and push it into the
         * stack.  The value pushed is a lvalue pointing to <special_lvalue>.
         * <special_lvalue> then is the POINTER_RANGE_- resp.
         * STRING_RANGE_LVALUE.
         *
         * TODO: Four different instructions for this? A single instruction plus
         * TODO:: argument would be as well.
         */

        inter_pc = pc;
        sp = range_lvalue(0x000, sp);
        break;

    CASE(F_NR_RANGE_LVALUE);           /* --- nr_range_lvalue     --- */
        /* Operator F_NR_RANGE_LVALUE (string|vector &v=sp[0]
         *                         , int i2=sp[-1], i1=sp[-2])
         *
         * Compute the range &(v[i1..<i2]) of lvalue <v> and push it into the
         * stack.  The value pushed is a lvalue pointing to <special_lvalue>.
         * <special_lvalue> then is the POINTER_RANGE_- resp.
         * STRING_RANGE_LVALUE.
         */

        inter_pc = pc;
        sp = range_lvalue(0x001, sp);
        break;

    CASE(F_RN_RANGE_LVALUE);           /* --- rn_range_lvalue     --- */
        /* Operator F_RN_RANGE_LVALUE (string|vector &v=sp[0]
         *                         , int i2=sp[-1], i1=sp[-2])
         *
         * Compute the range &(v[<i1..i2]) of lvalue <v> and push it into the
         * stack.  The value pushed is a lvalue pointing to <special_lvalue>.
         * <special_lvalue> then is the POINTER_RANGE_- resp.
         * STRING_RANGE_LVALUE.
         */

        inter_pc = pc;
        sp = range_lvalue(0x100, sp);
        break;

    CASE(F_RR_RANGE_LVALUE);           /* --- rr_range_lvalue     --- */
        /* Operator F_RR_RANGE_LVALUE (string|vector &v=sp[0]
         *                         , int i2=sp[-1], i1=sp[-2])
         *
         * Compute the range &(v[<i1..<i2]) of lvalue <v> and push it into the
         * stack.  The value pushed is a lvalue pointing to <special_lvalue>.
         * <special_lvalue> then is the POINTER_RANGE_- resp.
         * STRING_RANGE_LVALUE.
         */

        inter_pc = pc;
        sp = range_lvalue(0x101, sp);
        break;

    CASE(F_NX_RANGE_LVALUE);           /* --- nx_range_lvalue     --- */
        /* Operator F_NX_RANGE_LVALUE (string|vector &v=sp[0]
         *                            , int i1=sp[-1])
         *
         * Compute the range &(v[i1..]) of lvalue <v> and push it into the
         * stack.  The value pushed is a lvalue pointing to <special_lvalue>.
         * <special_lvalue> then is the POINTER_RANGE_- resp.
         * STRING_RANGE_LVALUE.
         *
         * We implement this by pushing '1' onto the stack and then
         * call F_NR_RANGE_LVALUE, effectively computing &(v[i1..<1]).
         */

        inter_pc = pc;
        sp++;
        sp[0] = sp[-1];       /* Pull up the 'v' */
        put_number(sp-1, 1);  /* 'Push' the 1 for the upper bound */
        sp = range_lvalue(0x001, sp);
        break;

    CASE(F_RX_RANGE_LVALUE);           /* --- rx_range_lvalue     --- */
        /* Operator F_RX_RANGE_LVALUE (string|vector &v=sp[0]
         *                            , int i1=sp[-1])
         *
         * Compute the range &(v[<i1..]) of lvalue <v> and push it into the
         * stack.  The value pushed is a lvalue pointing to <special_lvalue>.
         * <special_lvalue> then is the POINTER_RANGE_- resp.
         * STRING_RANGE_LVALUE.
         *
         * We implement this by pushing '1' onto the stack and then
         * call F_RR_RANGE_LVALUE, effectively computing &(v[<i1..<1]).
         */

        inter_pc = pc;
        sp++;
        sp[0] = sp[-1];       /* Pull up the 'v' */
        put_number(sp-1, 1);  /* 'Push' the 1 for the upper bound */
        sp = range_lvalue(0x101, sp);
        break;

    CASE(F_SIMUL_EFUN); /* --- simul_efun <code> [<num_arg>]   --- */
    {
        /* Call the simul_efun <code>. If it's a function taking varargs,
         * <num_arg> gives the number of arguments, otherwise the compiler
         * already took care of fixing the stack.
         *
         * <code> is an uint8 and indexes the function list *simul_efunp.
         * <num_arg> is an uint8.
         * TODO: Add a F_SIMUL_EFUN for codes > 0xff; right now this
         * TODO:: is compiled as CALL_OTHER. Affected are prolang.y and
         * TODO:: simul_efun.c
         */

        int                 code;      /* the function index */
        fun_hdr_p           funstart;  /* the actual function */
        object_t           *ob;        /* the simul_efun object */
        simul_efun_table_t *entry;

        ASSIGN_EVAL_COST  /* we're changing objects */

        /* Get the sefun code and the number of arguments on the stack */
        code = (int)LOAD_UINT8(pc);
        num_arg = simul_efunp[code].num_arg;
        if (num_arg == SIMUL_EFUN_VARARGS
         || simul_efunp[code].flags & TYPE_MOD_XVARARGS
           )
        {
            num_arg = (int)LOAD_UINT8(pc);
        }

        /* No external calls may be done when this object is destructed.
         */
        if (current_object->flags & O_DESTRUCTED)
        {
            pop_n_elems(num_arg);
            push_number(0);
            break;
        }

        /* Make sure the simul_efun object exists; loading it when
         * necessary.
         */
        if ( !(ob = simul_efun_object) )
        {
            inter_sp = sp;
            inter_pc = pc;
            if ( !(ob = get_simul_efun_object()) )
            {
                error("Couldn't load simul_efun object\n");
            }
        }

        /* Get the function code information */
        entry = &simul_efun_table[code];

        if ( NULL != (funstart = entry->funstart) )
        {
            /* The entry is valid: call the sefun by recursing into
             * eval_instruction(), so we can get the result from the
             * stack.
             * We recurse because some simul_efuns are called with
             * F_CALL_OTHER, and the functions should not be able
             * to see any difference.
             */
            program_t *prog;
            svalue_t *new_sp;

            push_control_stack(sp, pc, fp);
            csp->ob = current_object;
            csp->prev_ob = previous_ob;
            csp->funstart = funstart;
            csp->num_local_variables = num_arg;
            current_prog = prog = entry->program;
            function_index_offset = entry->function_index_offset;
#ifdef DEBUG
            if (!ob->variables && entry->variable_index_offset)
                fatal("%s Fatal: call sefun for object %p '%s' w/o variables, "
                      "but offset %ld\n"
                     , time_stamp(), ob, ob->name
                     , (long)(entry->variable_index_offset));
#endif
            current_variables = ob->variables;
            if (current_variables)
                current_variables += entry->variable_index_offset;
            new_sp = setup_new_frame2(funstart, sp, MY_TRUE, MY_FALSE);
            /* The simul_efun object should not use simul_efuns itself... */
            previous_ob = current_object;
            current_object = ob;
            current_strings = prog->strings;
            eval_instruction(FUNCTION_CODE(funstart), new_sp);
            sp -= num_arg - 1;
            /*
             * The result of the function call is on the stack.
             */
            break;
        }

        /* The simul_efun was discarded meanwhile and not recreated
         * - call it the old fashioned way with apply() in case it exists
         * in a slightly different form.
         */
        inter_sp = sp;
        inter_pc = pc;
        call_simul_efun(code, ob, num_arg);
        sp = inter_sp;
        /*
         * The result of the function call is on the stack.
         */
        break;
    }

    CASE(F_AGGREGATE);              /* --- aggregate <size>    --- */
    {
        /* Create an array ({ sp[<-size>+1], ..., sp[0] }), remove the
         * single values from the stack and leave the array as result.
         *
         * <size> is a (16-Bit) unsigned short.
         *
         * TODO: It is tempting to introduce flat 'literal arrays',
         * TODO:: which can be copied quickly and just need a few
         * TODO:: slots to filled in, if any.
         */

        int i;
        vector_t *v;
        unsigned short num;
        svalue_t *value, *item;

        /* Get the size */
        LOAD_SHORT(num, pc);
        /* Allocate the array */
        i = num;
        v = allocate_uninit_array(i);

        /* Set sp and value to the first single value on the stack */
        sp = value = sp - i + 1;

        /* Move the single values into the array.
         * Volatile strings are made shared during this.
         */
        item = v->item;
        while (--i >= 0)
            transfer_svalue_no_free_spc(item++, value++, sp, pc);

        /* Leave the array on the stack (ref count is already ok) */
        put_array(sp, v);
        break;
    }

    CASE(F_M_AGGREGATE);     /* --- m_aggregate <size> <width> --- */
    CASE(F_M_CAGGREGATE);   /* --- m_caggregate <size> <width> --- */
    {
        /* Create a mapping from the <size>*<width> single values on the
         * stack, remove the single values and leave the mapping as result.
         * Starting at the lowest entry (sp[-(<size>*<width>)]), the values
         * are laid out in <key>:<data 1>...<data <width>> order.
         * Keys may appear several times.
         *
         * m_aggregate: <size> and <width> are (16-Bit) unsigned shorts.
         * m_caggregate: <size> and <width> are uint8.
         *
         * TODO: It is tempting to introduce flat 'literal mappings',
         * TODO:: which can be copied quickly and just need a few
         * TODO:: slots to filled in, if any.
         */
        int i, j;
        mapping_t *m;
        svalue_t *data;
        int num_values;
        svalue_t *value;

        /* Get the size and width from the code.
         */
        if (instruction == F_M_CAGGREGATE)
        {
            i = LOAD_UINT8(pc);
            num_values = LOAD_UINT8(pc);
        }
        else
        {
            unsigned short num[2];

            LOAD_SHORT(num[0], pc);
            LOAD_SHORT(num[1], pc);
            i = num[0];
            num_values = num[1];
        }

        if (max_mapping_size && i > max_mapping_size)
            ERRORF(("Illegal mapping size: %ld\n", (long)i));

        /* Get the mapping */
        m = allocate_mapping(i, num_values);
        if (!m)
            ERROR("Out of memory\n")

        /* Set sp and value to the first single value on the stack.
         */
        sp = value = sp - (i * (num_values+1)) + 1;
        while (--i >= 0)
        {
            /* Create/reget the mapping entry */
            data = get_map_lvalue_unchecked(m, value);
            if (!data)
            {
                outofmemory("literal mapping");
                /* NOTREACHED */
                return MY_FALSE;
            }
            free_svalue(value++);
            for (j = num_values; --j >= 0;)
            {
                /* Copy over the entry data */
                if (data->type != T_NUMBER)
                    free_svalue(data);
                transfer_svalue_no_free_spc(data++, value++, sp, pc);
            }
        }

        /* Put the mapping onto the stack */
        put_mapping(sp, m);
        break;
    }

    CASE(F_PREVIOUS_OBJECT0);       /* --- previous_object0    --- */
        /* EFUN previous_object(void)
         *
         * Push the previous_object onto the stack, if existing and
         * not destructed.
         *
         * The compiler generates this code when it sees the previous_object()
         * efun used with no arguments.
         *
         * (Reminder: the efun previous_object(int) has a different meaning.)
         * TODO: How do other driver handle this?
         */
        if (previous_ob == 0 || (previous_ob->flags & O_DESTRUCTED))
            push_number(0);
        else
            push_object(previous_ob);
        break;

    CASE(F_LAMBDA_CCONSTANT);    /* --- lambda_cconstant <num> --- */
    {
        /* Push the constant value <num> of this lambda closure onto
         * the stack.
         *
         * The values are stored in an svalue[] before the actual
         * function code and uint8 <num> is used to index that array
         * from the end.
         */
#define MY_LAMBDA_VALUE_OFFSET (sizeof(svalue_t) + \
            ((PTRTYPE)(&((lambda_t *)0)->function.code[1])-(PTRTYPE) 0) )
        int ix;
        svalue_t * cstart;

        /* Get the value index */
        ix = LOAD_UINT8(pc);

        /* Get the pointer to the last constant value */
        cstart = (svalue_t *)((char *)(csp->funstart)
                                   - LAMBDA_VALUE_OFFSET);
        sp++;
        assign_checked_svalue_no_free(sp, cstart - ix, sp, pc);
        break;
    }

    CASE(F_LAMBDA_CONSTANT);     /* --- lambda_constant <num> --- */
    {
        /* Push the constant value <num> of this lambda closure onto
         * the stack.
         *
         * The values are stored in an svalue[] before the actual
         * function code and (16-Bit) ushort <num> is used to index
         * that array from the end.
         */
        unsigned short ix;
        svalue_t * cstart;

        /* Get the value index */
        LOAD_SHORT(ix, pc);

        /* Get the pointer to the last constant value */
        cstart = (svalue_t *)((char *)(csp->funstart)
                                   - LAMBDA_VALUE_OFFSET);

        sp++;
        assign_checked_svalue_no_free(sp, cstart - ix, sp, pc);
        break;
    }

    CASE(F_MAP_INDEX);              /* --- map_index           --- */
    {
        /* Operator F_MAP_INDEX( mapping m=sp[-2], mixed i=sp[-1], int j=sp[0])
         *
         * Compute m[i,j] and push it onto the stack.
         */

        mapping_t *m;
        mp_int n;
        svalue_t *data;

        if (sp[-2].type != T_MAPPING)
            ERRORF(("Illegal value for <mapping>[,]: not a mapping.\n"));
            /* TODO: Give type and value */
        if (sp->type != T_NUMBER)
            ERRORF(("Illegal sub-index for <mapping>[,]: not a number.\n"));
            /* TODO: Give type and value */

        m = sp[-2].u.map;
        n = sp->u.number;

        if (n < 0 || n >= m->num_values)
        {
            ERRORF(("Illegal sub-index %ld, mapping width is %ld.\n"
                 , (long)n, (long)m->num_values))
        }

        sp--; /* the key */
        data = get_map_value(m, sp);
        pop_stack();

        if (data == &const0)
        {
            put_number(sp, 0);
        }
        else
        {
            assign_checked_svalue_no_free(sp, data + n, sp, pc);
        }
        free_mapping(m);
        break;
    }

    CASE(F_PUSH_INDEXED_MAP_LVALUE); /* --- push_indexed_map_lvalue --- */
    {
        /* Operator F_PUSH_INDEXED_MAP_LVALUE( mapping m=sp[-2]
         *                                   , mixed i=sp[-1], int j=sp[0])
         *
         * Compute the lvalue &(m[i,j]) and push it into the stack. If v has
         * just one ref left, the indexed item is stored in indexing_quickfix
         * and the lvalue refers to that variable.
         */
        svalue_t *data;
        mapping_t *m;
        mp_int n;

        TYPE_TEST1(sp-2, T_MAPPING)
        TYPE_TEST3(sp, T_NUMBER)

        m = sp[-2].u.map;
        n = sp->u.number;
        if (n < 0 || n >= m->num_values)
        {
            ERRORF(("Illegal sub-index %ld, mapping width is %ld.\n"
                    , (long)n, (long)m->num_values))
        }

        sp--; /* the key */
        data = get_map_lvalue(m, sp);
        if (!data)
        {
            outofmemory("indexed lvalue");
            /* NOTREACHED */
            return MY_FALSE;
        }
        pop_stack();

        if (!m->ref)
        {
            assign_svalue (&indexing_quickfix, data + n);
            sp->type = T_LVALUE;
            sp->u.lvalue = &indexing_quickfix;
            break;
        }
        else
        {
            sp->type = T_LVALUE;
            sp->u.lvalue = data + n;
        }
        free_mapping(m);
        break;
    }

    CASE(F_FOREACH);           /* --- foreach <nargs> <offset> --- */
    {
        /* Initialize a foreach() loop. On the stack are <nargs>-1
         * lvalues where the value(s) are to be stored. The last
         * value on the stack is the value to loop over. (Do not
         * confuse <nargs> with the normal NUM_ARG!).
         *
         * ushort <offset> is the distance to the FOREACH_NEXT
         * instruction follwing the codeblock after the instruction,
         * counted from the byte following this instruction.
         *
         * The instruction pushes two or three more values onto
         * the stack to store its internal status.
         *
         *   sp[0]  -> number 'next':  index of the next value to assign (0).
         *   sp[-1] -> number 'count': number of values to loop over.
         *             x.u.generic:    <nargs>, or -<nargs> if the value
         *                             is mapping
         *   sp[-2] -> array 'm_indices': if the value is a mapping, this
         *                             is the array with the indices.
         *
         * After pushing the values onto the stack, the instruction
         * branches to the FOREACH_NEXT instruction to start the first
         * iteration.
         */

        int vars_required;
        int nargs;
        p_int count;
        unsigned short offset;

        nargs = LOAD_UINT8(pc);
        LOAD_SHORT(offset, pc);

        if (sp->type != T_STRING
         && sp->type != T_POINTER
         && sp->type != T_MAPPING)
            ERROR("foreach() requires a string, array or mapping.\n");
            /* TODO: What type did it get? */

        /* Find out how many variables we require */

        if (sp->type == T_STRING)
        {
            count = (p_int)svalue_strlen(sp);
            vars_required = 1;
        }
        else if (sp->type == T_POINTER)
        {
            check_for_destr(sp->u.vec);
            count = (p_int)VEC_SIZE(sp->u.vec);
            vars_required = 1;
        }
        else
        {
            mapping_t *m;
            vector_t  *indices;

            m = sp->u.map;
            vars_required = 1 + m->num_values;
            indices = m_indices(m);

            count = (p_int)MAP_SIZE(m);
              /* after m_indices(), else we'd count destructed entries */

            if (m->num_values == 0 || nargs-1 == 1)
            {
                /* Special case: we can replace the mapping
                 * by its indices only
                 */
                free_svalue(sp);
                put_array(sp, indices);
            }
            else
            {
                /* Normal case: push the indices array and
                 * remember the fact in nargs.
                 */
                sp++;
                put_array(sp, indices);
                nargs = -nargs;
            }
        }

        /* Push the count and the starting index */
        push_number(count); sp->x.generic = nargs;
        push_number(0);

#ifdef DEBUG
        /* The <nargs> lvalues and our temporaries act as hidden
         * local variables. We therefore adapt the variable count
         * so that a F_RETURN won't complain.
         */
        if (nargs >= 0)
            csp->num_local_variables += 2 + nargs;
        else
            csp->num_local_variables += 3 + (-nargs);
#endif

        /* Now branch to the FOREACH_NEXT */
        pc += offset;

        break;
    }

    CASE(F_FOREACH_NEXT);         /* --- foreach_next <offset> --- */
    {
        /* Start the next (resp. the first) iteration of a foreach()
         * loop. ushort <offset> is the distance to branch back to the
         * loop body, counted from the first byte of the next instruction.
         * For the stack layout, see F_FOREACH.
         */

        unsigned short offset;
        p_int     ix;
        svalue_t *lvalue;  /* Pointer to the first lvalue */


        LOAD_SHORT(offset, pc);

        /* Is there something left to iterate? */
        ix = sp->u.number;
        sp->u.number++;

        if (ix >= sp[-1].u.number)
            break; /* Nope */

        if (sp[-1].x.generic < 0)
        {
            /* We loop over a mapping */

            mapping_t *m;
            vector_t  *indices;
            svalue_t  *values;
            int        left;

            lvalue = sp + sp[-1].x.generic - 2;

            m = sp[-3].u.map;
            indices = sp[-2].u.vec;

            values = get_map_value(m, indices->item+ix);
            if (values == &const0)
            {
                /* Whoops, the entry has vanished.
                 * Start over with this instruction again, the
                 * index on the stack has been incremented already.
                 */
                pc -= 3;
                break;
            }

            /* Assign the index we used */
            {
                svalue_t *dest;

#ifdef DEBUG
                if (lvalue->type != T_LVALUE)
                    fatal("Bad argument to foreach(): not a lvalue\n");
                    /* TODO: Give type and value */
#endif
                dest = lvalue->u.lvalue;
                assign_svalue(dest, indices->item+ix);

                lvalue++;
            }

            /* Loop over the values and assign them */
            left = -(sp[-1].x.generic) - 2;
            if (left > m->num_values)
                left = m->num_values;

            for ( ; left > 0; left--, lvalue++, values++)
            {
                svalue_t *dest;

#ifdef DEBUG
                if (lvalue->type != T_LVALUE)
                    fatal("Bad argument to foreach(): not a lvalue\n");
                    /* TODO: Give type and value */
#endif
                dest = lvalue->u.lvalue;
                assign_svalue(dest, values);
            }

            /* Ta-Da! */
        }
        else
        {
            lvalue = sp - sp[-1].x.generic - 1;
#ifdef DEBUG
            if (lvalue->type != T_LVALUE)
                fatal("Bad argument to foreach(): not a lvalue\n");
                /* TODO: Give type and value */
#endif
            lvalue = lvalue->u.lvalue;

            if (sp[-2].type == T_STRING)
            {
                free_svalue(lvalue);
                put_number(lvalue, sp[-2].u.string[ix]);
            }
            else if (sp[-2].type == T_POINTER)
            {
                if (ix >= (p_int)VEC_SIZE(sp[-2].u.vec))
                    break;
                    /* Oops, this array shrunk while we're looping over it.
                     * We stop processing and continue with the following
                     * FOREACH_END instruction.
                     */

                assign_svalue(lvalue, sp[-2].u.vec->item+ix);
            }
            else
                fatal("foreach() requires a string, array or mapping.\n");
                /* If this happens, the check in F_FOREACH failed. */
        }

        /* All that is left is to branch back. */
        pc -= offset;
        break;
    }

    CASE(F_FOREACH_END);            /* --- foreach_end         --- */
    {
        /* The foreach() loop ended or was terminated by a break.
         * All there's left to do is cleaning up the stack.
         */

        int nargs;

        nargs = sp[-1].x.generic;
        if (nargs < 0)
            pop_n_elems(-nargs + 3);
        else
            pop_n_elems(nargs+2);

#ifdef DEBUG
        /* The <nargs> lvalues and our temporaries acted as hidden
         * local variables. We now count back the variable count
         * so that a F_RETURN won't complain.
         */
        if (nargs >= 0)
            csp->num_local_variables -= 2 + nargs;
        else
            csp->num_local_variables -= 3 + (-nargs);
#endif

        break;
    }

#ifdef F_JUMP
    CASE(F_JUMP);                   /* --- jump <dest>         --- */
    {
        /* Jump to the (48-Bit) ushort address <dest> (absolute jump).
         */

        unsigned long  desth;
        unsigned short destl;

        desth = LOAD_UINT8(pc);
        GET_SHORT(destl, pc);
        pc = current_prog->program + (desth << 16) + destl;
        break;
    }
#endif /* F_JUMP */

    /* --- Efuns: Miscellaneous --- */

    CASE(F_CLONEP);                 /* --- clonep              --- */
    {
        /* EFUN clonep()
         *
         *   int clonep()
         *   int clonep (object obj)
         *   int clonep (string obj)
         *
         * The efun returns 1 if <obj> is a clone, and 0 if it is not.
         * The <obj> can be given as the object itself, or by its name.
         * If <obj> is omitted, the current object is tested.
         * Arguments of other types return 0.
         */

        int i;

        if (sp->type == T_OBJECT)
        {
            i = (sp->u.ob->flags & O_CLONE);
        }
        else if (sp->type == T_STRING)
        {
            object_t *o;

            o = find_object(sp->u.string);
            if (!o)
                ERRORF(("No such object '%s'.\n", sp->u.string));
            i = o->flags & O_CLONE;
        }
        else
        {
            i = 0;
        }
        free_svalue(sp);
        put_number(sp, i ? 1 : 0);
        break;
    }

    CASE(F_CLOSUREP);               /* --- closurep            --- */
    {
        /* EFUN closurep()
         *
         *   int closurep(mixed)
         *
         * Returns 1 if the argument is a closure.
         */

        int i;

        i = sp->type == T_CLOSURE;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_FLOATP);                 /* --- floatp              --- */
    {
        /* EFUN floatp()
         *
         *   int floatp(mixed)
         *
         * Returns 1 if the argument is a float.
         */

        int i;

        i = sp->type == T_FLOAT;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_INTP);                   /* --- intp                --- */
    {
        /* EFUN intp()
         *
         *   int intp(mixed)
         *
         * Returns 1 if the argument is an integer.
         */

        int i;

        i = sp->type == T_NUMBER;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_MAPPINGP);               /* --- mappingp            --- */
    {
        /* EFUN mappingp()
         *
         *   int mappingp(mixed)
         *
         * Returns 1 if the argument is a mapping.
         */

        int i;

        i = sp->type == T_MAPPING;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_OBJECTP);                /* --- objectp              --- */
    {
        /* EFUN objectp()
         *
         *   int objectp(mixed)
         *
         * Returns 1 if the argument is an object.
         */

        int i;

        i = sp->type == T_OBJECT;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_POINTERP);               /* --- pointerp            --- */
    {
        /* EFUN pointerp()
         *
         *   int pointerp(mixed)
         *
         * Returns 1 if the argument is an array.
         */

        int i;

        i = sp->type == T_POINTER;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_STRINGP);                /* --- stringp             --- */
    {
        /* EFUN stringp()
         *
         *   int stringp(mixed)
         *
         * Returns 1 if the argument is a string.
         */

        int i;

        i = sp->type == T_STRING;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_SYMBOLP);                /* --- symbolp             --- */
    {
        /* EFUN symbolp()
         *
         *   int symbolp(mixed)
         *
         * Returns 1 if the argument is a symbol.
         */

        int i;

        i = sp->type == T_SYMBOL;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_CTIME);                  /* --- ctime               --- */
    {
        /* EFUN ctime()
         *
         *   string ctime(int clock = time())
         *   string ctime(int* uclock)
         *
         * Interpret the argument clock as number of seconds since Jan,
         * 1st, 1970, 0.00 and convert it to a nice date and time string.
         *
         * Alternatively, accept an array of two ints: the first is <clock>
         * value as in the first form, the second int is the number of
         * microseconds elapsed in the current second.
         */

        char *ts, *cp;

        if (sp->type != T_NUMBER)
        {
            TYPE_TEST1(sp, T_POINTER)
            if (VEC_SIZE(sp->u.vec) != 2)
                ERRORF(("Invalid array size for argument 1: %ld, expected 2\n"
                      , (long)VEC_SIZE(sp->u.vec)));
            if (sp->u.vec->item[0].type != T_NUMBER
             || sp->u.vec->item[1].type != T_NUMBER)
                ERROR("Invalid array for argument 1\n");
            ts = utime_string(sp->u.vec->item[0].u.number, sp->u.vec->item[1].u.number);
        }
        else
        {
            ts = time_string(sp->u.number);
        }

        /* If the string contains nl characters, extract the substring
         * before the first one. Else just copy the (volatile) result
         * we got.
         */
        cp = strchr(ts, '\n');
        if (cp)
        {
            int len = cp - ts;
            cp = xalloc((size_t)(len + 1));
            if (!cp)
                ERROR("Out of memory\n")
            strncpy(cp, ts, (size_t)len);
            cp[len] = 0;
        }
        else
        {
            cp = string_copy(ts);
            if (!cp)
                ERROR("Out of memory\n")
        }
        free_svalue(sp);
        put_malloced_string(sp, cp);
        break;
    }

    CASE(F_ED);                     /* --- ed <nargs>          --- */
        /* EFUN ed()
         *
         *   int ed()
         *   int ed(string file)
         *   int ed(string file, string func)
         *
         * Calling without arguments will start the editor ed with the
         * name of the error file, that was returned by
         * master->valid_read(0, geteuid(this_player()), "ed_start",
         * this_player()), usually something like ~/.err. If that file is
         * empty, ed will immediatly exit again.
         * Calling ed() with argument file will start the editor on the
         * file. If the optional argument func is given, this function
         * will be called after exiting the editor.
         *
         * Result is 1 if the editor could be started, else 0. TODO: ???
         */

        if (current_object->flags & O_DESTRUCTED)
        {
            /* could confuse the master... */
            ERROR("Calling ed from destructed object.\n")
        }

        GET_NUM_ARG
        assign_eval_cost();
        inter_pc = pc;
        inter_sp = sp;

        if (num_arg == 0)
        {
            ed_start(NULL, NULL, NULL);
            push_number(1);
            break;
        }
        else if (num_arg == 1)
        {
            TYPE_TEST1(sp, T_STRING)
            ed_start(sp->u.string, NULL, NULL);
            break;
        }
        else
        {
            TYPE_TEST1(sp-1, T_STRING)
            if (sp->type == T_STRING)
                ed_start((sp-1)->u.string, sp->u.string, current_object);
            else if (sp->type == T_NUMBER)
                ed_start((sp-1)->u.string, NULL, NULL);
            else
                goto bad_arg_2;
            pop_stack();
            break;
        }

    CASE(F_NEGATE);                 /* --- negate              --- */
        /* EFUN negate()
         *
         *   int|float negate(int|float arg)
         *
         * Negate the value <arg> and leave it on the stack.
         * Calls to this efun are mainly generated by the compiler when
         * it sees the unary '-' used.
         */

        if (sp->type == T_NUMBER)
        {
            if (sp->u.number == PINT_MIN)
                ERRORF(("Numeric overflow: - %ld\n", sp->u.number));
            sp->u.number = - sp->u.number;
            break;
        }
        else if (sp->type == T_FLOAT)
        {
            STORE_DOUBLE_USED
            double d;

            d = -READ_DOUBLE(sp);
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: -(%g)\n", READ_DOUBLE(sp)));
            STORE_DOUBLE(sp,d);
            break;
        }
        ERROR("Bad argument to unary minus\n")

    CASE(F_PRINTF);                 /* --- printf <nargs>      --- */
      {
        /* EFUN printf()
         *
         *   void printf(string format, ...)
         *
         * A cross between sprintf() and write(). Returns void and prints
         * the result string to the user.
         */

        char *str;

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        TYPE_TEST1(sp - num_arg + 1, T_STRING)
        str = string_print_formatted((sp-num_arg+1)->u.string
                                    , num_arg-1, sp-num_arg+2);
        if (command_giver)
            tell_object(command_giver, str);
        else
            add_message("%s", str);
        pop_n_elems(num_arg);
        break;
      }

    CASE(F_RANDOM);                 /* --- random              --- */
        /* EFUN random()
         *
         *   int random(int n)
         *
         * Returns a number in the random range [0 .. n-1].
         *
         * The random number generator is proven to deliver an equal
         * distribution of numbers over a big range, with no repetition of
         * number sequences for a long time.
         */

        TYPE_TEST1(sp, T_NUMBER)
        if (sp->u.number <= 0)
        {
            sp->u.number = 0;
            break;
        }
        sp->u.number = (p_int)random_number((uint32)sp->u.number);
        break;

    CASE(F_THROW);                  /* --- throw               --- */
        /* EFUN throw()
         *
         *   void throw(mixed arg)
         *
         * Abort execution. If the current program execution was initiated by
         * catch(), that catch expression will return arg as error code.
         */

        assign_eval_cost();
        sp--;
        transfer_svalue_no_free_spc(&catch_value, sp+1, sp, pc);
        inter_sp = sp;
        inter_pc = pc;
        throw_error(); /* do the longjump, with extra checks... */
        break;

    CASE(F_TIME);                   /* --- time                --- */
        /* EFUN time()
         *
         *   int time()
         *
         * Return number of seconds ellapsed since 1. Jan 1970, 0.0:0 GMT
         *
         * Actually the time is updated only once in every backend cycle.
         */

        push_number(current_time);
        break;

    CASE(F_UTIME);                  /* --- utime               --- */
      {
        /* EFUN utime()
         *
         *   int* utime()
         *
         * Return the time since 1. Jan 1970, 00:00:00 GMT in microsecond
         * precision.
         *
         * Return is an array:
         *   int[0]: number of seconds elapsed
         *   int[1]: number of microseconds within the current second.
         */

        svalue_t *v;
        vector_t *res;
        struct timeval tv;

        res = allocate_array(2);
        v = res->item;
        if (!gettimeofday(&tv, NULL))
        {
            v[0].u.number = tv.tv_sec;
            v[1].u.number = tv.tv_usec;
        }
        else
        {
            int errnum = errno;
            fprintf(stderr, "%s gettimeofday() failed: %d %s\n"
                   , time_stamp(), errnum, strerror(errnum));
            v[0].u.number = current_time;
            v[1].u.number = 0;
        }
        (void)push_referenced_vector(res);
        break;
      }

    /* --- Efuns: Strings --- */

    CASE(F_CAPITALIZE);             /* --- capitalize          --- */
        /* EFUN capitalize()
         *
         *     string capitalize(string str)
         *
         * Convert the first character in str to upper case, and return
         * the new string.
         */

        TYPE_TEST1(sp, T_STRING)
        if (islower((unsigned char)(sp->u.string[0])))
        {
            char *str;

            /* Change malloc'ed strings in place, for others
             * make a copy.
             */
            if (STRING_MALLOC == sp->x.string_type)
                sp->u.string[0] = toupper((unsigned char)sp->u.string[0]);
            else
            {
                str = string_copy(sp->u.string);
                str[0] = toupper((unsigned char)str[0]);
                pop_stack();
                push_malloced_string(str);
            }
        }
        break;

    CASE(F_CRYPT);                  /* --- crypt               --- */
    {
        /* EFUN crypt()
         *
         *   string crypt(string str, int seed)
         *   string crypt(string str, string seed)
         *
         * Crypt the string str using the integer seed or two characters
         * from the string seed as a seed. If seed is equal 0, then
         * a random seed is used.
         *
         * The result has the first two characters as the seed.
         */

        char *salt;
        char *res;
        char temp[3];
        static char choise[] =
            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";

        TYPE_TEST1(sp-1, T_STRING)
        if (sp->type == T_STRING && svalue_strlen(sp) >= 2)
        {
            salt = sp->u.string;
        }
        else if (sp->type == T_NUMBER)
        {
            temp[0] = choise[random_number((sizeof choise) - 1)];
            temp[1] = choise[random_number((sizeof choise) - 1)];
            temp[2] = '\0';
            salt = temp;
        }
        else
            goto bad_arg_2;

        res = string_copy(crypt((sp-1)->u.string, salt));
        pop_n_elems(2);
        push_malloced_string(res);
        break;
    }

    CASE(F_EXPLODE);                /* --- explode             --- */
    {
        /* EFUN explode()
         *
         *   string *explode(string str, string del)
         *
         * Return an array of strings, created when the string str is
         * split into substrings as divided by del.
         */

        vector_t *v;

        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_STRING)
        inter_sp = sp;
        inter_pc = pc;

        v = explode_string((sp-1)->u.string, sp->u.string);
        free_string_svalue(sp);
        sp--;
        free_string_svalue(sp);
        put_array(sp,v);
        break;
    }

    CASE(F_IMPLODE);                /* --- implode             --- */
    {
        /* EFUN implode()
         *
         *   string implode(mixed *arr, string del)
         *
         * Concatenate all strings found in array arr, with the string
         * del between each element. Only strings are used from the array.
         */

        char *str;

        TYPE_TEST1(sp-1, T_POINTER)
        TYPE_TEST2(sp,   T_STRING)

        str = implode_string((sp-1)->u.vec, sp->u.string);
        if (!str)
            ERROR("Out of memory\n")

        free_string_svalue(sp);
        sp--;
        free_array(sp->u.vec);

        if (str)
            put_malloced_string(sp, str);
        else
            put_number(sp, 0);
        break;
    }

    CASE(F_LOWER_CASE);             /* --- lower_case          --- */
    {
        /* EFUN lower_case()
         *
         *   string lower_case(string str)
         *
         * Convert all characters in str to lower case, and return the
         * new string.
         */

        char *str, *s, *d, c;
        ptrdiff_t initial_len;

        TYPE_TEST1(sp, T_STRING)

        /* Set s to the first uppercase character and store it in c */
        for ( s = sp->u.string
            ; '\0' != (c = *s) && !isupper((unsigned char)c)
            ; s++) NOOP;

        if (c)
        {
            /* Yes, there is something to change... */

            if (STRING_MALLOC == sp->x.string_type)
            {
                /* Scan the rest of the string and lower it */
                for ( ; '\0' != (c = *s); s++)
                    if (isupper((unsigned char)c))
                        *s = (char)tolower((unsigned char)c);
            }
            else
            {
                /* We need to make a copy of the shared string.
                 * so fold the copying with the case changing.
                 */

                initial_len = s - sp->u.string;
                str = xalloc(svalue_strlen(sp)+1);
                if (initial_len)
                    memcpy(str, sp->u.string, (size_t)initial_len);
                for(d = str + initial_len; '\0' != (c = *s++) ; )
                {
                    if (isupper((unsigned char)c))
                        c = (char)tolower((unsigned char)c);
                    *d++ = c;

                }
                *d = '\0';
                free_string_svalue(sp);
                put_malloced_string(sp, str);
            }
        }
        break;
    }

    CASE(F_REGEXP);                 /* --- regexp              --- */
    {
        /* EFUN regexp()
         *
         *   string *regexp(string *list, string pattern)
         *
         * Match the pattern pattern against all strings in list, and return a
         * new array with all strings that matched. This function uses the
         * same syntax for regular expressions as ed().
         */

        vector_t *v;

        TYPE_TEST1(sp-1, T_POINTER)
        TYPE_TEST2(sp,   T_STRING)
        v = match_regexp((sp-1)->u.vec, sp->u.string);
        pop_stack();
        free_svalue(sp);
        if (v == NULL)
            put_number(sp, 0);
        else
            put_array(sp,v);
        break;
    }

    CASE(F_SPRINTF);                /* --- sprintf <nargs>     --- */
    {
        /* EFUN sprintf()
         *
         *   string sprintf(string fmt, ...)
         *
         * Generate a string according to the <fmt> and the following
         * arguments and put it onto the stack.
         *
         * <fmt> follows the C-style sprintf-format string in style,
         * and partly in meaning, too.
         */

        char *s;

        /*
         * string_print_formatted() returns a pointer to it's internal
         * buffer, or to an internal constant...  Either way, it must
         * be copied before it's returned as a string.
         */

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        TYPE_TEST1(sp - num_arg + 1, T_STRING)
        s = string_print_formatted((sp-num_arg+1)->u.string,
                                   num_arg-1, sp-num_arg+2);
        pop_n_elems(num_arg);
        if (!s)
            push_number(0);
        else
            /* string_print_formatted() owns the string returned,
             * so copy it.
             */
            push_malloced_string(string_copy(s));
        break;
    }

    CASE(F_STRLEN);                 /* --- strlen              --- */
    {
        /* EFUN strlen()
         *
         *   int strlen(string str)
         *
         * Returns the length of the string str.
         */

        size_t i;

        if (sp->type == T_STRING)
        {
            i = _svalue_strlen(sp);
            free_string_svalue(sp);
            put_number(sp, (p_int)i);
            break;
        }
        if (sp->type == T_NUMBER && sp->u.number == 0)
            break;
        goto bad_arg_1;
    }

    CASE(F_TERMINAL_COLOUR);    /* --- terminal_colour <nargs> --- */
    {
        /* EFUN terminal_colour()
         *
         *   varargs string terminal_colour( string str, mapping|closure map,
         *                                   int wrap, int indent )
         *
         * Expands all colour-defines from the input-string and replaces them
         * by the apropriate values found for the color-key inside the given
         * mapping. The mapping has the format "KEY" : "value", non-string
         * contents are ignored.
         */

        int    indent = 0;
        int    wrap = 0;
        mapping_t *map = NULL;
        svalue_t  *cl = NULL;
        char * str;

        GET_NUM_ARG;
        if ( num_arg >= 3 )
        {
            if ( num_arg == 4 )
            {
                TYPE_TEST4(sp, T_NUMBER );
                indent = (sp--)->u.number;
                if (indent < 0)
                {
                    ERROR("terminal_colour() requires an indent >= 0.\n");
                    break;
                }
            }
            TYPE_TEST3(sp,T_NUMBER);
            wrap = (sp--)->u.number;
            if (wrap < 0)
            {
                ERROR("terminal_colour() requires a wrap >= 0.\n");
                break;
            }
        }

        TYPE_TEST1(sp-1, T_STRING);

        if (sp->type == T_MAPPING)
        {
            if (sp->u.map->num_values < 1)
            {
                ERROR("terminal_colour() requires a mapping with values.\n");
                break;
            }
            map = sp->u.map;
            cl = NULL;
        }
        else if (sp->type == T_CLOSURE)
        {
            map = NULL;
            cl = sp;
        }
        else if (sp->type != T_NUMBER || sp->u.number != 0)
        {
            goto bad_arg_2;
        }


        inter_sp = sp;
        inter_pc = pc;

        str = e_terminal_colour(sp[-1].u.string, map, cl, indent, wrap);

        pop_stack();

        if (str != sp->u.string)
        {
            /* terminal_colour() actually changed the string */
            free_svalue(sp);
            put_malloced_string(sp, str);
        }
        break;
    }

    CASE(F_CLEAR_BIT);              /* --- clear_bit           --- */
    {
        /* EFUN clear_bit()
         *
         *     string clear_bit(string str, int n)
         *
         * Return the new string where bit n is cleared in string str.
         * Note that the old string str is not modified.
         *
         * Each character contains 6 bits. So you can store a value
         * between 0 and 63 ( 2^6=64) in one character. Starting
         * character is the blank character " " which has the value 0.
         * The first charcter in the string is the one with the lowest
         * bits (0-5).
         */

        char *str;
        size_t len, ind, bitnum;
        svalue_t *strp;

        /* Get and test the arguments */
        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_NUMBER)
        bitnum = (size_t)sp->u.number;
        if (sp->u.number < 0)
            ERRORF(("clear_bit: negative bit number: %ld\n", (long)sp->u.number))
        if (bitnum > MAX_BITS)
            ERRORF(("clear_bit: too big bit number: %ld\n", (long)bitnum))

        sp = strp = sp-1;

        len = svalue_strlen(strp);
        ind = bitnum/6;
        if (ind >= len)
        {
            /* Return first argument unmodified! */
            break;
        }

        /* Malloc'ed strings are modified in place, others are copied first.
         */
        if (strp->x.string_type == STRING_MALLOC)
        {
            str = strp->u.string;
        }
        else
        {
            str = xalloc(len+1);
            memcpy(str, strp->u.string, len+1); /* Including null byte */
            free_string_svalue(strp);
            strp->x.string_type = STRING_MALLOC;
            strp->u.string = str;
        }

        if (str[ind] > 0x3f + ' ' || str[ind] < ' ')
            ERRORF(("Illegal bit pattern in clear_bit character %ld\n", (long)ind))

        str[ind] = (char)(((str[ind] - ' ') & ~(1 << (bitnum % 6))) + ' ');

        break;
    }

    CASE(F_SET_BIT);                /* --- set_bit             --- */
    {
        /* EFUN set_bit()
         *
         *   string set_bit(string str, int n)
         *
         * Return the new string where bit n is set in string str. Note
         * that the old string str is not modified.
         *
         * The new string will automatically be extended if needed.
         * TODO: Apply to an optional range (start, length) only.
         */

        char *str;
        size_t len, old_len, ind, bitnum;
        svalue_t *strp;

        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_NUMBER)

        bitnum = (size_t)sp->u.number;
        if (sp->u.number < 0)
            ERRORF(("set_bit: negative bit number: %ld\n", (long)sp->u.number))
        if (bitnum > MAX_BITS)
            ERRORF(("set_bit: too big bit number: %ld\n", (long)bitnum))
        sp = strp = sp-1;

        len = svalue_strlen(strp);
        old_len = len;
        ind = bitnum/6;

        /* Malloc'ed strings of the right size are modified in place,
         * others are copied first.
         */
        if ( (ind < len || (len = ind + 1, MY_FALSE) )
         &&  strp->x.string_type == STRING_MALLOC )
        {
            str = strp->u.string;
        }
        else
        {
            str = xalloc(len+1);
            str[len] = '\0';
            if (old_len)
                memcpy(str, strp->u.string, old_len);
            if (len > old_len)
                memset(str + old_len, ' ', len - old_len);
            free_string_svalue(strp);
            strp->x.string_type = STRING_MALLOC;
            strp->u.string = str;
        }

        if (str[ind] > 0x3f + ' ' || str[ind] < ' ')
            ERRORF(("Illegal bit pattern in set_bit character %ld\n", (long)ind))

        str[ind] = (char)(((str[ind] - ' ') | 1 << (bitnum % 6) ) + ' ');
        sp = strp;
        break;
    }

    CASE(F_TEST_BIT);               /* --- test_bit            --- */
    {
        /* EFUN test_bit()
         *
         *   int test_bit(string str, int n)
         *
         * Return 0 or 1 of bit n was set in string str.
         * TODO: Apply to an optional range (start, length) only.
         */

        size_t len;

        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_NUMBER)

        if (sp->u.number < 0)
            ERRORF(("test_bit: negative bit number: %ld\n", (long)sp->u.number))

        len = svalue_strlen(sp-1);
        if (sp->u.number/6 >= len)
        {
            sp--;
            free_string_svalue(sp);
            put_number(sp, 0);
            break;
        }

        if ( ((sp-1)->u.string[sp->u.number/6] - ' ')
            & 1 << (sp->u.number % 6) )
        {
            sp--;
            free_string_svalue(sp);
            put_number(sp, 1);
        }
        else
        {
            sp--;
            free_string_svalue(sp);
            put_number(sp, 0);
        }
        break;
    }

    CASE(F_OR_BITS);                /* --- or_bits             --- */
    CASE(F_AND_BITS);               /* --- and_bits            --- */
    CASE(F_XOR_BITS);               /* --- xor_bits            --- */
    {
        /* EFUN or_bits(), and_bits(), xor_bits()
         *
         *     string or_bits(string str1, string str2)
         *     string and_bits(string str1, string str2)
         *     string xor_bits(string str1, string str2)
         *
         * Perform a binary operation on the bitstrings <str1> and <str2>
         * and return the resulting string.
         *
         * Each character contains 6 bits. So you can store a value
         * between 0 and 63 ( 2^6=64) in one character. Starting
         * character is the blank character " " which has the value 0.
         * The first charcter in the string is the one with the lowest
         * bits (0-5).
         * TODO: Apply to an optional range (start, length) only.
         */

        size_t  len1, len2, result_len, arg_len;
        char   *arg, *result, *to_copy;
        Bool  use_short; /* TRUE for AND: use shorter string for result */

        result_len = 0;
        to_copy = NULL;

        use_short = (instruction == F_AND_BITS);

        /* Get and test the arguments */
        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_STRING)

        /* Sort the two arguments in shorter and longer string.
         * We will try to modify one of the two strings in-place.
         */
        result = NULL;
        len1 = svalue_strlen(sp-1);
        len2 = svalue_strlen(sp);

        if ((len1 >= len2 && !use_short)
         || (len1 < len2 && use_short)
           )
        {
            /* AND: sp-1 is the shorter result; sp the longer argument
             * else: sp-1 is the longer result; sp the shorter argument
             */

            arg = sp->u.string;

            if ((sp-1)->x.string_type == STRING_MALLOC)
            {
                result = (sp-1)->u.string;
                *(sp-1) = const0;
            }
            else
            {
                result_len = len1;
                to_copy = (sp-1)->u.string;
            }
        }
        else
        {
            /* AND: sp is the shorter result; sp-1 the longer argument
             * else: sp is the longer result; sp-1 the shorter argument
             */
            arg = (sp-1)->u.string;

            if (sp->x.string_type == STRING_MALLOC)
            {
                result = sp->u.string;
                *sp = const0;
            }
            else
            {
                result_len = len2;
                to_copy = sp->u.string;
            }
        }

        /* If needed, allocate a copy of the result string.
         */
        if (!result)
        {
            inter_pc = pc;
            inter_sp = sp;
            result = xalloc(result_len+1);
            if (!result)
                ERROR("Out of memory.\n")
            memcpy(result, to_copy, result_len+1);
        }

        /* Now perform the operation. */

        arg_len = (len2 > len1) ? len1 : len2;
        while (arg_len-- != 0)
        {
            char c1, c2;

            c1 = result[arg_len];
            c2 = arg[arg_len];
            if (c1 > 0x3f + ' ' || c1 < ' ')
                ERRORF(("Illegal bit pattern in or_bits character %d\n"
                       , (int)c1))
            if (c2 > 0x3f + ' ' || c2 < ' ')
                ERRORF(("Illegal bit pattern in or_bits character %d\n"
                       , (int)c2))
            if (instruction == F_AND_BITS)
                result[arg_len] = (char)((c1-' ') & (c2-' ')) + ' ';
            else if (instruction == F_OR_BITS)
                result[arg_len] = (char)((c1-' ') | (c2-' ')) + ' ';
            else if (instruction == F_XOR_BITS)
                result[arg_len] = (char)((c1-' ') ^ (c2-' ')) + ' ';
        }

        /* Clean up the stack and push the result. */
        free_svalue(sp--);
        free_svalue(sp);
        put_malloced_string(sp, result);

        break;
    }

    CASE(F_INVERT_BITS);            /* --- invert_bits            --- */
    {
        /* EFUN invert_bits()
         *
         *     string invert_bits(string str)
         *
         * Invert all bits in the bitstring <str> and return the
         * new string.
         *
         * TODO: Apply to an optional range (start, length) only.
         */

        char * src, * dest, * result;
        long   len;

        /* Get and test the arguments */
        TYPE_TEST1(sp, T_STRING)

        src = sp->u.string;
        len = (size_t)_svalue_strlen(sp);

        /* If it is a malloced string, modify it in place,
         * otherwise allocate a copy.
         */
        if (sp->x.string_type == STRING_MALLOC)
        {
            dest = src;
            result = NULL;
        }
        else
        {
            inter_pc = pc;
            inter_sp = sp;
            result = dest = xalloc((size_t)len+1);
            if (!dest)
                ERROR("Out of memory\n")
        }

        /* Invert the string */
        while (len-- > 0)
        {
            *dest++ = (char)(' ' + (~(*src++ - ' ') & 0x3F));
        }

        *dest = '\0';

        /* Push the result */
        if (result != NULL)
        {
            free_svalue(sp);
            put_malloced_string(sp, result);
        }

        break;
    }

    CASE(F_LAST_BIT);               /* --- last_bit            --- */
    {
        /* EFUN last_bit()
         *
         *     int last_bit(string str)
         *
         * Return the number of the last set bit in bitstring <str>.
         * If no bit is set, return -1.
         *
         * TODO: extend this to true int-bitflags?
         *
         * Each character contains 6 bits. So you can store a value
         * between 0 and 63 ( 2^6=64) in one character. Starting
         * character is the blank character " " which has the value 0.
         * The first charcter in the string is the one with the lowest
         * bits (0-5).
         * TODO: Apply to an optional range (start, length) only.
         */

        mp_int   pos;
        long     len;
        char   * str;
        int      c;

        pos = -1;

        /* Get and test the arguments */
        TYPE_TEST1(sp, T_STRING)

        str = sp->u.string;
        len = (long)_svalue_strlen(sp);

        /* First, find the last non-zero character */
        c = 0;
        while (len-- > 0 && (c = str[len]) == ' ') NOOP;

        if (len >= 0)
        {
            int pattern;

            /* Found a character, now determine the bit position */
            c -= ' ';
            pos = 6 * len + 5;
            for ( pattern = 1 << 5
                ; pattern && !(c & pattern)
                ; pattern >>= 1, pos--)
                NOOP;
        }

        /* Clear the stack and push the result */
        free_svalue(sp);
        put_number(sp, pos);

        break;
    }

    CASE(F_NEXT_BIT);               /* --- next_bit            --- */
    {
        /* EFUN next_bit()
         *
         *     int next_bit(string str, int start, int find_zero)
         *
         * Return the number of the next bit after position <start>
         * in bitstring <str>. If <find_zero> is non-null, the next
         * unset bit is found, else the next set bit.
         * If there is no such bit, return -1.
         *
         * If <start> is negative, the string is searched from the
         * beginning.
         *
         * TODO: extend this to true int-bitflags?
         *
         * Each character contains 6 bits. So you can store a value
         * between 0 and 63 ( 2^6=64) in one character. Starting
         * character is the blank character " " which has the value 0.
         * The first charcter in the string is the one with the lowest
         * bits (0-5).
         */

        mp_int   found;    /* Resultvalue */
        size_t   pos;      /* Searchposition */
        size_t   search;   /* Searchindex */
        int      pattern;  /* Pattern for next bit to test */
        long     len;      /* Length of the string */
        long     start;    /* Startposition */
        char   * str;      /* the bitstring */
        int      invert;   /* when looking for 0 bits, an inverter mask */

        /* Get and test the arguments */
        TYPE_TEST1(sp-2, T_STRING)
        TYPE_TEST2(sp-1, T_NUMBER)

        str = (sp-2)->u.string;
        len = (long)_svalue_strlen(sp-2);

        start = (sp-1)->u.number;
        if (start < 0)
        {
            pattern = 0x01;
            pos = 0;
            search = 0;
        }
        else if (start % 6 == 5)
        {
            pattern = 0x01;
            pos = (size_t)start + 1;
            search = (size_t)start / 6 + 1;
        }
        else
        {
            pattern = 1 << (start % 6 + 1);
            pos = (size_t)start + 1;
            search = (size_t)start / 6;
        }

        invert = 0;
        if (!sp->type == T_NUMBER || sp->u.number)
            invert = 0x3f;

        /* Now search for the next bit */
        found = -1;

        while (found < 0 && search < len)
        {
            int c = str[search] - ' ';

            if (c < 0 || c > 0x3f)
                ERRORF(("Illegal bit pattern in next_bit character %d\n"
                       , c+' '))
            c ^= invert;

            while (found < 0 && pattern < (1 << 6))
            {
                if (c & pattern)
                {
                    found = (mp_int)pos;
                    break;
                }
                pattern <<= 1;
                pos++;
            }
            pattern = 0x01;
            search++;
        }

        /* Cleanup the stack and push the result */
        free_svalue(sp--);
        free_svalue(sp--);
        free_svalue(sp);
        put_number(sp, found);

        break;
    }

    CASE(F_COUNT_BITS);             /* --- count_bits          --- */
    {
        /* EFUN count_bits()
         *
         *     int count_bits(string str)
         *
         * Return the number of set bits in bitstring <str>.
         *
         * TODO: Apply to an optional range (start, length) only.
         */

        char * str;
        long   count;


        /* Get and test the arguments */
        TYPE_TEST1(sp, T_STRING)

        str = sp->u.string;

        for (count = 0; *str; str++)
        {
            int c = *str - ' ';

            if (c > 0x3F || c < 0)
                ERRORF(("Illegal character in count_bits: %d\n", (int)c + ' '))

            /* Count the bits in this character */
            count += ( (c & 0x01) )
                   + ( (c & 0x02) >> 1)
                   + ( (c & 0x04) >> 2)
                   + ( (c & 0x08) >> 3)
                   + ( (c & 0x10) >> 4)
                   + ( (c & 0x20) >> 5);
        }

        /* Return the result */

        free_svalue(sp);
        put_number(sp, count);

        break;
    }

    /* --- Efuns: Arrays and Mappings --- */

    CASE(F_ALLOCATE);               /* --- allocate <nargs>    --- */
    {
        /* EFUN allocate()
         *
         *     mixed *allocate(int|int* size)
         *     mixed *allocate(int|int* size, mixed init_value)
         *
         * Allocate an array of <size> elements (if <size> is an array,
         * the result will be a multidimensional array), either empty or all
         * elements initialized with <init_value>. If <init_value> is a
         * mapping or array, allocate will create shallow copies of them.
         */

        vector_t *v;
        svalue_t *argp;
        Bool hasInitValue = MY_FALSE;

        GET_NUM_ARG

        argp = sp - num_arg + 1;

        inter_sp = sp;
        inter_pc = pc;

        if (num_arg == 2 && (sp->type != T_NUMBER || sp->u.number != 0))
        {
            hasInitValue = MY_TRUE;

            /* If the initialisation value is not a shared string, we
             * better make it shared.
             */
            if (sp->type == T_STRING && sp->x.string_type != STRING_SHARED)
            {
                char * str = make_shared_string(sp->u.string);
                if (!str)
                {
                    ERRORF(("Out of memory for string (%lu bytes)\n"
                           , (unsigned long)strlen(sp->u.string) ));
                    /* NOTREACHED */

                }
                free_string_svalue(sp);
                put_string(sp, str);
            }

            /* If the initialisation value is a mapping, remove all
             * destructed elements so that we can use copy_mapping()
             * later on.
             */
            if (sp->type == T_MAPPING)
            {
                check_map_for_destr(sp->u.map);
            }
        }

        if (argp->type == T_NUMBER)
        {
            if (!hasInitValue)
                v = allocate_array(argp->u.number);
            else
            {
                int i;
                svalue_t *svp;

                v = allocate_uninit_array(argp->u.number);
                for (svp = v->item, i = 0; i < argp->u.number; i++, svp++)
                    copy_svalue_no_free(svp, sp);
            }
        }
        else if (argp->type == T_POINTER
              && (    VEC_SIZE(argp->u.vec) == 0
                   || (   VEC_SIZE(argp->u.vec) == 1
                       && argp->u.vec->item->type == T_NUMBER
                       && argp->u.vec->item->u.number == 0)
                 )
                )
        {
            /* Special case: result is the empty array.
             * The condition catches ( ({}) ) as well as ( ({0}) )
             * (the generic code below can't handle either of them).
             */
            v = allocate_array(0);
        }
        else if (argp->type == T_POINTER)
        {
            svalue_t *svp;
            size_t dim, num_dim;
            size_t count;
            size_t * curpos = NULL;
            size_t * sizes = NULL;
            vector_t ** curvec = NULL;

            num_dim = VEC_SIZE(argp->u.vec);

            curpos = xalloc(num_dim * sizeof(*curpos));
            if (!curpos)
            {
                error("Out of memory (%lu bytes).\n"
                     , (unsigned long)num_dim * sizeof(*curpos));
                /* NOTREACHED */
            }
            sizes = xalloc(num_dim * sizeof(*sizes));
            if (!sizes)
            {
                xfree(curpos);
                error("Out of memory (%lu bytes).\n"
                     , (unsigned long)num_dim * sizeof(*sizes));
                /* NOTREACHED */
            }
            curvec = xalloc(num_dim * sizeof(*curvec));
            if (!curvec)
            {
                xfree(curpos);
                xfree(sizes);
                error("Out of memory (%lu bytes).\n"
                     , (unsigned long)num_dim * sizeof(*curvec));
                /* NOTREACHED */
            }

            /* Check the size array for consistency, and also count how many
             * elements we're going to allocate.
             */
            for ( dim = 0, count = 0, svp = argp->u.vec->item
                ; dim < num_dim
                ; dim++, svp++
                )
            {
                p_int size;

                if (svp->type != T_NUMBER)
                {
                    xfree(curpos);
                    xfree(sizes);
                    xfree(curvec);
                    error("Bad argument to allocate(): size[%d] is not an int.\n"
                         , (int)dim);
                    /* NOTREACHED */
                }

                size = svp->u.number;

                if (size < 0 || (max_array_size && (size_t)size > max_array_size))
                {
                    xfree(curpos);
                    xfree(sizes);
                    xfree(curvec);
                    error("Illegal array size: %ld\n", (long)size);
                    /* NOTREACHED */
                }

                if (size == 0 && dim < num_dim-1)
                {
                    error("Only the last dimension can have empty arrays.\n");
                    /* NOTREACHED */
                }

                count *= (size_t)size;
                if (max_array_size && count > max_array_size)
                {
                    xfree(curpos);
                    xfree(sizes);
                    xfree(curvec);
                    error("Illegal total array size: %lu\n", (unsigned long)count);
                    /* NOTREACHED */
                }

                sizes[dim] = (size_t)size;
                curvec[dim] = NULL;
            }

            /* Now loop over the dimensions, creating the array structure */
            dim = 0;
            curpos[0] = 0;
            while (dim > 0 || curpos[0] < sizes[0])
            {
                if (!curvec[dim])
                {
                    /* We just entered this dimension.
                     * Create a new array and initialise the loop.
                     */
                    if (hasInitValue || dim+1 < num_dim)
                    {
                        curvec[dim] = allocate_uninit_array(sizes[dim]);
                    }
                    else
                    {
                        curvec[dim] = allocate_array(sizes[dim]);
                        /* This is the last dimension, and there is nothing
                         * to initialize: return immediately to the higher level
                         */
                        curpos[dim] = sizes[dim]; /* In case dim == 0 */
                        if (dim > 0)
                            dim--;
                        continue;
                    }
                    curpos[dim] = 0;
                }

                /* curvec[dim] is valid, and we have to put the next
                 * element in at index curpos[dim].
                 */
                if (dim == num_dim-1)
                {
                    /* Last dimension: assign the init value */
                    if (hasInitValue && curpos[dim] < sizes[dim])
                    {
                        copy_svalue_no_free( curvec[dim]->item + curpos[dim]
                                           , sp);
                    }
                }
                else if (!curvec[dim+1])
                {
                    /* We need a vector from a lower dimension, but it doesn't
                     * exist yet: setup the loop parameters to go into
                     * that lower level.
                     */
                    dim++;
                    continue;
                }
                else if (curpos[dim] < sizes[dim])
                {
                    /* We got a vector from a lower lever */
                    put_array(curvec[dim]->item+curpos[dim], curvec[dim+1]);
                    curvec[dim+1] = NULL;
                }

                /* Continue to the next element. If we are at the end
                 * of this dimension, return to the next higher one.
                 */
                curpos[dim]++;
                if (curpos[dim] >= sizes[dim] && dim > 0)
                {
                    dim--;
                }
            } /* while() */

            /* The final vector is now in curvec[0] */
            v = curvec[0];

            xfree(curpos);
            xfree(sizes);
            xfree(curvec);
        }
        else
        {
            error("Illegal arg 1 to allocate(): neither 'int' nore 'int*'.\n");
        } /* if (argp->type) */

        if (num_arg > 1)
            free_svalue(sp--);

        free_svalue(sp);
        put_array(sp, v);
        break;
    }

#ifdef F_MEMBER_ARRAY
    CASE (F_MEMBER_ARRAY);          /* --- member_array        --- */
    {
        /* EFUN member_array()
         *
         *   int member_array(mixed item, mixed *arr)
         *   int member_array(mixed item, string arr)
         *
         * Returns the index of the first occurence of item in array arr,
         * or occurence of a character in a string. If not found, then -1
         * is returned.
         * TODO: Practically obsoleted by member().
         */

        /* Search in an array */

        if (sp->type == T_POINTER)
        {
            vector_t *vec;   /* Vector searched */
            svalue_t *item;  /* Pointer into the vector array */
            svalue_t *key;   /* Item searched */
            long   cnt;           /* Size of vec */

            vec = sp->u.vec;
            item = vec->item;
            key = sp - 1;
            cnt = (long)VEC_SIZE(vec);
            switch(key->type)
            {
            case T_STRING:
              {
                char *str;

                str = key->u.string;
                for(; --cnt >= 0; item++)
                {
                    if (item->type == T_STRING
                     && !strcmp(key->u.string, item->u.string)
                       )
                        break;
                }
                break;
              }

            case T_CLOSURE:
              {
                short type;

                type = key->type;
                for(; --cnt >= 0; item++)
                {
                    if (item->type == type
                     && !closure_cmp(key, item)
                       )
                        break;
                }
                break;
              }

            case T_FLOAT:
            case T_SYMBOL:
            case T_QUOTED_ARRAY:
              {
                short type;
                short x_generic;

                type = key->type;
                x_generic = key->x.generic;
                for(; --cnt >= 0; item++)
                {
                    if (key->u.string == item->u.string
                     && x_generic == item->x.generic
                     && item->type == type
                       )
                        break;
                }
                break;
              }

            case T_NUMBER:
                if (!key->u.number)
                {
                    /* Search for 0 is special: it also finds destructed
                     * objects resp. closures on destructed objects.
                     */
                    short type;

                    for (; --cnt >= 0; item++)
                    {
                        if ( (type = item->type) == T_NUMBER)
                        {
                            if ( !item->u.number )
                                break;
                        }
                        else if (destructed_object_ref(item))
                        {
                            assign_svalue(item, &const0);
                            break;
                        }
                    }
                    break;
                }

                /* FALLTHROUGH */

            case T_MAPPING:
            case T_OBJECT:
            case T_POINTER:
              {
                short type = key->type;

                for(; --cnt >= 0; item++)
                {
                    if (key->u.number == item->u.number
                     && item->type == type)
                        break;
                }
                break;
              }

            default:
                if (sp[-1].type == T_LVALUE)
                    error("Reference passed to member_array()\n");
                fatal("Bad type to member_array(): %d\n", sp[-1].type);
            }

            if (cnt >= 0)
            {
                cnt = (long)VEC_SIZE(vec) - cnt - 1;
            }
            /* else return -1 for failure */

            pop_stack();
            free_svalue(sp);
            put_number(sp, cnt);
            break;
        }

        /* Or search in a string */

        if (sp->type == T_STRING)
        {
            char *str, *str2;
            int i;

            if (sp[-1].type != T_NUMBER)
                goto bad_arg_1;
            str = sp->u.string;
            i = sp[-1].u.number;
            str2 = i & ~0xff ? NULL : strchr(str, i);
            i = str2 ? str2 - str : -1;
            pop_stack();
            free_svalue(sp);
            put_number(sp, i);
            break;
        }

        /* Otherwise, it's something unsearchable */

        goto bad_arg_2;
    }
#endif /* F_MEMBER_ARRAY */

    CASE (F_MEMBER);                /* --- member              --- */
    {
        /* EFUN member()
         *
         *   int member(mixed *array, mixed elem)
         *   int member(mapping m, mixed key)
         *   int member(string s, int elem)
         *
         * For arrays and strings, returns the index of the second arg in
         * the first arg, or -1 if none found. For mappings it checks, if
         * key is present in mapping m and returns 1 if so, 0 if key is
         * not in m.
         */

        /* --- Search an array --- */

        if (sp[-1].type == T_POINTER)
        {

            vector_t *vec;
            union  u       sp_u;
            long cnt;

            vec = sp[-1].u.vec;
            cnt = (long)VEC_SIZE(vec);
            sp_u = sp->u;

            switch(sp->type)
            {
            case T_STRING:
              {
                char *str;
                svalue_t *item;

                str = sp_u.string;
                for(item = vec->item; --cnt >= 0; item++)
                {
                    if (item->type == T_STRING
                     && !strcmp(sp_u.string, item->u.string))
                        break;
                }
                break;
              }

            case T_CLOSURE:
              {
                short type;
                svalue_t *item;

                type = sp->type;
                for(item = vec->item; --cnt >= 0; item++)
                {
                    if (item->type == type
                     && !closure_cmp(sp, item)
                       )
                        break;
                }
                break;
              }

            case T_FLOAT:
            case T_SYMBOL:
            case T_QUOTED_ARRAY:
              {
                short x_generic;
                short type;
                svalue_t *item;

                type = sp->type;
                x_generic = sp->x.generic;
                for(item = vec->item; --cnt >= 0; item++)
                {
                    if (sp_u.string == item->u.string
                     && x_generic == item->x.generic
                     && item->type == type)
                        break;
                }
                break;
              }

            case T_NUMBER:
                if (!sp_u.number)
                {
                    /* Search for 0 is special: it also finds destructed
                     * objects and closures on destructed objects.
                     */

                    svalue_t *item;
                    short type;

                    for (item = vec->item; --cnt >= 0; item++)
                    {
                        if ( (type = item->type) == T_NUMBER)
                        {
                            if ( !item->u.number )
                                break;
                        }
                        else if (destructed_object_ref(item))
                        {
                            assign_svalue(item, &const0);
                            break;
                        }
                    }
                    break;
                }

                /* FALLTHROUGH */

            case T_MAPPING:
            case T_OBJECT:
            case T_POINTER:
              {
                svalue_t *item;
                short type = sp->type;

                for (item = vec->item; --cnt >= 0; item++)
                {
                    if (sp_u.number == item->u.number
                     && item->type == type)
                        break;
                }
                break;
              }

            default:
                if (sp->type == T_LVALUE)
                    error("Reference passed to member()\n");
                fatal("Bad type to member(): %d\n", sp->type);
            }

            if (cnt >= 0)
            {
                cnt = (long)VEC_SIZE(vec) - cnt - 1;
            }
            /* else return -1 for failure */

            pop_stack();
            free_svalue(sp);
            put_number(sp, cnt);
            break;
        }

        /* --- Search a string --- */

        if (sp[-1].type == T_STRING)
        {
            char *str, *str2;
            int i;

            if (sp->type != T_NUMBER)
                goto bad_arg_2;
            str = sp[-1].u.string;
            i = sp->u.number;
            str2 = i & ~0xff ? NULL : strchr(str, i);
            i = str2 ? str2 - str : -1;
            pop_stack();
            free_svalue(sp);
            put_number(sp, i);
            break;
        }

        /* --- Search a string --- */

        if (sp[-1].type == T_MAPPING)
        {
            int i;

            i = get_map_value(sp[-1].u.map, sp) != &const0;
            pop_stack();
            free_svalue(sp);
            put_number(sp, i);
            break;
        }

        /* Otherwise it's not searchable */

        goto bad_arg_1;
    }

    CASE(F_MKMAPPING);              /* mkmapping <nargs>       --- */
    {
        /* EFUN mkmapping()
         *
         *   mapping mkmapping(mixed *arr1, mixed *arr2,...)
         *
         * Returns a mapping with indices from 'arr1' and values from
         * 'arr2'... . arr1[0] will index arr2...[0], arr1[1] will index
         * arr2...[1], etc. If the arrays are of unequal size, the mapping
         * will only contain as much elements as are in the smallest
         * array.
         */

        long i, length, num_values;
        mapping_t *m;
        svalue_t *key;

        GET_NUM_ARG

        /* Check the arguments and set length to the size of
         * the shortest array.
         */
        length = LONG_MAX;
        for (i = -num_arg; ++i <= 0; )
        {
            if ( sp[i].type != T_POINTER )
                bad_arg_pc(i+num_arg, instruction, sp, pc);
            if (length > (long)VEC_SIZE(sp[i].u.vec))
                length = (long)VEC_SIZE(sp[i].u.vec);
        }

        if (max_mapping_size && length > max_mapping_size)
            ERRORF(("Illegal mapping size: %ld\n", length));

        /* Allocate the mapping */
        num_values = num_arg - 1;
        m = allocate_mapping(length, num_values);
        if (!m)
            ERROR("Out of memory\n")

        /* Shift key through the first array and assign the values
         * from the others.
         */
        key = &(sp-num_values)->u.vec->item[length];
        while (--length >= 0)
        {
            svalue_t *dest;

            dest = get_map_lvalue_unchecked(m, --key);
            if (!dest)
            {
                outofmemory("mapping entry");
                /* NOTREACHED */
                return MY_FALSE;
            }
            for (i = -num_values; ++i <= 0; )
            {
                /* If a key value appears multiple times, we have to free
                 * a previous assigned value to avoid a memory leak
                 */
                assign_svalue(dest++, &sp[i].u.vec->item[length]);
            }
        }

        /* Clean up the stack and push the result */
        pop_n_elems(num_arg);
        push_mapping(m);  /* Adds the second ref */
        deref_mapping(sp->u.map); /* This will make ref count == 1 */
        break;
    }

    CASE(F_M_ADD);                  /* m_add <nargs>           --- */
    {
        /* EFUN m_add()
         *
         *   mapping m_add(mapping m, mixed key, mixed data...)
         *
         * Add (or replace) an entry with index <key> in mapping <map>.
         * The modified mapping is also returned as result.
         *
         * The values for the entry are taken from the <data> arguments.
         * Unassigned entry values default to 0, extraneous <data> arguments
         * are ignore.
         */

        mapping_t *m;
        svalue_t *argp;
        svalue_t *entry;
        int num_values;

        GET_NUM_ARG

        argp = sp - num_arg + 1;

        /* Check the arguments. */
        TYPE_TEST1(argp, T_MAPPING);
        m = argp->u.map;

        /* Get (or create) the mapping entry */
        entry = get_map_lvalue(m, argp+1);

        /* Transfer the given values from the stack into the mapping
         * entry.
         */
        num_values = m->num_values;
        if (num_values > num_arg - 2)
            num_values = num_arg - 2;
        for ( argp += 2
            ; num_values > 0 && argp <= sp
            ; num_values--, argp++, entry++
            )
        {
            transfer_svalue_no_free(entry, argp);
            /* And since we take out values from under sp, play it
             * safe:
             */
            put_number(argp, 0);
        }

        /* We leave the reference to the mapping on the stack as result,
         * but pop everything else.
         */
        pop_n_elems(num_arg-1);
        break;
    }

    CASE(F_M_DELETE);               /* --- m_delete            --- */
    {
        /* EFUN m_delete()
         *
         *   mapping m_delete(mapping map, mixed key)
         *
         * Remove the entry with index 'key' from mapping 'map'. The
         * changed mapping 'map' is also returned as result.
         * If the mapping does not have an        entry with index 'key',
         * nothing is changed.
         */

        mapping_t *m;

        TYPE_TEST1(sp-1, T_MAPPING)
        m = (sp-1)->u.map;
        remove_mapping(m, sp);
        pop_stack();
        /* leave the mapping on the stack */
        break;
    }

    CASE(F_M_INDICES);              /* --- m_indices           --- */
    {
        /* EFUN m_indices()
         *
         *   mixed *m_indices(mapping map)
         *
         * Returns an array containing the indices of mapping 'map'.
         */

        mapping_t *m;
        vector_t *v;

        TYPE_TEST1(sp, T_MAPPING)
        m = sp->u.map;
        inter_pc = pc;
        inter_sp = sp;

        v = m_indices(m);

        free_mapping(m);
        put_array(sp,v);
        break;
    }

    CASE(F_M_VALUES);               /* --- m_values            --- */
    {
        /* EFUN m_values()
         *
         *   mixed *m_values(mapping map)
         *   mixed *m_values(mapping map, int index)
         *
         * Returns an array with the values of mapping 'map'.
         * If <index> is given as a number between 0 and the width of
         * the mapping, the values from the given column are returned,
         * else the values of the first column.
         */

        mapping_t *m;
        vector_t *v;
        struct mvf_info vip;
        mp_int size;
        int num;

        /* Get and check the arguments */
        TYPE_TEST2(sp, T_NUMBER);
        num = sp->u.number;
        sp--;
        if (sp->type != T_MAPPING || (m = sp->u.map)->num_values < 1)
            goto bad_arg_1;
        if (num < 0 || num >= m->num_values)
            ERROR("Illegal index to m_values().\n");

        /* Get the size of the mapping */
        check_map_for_destr(m);
        size = (mp_int)MAP_SIZE(m);

        v = allocate_array(size);

        /* Extract the desired column from the mapping */
        vip.svp = v->item;
        vip.num = num;
        walk_mapping(m, m_values_filter, &vip);
        free_mapping(m);

        /* Push the result */
        put_array(sp,v);
        break;
    }

    CASE(F_SIZEOF);                 /* --- sizeof              --- */
    {
        /* EFUN sizeof()
         *
         *   int sizeof(mixed arr)
         *
         * Returns the number of elements of an array, the number of
         * keys in a mapping, or the number of characters in a string.
         *
         * As a special case, the number 0 can be passed, and the function
         * will return 0.
         */

        long i;

        if (sp->type == T_STRING)
        {
            i = (long)svalue_strlen(sp);
            free_svalue(sp);
            put_number(sp, i);
            break;
        }

        if (sp->type == T_POINTER)
        {
            i = (long)VEC_SIZE(sp->u.vec);
            free_svalue(sp);
            put_number(sp, i);
            break;
        }

        if (sp->type == T_MAPPING)
        {
            mapping_t *m = sp->u.map;
            check_map_for_destr(m);
            i = (long)MAP_SIZE(m);
            free_svalue(sp);
            put_number(sp, i);
            break;
        }

        if (sp->type == T_NUMBER && sp->u.number == 0)
            break;

        goto bad_arg_1;
    }

    CASE(F_UNIQUE_ARRAY);           /* --- unique_array        --- */
    {
        /* EFUN unique_array()
         *
         *   mixed unique_array(object *obarr, string seperator)
         *   mixed unique_array(object *obarr, string seperator, mixed skip)
         *
         * Groups objects together for which the separator function
         * returns the same value. obarr should be an array of objects,
         * other types are ignored. The separator function is called only
         * once in each object in obarr. If no separator function is
         * given, 0 is used instead of a return value.
         * If a 3rd argument is given and this argument matches the
         * return value of the separator function this object will not be
         * included in the returned array.
         */

        vector_t *res;

        assign_eval_cost();
        inter_pc = pc;
        inter_sp = sp;
        TYPE_TEST1(sp-2, T_POINTER)
        TYPE_TEST2(sp-1, T_STRING)

        check_for_destr((sp-2)->u.vec);
        res = make_unique((sp-2)->u.vec, (sp-1)->u.string, sp);

        /* Clean up the stack and push the result */
        pop_stack();
        pop_stack();
        free_svalue(sp);

        if (res)
        {
            put_array(sp, res);
        }
        else
            put_number(sp, 0);

        break;
    }

    CASE(F_UNMKMAPPING);            /* --- unmkmapping         --- */
    {
        /* EFUN unmkmapping()
         *
         *   mixed* unmkmapping(mapping map)
         *
         * Take mapping <map> and return an array of arrays with the keys
         * and values from the mapping.
         *
         * The return array has the form ({ keys[], data0[], data1[], ... }).
         */

        svalue_t *svp;
        mapping_t *m;
        vector_t *v;
        struct mvf_info vip;
        mp_int size;
        int i;

        /* Get the arguments */
        if (sp->type != T_MAPPING)
            goto bad_arg_1;
        m = sp->u.map;

        /* Determine the size of the mapping and allocate the result vector */
        check_map_for_destr(m);
        size = (mp_int)MAP_SIZE(m);
        v = allocate_array(m->num_values+1);

        /* Allocate the sub vectors */
        for (i = 0, svp = v->item; i <= m->num_values; i++, svp++)
        {
            vector_t *v2;

            v2 = allocate_array(size);
            put_array(svp, v2);
        }

        /* Copy the elements from the mapping into the vector brush */
        vip.svp = v->item;
        vip.num = 0;
        vip.width = m->num_values;
        walk_mapping(m, m_unmake_filter, &vip);

        /* Clean up the stack and push the result */
        free_mapping(m);
        put_array(sp,v);
        break;
    }

    CASE(F_WIDTHOF);                /* --- widthof             --- */
    {
        /* EFUN widthof()
         *
         *   int widthof (mapping map)
         *
         * Returns the number of values per key of mapping <map>.
         * If <map> is 0, the result is 0.
         */

        int width;

        if (sp->type == T_NUMBER && sp->u.number == 0)
            break;
        TYPE_TEST1(sp, T_MAPPING)
        width = sp->u.map->num_values;
        free_mapping(sp->u.map);
        put_number(sp, width);
        break;
    }

    /* --- Efuns: Functions and Closures --- */

    CASE(F_APPLY);                  /* --- apply <nargs>       --- */
    {
        /* EFUN apply()
         *
         *     mixed apply(closure cl, ...)
         *
         * Call the closure <cl> and pass it all the extra arguments
         * given in the call. If the last argument is an array, it
         * is flattened, ie. passed as a bunch of single arguments.
         * TODO: Use the MudOS-Notation '(*f)(...)' as alternative.
         */

        svalue_t *args;

        GET_NUM_ARG

        args = sp -num_arg +1;

        if (args->type != T_CLOSURE)
        {
            /* Not a closure: pop the excess args and return <cl>
             * as result.
             */

            while (--num_arg)
                free_svalue(sp--);
            break;
        }

        if (sp->type == T_POINTER)
        {
            /* The last argument is an array: flatten it */

            vector_t *vec;  /* the array */
            svalue_t *svp;  /* pointer into the array */
            long i;              /* (remaining) vector size */

            vec = sp->u.vec;
            i = (long)VEC_SIZE(vec);
            num_arg += i - 1;

            /* Check if the target closure can handle
             * all the arguments without overflowing the stack.
             */
            switch( (sp - num_arg + i)->x.closure_type )
            {
            default:
                if ((sp - num_arg + i)->x.closure_type >= 0)
                    bad_arg_pc(num_arg - i + 1, instruction, sp, pc);
                    /* TODO: Be more specific */
                /* else: operator/sefun/efun closure: FALLTHROUGH */
            case CLOSURE_LFUN:
            case CLOSURE_ALIEN_LFUN:
            case CLOSURE_LAMBDA:
            case CLOSURE_BOUND_LAMBDA:
                if (num_arg + (sp - VALUE_STACK) < EVALUATOR_STACK_SIZE)
                    break;
                ERRORF(("VM Stack overflow: %ld too high.\n"
                       , (long)(num_arg + (sp - VALUE_STACK) - EVALUATOR_STACK_SIZE) ));
                break;
            }

            /* Push the array elements onto the stack, overwriting the
             * array value itself.
             */
            if (deref_array(vec))
            {
                for (svp = vec->item; --i >= 0; )
                {
                    if (destructed_object_ref(svp))
                    {
                        put_number(sp, 0);
                        sp++;
                        svp++;
                    }
                    else
                        assign_svalue_no_free(sp++, svp++);
                }
            }
            else
            {
                /* The array will be freed, so use a faster function */
                for (svp = vec->item; --i >= 0; ) {
                    if (destructed_object_ref(svp))
                    {
                        put_number(sp, 0);
                        sp++;
                        svp++;
                    }
                    else
                    {
                        sp++;
                        transfer_svalue_no_free_spc(sp-1, svp++, sp, pc);
                    }
                }
                free_empty_vector(vec);
            }

            sp--; /* undo the last extraneous sp++ */
        }

        /* Prepare to call the closure */

        args = sp -num_arg +1;
        TYPE_TEST1(args, T_CLOSURE)

        /* No external calls may be done when this object is
         * destructed.
         */
        if (current_object->flags & O_DESTRUCTED)
        {
            pop_n_elems(num_arg);
            push_number(0);
            break;
        }

        inter_pc = pc;
        inter_sp = sp;
        ASSIGN_EVAL_COST

        call_lambda(args, num_arg - 1);

        /* Cleanup the stack (note that the closure might have
         * been removed through object destruction)
         */
        sp = args;
        free_svalue(sp);
        *sp = sp[1];
        break;
    }

    CASE(F_BIND_LAMBDA);            /* --- bind_lambda         --- */
    {
        /* EFUN bind_lambda()
         *
         *     closure bind_lambda(closure cl, object ob = 1)
         *
         * Binds an unbound closure <cl> to object <ob> and return the
         * bound closure.
         *
         * If the optional argument ob is not this_object(), the privilege
         * violation ("bind_lambda", this_object(), ob) occurs.
         *
         * If the argument <ob> is omitted, the closure is bound to
         * this_object(), and additionally the function accepts unbindable
         * closures without complaint.
         *
         * Note: the 'default' value for <ob> is const1 so that the omittal
         * can be detected.
         */

        object_t *ob;

        TYPE_TEST1(sp-1, T_CLOSURE)
        if (sp->type == T_OBJECT)
        {
            /* If <ob> is given, check for a possible privilege breach */
            ob = sp->u.ob;
            if (ob != current_object
             && !privilege_violation("bind_lambda", sp))
            {
                free_object(ob, "bind_lambda");
                sp--;
                break;
            }
        }
        else if (sp->type == T_NUMBER && sp->u.number == 1)
        {
            /* this_object is ok */
            ob = ref_object(current_object, "bind_lambda");
        }
        else
            goto bad_arg_2;

        sp--;  /* points to the closure now */

        switch(sp->x.closure_type)
        {
        case CLOSURE_LFUN:
        case CLOSURE_LAMBDA:
        case CLOSURE_IDENTIFIER:
        case CLOSURE_PRELIMINARY:
            /* Unbindable closures. Free the ob reference and
             * throw an error (unless <ob> has been omitted)
             */
            free_object(ob, "bind_lambda");
            if (sp[1].type == T_NUMBER)
                break;
            goto bad_arg_1;

        case CLOSURE_ALIEN_LFUN:
            /* Rebind an alien lfun to the given object */
            free_object(sp->u.lambda->ob, "bind_lambda");
            sp->u.lambda->ob = ob;
            break;

        default:
            /* efun, simul_efun, operator closures: rebind it */

            free_object(sp->u.ob, "bind_lambda");
            sp->u.ob = ob;
            break;

        case CLOSURE_BOUND_LAMBDA:
          {
            /* Rebind an already bound lambda closure */

            lambda_t *l;

            if ( (l = sp->u.lambda)->ref == 1)
            {
                /* We are the only user of the lambda: simply rebind it.
                 */

                object_t **obp;

                obp = &l->ob;
                free_object(*obp, "bind_lambda");
                *obp = ob;
                break;
            }
            else
            {
                /* We share the closure with others: create our own
                 * copy, bind it and put it onto the stack in place of
                 * the original one.
                 */
                lambda_t *l2;

                l->ref--;
                l2 = xalloc(sizeof *l);
                l2->ref = 1;
                l2->ob = ob;
                l2->function.lambda = l->function.lambda;
                l->function.lambda->ref++;
                sp->u.lambda = l2;
                break;
            }
          }

        case CLOSURE_UNBOUND_LAMBDA:
          {
            /* Whee, an unbound lambda: create the bound-lambda structure
             * and put it onto the stack in place of the unbound one.
             */

            lambda_t *l;

            l = xalloc(sizeof *l);
            l->ref = 1;
            l->ob = ob;
            l->function.lambda = sp->u.lambda;
            /* The ref to the unbound closure is just transferred from
             * sp to l->function.lambda.
             */
            sp->x.closure_type = CLOSURE_BOUND_LAMBDA;
            sp->u.lambda = l;
            break;
          }
        }
        break;
    }

    CASE(F_CALL_OTHER);             /* --- call_other <nargs>  --- */
    {
        /* EFUN call_other()
         *
         *     unknown call_other(object|string ob, string str, mixed arg, ...)
         *     unknown ob->fun(mixed arg, ...)
         *
         * Call a member function in another object with an argument. The
         * return value is returned from the other object.  The object can be
         * given directly or as a string (i.e. its file name). If it is given
         * by a string and the object does not exist yet, it will be loaded.
         *
         *     unknown * call_other(object|string *ob, string str, mixed arg, ...)
         *     unknown * ob->fun(mixed arg, ...)
         *
         * Call a member function in other objects with the given arguments.
         * The return values is returned collected in an array.
         * Every object can be given directly or as a string (i.e. its file name).
         * If it is given by a string and the object does not exist yet, it will
         * be loaded.
         *
         * TODO: A VOID_CALL_OTHER would be nice to have when the result
         * TODO:: is not used.
         */

        svalue_t *arg;
        object_t *ob;

        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;

        arg = sp - num_arg + 1;

        /* Test the arguments */
        if (arg[0].type != T_OBJECT
         && arg[0].type != T_STRING
         && arg[0].type != T_POINTER
           )
            goto bad_arg_1;

        TYPE_TEST2(arg+1, T_STRING)
        if (arg[1].u.string[0] == ':')
            ERRORF(("Illegal function name in call_other: %s\n",
                  arg[1].u.string))

        /* No external calls may be done when this object is
         * destructed.
         */
        if (current_object->flags & O_DESTRUCTED)
        {
            pop_n_elems(num_arg);
            push_number(0);
            break;
        }

        if (arg[0].type != T_POINTER)
        {
            /* --- The normal call other to a single object --- */

            ASSIGN_EVAL_COST

            if (arg[0].type == T_OBJECT)
                ob = arg[0].u.ob;
            else if (arg[0].type == T_STRING)
            {
                ob = get_object(arg[0].u.string);
                if (ob == NULL)
                    ERROR("call_other() failed\n")
            }
            else
                goto bad_arg_1;

            /* Traceing, if necessary */
            if (TRACEP(TRACE_CALL_OTHER) && TRACE_IS_INTERACTIVE())
            {
                if (!++traceing_recursion)
                {
                    do_trace("Call other ", arg[1].u.string, "\n");
                }
                traceing_recursion--;
            }

            /* Call the function with the remaining args on the stack.
             */
            if (!apply_low(arg[1].u.string, ob, num_arg-2, MY_FALSE))
            {
                /* Function not found */
                pop_n_elems(num_arg);
                push_number(0);
                break;
            }
            sp -= num_arg - 3;

            /* The result of the function call is on the stack. But so
             * is the function name and object that was called.
             * These have to be removed.
             */
            arg = sp;           /* Remember where the function call result is */
            free_string_svalue(--sp);
            free_svalue(--sp);  /* Remove old arguments to call_other */
            *sp = *arg;         /* Re-insert function result */
        }
        else
        {
            /* --- The other call other to an array of objects --- */

            svalue_t *svp;
            size_t    size;

            /* The array with the objects will also hold the results.
             * For that, it mustn't be shared, therefore we create a
             * copy if necessary.
             */
            size = VEC_SIZE(arg->u.vec);
            if (arg->u.vec->ref != 1 && size != 0)
            {
                vector_t *vec;
                svalue_t *to;

                vec = allocate_array_unlimited(size);
                if (!vec)
                    ERROR("Out of memory.\n");
                for (svp = arg->u.vec->item, to = vec->item
                    ; size != 0
                    ; size--, svp++, to++)
                    assign_svalue_no_free(to, svp);
                free_array(arg->u.vec);
                arg->u.vec = vec; /* adopts the reference */
            }

            /* Now loop over the array of objects and call the function
             * in each of it. For that, the arguments are duly replicated
             * for every call.
             */
            size = VEC_SIZE(arg->u.vec);
            svp = arg->u.vec->item;
            for ( ; size != 0; size--, svp++)
            {
                int i;

                ASSIGN_EVAL_COST
                inter_sp = sp; /* Might be clobbered from previous loop */

                if (svp->type == T_OBJECT)
                    ob = svp->u.ob;
                else if (svp->type == T_STRING)
                {
                    ob = get_object(svp->u.string);
                    if (ob == NULL)
                    {
                        ERROR("call_other() failed\n")
                        /* NOTREACHED */
                        continue;
                    }
                }
                else if (svp->type == T_NUMBER && svp->u.number == 0)
                {
                    free_svalue(svp);
                    put_number(svp, 0);
                    continue;
                }
                else
                    ERRORF(("Bad argument for call_other() at index %ld\n"
                           , (long)(svp - arg->u.vec->item)));

                /* Destructed objects yield 0 */
                if (ob->flags & O_DESTRUCTED)
                {
                    free_svalue(svp);
                    put_number(svp, 0);
                    continue;
                }

                /* Traceing, if necessary */
                if (TRACEP(TRACE_CALL_OTHER) && TRACE_IS_INTERACTIVE())
                {
                    if (!++traceing_recursion)
                    {
                        do_trace("Call other ", arg[1].u.string, "\n");
                    }
                    traceing_recursion--;
                }

                /* Duplicate the arguments to pass, increasing sp on
                 * the way. Optimizing this for the last pass is
                 * dangerous as not every iteration will come here.
                 */
                for (i = 2; i < num_arg; i++)
                    assign_svalue_no_free(++sp, arg+i);

                /* Call the function with the remaining args on the stack.
                 */
                inter_sp = sp; /* update to new setting */
                if (!apply_low(arg[1].u.string, ob, num_arg-2, MY_FALSE))
                {
                    /* Function not found: clean up the stack and
                     * assign 0 as result.
                     */
                    pop_n_elems(num_arg-2);
                    free_svalue(svp);
                    put_number(svp, 0);
                }
                else
                {
                    /* Function found - assign the result from the stack */
                    sp -= num_arg-3;
                    free_svalue(svp);
                    transfer_svalue_no_free(svp, sp--);
                }
            } /* for (objects in array) */

            /* Remove the function call arguments from the stack.
             */
            pop_n_elems(num_arg-2);

            /* Calls complete, left on the stack are now the function name
             * and, in arg, the final result.
             */
            free_string_svalue(sp); sp--;
        }

        break;
    }

    CASE(F_FUNCALL);                /* --- funcall <nargs>     --- */
    {
        /* EFUN funcall()
         *
         *   mixed funcall(closure cl, mixed arg ...)
         *
         * Evaluates the closure. The extra args will be passed as args
         * to the closure. If cl is not a closure, it will simply be
         * returned.
         */

        svalue_t *args;

        GET_NUM_ARG
        args = sp -num_arg +1;

        if (args->type == T_CLOSURE)
        {
            /* No external calls may be done when this object is
             * destructed.
             */
            if (current_object->flags & O_DESTRUCTED) {
                pop_n_elems(num_arg);
                push_number(0);
                break;
            }

            inter_pc = pc;
            inter_sp = sp;
            ASSIGN_EVAL_COST

            /* Call the closure and push the result */
            call_lambda(args, num_arg - 1);
            sp = args;
            free_svalue(sp);
            *sp = sp[1];
        }
        else
        {
            /* Not a closure: pop the excess args and return <cl>
             * as result.
             */

            while (--num_arg)
                free_svalue(sp--);
        }
        break;
    }

    CASE(F_LAMBDA);                 /* --- lambda              --- */
    {
        /* EFUN lambda()
         *
         *   closure lambda(mixed *arr, mixed)
         *
         * Constructs a lambda closure, like lambda function in LISP.
         * The closure is bound the creating object, and thus can contain
         * references to global variables.
         *
         * The first argument is an array describing the arguments
         * (symbols) passed to the closure upon evaluation by funcall()
         * or apply(). It may be 0 if no arguments are required.
         */

        lambda_t *l;
        vector_t *args;

        inter_pc = pc;
        inter_sp = sp;
        if (sp[-1].type != T_POINTER)
        {
            /* If '0' is given for the args array, replace it
             * with the null-vector.
             */
            if (sp[-1].type != T_NUMBER || sp[-1].u.number)
                goto bad_arg_1;
            args = ref_array(&null_vector);
        }
        else
        {
            args = sp[-1].u.vec;
        }

        /* Create the lambda closure */
        l = lambda(args, sp, current_object);
        l->ob = ref_object(current_object, "lambda");

        /* Clean up the stack and push the result */
        pop_stack();
        free_array(args);

        sp->type = T_CLOSURE;
        sp->x.closure_type = CLOSURE_LAMBDA;
        sp->u.lambda = l;
        break;
    }

    CASE(F_QUOTE);                  /* --- quote               --- */
    {
        /* EFUN quote()
         *
         *   mixed quote(mixed)
         *
         * Converts arrays to quoted arrays and strings to symbols.
         * Symbols and quoted arrays get quoted once more.
         */

        switch (sp->type)
        {
        case T_QUOTED_ARRAY:
        case T_SYMBOL:
            sp->x.quotes++;
            break;

        case T_POINTER:
            sp->type = T_QUOTED_ARRAY;
            sp->x.quotes = 1;
            break;

        case T_STRING:
            if (sp->x.string_type != STRING_SHARED)
            {
                /* Symbols must be shared strings */

                char *str = sp->u.string;

                sp->u.string = make_shared_string(str);
                if (sp->x.string_type == STRING_MALLOC)
                    xfree(str);
            }
            sp->type = T_SYMBOL;
            sp->x.quotes = 1;
            break;

        default:
            goto bad_arg_1;
        }

        break;
    }

    CASE(F_UNQUOTE);                /* --- unquote             --- */
    {
        /* EFUN unquote()
         *
         *   mixed unquote(mixed)
         *
         * Removes a quote from quoted arrays and symbols. When the
         * last quote from a symbol is removed, the result is a string.
         */

        switch (sp->type)
        {
        case T_QUOTED_ARRAY:
            sp->x.quotes--;
            if (!sp->x.quotes)
                sp->type = T_POINTER;
            break;

        case T_SYMBOL:
            sp->x.quotes--;
            if (!sp->x.quotes)
            {
                sp->type = T_STRING;
                sp->x.string_type = STRING_SHARED;
            }
            break;

        default:
            goto bad_arg_1;
        }

        break;
    }

    CASE(F_SYMBOL_FUNCTION);        /* --- symbol_function     --- */
    {
        /* EFUN symbol_function()
         *
         *   closure symbol_function(symbol arg)
         *   closure symbol_function(string arg)
         *   closure symbol_function(string arg, object|string ob)
         *
         * Constructs a lfun closure, efun closure or operator closure
         * from the first arg (string or symbol). For lfuns, the second
         * arg is the object that the lfun belongs to, specified by
         * the object itself or by its name (the object will be loaded
         * in the second case)
         *
         * Private lfuns can never be accessed this way, static and
         * protected lfuns only if <ob> is the current object.
         */

        object_t *ob;
        program_t *prog;
        int i;

        /* If 'arg' is not a symbol, make sure it's a shared string. */
        if (sp[-1].type != T_SYMBOL)
        {
            if (sp[-1].type == T_STRING)
            {
                if (sp[-1].x.string_type != STRING_SHARED)
                {
                    char *str;

                    str = sp[-1].u.string;
                    sp[-1].u.string = make_shared_string(str);
                    if (sp[-1].x.string_type == STRING_MALLOC)
                        xfree(str);
                    sp[-1].x.string_type = STRING_SHARED;
                }
            }
            else
                goto bad_arg_1;
        }

        /* If 'ob' is not of type object, it might be the name of
         * an object to load, or we need to make an efun symbol.
         */
        if (sp->type != T_OBJECT)
        {
            /* If it's the number 0, an efun symbol is desired */
            if (sp->type == T_NUMBER && sp->u.number == 0)
            {
                sp--;
                inter_pc = pc;
                symbol_efun(sp);
                break;
            }

            /* Find resp. load the object by name */
            TYPE_TEST2(sp, T_STRING)
            inter_sp = sp;
            inter_pc = pc;
            ob = get_object(sp->u.string);
            if (!ob)
                error("Object '%s' not found.\n", sp->u.string);
            free_svalue(sp);
            put_ref_object(sp, ob, "symbol_function");
        }
        else
        {
            ob = sp->u.ob;
        }

        /* We need the object's program */
        if (O_PROG_SWAPPED(ob))
        {
            ob->time_of_ref = current_time;
            if (load_ob_from_swap(ob) < 0)
            {
                inter_sp = sp;
                error("Out of memory\n");
            }
        }

        /* Find the function in the program */
        prog = ob->prog;
        i = find_function(sp[-1].u.string, prog);

        /* If the function exists and is visible, create the closure
         */
        if ( i >= 0
          && ( !(prog->functions[i] & (TYPE_MOD_STATIC|TYPE_MOD_PROTECTED|TYPE_MOD_PRIVATE) )
             || (    !(prog->functions[i] & TYPE_MOD_PRIVATE)
                  && current_object == ob)
             )
           )
        {
            lambda_t *l;
            ph_int closure_type;

            l = xalloc(sizeof *l);
            if (!l)
            {
                error("Out of memory.\n");
                /* NOTREACHED */
                break;
            }

            l->ref = 1;
            l->ob = current_object;
              /* If ob == current_object, adopt the reference,
               * otherwise a reference will be added below.
               */

            /* Set the closure */
            if (ob == current_object)
            {
                if (!(prog->flags & P_REPLACE_ACTIVE)
                 || !lambda_ref_replace_program( l
                                               , CLOSURE_ALIEN_LFUN
                                               , 0, NULL, NULL)
                   )
                {
                    current_object->flags |= O_LAMBDA_REFERENCED;
                    l->function.index = (unsigned short)i;
                    closure_type = CLOSURE_LFUN;
                }
                else
                {
                    l->function.alien.ob
                      = ref_object(current_object, "symbol_function");
                    l->function.alien.index = (unsigned short)i;
                    closure_type = CLOSURE_ALIEN_LFUN;
                }
            }
            else
            {
                object_t *curobj = current_object; /* saved */

                l->function.alien.ob = ob; /* adopt the ref */
                l->function.alien.index = (unsigned short)i;
                closure_type = CLOSURE_ALIEN_LFUN;

                ref_object(current_object, "symbol_function");
                  /* The ref missing from l->ob=current_object above */

                current_object = ob;
                  /* Required by lambda_ref_replace_program()
                   * It will be restored below from curobj.
                   */

                if (!(prog->flags & P_REPLACE_ACTIVE)
                 || !lambda_ref_replace_program( l
                                               , CLOSURE_ALIEN_LFUN
                                               , 0, NULL, NULL)
                   )
                {
                    ob->flags |= O_LAMBDA_REFERENCED;
                }

                current_object = curobj;
            }

            /* Clean up the stack and push the result */
            sp--;
            deref_string(sp->u.string);
            sp->type = T_CLOSURE;
            sp->x.closure_type = closure_type;
            sp->u.lambda = l;

            break;
        }

        /* Symbol can't be created - free the stack and push 0 */
        free_object(ob, "symbol_function");
        sp--;
        free_string(sp->u.string);
        put_number(sp, 0);
        break;
    }

    CASE(F_CALL_OUT);               /* --- call_out <nargs>    --- */
        /* EFUN call_out()
         *
         *     void call_out(string fun, int delay, mixed arg, ...)
         *     void call_out(closure cl, int delay, mixed arg, ...)
         *
         * Set up a call to function fun or closure cl in the current
         * object. The call will take place in delay seconds, with the
         * remaining argument list provided. delay can be a minimum time
         * of one second.
         */

        GET_NUM_ARG
        inter_pc = pc;
        sp = new_call_out(sp, (short)num_arg);
        break;

    CASE(F_CALL_OUT_INFO);          /* --- call_out_info       --- */
        /* EFUN call_out_info()
         *
         *     mixed *call_out_info(void)
         *
         * Get information about all pending call outs. The result is an
         * array in which every entry is itself an array describing one
         * call_out.
         *
         * The efun causes the the privilege violation ("call_out_info",
         * this_object()). If it is not satisfied, the result will be
         * the empty array.
         */

        inter_sp = sp;
        inter_pc = pc;
        if (privilege_violation("call_out_info", &const0))
        {
            push_referenced_vector(get_all_call_outs());
        }
        else
        {
            push_vector(&null_vector);
        }
        break;

    CASE(F_FIND_CALL_OUT);          /* --- find_call_out       --- */
    {
        /* EFUN find_call_out()
         *
         *   int find_call_out(string func)
         *   int find_call_out(closure func)
         *
         * Find the first call-out due to be executed for function func
         * in the current object, and return the time left. If no call-out
         * is found return -1.
         */

        inter_pc = pc;
        find_call_out(current_object, sp, MY_FALSE);
        break;
    }

    CASE(F_REMOVE_CALL_OUT);        /* --- remove_call_out     --- */
    {
        /* EFUN remove_all_out()
         *
         *   int remove_call_out(string fun)
         *   int remove_call_out(closure fun)
         *
         * Remove next pending call-out for function fun in this object.
         * The time left is returned.
         *
         * -1 is returned if there were no call-outs pending to this
         * function.
         */

        inter_pc = pc;
        find_call_out(current_object, sp, MY_TRUE);
        break;
    }

    CASE(F_SET_HEART_BEAT);         /* --- set_heart_beat      --- */
    {
        /* EFUN set_heart_beat()
         *
         *   int set_heart_beat(int flag)
         *
         * Enable or disable heart beat. The driver will apply
         * the lfun heart_beat() to the current object every 2 seconds,
         * if it is enabled. If the heart beat is not needed for the
         * moment, then do disable it. This will reduce system overhead.
         *
         * Return true for success, and false for failure.
         *
         * Disabling an already disabled heart beat (and vice versa
         * enabling and enabled heart beat) counts as failure.
         */

        int i;

        TYPE_TEST1(sp, T_NUMBER)
        i = set_heart_beat(current_object, sp->u.number);
        sp->u.number = i;
        break;
    }

    /* --- Efuns: Objects --- */

    CASE(F_CLONE_OBJECT);           /* --- clone_object        --- */
    {
        /* EFUN clone_object()
         *
         *   object clone_object(string name)
         *   object clone_object(object template)
         *
         * Clone a new object from definition <name>, or alternatively from
         * the object <template>. In both cases, the new object is given an
         * unique name and returned.
         */

        object_t *ob;

        assign_eval_cost();
        inter_sp = sp;
        inter_pc = pc;

        /* Get the argument and clone the object */
        if (sp->type == T_STRING)
            ob = clone_object(sp->u.string);
        else if (sp->type == T_OBJECT)
            ob = clone_object(sp->u.ob->load_name);
        else
            goto bad_arg_1;

        free_svalue(sp);

        if (ob)
            put_ref_object(sp, ob, "F_CLONE_OBJECT");
        else
            put_number(sp, 0);
        break;
    }

    CASE(F_DESTRUCT);               /* --- destruct            --- */
        /* EFUN destruct()
         *
         *   void destruct(object ob)
         *
         * Completely destroy and remove object ob (if not already done so).
         * After the call to destruct(), no global variables will exist any
         * longer, only local ones, and arguments.
         *
         * If an object self-destructs, it will not immediately terminate
         * execution. If the efun this_object() will be called by the
         * destructed object, the result will be 0.
         *
         * The efun accepts destructed objects as argument (which appear
         * as the number 0) and the simply acts as a no-op in that case.
         *
         * Internally, the object is not destructed immediately, but
         * instead put into a list and finally destructed after the
         * current execution has ended.
         */

        if (T_NUMBER != sp->type || sp->u.number)
        {
            assign_eval_cost();
            TYPE_TEST1(sp, T_OBJECT)
            inter_sp = sp;
            inter_pc = pc;
            destruct_object(sp);
        }
        pop_stack();
        break;

    CASE(F_EXEC);                   /* --- exec                --- */
    {
        /* EFUN exec()
         *
         *   object exec(object new, object old)
         *
         * exec() switches the connection from the interactive object old
         * to the object new. If the new object is also interactive, it's
         * connection will be transferred to the old object, thus
         * exchaning the two connections between the object. If the new
         * object is not interactive, the old will not be interactive
         * anymore after the exec call succeeded.
         * It is used to load different "user objects" or to reconnect
         * link dead users.
         *
         * To provide security mechanisms, the interpreter calls
         * master->valid_exec(current_program, new, old), which must
         * return anything other than 0 to allow this exec() invocation.
         */

        int i;

        assign_eval_cost();
        TYPE_TEST1(sp-1, T_OBJECT)
        TYPE_TEST2(sp,   T_OBJECT)
        inter_sp = sp;
        inter_pc = pc;
        i = replace_interactive((sp-1)->u.ob, sp->u.ob, current_prog->name);
                   /* FinalFrontier suggests 'current_object->prog->name' */
        pop_stack();
        free_svalue(sp); /* object might have been destructed */
        put_number(sp, i);
        break;
    }

    CASE(F_FIND_OBJECT);            /* --- find_object         --- */
    {
        /* EXEC find_object()
         *
         *   object find_object(string str)
         *
         * Find an object with the file_name str. If the object isn't loaded,
         * it will not be found.
         */

        object_t *ob;

        TYPE_TEST1(sp, T_STRING)
        ob = find_object(sp->u.string);
        free_svalue(sp);
        if (ob)
            put_ref_object(sp, ob, "find_object");
        else
            put_number(sp, 0);
        break;
    }

    CASE(F_FUNCTION_EXISTS);        /* --- function_exists     --- */
    {
        /* EXEC function_exists()
         *
         *   string function_exists(string str, object ob)
         *
         * Return the file name of the object that defines the function
         * str in object ob. The returned value can be different from
         * file_name(ob) if the function is defined in an inherited
         * object. In native mode, the returned name always begins with a
         * '/' (absolute path). 0 is returned if the function was not
         * defined, or was defined as static.
         */

        char *str, *res, *p;

        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_OBJECT)
        inter_sp = sp; /* error possible when out of memory */
        str = function_exists((sp-1)->u.string, sp->u.ob);
        free_svalue(sp);
        free_svalue(--sp);
        if (str)
        {
            /* Make a copy of the string so that we can remove
             * a the trailing '.c'. In non-compat mode, we also
             * have to add the leading '/'.
             */
            p = strrchr (str, '.');

            if (p)
                *p = '\0';  /* temporarily mask out the '.c' */

            if (compat_mode)
                res = string_copy (str);
            else
                res = add_slash(str);

            if (p)
                *p = '.';  /* undo the change above */

            if (!res)
            {
                sp--;
                ERROR("Out of memory\n")
            }
            put_malloced_string(sp, res);
        }
        else
        {
            put_number(sp, 0);
        }
        break;
    }

    CASE(F_INPUT_TO);               /* --- input_to <nargs>    --- */
    {
        /* EFUN input_to()
         *
         *   void input_to(string fun)
         *   void input_to(string fun, int flag, ...)
         *
         * Enable next line of user input to be sent to the local
         * function fun as an argument. The input line will not be
         * parsed, only when it starts with a "!" (like a kind of shell
         * escape) (this feature may be disabled).
         * The function <fun> may be static, but must not be private (or
         * it won't be found).
         *
         * Note that fun is not called immediately but after pressing the
         * RETURN key.
         *
         * If input_to() is called more than once in the same execution,
         * only the first call has any effect.
         *
         * The optional 3rd and following args will be passed as second and
         * subsequent args to the function fun. (This feature is was
         * added only recently, to avoid the need for global variables)
         */

        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        sp = e_input_to(sp, num_arg);
        break;
    }

    CASE(F_INTERACTIVE);            /* --- interactive         --- */
    {
        /* EFUN interactive()
         *
         *   int interactive(object ob)
         *
         * Return non-zero if ob, or when the argument is omitted, this
         * object(), is an interactive user. Will return 1 if the
         * object is interactive, else 0.
         */

        int i;
        object_t *ob;
        interactive_t *ip;

        TYPE_TEST1(sp, T_OBJECT)
        ob = sp->u.ob;
        (void)O_SET_INTERACTIVE(ip, ob);
        i = ip && !ip->do_close;
        deref_object(ob, "interactive");
        put_number(sp, i);
        break;
    }

    CASE(F_LOAD_NAME);              /* --- load_name           --- */
    {
        /* EFUN load_name()
         *
         *   string load_name()
         *   string load_name(object obj)
         *   string load_name(string obj)
         *
         * Return the load name for the object <obj> which may be given
         * directly or by its name.
         *
         * If <obj> is a clone, return the load_name() of <obj>'s blueprint.
         * If <obj> is a blueprint, return the filename from which the
         * blueprint was compiled.
         *
         * If <obj> is given by name but not/no longer existing, the
         * function synthesizes the load name as it should be and returns
         * that. If the given name is illegal, the function returns 0.
         *
         * As a special case, if <ob> is 0, the function returns 0.
         *
         * For virtual objects this efun of course returns the virtual
         * filename.  If <obj> is omitted, the name for the current object is
         * returned.
         *
         * In contrast to the object_name(), the load name can not be changed
         * by with rename_object(). However, if an object uses
         * replace_program() the load name no longer reflects the actual
         * behaviour of an object.
         *
         * The returned name starts with a '/', unless the driver is running
         * in COMPAT mode.
         */

        char *s;       /* String argument */
        char *name;    /* Result string, maybe 's' itself */
        char *hash;    /* Position of the hash in the name */
        char *mem;     /* Allocated memory blocks */
        object_t *ob;

        /* If the argument is 0, return 0. */
        if (sp->type == T_NUMBER && sp->u.number == 0)
        {
            break;
        }

        /* If the argument is an object, we just need to read the name */
        if (sp->type == T_OBJECT)
        {
            s = sp->u.ob->load_name;
            free_object_svalue(sp);
            put_ref_string(sp, s);
            break;
        }

        if (sp->type != T_STRING)
            goto bad_arg_1;

        /* Argument is a string: try to find the object for it */
        s = sp->u.string;
        ob = find_object(s);
        if (ob)
        {
            /* Got it */
            s = ob->load_name;
            free_string_svalue(sp);
            put_ref_string(sp, s);
            break;
        }

        /* There is no object for the string argument: just normalize
         * the string. First check if it ends in #<number>.
         */
        mem = NULL;
        hash = strchr(s, '#');
        if (!hash)
        {
            /* No '#' at all: make the name sane directly */
            name = (char *)make_name_sane(s, !compat_mode);
            if (!name)
                name = s;
        }
        else
        {
            char *p;
            size_t len;

            /* All characters after the '#' must be digits */
            for (p = hash+1; '\0' != *p; p++)
                if (*p < '0' || *p > '9')
                    /* Illegal name: break to return svalue 0 */
                    break;

            if ('\0' != *p)
            {
                /* Illegal name: break to return svalue 0 */
                free_string_svalue(sp);
                put_number(sp, 0);
                break;
            }

            /* Good, we can slash off the '#<number>' */
            len = (size_t)(hash - s);
            p = mem = xalloc(len+1);
            if (!p)
                ERROR("Out of memory.");
            strncpy(p, s, len);
            p[len] = '\0';

            /* Now make the name sane */
            name = (char *)make_name_sane(p, !compat_mode);
            if (!name)
                name = p;
        }

        /* name now points to the synthesized load_name and
         * may be the argument (== s), allocated (== mem), or
         * points to a static buffer otherwise.
         */

        /* '/.c' is a legal object name, so make sure that
         * the result will be '/' (in compat mode).
         */
        if (compat_mode && '\0' == *name)
            name = "/";

        /* Now return the result */
        if (s != name)
        {
            free_string_svalue(sp);
            if (name == mem)
            {
                put_malloced_string(sp, name);
                mem = NULL;  /* do not deallocate this */
            }
            else
            {
                put_volatile_string(sp, name);
            }
        }

        if (mem)
            xfree(mem);

        break;
    }

    CASE(F_LOAD_OBJECT);            /* --- load_object         --- */
    {
        /* EFUN load_object()
         *
         *   object load_object(string name)
         *
         * Load the object from the file <name> and return it. If the
         * object already exists, just return it.
         *
         * This efun can be used only to load blueprints - for clones, use
         * the efun clone_object().
         */

        object_t *ob;

        ASSIGN_EVAL_COST
        inter_pc = pc;
        inter_sp = sp;
        TYPE_TEST1(sp, T_STRING);
        ob = get_object(sp->u.string);
        free_svalue(sp);
        if (ob)
            put_ref_object(sp, ob, "F_LOAD_OBJECT");
        else
            put_number(sp, 0);
        break;
    }

    CASE(F_OBJECT_NAME);            /* --- object_name         --- */
    {
        /* EFUN object_name()
         *
         *   string object_name()
         *   string object_name(object ob)
         *
         * Get the name of an object <ob> or, if no argument is given, of
         * the current object.
         *
         * As a special case, if <ob> is 0, return 0.
         *
         * This name is the name under which the object is stored in the
         * muds object table. It is initialised at the creation of the
         * object such that blueprints are named after the file they are
         * compiled from (without the trailing '.c'), and clones receive
         * the name of their blueprint, extended by '#' followed by
         * a unique non-negative number. These rules also apply to
         * virtual objects - the real name/type of virtual objects
         * is ignored.
         *
         * The name of an object can be changed with rename_object(), and
         * object_name() will reflect any of these changes.
         *
         * The returned name always begins with '/' (absolute path),
         * except when the parser runs in COMPAT mode.
         */

        char *name,*res;

        /* If the argument is 0, return 0. */
        if (sp->type == T_NUMBER && sp->u.number == 0)
        {
            break;
        }

        TYPE_TEST1(sp, T_OBJECT)

        name = sp->u.ob->name;
        if (compat_mode)
            res = string_copy(name);
        else
            res = add_slash(name);

        if (!res)
            ERROR("Out of memory\n")
        free_object_svalue(sp);
        put_malloced_string(sp, res);
        break;
    }

    CASE(F_REPLACE_PROGRAM);     /* --- replace_program <narg> --- */
    {
        /* EFUN replace_program()
         *
         *   void replace_program()
         *   void replace_program(string program)
         *
         * Substitutes a program with the inherited program <program>. If
         * the object inherits only one program, the argument may be omitted
         * and the efun will automatically select the one inherited program.
         *
         * This efun is useful if you
         * consider the performance and memory consumption of the driver. A
         * program which doesn't need any additional variables and functions
         * (except during creation) can call replace_program() to increase the
         * function-cache hit-rate of
         * the driver which decreases with the number of programs in the
         * system. Any object can call replace_program() but looses all extra
         * variables and functions which are not defined by the inherited
         * program.
         *
         * When replace_program() takes effect, shadowing is stopped on
         * the object since 3.2@166.
         *
         * It is not possible to replace the program of an object after
         * (lambda) closures have been bound to it. It is of course
         * possible to first replace the program and then bind lambda
         * closures to it.
         *
         * The program replacement does not take place with the call to
         * the efun, but is merely scheduled to be carried out at the end
         * of the backend cycle. This may cause closures to have
         * references to then vanished lfuns of the object. This poses no
         * problem as long as these references are never executed after
         * they became invalid.
         */

        replace_ob_t *tmp;
        long name_len;
        char *name;
        program_t *new_prog;  /* the replacing program */
        program_t *curprog;   /* the current program */
        int offsets[2];       /* the offsets of the replacing prog */

        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;

        if (num_arg > 0)
            TYPE_TEST1(sp, T_STRING)

        if (!current_object)
            ERROR("replace_program called with no current object\n")
        if (current_object == simul_efun_object)
            ERROR("replace_program on simul_efun object\n")
        if (current_object->flags & O_LAMBDA_REFERENCED)
        {
            /* This was an ERROR(), and should be a proper warning, except
             * that the driver doesn't have such a thing.
             */
            debug_message("%s Object '%s', program '%s': Cannot schedule "
                          "replace_program() after binding lambda closures.\n"
                         , time_stamp(), current_object->name
                         , current_prog->name
                         );
            for ( ; num_arg != 0; num_arg--)
                pop_stack();
            break;
        }

        curprog = current_object->prog;

        if (num_arg < 1)
        {
            size_t replace_index;  /* Inherit index of the replacing program */

            /* Just take the first normal inherited program */
            if (curprog->num_inherited < 1)
                ERROR("replace_program called with no inherited program\n");

            replace_index = 0;
            if (curprog->num_inherited > 1)
            {
                /* The object might have extra inherits caused by virtual
                 * variables. Since they preceed the associated 'real'
                 * inherit, search forward in the inherit list for the real
                 * one.
                 */
                for ( ; replace_index < curprog->num_inherited
                      ; replace_index++)
                {
                    if (!(curprog->inherit[replace_index].inherit_type
                          & INHERIT_TYPE_EXTRA))
                        break;
                }
                /* replace_index must now be the last inherit for the
                 * auto-replace_program to work.
                 */
                if (replace_index + 1 != curprog->num_inherited)
                {
                    ERROR("replace_program() requires argument for object "
                          "with more than one inherit\n");
                    /* NOTREACHED */
                }
            }

            new_prog = curprog->inherit[replace_index].prog;
            offsets[0] = curprog->inherit[replace_index].variable_index_offset;
            offsets[1] = curprog->inherit[replace_index].function_index_offset;
        }
        else
        {
            /* Create the full program name with a trailing '.c' and without
             * a leading '/' to match the internal name representation.
             */
            name_len = (long)svalue_strlen(sp);
            name = alloca((size_t)name_len+3);
            strcpy(name, sp->u.string);
            if (name[name_len-2] != '.' || name[name_len-1] != 'c')
                strcat(name,".c");
            if (*name == '/')
                name++;

            new_prog = search_inherited(name, curprog, offsets);
            if (!new_prog)
            {
                /* Given program not inherited, maybe it's the current already.
                 */
                if (!strcmp(name, curprog->name ))
                {
                    new_prog = current_object->prog;
                    offsets[0] = offsets[1] = 0;
                }
                else
                {
                    ERROR("program to replace the current one with has "
                          "to be inherited\n")
                }
            }
            pop_stack();
        }

        /* Program found, now check if it contains virtual variables.
         * See b-030119 for an explanation.
         */
        if (offsets[0] != 0)
        {
            int i;
            Bool has_virtual = MY_FALSE;

            for (i = 0; !has_virtual && i < new_prog->num_variables; i++)
            {
                if (new_prog->variable_names[i].flags & TYPE_MOD_VIRTUAL)
                    has_virtual = MY_TRUE;
            }
            if (has_virtual)
            {
                debug_message("replacement program '%s' has virtual variables "
                              "and is not the first inherited program\n"
                             , new_prog->name);
                break;
            }
        }

        /* Program found, now create a new replace program entry, or
         * change an existing one.
         */
        if (!(curprog->flags & P_REPLACE_ACTIVE)
         || !(tmp = retrieve_replace_program_entry()) )
        {
            tmp = xalloc(sizeof *tmp);
            tmp->lambda_rpp = NULL;
            tmp->ob = current_object;
            tmp->next = obj_list_replace;
            obj_list_replace = tmp;
            curprog->flags |= P_REPLACE_ACTIVE;
        }

        tmp->new_prog = new_prog;
        tmp->var_offset = offsets[0];
        tmp->fun_offset = offsets[1];
        break;
    }

    CASE(F_SET_NEXT_RESET);         /* --- set_next_reset()    --- */
    {
        /* EFUN set_next_reset()
         *
         *   int set_next_reset (int delay)
         *
         * Instruct the gamedriver to reset this object not earlier than in
         * <delay> seconds. If a negative value is given as delay, the object
         * will never reset (useful for blueprints). If 0 is given, the
         * object's reset time is not changed.
         *
         * Result is the former delay to the objects next reset (which can be
         * negative if the reset was overdue).
         */

        int new_time;

        TYPE_TEST1(sp, T_NUMBER)
        new_time = sp->u.number;
        if (current_object->flags & O_DESTRUCTED)
        {
            sp->u.number = 0;
        }
        else
        {
            sp->u.number = current_object->time_reset - current_time;
            if (new_time < 0)
                current_object->time_reset = 0;
            else if (new_time > 0)
                current_object->time_reset = new_time + current_time;
        }
        break;
    }

    CASE(F_SET_THIS_OBJECT);        /* --- set_this_object     --- */
    {
        /* EFUN set_this_object()
         *
         *   void set_this_object(object object_to_pretend_to_be);
         *
         * Set this_object() to <object_to_pretend_to_be>. A privilege
         * violation ("set_this_object", this_object(), object_to_be)
         * occurs.
         *
         * It changes the result of this_object() in the using function, and
         * the result of previous_object() in functions called in other
         * objects by call_other(). Its effect will remain till there is a
         * return of an external function call, or another call of
         * set_this_object(). While executing code in the master
         * object's program or the primary simul_efun object's program,
         * set_this_object() is granted even if this_object() is altered by
         * set_this_object(). This does not apply to functions inherited from
         * other programs.
         *
         * Use it with extreme care to avoid inconsistencies.  After a call of
         * set_this_object(), some LPC-constructs might behave in an odd
         * manner, or even crash the system. In particular, using global
         * variables or calling local functions (except by call_other) is
         * illegal.
         *
         * With the current implementation, global variables can be accessed,
         * but this is not guaranteed to work in subsequent versions.
         *
         * Allowed are call_other, map functions, access of local variables
         * (which might hold array pointers to a global array), simple
         * arithmetic and the assignment operators.
         */

        TYPE_TEST1(sp, T_OBJECT)
        if ((master_ob != NULL && current_variables == master_ob->variables)
         || (simul_efun_object != NULL && current_variables == simul_efun_object->variables)
         || privilege_violation("set_this_object", sp))
        {
            struct control_stack *p;

            /* Find the 'extern_call' entry in the call stack which
             * determined the current this_object().
             */
            for (p = csp; !p->extern_call; p--) NOOP;

            p->extern_call |= CS_PRETEND;
            p->pretend_to_be = current_object = sp->u.ob;
        }
        pop_stack();
        break;
    }

    CASE(F_SNOOP);                  /* --- snoop <nargs>       --- */
    {
        /* EFUN snoop()
         *
         *   object snoop(object snooper)
         *   object snoop(object snooper, object snoopee)
         *
         * Starts a snoop from 'snooper' on 'snoopee', or if 'snoopee' is not
         * given, terminates any snoop from 'snooper'.
         * On success, 'snoopee' is returned, else 0.
         *
         * The snoop is checked with the master object for validity.
         * It will also fail if the 'snoopee' is being snooped already or
         * if a snoop would result in a recursive snoop action.
         */

        int i;

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        if (num_arg == 1)
        {
            TYPE_TEST1(sp,   T_OBJECT)
            i = set_snoop(sp->u.ob, 0);
        }
        else
        {
            TYPE_TEST1(sp-1, T_OBJECT)
            TYPE_TEST2(sp,   T_OBJECT)
            i = set_snoop((sp-1)->u.ob, sp->u.ob);
            pop_stack();
        }
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_TELL_OBJECT);            /* --- tell_object         --- */
        /* EFUN tell_object()
         *
         *   void tell_object(object ob, string str)
         *
         * Send a message str to object ob. If it is an interactive
         * object (a user), then the message will go to him (her?),
         * otherwise the lfun catch_tell() of the living will be called
         * with the message as argument.
         */

        ASSIGN_EVAL_COST
        TYPE_TEST1(sp-1, T_OBJECT)
        TYPE_TEST2(sp,   T_STRING)
        inter_sp = sp;
        inter_pc = pc;
        tell_object((sp-1)->u.ob, sp->u.string);
        free_string_svalue(sp);
        sp--;
        if (sp->type == T_OBJECT) /* not self-destructed */
            free_object_svalue(sp);
        sp--;
        break;

    CASE(F_THIS_INTERACTIVE);       /* --- this_interactive    --- */
        /* EFUN this_interactive()
         *
         *   object this_interactive(void)
         *
         * this_interactive() returns the current interactive object, if
         * any, i.e. the one who "hit the RETURN key".
         */

        if (current_interactive
         && !(current_interactive->flags & O_DESTRUCTED))
            push_object(current_interactive);
        else
            push_number(0);
        break;

    CASE(F_THIS_OBJECT);            /* --- this_object         --- */
        /* EFUN this_object()
         *
         *   object this_object(void)
         *
         * Return the object pointer for this object.
         */

        if (current_object->flags & O_DESTRUCTED)
        {
            push_number(0);
            break;
        }
        push_object(current_object);
        break;

    CASE(F_USERS);                  /* --- users               --- */
        /* EFUN users
         *
         *   object *users(void)
         *
         * Return an array containing all interactive users.
         */

        push_referenced_vector(users());
        break;

    CASE(F_WRITE);                  /* --- write               --- */
        /* EFUN write()
         *
         *   void write(mixed msg)
         *
         * Write out something to the current user. What exactly will
         * be printed in the end depends of the type of msg.
         *
         * If it is a string or a number then just prints it out.
         *
         * If it is an object then the object will be printed in the
         * form: "OBJ("+file_name((object)mix)+")"
         *
         * If it is an array just "<ARRAY>" will be printed.
         *
         * If the write() function is invoked by a command of an living
         * but not interactive object and the given argument is a string
         * then the lfun catch_tell() of the living will be invoked with
         * the message as argument.
         *
         * TODO: this efun could be made the counterpart of MudOS' receive().
         * TODO: the accepted types and their interpretations should follow
         * TODO:: the to_string() or sprintf() representations.
         */

        assign_eval_cost();
        inter_pc = pc;
        inter_sp = sp;
        e_write(sp);
        pop_stack();
        break;

    /* --- Efuns: Files --- */

    CASE(F_CAT);                    /* --- cat <nargs>         --- */
    {
        /* EFUN cat()
         *
         *     int cat(string pathi [, int start [, int num]])
         *
         * List the file found at path.
         * The optional arguments start and num are start line
         * number and number of lines. If they are not given the whole
         * file is printed from the beginning.
         *
         * Result is the number of lines printed.
         */

        int i;
        svalue_t *arg;
        int start, len;

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        arg = sp- num_arg + 1;

        /* Get and test the arguments */
        TYPE_TEST1(arg, T_STRING)
        start = 0; len = 0;
        if (num_arg > 1)
        {
            TYPE_TEST2(arg+1, T_NUMBER)
            start = arg[1].u.number;
            if (num_arg == 3)
            {
                if (arg[2].type != T_NUMBER)
                    goto bad_arg_3;
                len = arg[2].u.number;
            }
        }

        /* Print the file */
        i = e_print_file(arg[0].u.string, start, len);
        pop_n_elems(num_arg);
        push_number(i);
        break;
    }

    CASE(F_FILE_SIZE);              /* --- file_size           --- */
    {
        /* EFUN file_size()
         *
         *   int file_size(string file)
         *
         * Returns the size of the file in bytes.
         *
         * Size FSIZE_NOFILE (-1) indicates that the file either does not
         * exist, or that it is not readable for the calling object/user.
         * Size FSIZE_DIR (-2) indicates that it is a directory.
         */

        int i;

        assign_eval_cost();
        TYPE_TEST1(sp, T_STRING)
        inter_sp = sp;
        inter_pc = pc;
        i = e_file_size(sp->u.string);
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_GET_DIR);                /* --- get_dir             --- */
    {
        /* EFUN get_dir()
         *
         *   string *get_dir(string str, int mask)
         *
         * This function takes a path as argument and returns an array of
         * file names and attributes in that directory.
         *
         * The filename part of the path may contain '*' or '?' as
         * wildcards: every '*' matches an arbitrary amount of characters
         * (or just itself). Thus get_dir ("/path/ *") would return an
         * array of all files in directory "/path/", or just ({ "/path/ *"
         * }) if this file happens to exist.
         *
         * The optional second argument mask can be used to get
         * information about the specified files.
         *
         * GETDIR_EMPTY (0x00)  return an empty array (not very useful)
         * GETDIR_NAMES (0x01)  put the file names into the returned array.
         * GETDIR_SIZES (0x02)  put the file sizes into the returned array.
         * GETDIR_DATES (0x04)  put the file modification dates into the
         *                      returned array.
         * GETDIR_UNSORTED (0x20) if this mask bit is set, the result of
         *                      get_dir() will _not_ be sorted.
         * The values of mask can be added together.
         */

        vector_t *v;

        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_NUMBER)
        inter_sp = sp;
        inter_pc = pc;

        v = e_get_dir(sp[-1].u.string, sp->u.number);
        sp--; /* think 'free_int_svalue(sp--) */
        free_string_svalue(sp);
        if (v)
        {
            put_array(sp, v);
        }
        else
        {
            put_number(sp, 0);
        }
        break;
    }

    CASE(F_MKDIR);                  /* --- mkdir               --- */
    {
        /* EFUN mkdir()
         *
         *   int mkdir(string path)
         *
         * Make a directory named path. Return 1 for success and 0 for
         * failure.
         */

        int i;
        char *path;

        assign_eval_cost();
        inter_pc = pc;
        inter_sp = sp;
        TYPE_TEST1(sp, T_STRING)

        path = check_valid_path(sp->u.string, current_object, "mkdir", MY_TRUE);
        i = !(path == 0 || mkdir(path, 0775) == -1);
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_READ_BYTES);             /* --- read_bytes <nargs>  --- */
    {
        /* EFUN read_bytes()
         *
         *   string read_bytes (string file, int start, int number)
         *
         * Reads a given amount of bytes from file.
         * If <start> is not given or 0, the file is read from the
         * beginning, else from the <start>th byte on. If <start> is
         * negative, it is counted from the end of the file.
         * <number> is the number of bytes to read. 0 or negative values
         * are possible, but not useful.
         * If <start> would be outside the actual size of the file, 0 is
         * returned instead of a string.
         * TODO: Can't read nul-characters.
         */

        char *str;
        svalue_t *arg;
        int start, len;

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        arg = sp- num_arg + 1;
        TYPE_TEST1(arg,   T_STRING)

        /* Get the arguments */
        start = 0;
        len = 0;
        if (num_arg > 1)
        {
            TYPE_TEST2(arg+1, T_NUMBER)
            start = arg[1].u.number;
            if (num_arg == 3)
            {
                if (arg[2].type != T_NUMBER)
                    goto bad_arg_2;
                len = arg[2].u.number;
                sp--;
            }

            sp--;
        }

        /* Read the file */
        str = e_read_bytes(arg[0].u.string, start, len);
        pop_stack();
        if (str == 0)
            push_number(0);
        else
        {
            push_string_malloced(str);
            xfree(str);
        }
        break;
    }

    CASE(F_READ_FILE);              /* --- read_file           --- */
    {
        /* EFUN read_file()
         *
         *   string read_file(string file, int start, int number)
         *
         * Reads lines from file.
         * If <start> is not given or 0, the file is read from the
         * beginning, else from the numbered line on.
         * If <number> is not given or 0, the whole file is read, else
         * just the given amount of lines.
         * If <start> would be outside the actual size of the file, 0 is
         * returned instead of a string.
         */

        char *str;
        svalue_t *arg;
        int start, len;

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        arg = sp- num_arg + 1;
        TYPE_TEST1(arg,   T_STRING)

        /* Get the arguments */
        start = 0;
        len = 0;
        if (num_arg > 1)
        {
            TYPE_TEST2(arg+1, T_NUMBER)
            start = arg[1].u.number;
            if (num_arg == 3)
            {
                if (arg[2].type != T_NUMBER)
                    goto bad_arg_3;
                len = arg[2].u.number;
                sp--;
            }
            sp--;
        }

        /* Read the file */
        str = e_read_file(arg[0].u.string, start, len);
        pop_stack();
        if (str == 0)
            push_number(0);
        else
        {
            push_malloced_string(str);
        }
        break;
    }

    CASE(F_RENAME);                 /* --- rename              --- */
    {
        /* EFUN rename()
         *
         *   int rename(string from, string to)
         *
         * The efun rename() will move from to the new name to. If from
         * is a file, then to may be either a file or a directory. If
         * from is a directory, then to has to be a directory. If to
         * exists and is a directory, then from will be placed in that
         * directory and keep its original name.
         *
         * You must have write permission for from to rename the file.
         *
         * On successfull completion rename() will return 0. If any error
         * occurs 1 is returned.
         */

        int i;

        assign_eval_cost();
        inter_pc = pc;
        inter_sp = sp;
        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_STRING)

        i = e_rename((sp-1)->u.string, sp->u.string);
        pop_n_elems(2);
        push_number(i);
        break;
    }

    CASE(F_RM);                     /* --- rm                  --- */
    {
        /* EFUN rm()
         *
         *   int rm(string file)
         *
         * Remove the file. Returns 0 for failure and 1 for success.
         */

        int i;

        assign_eval_cost();
        TYPE_TEST1(sp, T_STRING)
        inter_sp = sp;
        inter_pc = pc;
        i = e_remove_file(sp->u.string);
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_RMDIR);                  /* --- rmdir               --- */
    {
        /* EFUN rmdir()
         *
         *   int rmdir(string dir)
         *
         * Remove directory dir. Return 1 on success, 0 on failure.
         */

        int i;
        char *path;

        assign_eval_cost();
        inter_pc = pc;
        inter_sp = sp;
        TYPE_TEST1(sp, T_STRING)
        path = check_valid_path(sp->u.string, current_object, "rmdir", MY_TRUE);
        i = !(path == 0 || rmdir(path) == -1);
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_TAIL);                   /* --- tail                --- */
        /* EFUN tail()
         *
         *   void tail(string file)
         *
         * Print out the tail of a file. There is no specific amount of
         * lines given to the output. Only a maximum of 1000 bytes will
         * be printed.
         */

        assign_eval_cost();
        TYPE_TEST1(sp, T_STRING)
        inter_sp = sp;
        inter_pc = pc;
        if (e_tail(sp->u.string))
            assign_svalue(sp, &const1);
        else
            assign_svalue(sp, &const0);
        break;

    CASE(F_WRITE_BYTES);            /* --- write_bytes         --- */
    {
        /* EFUN write_bytes()
         *
         *   int write_bytes(string file, int start, string str)
         *
         * Write string str to file file by overwriting the old bytes at
         * position start. If start is a negative value then it will be
         * counted from the end of the file. The file will not be
         * appended, instead the function will be aborted. Returns 1 for
         * success 0 for failure during execution.
         */

        int i;

        assign_eval_cost();
        TYPE_TEST1(sp-2, T_STRING)
        TYPE_TEST2(sp-1, T_NUMBER)
        inter_sp = sp;
        inter_pc = pc;
        if (sp->type != T_STRING)
            goto bad_arg_3;

        i = e_write_bytes((sp-2)->u.string, (sp-1)->u.number, sp->u.string);
        pop_stack();
        sp--;
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_WRITE_FILE);             /* --- write_file          --- */
    {
        /* EFUN write_file()
         *
         *   int write_file(string file, string str)
         *
         * Append the string str to the file file. Returns 1 for success
         * and 0 if any failure occured.
         */

        int i;

        assign_eval_cost();
        TYPE_TEST1(sp-1, T_STRING)
        TYPE_TEST2(sp,   T_STRING)
        inter_sp = sp;
        inter_pc = pc;

        i = e_write_file((sp-1)->u.string, sp->u.string);
        pop_stack();
        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    /* --- Efuns: Driver and System --- */

    CASE(F_QUERY_LOAD_AVERAGE);     /* --- query_load_average  --- */
        /* EFUN query_load_average()
         *
         *   string query_load_average(void)
         *
         * Returns the load of the mud. Two figures are given, executed
         * commands/second and compiled lines/second.
         */

        push_string_malloced(query_load_av());
        break;

    /* --- Efuns: Inventories --- */

    CASE(F_ALL_INVENTORY);          /* --- all_inventory       --- */
    {
        /* EFUN all_inventory()
         *
         *     object *all_inventory(object ob = this_object())
         *
         * Returns an array of the objects contained in the inventory
         * of ob.
         */

        vector_t *vec;

        TYPE_TEST1(sp, T_OBJECT)
        inter_sp = sp;
        inter_pc = pc;

        vec = all_inventory(sp->u.ob);
        free_object_svalue(sp);

        if (vec == NULL)
        {
            put_number(sp, 0);
        }
        else
        {
            put_array(sp, vec);
        }
        break;
    }

    CASE(F_DEEP_INVENTORY);         /* --- deep_inventory      --- */
    {
        /* EFUN deep_inventory()
         *
         *   object *deep_inventory(void)
         *   object *deep_inventory(object ob)
         *
         * Returns an array of the objects contained in the inventory of
         * ob (or this_object() if no arg given) and in the inventories
         * of these objects, climbing down recursively.
         */

        vector_t *vec;

        TYPE_TEST1(sp, T_OBJECT)
        inter_sp = sp;
        inter_pc = pc;
        vec = deep_inventory(sp->u.ob, 0);
        free_object_svalue(sp);
        put_array(sp, vec);
        break;
    }

    CASE(F_ENVIRONMENT);            /* --- environment <nargs> --- */
    {
        /* EFUN environment()
         *
         *   object environment(void)
         *   object environment(object obj)
         *   object environment(string obj)
         *
         * Returns the surrounding object of obj (which may be specified
         * by name). If no argument is given, it returns the surrounding
         * of the current object.
         *
         * Destructed objects do not have an environment.
         */

        object_t *ob;

        GET_NUM_ARG
        if (num_arg)
        {
            if (sp->type == T_OBJECT)
            {
                ob = sp->u.ob->super;
                free_object_svalue(sp);
            }
            else if (sp->type == T_STRING)
            {
                ob = find_object(sp->u.string);
                if (!ob || ob->super == NULL || (ob->flags & O_DESTRUCTED))
                    ob = NULL;
                else
                    ob = ob->super;
                free_string_svalue(sp);
            }
            else
                goto bad_arg_1;
        }
        else if (!(current_object->flags & O_DESTRUCTED))
        {
            ob = current_object->super;
            sp++;
        }
        else
        {
            ob = NULL; /* != environment(this_object()) *boggle* */
            sp++;
        }

        if (ob)
            put_ref_object(sp, ob, "environment");
        else
            put_number(sp, 0);
        break;
    }

    CASE(F_FIRST_INVENTORY);        /* --- first_inventory     --- */
    {
        /* EFUN first_inventory()
         *
         *   object first_inventory()
         *   object first_inventory(string ob)
         *   object first_inventory(object ob)
         *
         * Get the first object in the inventory of ob, where ob is
         * either an object or the file name of an object. If ob is not
         * given, the current object is assumed.
         */

        object_t *ob;

        if (sp->type == T_OBJECT)
        {
            ob = sp->u.ob->contains;
            free_object_svalue(sp);
        }
        else if (sp->type == T_STRING)
        {
            ob = get_object(sp->u.string);
            if (!ob)
                ERRORF(("No object '%s' for first_inventory()\n", sp->u.string));
            free_string_svalue(sp);
            ob = ob->contains;
        }
        else
            goto bad_arg_1;

        if (ob)
            put_ref_object(sp, ob, "first_inventory");
        else
            put_number(sp, 0);
        break;
    }

    CASE(F_MOVE_OBJECT);            /* --- move_object         --- */
    {
        /* EFUN move_object()
         *
         *   void move_object(mixed item, mixed dest)
         *
         * The item, which can be a file_name or an object, is moved into
         * it's new environment dest, which can also be file_name or an
         * object.
         *
         * In !compat mode, the only object that can be moved with
         * move_object() is the calling object itself.
         *
         * Since 3.2.1, the innards of move_object() are implemented in
         * the mudlib, using the M_MOVE_OBJECT driver hooks.
         */

        object_t *item, *dest;

        ASSIGN_EVAL_COST
        inter_pc = pc;
        inter_sp = sp;

        if ((sp-1)->type == T_OBJECT)
            item = (sp-1)->u.ob;
        else if ((sp-1)->type == T_STRING)
        {
            item = get_object((sp-1)->u.string);
            if (!item)
                error("move_object failed\n");
            free_string_svalue(sp-1);
            put_ref_object(sp-1, item, "move_object");
        }
        else
            goto bad_arg_1;

        if (sp->type == T_OBJECT)
            NOOP;
        else if (sp->type == T_STRING)
        {
            dest = get_object(sp->u.string);
            if (!dest)
                error("move_object failed\n");
            free_string_svalue(sp);
            put_ref_object(sp, dest, "move_object");
        }
        else
            goto bad_arg_2;

        /* move_object() reads its arguments directly from the stack */
        move_object();
        sp -= 2;
        break;
    }

    CASE(F_NEXT_INVENTORY);         /* --- next_inventory      --- */
    {
        /* EFUN next_inventory()
         *
         *   object next_inventory()
         *   object next_inventory(object ob)
         *
         * Get next object in the same inventory as ob. If ob is not
         * given, the current object will be used.
         *
         * This efun is mostly used together with the efun
         * first_inventory().
         */

        object_t *ob;

        TYPE_TEST1(sp, T_OBJECT)
        ob = sp->u.ob;
        free_object(ob, "next_inventory");
        if (ob->next_inv)
            put_ref_object(sp, ob->next_inv, "next_inventory");
        else
            put_number(sp, 0);
        break;
    }

    CASE(F_PRESENT);                /* --- present             --- */
      {
        /* EFUN present()
         *
         *   object present(mixed str)
         *   object present(mixed str, object ob)
         *
         * If an object that identifies (*) to the name ``str'' is present
         * in the inventory or environment of this_object (), then return
         * it. If "str" has the form "<id> <n>" the <n>-th object matching
         * <id> will be returned.
         *
         * "str" can also be an object, in which case the test is much faster
         * and easier.
         *
         * A second optional argument ob is the enviroment where the search
         * for str takes place. Normally this_player() is a good choice.
         * Only the inventory of ob is searched, not its environment.
         */

        svalue_t *arg;
        object_t *ob;

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        arg = sp - num_arg + 1;

        /* Get the arguments */
        if (arg->type != T_STRING && arg->type != T_OBJECT)
            goto bad_arg_1;
        ob = NULL;
        if (num_arg > 1)
        {
            TYPE_TEST2(arg+1, T_OBJECT)
            ob = arg[1].u.ob;
            pop_stack();
        }

        inter_sp = sp;
        ob = e_object_present(arg, ob);

        free_svalue(arg);
        if (ob)
            put_ref_object(sp, ob, "present");
        else
            put_number(sp, 0);
        break;
      }

    CASE(F_SAY);                    /* --- say <nargs>         --- */
      {
        /* EFUN say()
         *
         *   void say(string str)
         *   void say(string str, object exclude)
         *   void say(string str, object *excludes)
         *   void say(mixed *arr)
         *   void say(mixed *arr, object exclude)
         *   void say(mixed *arr, object *excludes)
         *
         * There are two major modes of calling:
         *
         * If the first argument is a string <str>, it will be send to
         * all livings in the current room	except to the initiator.
         *
         * If the first argument is an array <arr>, the lfun catch_msg()
         * of all living objects except the initiator will be called.
         * This array will be given as first argument to the lfun, and
         * the initiating object as the second.
         *
         * By specifying a second argument to the efun one can exclude
         * more objects than just the initiator. If the second argument
         * is a single object <exclude>, both the given object and the
         * initiator are excluded from the call. If the second argument
         * is an array <excludes>, all objects and just the objects in
         * this array are excluded from the call.
         *
         * The 'initiator' is determined according to these rules:
         *   - if the say() is called from within a living object, this
         *     becomes the initiator
         *   - if the say() is called from within a dead object as result
         *     of a user action (i.e. this_player() is valid), this_player()
         *     becomes the initiator.
         *   - Else the object calling the say() becomes the initiator.
         */

        static LOCAL_VEC2(vtmp, T_NUMBER, T_NUMBER);
          /* Default 'avoid' array passed to say() giving the object
           * to exclude in the second item. The first entry is reserved
           * for e_say() to insert its command_giver object.
           */

        ASSIGN_EVAL_COST
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;

#if defined(DEBUG) && defined(MALLOC_smalloc)
        static_vector2 = &vtmp.v;
        /* TODO: Remove this once VEC_SIZE() is proven to be accurate.
         */
#endif

        if (num_arg == 1)
        {
            /* No objects to exclude */

            if (sp->type != T_STRING && sp->type != T_POINTER)
                goto bad_arg_1;

            vtmp.v.item[0].type = T_NUMBER;
              /* this marks the place for the command_giver */
            vtmp.v.item[1].type = T_NUMBER;
              /* nothing to exclude... */
            e_say(sp, &vtmp.v);
        }
        else
        {
            /* We have objects to exclude */

            if (sp[-1].type != T_STRING && sp[-1].type != T_POINTER)
                goto bad_arg_1;

            if ( sp->type == T_POINTER )
            {
                e_say(sp-1, sp->u.vec);
            }
            else if (sp->type == T_OBJECT)
            {
                vtmp.v.item[0].type = T_NUMBER;
                put_ref_object(vtmp.v.item+1, sp->u.ob, "say");
                e_say(sp-1, &vtmp.v);
            }
            else
                goto bad_arg_2;
            pop_stack();
        }

        /* We may have received object references in vtmp - clear them */
        if (vtmp.v.item[0].type != T_NUMBER)
        {
            free_svalue(&(vtmp.v.item[0]));
            vtmp.v.item[0].type = T_NUMBER;
        }
        if (vtmp.v.item[1].type != T_NUMBER)
        {
            free_svalue(&(vtmp.v.item[1]));
            vtmp.v.item[1].type = T_NUMBER;
        }

        pop_stack();
        break;
      }

    CASE(F_TELL_ROOM);              /* --- tell_room <nargs>   --- */
      {
        /* EFUN tell_room()
         *
         *   void tell_room(string|object ob, string str)
         *   void tell_room(string|object ob, string str, object *exclude)
         *   void tell_room(string|object ob, mixed *msg)
         *   void tell_room(string|object ob, mixed *msg, object *exclude)
         *
         * Send a message str to all living objects in the room ob. ob
         * can also be the name of the room given as a string. If a
         * receiving object is not a interactive user the lfun
         * catch_tell() of the object will be invoked with the message as
         * argument. If living objects define catch_tell(), the string
         * will also be sent to that instead of being written to the
	 * user. If the object is given as its filename, the driver
         * looks up the object under that name, loading it if necessary.
         * If array *exclude is given, all objects contained in
         * *exclude are excluded from the message str.
         *
         * If the second arg is an array, catch_msg() will be called in
         * all listening livings.
         */

        svalue_t *arg;
        vector_t *avoid;
        object_t *ob;

        ASSIGN_EVAL_COST
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        arg = sp- num_arg + 1;

        /* Test the arguments */
        if (arg[0].type == T_OBJECT)
            ob = arg[0].u.ob;
        else if (arg[0].type == T_STRING)
        {
            ob = get_object(arg[0].u.string);
            if (!ob)
                ERROR("Object not found.\n")
        }
        else
            goto bad_arg_1;

        if (arg[1].type != T_STRING && arg[1].type != T_POINTER)
            goto bad_arg_2;

        if (num_arg == 2)
        {
            avoid = &null_vector;
        }
        else
        {
            /* Sort the list of objects to exclude for faster
             * operation.
             */

            vector_t *vtmpp;
            static svalue_t stmp = { T_POINTER };

            if (arg[2].type != T_POINTER)
                goto bad_arg_3;

            stmp.u.vec = arg[2].u.vec;
            vtmpp = order_alist(&stmp, 1, MY_TRUE);
            avoid = vtmpp->item[0].u.vec;
            sp->u.vec = avoid; /* in case of an error, this will be freed. */
            sp--;
            vtmpp->item[0].u.vec = stmp.u.vec;
            free_array(vtmpp);
        }

        e_tell_room(ob, sp, avoid);

        if (num_arg > 2)
            free_array(avoid);
        pop_stack();
        pop_stack();
        break;
      }

    /* --- Efuns: Verbs and Commands --- */

    CASE(F_ADD_ACTION);             /* --- add_action <nargs>  --- */
    {
        /* EFUN add_action()
         *
         *   void add_action(string fun, string cmd [, int flag])
         *   void add_action(string fun) // historical
         *
         * Add an action (verb + function) to the commandgiver.
         * TODO: In the long run, get rid of actions.
         */

        svalue_t *arg;
        svalue_t *verb;

        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;
        arg = sp - num_arg + 1;
        TYPE_TEST1(arg, T_STRING)

        verb = NULL;
        if (num_arg >= 2)
        {
            TYPE_TEST2(arg+1, T_STRING)
            if (num_arg > 2)
            {
                if (arg[2].type != T_NUMBER)
                    goto bad_arg_3;
            }
            verb = &arg[1];
        }

        if (e_add_action( &arg[0], verb
                      , num_arg > 2 ? arg[2].u.number : 0))
        {
            /* silent error condition, deallocate strings by hand */
            pop_n_elems(num_arg);
        }
        else
        {
            /* add_action has reused the strings or freed it */
            sp -= num_arg;
        }
        break;
    }

    CASE(F_COMMAND);                /* --- command <nargs>     --- */
    {
        /* EFUN command()
         *
         *   int command(string str)             // native
         *   int command(string str, object ob)  // !native
         *
         * Execute str as a command given directly by the user. Any
         * effects of the command will apply to the current object.
         *
         * Return value is 0 for failure. Otherwise a numeric value is
         * returned which tells the evaluation cost. Bigger number means
         * higher cost.  The evaluation cost is approximately the number
         * of LPC machine code instructions executed.
         *
         * In native mode, command() can effect only the calling object.
         * If native mode is not enabled, command() can get an optional
         * second arg, that specifies the object that the command is to
         * be applied to.
         * If command() is called on another object, it is not possible
         * to call static functions in this way, to give some protection
         * against illegal forces.
         *
         * TODO: With add_action(), this should go in the long run.
         */

        int i;
        svalue_t *arg;

        assign_eval_cost();
        GET_NUM_ARG
        inter_pc = pc;
        inter_sp = sp;

        arg = sp - num_arg + 1;
        if (num_arg == 1)
        {
            TYPE_TEST1(sp,   T_STRING)
            i = e_command(arg[0].u.string, NULL);
        }
        else
        {
            TYPE_TEST1(sp-1, T_STRING)
            TYPE_TEST2(sp,   T_OBJECT)
            i = e_command(arg[0].u.string, arg[1].u.ob);
            pop_stack();
        }

        free_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_DISABLE_COMMANDS);       /* --- disable_commands    --- */
        /* EFUN disable_commands()
         *
         *   void disable_commands()
         *
         * Disable this object to use commands normally accessible to
         * users.
         */

        inter_sp = sp;
        enable_commands(MY_FALSE);
        break;

    CASE(F_ENABLE_COMMANDS);        /* --- enable_commands     --- */
        /* EFUN enable_commands()
         *
         *   void enable_commands()
         *
         * Enable this object to use commands normally accessible to
         * users.
         */

        inter_sp = sp;
        enable_commands(MY_TRUE);
        break;

    CASE(F_LIVING);                 /* --- living              --- */
    {
        /* EFUN living()
         *
         * int living(object ob)
         *
         * Return true if ob is a living object (that is,
         * enable_commands() has been called from inside the ob).
         * ob may be 0, in which case the result is obviously 0, too.
         */

        int i;

        if (sp->type != T_OBJECT)
        {
            if (sp->type == T_NUMBER && !sp->u.number)
                break;
            goto bad_arg_1;
        }

        i = (sp->u.ob->flags & O_ENABLE_COMMANDS) != 0;
        free_object_svalue(sp);
        put_number(sp, i);
        break;
    }

    CASE(F_QUERY_ACTIONS);          /* --- query_actions       --- */
    {
        /* EFUN query_actions()
         *
         *   mixed *query_actions(object ob, mixed mask_or_verb)
         *
         * query_actions takes either an object or a filename as first
         * argument and a bitmask (int) or string as a second argument.
         * If the second argument is a string, query_actions() will return
         * an array containing information (see below) on the verb or
         * zero if the living object "ob" cannot use the verb. If the
         * second argument is a bitmask, query_actions() will return a
         * flat array containing information on all verbs added to ob.
         * The second argument is optional (default is the bitmask 1).
         *      1:  the verb
         *      2:  type
         *      4:  short_verb
         *      8:  object
         *     16: function
         *
         * "type" is one of the values defined in <sent.h> (/sys/sent.h)
         * (which is provided with the parser source).
         *
         * SENT_PLAIN       added with add_action (fun, cmd);
         * SENT_SHORT_VERB  added with add_action (fun, cmd, 1);
         * SENT_NO_SPACE    added with add_action (fun); add_xverb (cmd);
         * SENT_NO_VERB     just an add_action (fun); without a verb
         * SENT_MARKER      internal, won't be in the returned array
         */

        vector_t *v;
        svalue_t *arg;
        object_t *ob;

        arg = sp - 1;
        inter_sp = sp;
        inter_pc = pc;

        /* Get the arguments */
        if (arg[0].type == T_OBJECT)
            ob = arg[0].u.ob;
        else
        {
            TYPE_TEST1(arg, T_STRING);
            ob = get_object(arg[0].u.string);
            if (!ob)
                error("query_actions() failed\n");
        }

        /* Get the actions */
        if (arg[1].type == T_STRING)
            v = e_get_action(ob, arg[1].u.string);
        else if (arg[1].type == T_NUMBER)
            v = e_get_all_actions(ob, arg[1].u.number);
        else {
            TYPE_TEST2(arg+1, T_OBJECT);
            v = e_get_object_actions(ob, arg[1].u.ob);
        }

        /* Clean up the stack and push the result */
        pop_stack();
        free_svalue(arg);
        if (v)
        {
            put_array(arg, v);
        } else
            put_number(sp, 0);

        break;
    }

    CASE(F_QUERY_VERB);             /* --- query_verb          --- */
    {
        /* EFUN query_verb()
         *
         *   string query_verb(void)
         *   string query_verb(int flag)
         *
         * Return the verb of the current command, of 0 if not executing from
         * a command. If <flag> is 0 or not given, the verb as given by the user
         * is returned; if <flag> is non-0, the verb as specified in the
         * add_action() statement is returned.
         *
         * Give the name of the current command, or 0 if not executing
         * from a command. This allows add_action() of several commands
         * This efun allows add_action() of several commands
         * to the same function. query_verb() returns 0 when invoked by a
         * function which was started by a call_out or the heart beat.
         * Also when a user logs in query_verb() returns 0.
         */
        p_int flag = sp->u.number;
        free_svalue(sp);
        if (flag == 0)
        {
            if (!last_verb)
            {
                put_number(sp, 0);
                break;
            }
            put_ref_string(sp, last_verb);
        }
        else
        {
            if (!last_action_verb)
            {
                put_number(sp, 0);
                break;
            }
            put_ref_string(sp, last_action_verb);
        }
        break;
    }

    CASE(F_QUERY_COMMAND);          /* --- query_command       --- */
      {
        /* EFUN query_command()
         *
         *   string query_command(void)
         *
         * Return the full command string, or 0 if not executing from
         * a command.
         *
         * The string returned is the string as seen by the parser:
         * after any modify_command handling and after stripping
         * trailing spaces.
         */

        char * str;

        if (!last_command)
        {
            push_number(0);
            break;
        }
        inter_pc = pc;
        inter_sp = sp;
        str = string_copy(last_command);
        if (!str)
            ERROR("Out of memory.\n")

        push_malloced_string(str);
        break;
      }

    CASE(F_THIS_PLAYER);            /* --- this_player         --- */
        /* EFUN this_player()
         *
         *   object this_player(void)
         *
         * Return the current command giver.  This can be an interactive
         * user or a living object like a npc.
         *
         * If called from inside the heart_beat() of a not living object
         * 0 will be returned.
         */

        if (command_giver && !(command_giver->flags & O_DESTRUCTED))
            push_object(command_giver);
        else
            push_number(0);
        break;

    /* --- Efuns: Uids and Euids --- */

#ifdef F_EXPORT_UID
    CASE(F_EXPORT_UID);             /* --- export_uid          --- */
    {
        /* EFUN export_uid()
         *
         *   void export_uid(object ob)
         *
         * Set the uid of object ob to the current object's effective uid.
         * It is only possible when object ob has an effective uid of 0.
         * TODO: seteuid() goes through the driver, why not this one, too?
         * TODO:: Actually, this efun is redundant, archaic and should
         * TODO:: vanish altogether.
         */

        object_t *ob;

        TYPE_TEST1(sp, T_OBJECT)
        if (!current_object->eff_user)
            ERROR("Illegal to export uid 0\n")
        ob = sp->u.ob;
        if (!ob->eff_user)        /* Only allowed to export when null */
            ob->user = current_object->eff_user;
        free_object(ob, "export_uid");
        sp--;
        break;
    }
#endif /* F_EXPORT_UID */

#ifdef F_GETEUID
    CASE(F_GETEUID);             /* --- geteuid                --- */
    {
        /* EFUN geteuid()
         *
         *   string geteuid(object ob)
         *
         * Get the effective user-id of the object (mostly a wizard or
         * domain name). Standard objects cloned by this object will get
         * that userid. The effective userid is also used for checking access
         * permissions. If ob is omitted, is this_object() as default.
         */

        object_t *ob;

        TYPE_TEST1(sp, T_OBJECT)
        ob = sp->u.ob;

        if (ob->eff_user)
        {
            char *tmp;
            tmp = ob->eff_user->name;
            pop_stack();
            push_volatile_string(tmp);
        }
        else
        {
            free_svalue(sp);
            put_number(sp, 0);
        }
        break;
    }
#endif /* F_GETEUID */

#ifdef F_SETEUID
    CASE(F_SETEUID);                /* --- seteuid                --- */
    {
        /* EFUN seteuid()
         *
         *   int seteuid(string str)
         *
         * Set effective uid to str. The calling object must be
         * privileged to do so by the master object. In most
         * installations it can always be set to the current uid of the
         * object, to the uid of the creator of the object file, or to 0.
         *
         * When this value is 0, the current object's uid can be changed
         * by export_uid(), and only then.
         *
         * Objects with euid 0 cannot load or clone other objects.
         */

        svalue_t *ret;
        svalue_t *argp;

        argp = sp;
        if (argp->type == T_NUMBER)
        {
            /* Clear the euid of this_object */

            if (argp->u.number != 0)
                goto bad_arg_1;
            current_object->eff_user = 0;
            free_svalue(argp);
            put_number(argp, 1);
            break;
        }

        if (argp->type != T_STRING)
            goto bad_arg_1;

        /* Call the master to clear this use of seteuid() */

        assign_eval_cost();
        inter_sp = _push_volatile_string(argp->u.string,
            _push_valid_ob(current_object, sp) );
        inter_pc = pc;
        ret = apply_master(STR_VALID_SETEUID, 2);
        if (!ret || ret->type != T_NUMBER || ret->u.number != 1)
        {
            free_svalue(argp);
            put_number(argp, 0);
            break;
        }
        current_object->eff_user = add_name(argp->u.string);
        free_svalue(argp);
        put_number(argp, 1);
        break;
    }
#endif /* F_SETEUID */

#if defined(F_GETUID) || defined(F_CREATOR)
#ifdef F_GETUID
    CASE(F_GETUID);                 /* --- getuid              --- */
#else
    CASE(F_CREATOR);                /* --- creator             --- */
#endif
    {
        /* EFUN getuid()
         *
         *   string getuid(object ob)
         *   string creator(object ob)
         *
         * User-ids are not used in compat mode, instead the uid is
         * then called 'creator'.
         * Get user-id of the object, i.e. the name of the wizard or
         * domain that is responsible for the object. This name is also
         * the name used in the wizlist. If no arg is given, use
         * this_object() as default.
         */

        object_t *ob;
        char *name;

        TYPE_TEST1(sp, T_OBJECT)
        ob = sp->u.ob;
        deref_object(ob, "getuid");
        if ( NULL != (name = ob->user->name) )
            put_ref_string(sp, name);
        else
            put_number(sp, 0);
        break;
    }
#endif

    /* --- Optional Efuns: Technical --- */

#ifdef F_BREAK_POINT
    CASE(F_BREAK_POINT);            /* --- break_point         --- */
        /* EFUN break_point()
         *
         *   void break_point()
         *
         * This function is for system internal use and should never be called
         * by user objects. It is supposed to check the stack integrity and
         * aborts the driver when it detects corruption.
         *
         */

        if (sp - fp - csp->num_local_variables + 1 != 0)
            fatal("Bad stack pointer.\n");
        break;
#endif

#ifdef F_RUSAGE
    CASE(F_RUSAGE);                 /* --- rusage              --- */
    {
        /* EFUN rusage()
         *
         *   int *rusage(void)
         *
         * Return an array with current system resource usage statistics,
         * as returned by the getrusage(2) of Unix.
         * namely: utime, stime, maxrss, rus.ru_ixrss, rus.ru_idrss,
         * rus.ru_isrss, rus.ru_minflt, rus.ru_majflt, rus.ru_nswap,
         * rus.ru_inblock, rus.ru_oublock, rus.ru_msgsnd,
         * rus.ru_msgrcv, rus.ru_nsignals, rus.ru_nvcsw,
         * rus.ru_nivcsw
         * TODO: The indices should be in an include file.
         */

        struct rusage rus;
        vector_t *res;
        svalue_t *v;
#ifndef GETRUSAGE_RESTRICTED
        int maxrss;
#endif

        if (getrusage(RUSAGE_SELF, &rus) < 0) {
            push_number(0);
            break;
        }
        res = allocate_array(16);
        v = res->item;
        v[ 0].u.number = RUSAGE_TIME(rus.ru_utime);
        v[ 1].u.number = RUSAGE_TIME(rus.ru_stime);
#ifndef GETRUSAGE_RESTRICTED
        maxrss = rus.ru_maxrss;
#ifdef sun
        maxrss *= getpagesize() / 1024;
#endif
        v[ 2].u.number = maxrss;
        v[ 3].u.number = rus.ru_ixrss;
        v[ 4].u.number = rus.ru_idrss;
        v[ 5].u.number = rus.ru_isrss;
        v[ 6].u.number = rus.ru_minflt;
        v[ 7].u.number = rus.ru_majflt;
        v[ 8].u.number = rus.ru_nswap;
        v[ 9].u.number = rus.ru_inblock;
        v[10].u.number = rus.ru_oublock;
        v[11].u.number = rus.ru_msgsnd;
        v[12].u.number = rus.ru_msgrcv;
        v[13].u.number = rus.ru_nsignals;
        v[14].u.number = rus.ru_nvcsw;
        v[15].u.number = rus.ru_nivcsw;
#endif /* GETRUSAGE_RESTRICTED */
        push_referenced_vector(res);
        break;
    }
#endif

    /* --- Optional Efuns: Alists --- */

#ifdef F_ASSOC
    CASE(F_ASSOC);                  /* --- assoc <nargs>       --- */
    {
        /* EFUN assoc()
         *
         *     int   assoc (mixed key, mixed *keys)
         *     mixed assoc (mixed key, mixed *alist [, mixed fail] )
         *     mixed assoc (mixed key, mixed *keys, mixed *data [, mixed fail])
         *
         * Search for <key> in the <alist> resp. in the <keys>.
         *
         * When the key list of an alist contains destructed objects
         * it is better not to free them till the next reordering by
         * order_alist to retain the alist property.
         *
         * TODO: Make the alist-efuns xefuns.
         */
        svalue_t *args;
        vector_t *keys,*data;
        svalue_t *fail_val;
        int ix;

        GET_NUM_ARG
        args = sp -num_arg +1;
        TYPE_TEST2(args+1, T_POINTER)

        /* Analyse the arguments */
        if ( !VEC_SIZE(args[1].u.vec)
         ||  args[1].u.vec->item[0].type != T_POINTER )
        {
            keys = args[1].u.vec;
            if (num_arg == 2)
            {
                data = NULL;
            }
            else
            {
                if (args[2].type != T_POINTER
                 || VEC_SIZE(args[2].u.vec) != VEC_SIZE(keys))
                {
                    goto bad_arg_3;
                }
                data = args[2].u.vec;
            }
            if (num_arg == 4)
            {
                fail_val = &args[3];
            }
            else
            {
                fail_val = &const0;
            }
        }
        else
        {
            keys = args[1].u.vec->item[0].u.vec;
            if (VEC_SIZE(args[1].u.vec) > 1)
            {
                if (args[1].u.vec->item[1].type != T_POINTER
                 || VEC_SIZE(args[1].u.vec->item[1].u.vec) != VEC_SIZE(keys))
                {
                    goto bad_arg_2;
                }
                data = args[1].u.vec->item[1].u.vec;
            }
            else
            {
                data = NULL;
            }

            if (num_arg == 3) fail_val = &args[2];
            else if (num_arg == 2) fail_val = &const0;
            else
            {
                ERROR ("too many args to efun assoc\n")
                return MY_FALSE;
            }
        }

        /* Call assoc() and push the result */
        ix = assoc(&args[0],keys);
        if (data == NULL)
        {
            pop_n_elems(num_arg);
            push_number(ix);
        }
        else
        {
            assign_svalue(args
                         , ix == -1
                           ? fail_val
                           : (destructed_object_ref(&data->item[ix])
                             ? &const0
                             : &data->item[ix])
                         );
            pop_n_elems(num_arg-1);
        }
        break;
    }
#endif /* F_ASSOC */

#ifdef F_INSERT_ALIST
    CASE(F_INSERT_ALIST)           /* --- insert_alist <nargs> --- */
    {
        /* EFUN insert_alist()
         *
         *   mixed* insert_alist (mixed key, mixed data..., mixed * alist)
         *   int    insert_alist (mixed key, mixed * keys)
         *
         * 1. Form: Alist Insertion
         *
         *   The <key> and all following <data> values are inserted
         *   into the <alist>. If an entry for <key> already exists
         *   in the list, just the data values are replaced. The number
         *   of <data> values must match the number of data arrays
         *   in the alist, naturally.
         *
         *   Result is the updated <alist>.
         *
         * 2. Form: Key Insertion
         *
         *   Insert the <key> into the (ordered) array of <keys>, so that
         *   subsequent assoc()s can perform quick lookups. Result is the
         *   index at which <key> was inserted (or already found).
         *
         *   CAVEAT: when working with string keys, the index might no longer
         *     be valid after the next call to insert_alist().
         */
        /* When the key list of an alist contains destructed objects
           it is better not to free them till the next reordering by
           order_alist to retain the alist property.
         */
        int i;
        vector_t *list;
        long listsize;
        size_t keynum;
        svalue_t *key,*key_data,*ret;
        static LOCAL_VEC1(insert_alist_vec, T_NUMBER);
          /* Mock-alist for the insert_alist() key-insertion form.
           */

        GET_NUM_ARG

#if defined(DEBUG) && defined(MALLOC_smalloc)
        static_vector1 = &insert_alist_vec.v;
        /* TODO: Remove this once VEC_SIZE() is proven to be accurate.
         */
#endif

        if (sp->type != T_POINTER)
            bad_arg_pc(num_arg,F_INSERT_ALIST, sp, pc);

        /* Make up an alist if only a key-insertion is required */
        if ( !(listsize = (long)VEC_SIZE(sp->u.vec))
         ||  sp->u.vec->item[0].type != T_POINTER )
        {
            list = &insert_alist_vec.v;
            *list->item = *sp;
            listsize = 1;
        }
        else
            list = sp->u.vec;

        /* Check the validity of the alist */
        keynum = VEC_SIZE(list->item[0].u.vec);
        for (i = 1; i < listsize; i++)
        {
            if (list->item[i].type != T_POINTER
             || VEC_SIZE(list->item[i].u.vec) != keynum)
            {
                bad_arg_pc(num_arg,F_INSERT_ALIST, sp, pc);
            }
        }

        /* Get and test the data to insert */
        if (num_arg == 2)
        {
            if (sp[-1].type != T_POINTER)
            {
                key_data = NULL;
                key = sp-1;
            }
            else
            {
                if (VEC_SIZE(sp[-1].u.vec) != (size_t)listsize)
                    goto bad_arg_1;
                key_data = key = sp[-1].u.vec->item;
            }
        }
        else
        {
            if (num_arg - 1 != listsize)
                goto bad_arg_1;
            key_data = key = sp-num_arg+1;
        }

        /* Do the insertion */
        inter_sp = sp; /* array might get too big */
        ret = insert_alist(key,key_data,list);
        pop_n_elems(num_arg);
        sp++;
        *sp = *ret;
        break;
    }
#endif /* F_INSERT_ALIST */

#ifdef F_INTERSECT_ALIST
    CASE(F_INTERSECT_ALIST);        /* --- intersect_alist     --- */
    {
        /* EFUN intersect_alist()
         *
         *   mixed * intersect_alist (mixed * list1, mixed * list2)
         *
         * Does a fast set intersection on alist key vectors (NOT on full
         * alists!).  The operator '&' does set intersection on arrays in
         * general.
         */

        vector_t *tmp;

        TYPE_TEST1(sp-1, T_POINTER)
        TYPE_TEST2(sp,   T_POINTER)
        tmp = intersect_alist( (sp-1)->u.vec, sp->u.vec );
        pop_stack();
        free_array(sp->u.vec);
        sp->u.vec = tmp;
        break;
    }
#endif /* F_INTERSECT_ALIST */

#ifdef F_ORDER_ALIST
    CASE(F_ORDER_ALIST);            /* --- order_alist <nargs> --- */
    {
        /* EFUN order_alist()
         *
         *   mixed *order_alist(mixed *keys, mixed *|void data, ...)
         *
         * Creates an alist.
         *
         * Either takes an array containing keys, and others containing
         * the associated data, where all arrays are to be of the same
         * length, or takes a single array that contains as first member
         * the array of keys and has an arbitrary number of other members
         * containing data, each of wich has to be of the same length as
         * the key array. Returns an array holding the sorted key array
         * and the data arrays; the same permutation that is applied to
         * the key array is applied to all data arrays.
         */

        int i;
        svalue_t *args;
        vector_t *list;
        long listsize;
        Bool reuse;
        size_t keynum;

        GET_NUM_ARG
        args = sp-num_arg+1;

        /* Get the key array to order */
        TYPE_TEST1(args, T_POINTER)
        if (num_arg == 1
          && ((list = args->u.vec), (listsize = (long)VEC_SIZE(list)))
          && list->item[0].type == T_POINTER)
        {
            args     = list->item;
            reuse = (list->ref == 1);
        }
        else
        {
            listsize = num_arg;
            reuse = MY_TRUE;
        }
        keynum = VEC_SIZE(args[0].u.vec);

        /* Get the data arrays to order */
        for (i = 0; i < listsize; i++)
        {
            if (args[i].type != T_POINTER
             || VEC_SIZE(args[i].u.vec) != keynum)
            {
                ERRORF(("bad data array %d in call to order_alist\n",i))
            }
        }

        /* Create the alist */
        list = order_alist(args, listsize, reuse);
        pop_n_elems(num_arg);
        sp++;
        put_array(sp, list);
        break;
    }
#endif /* F_ORDER_ALIST */

    /* --- Optional Efuns: Miscellaneous --- */

#ifdef USE_SET_LIGHT
    CASE(F_SET_LIGHT);              /* --- set_light           --- */
    {
        /* EFUN set_light()
         *
         * int set_light(int n)
         *
         * An object is by default dark. It can be set to not dark by
         * calling set_light(1). The environment will then also get this
         * light. The returned value is the total number of lights in
         * this room. So if you call set_light(0) it will return the
         * light level of the current object.
         *
         * Note that the value of the argument is added to the light of
         * the current object.
         */

        object_t *o1;

        TYPE_TEST1(sp, T_NUMBER)
        add_light(current_object, sp->u.number);
        o1 = current_object;
        while (o1->super)
            o1 = o1->super;
        sp->u.number = o1->total_light;
        break;
    }
#endif /* USE_SET_LIGHT */

    /* --- XEfun and XCodes --- */

    CASE(F_ESCAPE);                  /* --- escape <instr> ... --- */
      {

        /* A prefixed instruction. */

#define XCASE(x) CASE((x)-0x100)
#undef GET_NUM_ARG
#define GET_NUM_ARG num_arg = EXTRACT_UCHAR(pc); pc++;

#undef TYPE_TEST1
#define TYPE_TEST1(arg, t) if ( (arg)->type != t ) goto xbad_arg_1;

        int code;  /* the actual instruction code */

        code = LOAD_CODE(pc);

#ifdef TRACE_CODE
        previous_instruction[last] = code + 0x100;
#endif

#ifdef OPCPROF
        opcount[code+0x100]++;
#endif

        switch(code)
        {

        default:
            fatal("Unknown stackmachine escape code: %d (%d)\n"
                 , code, code+0x100);
        xbad_arg_1: instruction = code + 0x100; goto bad_arg_1;
        xbad_arg_2: instruction = code + 0x100; goto bad_arg_2;
        xbad_arg_3: instruction = code + 0x100; goto bad_arg_3;

        /* --- Machine Instructions --- */

        XCASE(F_END_CATCH);         /* --- esc end_catch       --- */
            /* For a catch(...guarded code...) statement, the compiler
             * generates a F_END_CATCH as last instruction of the
             * guarded code.
             *
             * Executed when no error occured, it returns into
             * catch_instruction() to clean up the
             * error recovery information pushed by the F_CATCH
             * and leave a 0 on the stack.
             *
             * dump_trace() checks for this bytecode, but accepts a normal
             * instruction as well as an escaped instruction.
             */

            return MY_TRUE;
            break;

                      /* --- esc breakn_continue <num> <offset> ---*/
        XCASE(F_BREAKN_CONTINUE);
            /* Implement the 'continue;' statement from within
             * a nested surrounding structure.
             *
             * Pop <num>+1 (uint8) break-levels from the break stack
             * and jump by (16-Bit) short <offset> bytes, counted from the
             * first by of <offset>
             */

            break_sp +=
              LOAD_UINT8(pc) * (sizeof(svalue_t)/sizeof(*break_sp));
            /* FALLTHROUGH */

                             /* --- esc break_continue <offset> ---*/
        XCASE(F_BREAK_CONTINUE);
        {
            /* Implement the 'continue;' statement for the immediate
             * surrounding structure.
             *
             * Pop one break-level from the break stack and jump
             * by (16-Bit) unsigned short <offset> bytes, counted from the
             * first by of <offset>
             *
             * Pitfall: the offset is added to the current pc in 16-Bit
             * unsigned arithmetic, allowing to jump backwards using big
             * enough values.
             *
             * TODO: Make that a proper signed short.
             */

            /* TODO: uint16 */ unsigned short offset;

            break_sp += sizeof(svalue_t)/sizeof(*break_sp);
            GET_SHORT(offset, pc);
            offset += pc - current_prog->program;
            pc = current_prog->program + offset;
            break;
        }

                      /* --- esc push_protected_indexed_lvalue --- */
        XCASE(F_PUSH_PROTECTED_INDEXED_LVALUE);
            /* Op. (vector  v=sp[-1], int   i=sp[0])
             * Op. (mapping v=sp[-1], mixed i=sp[0])
             *
             * Compute the lvalue &(v[i]), store it in a struct
             * protected_lvalue, and push the protector as PROTECTED_LVALUE
             * into the stack.
             */

            sp = push_protected_indexed_lvalue(sp, pc);
            break;

                     /* --- esc push_protected_rindexed_lvalue --- */
        XCASE(F_PUSH_PROTECTED_RINDEXED_LVALUE);
            /* Op. (vector v=sp[-1], int i=sp[0])
             *
             * Compute the lvalue &(v[<i]), store it in a struct
             * protected_lvalue, and push the protector as PROTECTED_LVALUE
             * into the stack.
             */

            sp = push_protected_rindexed_lvalue(sp, pc);
            break;

                  /* --- esc push_protected_indexed_map_lvalue --- */
        XCASE(F_PUSH_PROTECTED_INDEXED_MAP_LVALUE);
            /* Op. (mapping m=sp[-2], mixed i=sp[-1], int   j=sp[0])
             *
             * Compute the lvalue &(m[i:j]), store it in a struct
             * protected_lvalue, and push the protector as PROTECTED_LVALUE
             * into the stack.
             */

            push_protected_indexed_map_lvalue(sp, pc);
            break;

                             /* --- esc protected_index_lvalue --- */
        XCASE(F_PROTECTED_INDEX_LVALUE);
            /* Operator (string|vector &v=sp[0], int   i=sp[-1])
             *          (mapping       &v=sp[0], mixed i=sp[-1])
             *
             * Compute the index &(*v[i]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector as
             * PROTECTED_LVALUE onto the stack.
             *
             * If <v> is a protected non-string-lvalue, the protected_lvalue
             * referenced by <v>.u.lvalue will be deallocated, and the
             * protector itself will be stored in <last_indexing_protector>
             * for the time being.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             */

            sp = protected_index_lvalue(sp, pc);
            break;

                            /* --- esc protected_rindex_lvalue --- */
        XCASE(F_PROTECTED_RINDEX_LVALUE);
            /* Operator (string|vector &v=sp[0], int   i=sp[-1])
             *
             * Compute the index &(*v[<i]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector as
             * PROTECTED_LVALUE onto the stack.
             *
             * If <v> is a protected non-string-lvalue, the protected_lvalue
             * referenced by <v>.u.lvalue will be deallocated, and the
             * protector itself will be stored in <last_indexing_protector>
             * for the time being.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             */

            sp = protected_rindex_lvalue(sp, pc);
            break;

                              /* --- esc protected_range_lvalue --- */
        XCASE(F_PROTECTED_RANGE_LVALUE);
            /* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
             *
             * Compute the range &(v[i1..i2]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector onto the
             * stack.
             *
             * If <v> is a protected lvalue itself, its protecting svalue will
             * be used in the result protector.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             */

            inter_pc = pc;
            sp = protected_range_lvalue(0x000, sp);
            break;

                           /* --- esc protected_nr_range_lvalue --- */
        XCASE(F_PROTECTED_NR_RANGE_LVALUE);
            /* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
             *
             * Compute the range &(v[i1..<i2]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector onto the
             * stack.
             *
             * If <v> is a protected lvalue itself, its protecting svalue will
             * be used in the result protector.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             */

            inter_pc = pc;
            sp = protected_range_lvalue(0x001, sp);
            break;

                           /* --- esc protected_rn_range_lvalue --- */
        XCASE(F_PROTECTED_RN_RANGE_LVALUE);
            /* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
             *
             * Compute the range &(v[<i1..i2]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector onto the
             * stack.
             *
             * If <v> is a protected lvalue itself, its protecting svalue will
             * be used in the result protector.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             */

            inter_pc = pc;
            sp = protected_range_lvalue(0x100, sp);
            break;

                           /* --- esc protected_rr_range_lvalue --- */
        XCASE(F_PROTECTED_RR_RANGE_LVALUE);
            /* X-Op (string|vector &v=sp[0], int i2=sp[-1], i1=sp[-2])
             *
             * Compute the range &(v[<i1..<i2]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector onto the
             * stack.
             *
             * If <v> is a protected lvalue itself, its protecting svalue will
             * be used in the result protector.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             */

            inter_pc = pc;
            sp = protected_range_lvalue(0x101, sp);
            break;

                           /* --- esc protected_nx_range_lvalue --- */
        XCASE(F_PROTECTED_NX_RANGE_LVALUE);
            /* X-Op (string|vector &v=sp[0], i1=sp[-1])
             *
             * Compute the range &(v[i1..]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector onto the
             * stack.
             *
             * If <v> is a protected lvalue itself, its protecting svalue will
             * be used in the result protector.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             *
             * We implement it by pushing '1' onto the stack and then
             * calling protected_nr_range_lvalue, effectively computing
             * &(v[i1..<1]).
             */

            inter_pc = pc;
            sp++;
            sp[0] = sp[-1];       /* Pull up the 'v' */
            put_number(sp-1, 1);  /* 'Push' the 1 for the upper bound */
            sp = protected_range_lvalue(0x001, sp);
            break;

                           /* --- esc protected_rx_range_lvalue --- */
        XCASE(F_PROTECTED_RX_RANGE_LVALUE);
            /* X-Op (string|vector &v=sp[0], int i1=sp[-1])
             *
             * Compute the range &(v[<i1..]) of lvalue <v>, wrap it into a
             * protector, and push the reference to the protector onto the
             * stack.
             *
             * If <v> is a protected lvalue itself, its protecting svalue will
             * be used in the result protector.
             *
             * If <v> is a string-lvalue, it is made a malloced string if
             * necessary.
             *
             * We implement it by pushing '1' onto the stack and then
             * calling protected_nr_range_lvalue, effectively computing
             * &(v[i1..<1]).
             */

            inter_pc = pc;
            sp++;
            sp[0] = sp[-1];       /* Pull up the 'v' */
            put_number(sp-1, 1);  /* 'Push' the 1 for the upper bound */
            sp = protected_range_lvalue(0x101, sp);
            break;

        XCASE(F_UNDEF);             /* --- esc undef           --- */
          {
            /* Catch-all instructions for declared but not implemented
             * (defined) functions. Usually used by the compiler to
             * handle prototypes (in that case it is the first and only
             * instruction of the generated stub), it is also inserted
             * into closures when the object the closure is bound to
             * is destructed.
             * Note: this instruction MUST be the first in the function.
             */

            char *name;

            /* pc has already been incremented */
            if (pc > current_prog->program && pc <= PROGRAM_END(*current_prog))
            {
                /* Copy the function name pointer into name.
                 */
                memcpy(&name, FUNCTION_NAMEP(FUNCTION_FROM_CODE(pc-2)), sizeof name);
            }
            else
            {
            	/* It is a vanished closure */
                name = "Object the closure was bound to has been destructed";
                /* TODO: WHich object? This can also happen as result of
                 * TODO:: a replace_program */
            }
            ERRORF(("Undefined function: %s\n", name))
          }

        /* --- XEfuns: Miscellaneous --- */

        XCASE(F_ABS);               /* --- esc abs             --- */
        {
            /* XEFUN abs()
             *
             *  int   abs (int arg)
             *  float abs (float arg)
             *
             * Returns the absolute value of the argument <arg>.
             */

            switch(sp->type)
            {
            default:
                goto xbad_arg_1;

            case T_NUMBER:
                if (sp->u.number == PINT_MIN)
                {
                    ERRORF(("Numeric overflow: abs(%ld)\n"
                           , (long)sp->u.number));
                    /* NOTREACHED */
                    break;
                }
                if (sp->u.number < 0)
                    sp->u.number = - sp->u.number;
                break;

            case T_FLOAT:
              {
                STORE_DOUBLE_USED
                double x;

                x = READ_DOUBLE(sp);
                if (x < 0.0)
                    STORE_DOUBLE(sp, -(x));
                break;
              }
            }
            break;
        }

        XCASE(F_SIN);               /* --- esc sin             --- */
          {
            /* XEFUN sin()
             *
             *  float sin(int|float)
             *
             * Returns the sinus of the argument.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_1;
                d = sin((double)(sp->u.number));

            }
            else
                d = sin(READ_DOUBLE(sp));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_ASIN);              /* --- esc asin            --- */
          {
            /* XEFUN asin()
             *
             *  float asin(float)
             *
             * Returns the inverse sinus of the argument.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type != T_FLOAT || (d = READ_DOUBLE(sp)) < -1. || d > 1. )
                goto xbad_arg_1;
            d = asin(d);
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_COS);               /* --- esc cos             --- */
          {
            /* XEFUN cos()
             *
             *  float cos(int|float)
             *
             * Returns the cosinus of the argument.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_1;
                d = cos((double)(sp->u.number));

            }
            else
                d = cos(READ_DOUBLE(sp));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_ACOS);              /* --- esc acos            --- */
          {
            /* XEFUN acos()
             *
             *  float acos(float)
             *
             * Returns the inverse cosinus of the argument.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type != T_FLOAT || (d = READ_DOUBLE(sp)) < -1. || d > 1. )
                goto xbad_arg_1;
            d = acos(d);
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_TAN);               /* --- esc tan             --- */
          {
            /* XEFUN tan()
             *
             *  float tan(int|float)
             *
             * Returns the tangens of the argument.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_1;
                d = tan((double)(sp->u.number));

            }
            else
                d = tan(READ_DOUBLE(sp));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_ATAN);              /* --- esc atan            --- */
          {
            /* XEFUN atan()
             *
             *   float atan(int|float)
             *
             * Returns the inverse tangens of the argument.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_1;
                d = atan((double)(sp->u.number));

            }
            else
                d = atan(READ_DOUBLE(sp));
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: atan(%g)\n", READ_DOUBLE(sp)));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_ATAN2);             /* --- esc atan2           --- */
          {
            /* XEFUN atan2()
             *
             *   float atan2(int|float y, int|float x)
             *
             * Returns the inverse tangens of the argument.
             */

            STORE_DOUBLE_USED
            double x, y, d;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_2;
                x = (double)(sp->u.number);
            }
            else
                x = READ_DOUBLE(sp);
            if (sp[-1].type != T_FLOAT)
            {
                if (sp[-1].type != T_NUMBER) goto xbad_arg_1;
                y = (double)sp[-1].u.number;
            }
            else
                y = READ_DOUBLE(sp-1);
            d = atan2(y, x);
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: atan2(%g, %g)\n", y, x));
            pop_stack();
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_LOG);               /* --- esc log             --- */
          {
            /* XEFUN log()
             *
             *   float log(int|float)
             *
             * Returns the natural logarithmus of the argument.
             */

            STORE_DOUBLE_USED
            double d, e;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_1;
                d = (double)sp->u.number;
            }
            else
                d = READ_DOUBLE(sp);
            if (d <= 0.)
                goto xbad_arg_1;
            e = log(d);
            if (e < (-DBL_MAX) || e > DBL_MAX)
                ERRORF(("Numeric overflow: log(%g)\n", d));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, e);
            break;
          }

        XCASE(F_EXP);               /* --- esc exp             --- */
          {
            /* XEFUN exp()
             *
             *   float exp(int|float)
             *
             * Returns the e to the power of the argument.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_1;
                d = exp((double)sp->u.number);
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: exp(%ld)\n", sp->u.number));
            }
            else
            {
                d = exp(READ_DOUBLE(sp));
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: exp(%g)\n", READ_DOUBLE(sp)));
            }
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_SQRT);              /* --- esc sqrt            --- */
          {
            /* XEFUN sqrt()
             *
             *   float sqrt(int|float)
             *
             * Returns the square root of the argument.
             */

            STORE_DOUBLE_USED
            double d, e;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_1;
                d = (double)sp->u.number;
            }
            else
                d = READ_DOUBLE(sp);
            if (d < 0.)
                goto xbad_arg_1;
            e = sqrt(d);
            if (e < (-DBL_MAX) || e > DBL_MAX)
                ERRORF(("Numeric overflow: sqrt(%g)\n", d));
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, e);
            break;
          }

        XCASE(F_CEIL);              /* --- esc ceil            --- */
          {
            /* XEFUN ceil()
             *
             *   float ceil(int|float)
             *
             * Returns the smallest whole number which is still bigger
             * than the argument. For integer arguments, the result will
             * be the argument value itself, albeit converted to a float.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type == T_FLOAT)
            {
                d = ceil(READ_DOUBLE(sp));
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: ceil(%g)\n", READ_DOUBLE(sp)));
            }
            else if (sp->type == T_NUMBER)
            {
                d = sp->u.number;
                sp->type = T_FLOAT;
            }
            else
                goto xbad_arg_1;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_FLOOR);             /* --- esc floor           --- */
          {
            /* XEFUN floor()
             *
             *   float floor(int|float)
             *
             * Returns the biggest whole number which is not larger
             * than the argument. For integer arguments, the result will
             * be the argument value itself, albeit converted to a float.
             */

            STORE_DOUBLE_USED
            double d;

            if (sp->type == T_FLOAT)
            {
                d = floor(READ_DOUBLE(sp));
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: floor(%g)\n", READ_DOUBLE(sp)));
            }
            else if (sp->type == T_NUMBER)
            {
                d = sp->u.number;
                sp->type = T_FLOAT;
            }
            else
                goto xbad_arg_1;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_POW);               /* --- esc pow             --- */
          {
            /* XEFUN pow()
             *
             *   float pow(int|float x, int|float y)
             *
             * Returns x to the power of y.
             */

            STORE_DOUBLE_USED
            double x, y, d;

            if (sp->type != T_FLOAT)
            {
                if (sp->type != T_NUMBER) goto xbad_arg_2;
                y = (double)(sp->u.number);
            }
            else
                y = READ_DOUBLE(sp);
            if (sp[-1].type != T_FLOAT)
            {
                if (sp[-1].type != T_NUMBER) goto xbad_arg_1;
                x = (double)sp[-1].u.number;
            }
            else
                x = READ_DOUBLE(sp-1);
            if (x == 0. && y < 0.)
                ERROR("Can't raise 0 to negative powers.\n")
            if (x < 0.  && y != (double)((long)y))
                ERROR("Can't raise negative number to fractional powers.\n")
            d = pow(x, y);
            if (d < (-DBL_MAX) || d > DBL_MAX)
                ERRORF(("Numeric overflow: pow(%g, %g)\n", x, y));
            pop_stack();
            sp->type = T_FLOAT;
            STORE_DOUBLE(sp, d);
            break;
          }

        XCASE(F_GET_TYPE_INFO);     /* --- esc get_type_info   --- */
          {
            /* XEFUN get_type_info()
             *
             *   mixed get_type_info(mixed arg, int flag)
             *
             * Returns info about the type of arg, as controlled by the flag.
             *
             * If the optional argument flag is not a number, an array is
             * returned, whose first element is an integer denoting the data
             * type, as defined in <lpctypes.h>. The second entry can contain
             * additional information about arg.
             * If flag is the number 0, only the first element of that array
             * (i.e. the data type) is returned (as int).
             * If flag is 1, the second element is returned.
             * If <arg> is a closure, the <flag> setting 2 lets the efun
             * return the object the closure is bound to.
             * For every other <flag> setting, -1 is returned.
             *
             * The secondary information is:
             *   - for mappings the width, ie the number of data items per key.
             *   - for symbols and quoted arrays the number of quotes.
             *   - for closures the closure type
             *   - for strings 0 for shared strings, and non-0 for others.
             *   - -1 for all other datatypes.
             *
             * TODO: The flags should be defined in an include file.
             * TODO: The array returned for closures should contain all
             * TODO:: three items.
             */

            mp_int i, j;

            i = sp[-1].type;

            /* Determine the second return value */
            switch(i)
            {
            default:
                j = -1;
                break;
            case T_STRING:
                j = (sp[-1].x.string_type == STRING_SHARED) ? 0 : 1;
                break;
            case T_MAPPING:
                j = sp[-1].u.map->num_values;
                break;
            case T_CLOSURE:
                if ( sp->type == T_NUMBER && sp->u.number == 2)
                {
                    object_t *ob;

                    ob = NULL;
                    sp--;
                    switch(sp->x.closure_type)
                    {
                    default:
                        /* efun, simul-efun, operator closure */
                        ob = sp->u.ob;
                        break;
                    case CLOSURE_LFUN:
                    case CLOSURE_IDENTIFIER:
                    case CLOSURE_BOUND_LAMBDA:
                    case CLOSURE_LAMBDA:
                        ob = sp->u.lambda->ob;
                        break;
                    case CLOSURE_ALIEN_LFUN:
                        ob = sp->u.lambda->function.alien.ob;
                        break;
                    case CLOSURE_UNBOUND_LAMBDA:
                        ob = NULL;
                        break;
                    }
                    free_closure(sp);
                    if (!ob || ob->flags & O_DESTRUCTED)
                        put_number(sp, 0);
                    else
                        put_ref_object(sp, ob, "get_type_info");
                    goto again;
                    /* NOTREACHED */
                }
            case T_SYMBOL:
            case T_QUOTED_ARRAY:
                j = sp[-1].x.generic;
                break;
            }

            /* Depending on flag, return the proper value */
            if (sp->type == T_NUMBER)
            {
                free_svalue(--sp);
                if (sp[1].u.number != 1)
                {
                    if (sp[1].u.number)
                        j = -1;
                    else
                        j = i;
                }
                put_number(sp, j);
            }
            else
            {
                vector_t *v;

                inter_sp = sp;
                inter_pc = pc;
                v = allocate_array(2);
                v->item[0].u.number = i;
                v->item[1].u.number = j;
                free_svalue(sp);
                free_svalue(--sp);
                put_array(sp,v);
            }

            break;
          }

        XCASE(F_RAISE_ERROR);       /* --- esc raise_error     --- */
          {
            /* XEFUN raise_error()
             *
             *   void raise_error(string arg)
             *
             * Abort execution. If the current program execution was initiated
             * by catch(), that catch expression will return arg as error
             * code, else the arg will printed as error message. This
             * is very similar to throw(), but while throw() is intended to be
             * called inside catch(), raise_error() can be called
             * anywhere.
             */

            if (sp->type != T_STRING)
                goto xbad_arg_1;
            ERRORF(("%s", sp->u.string));
          }

        XCASE(F_REFERENCEP);        /* --- esc referencep      --- */
          {
            /* XEFUN referencep()
             *
             *   int referencep(mixed arg)
             *
             * Returns true if arg was passed by reference to the current
             * function, instead of the usual call-by-value.
             */

            int i;

            if (sp->type != T_LVALUE)
                goto xbad_arg_1;
            i = sp->u.lvalue->type == T_LVALUE;
            free_svalue(sp);
            put_number(sp, i);
            break;
          }

        XCASE(F_TYPEOF);            /* --- esc typeof          --- */
          {
            /* XEFUN typeof()
             *
             *   int typeof(mixed)
             *
             * Returns a code for the type of the argument, as defined in
             * <sys/lpctypes.h>
             */

            mp_int i = sp->type;
            free_svalue(sp);
            put_number(sp, i);
            break;
          }

        XCASE(F_TO_INT);            /* --- esc to_int          --- */
          {
            /* EFUN to_int()
             *
             *   int to_int(string)
             *   int to_int(float)
             *   int to_int(int)
             *   int to_int(closure)
             *
             * Floats are truncated to integer values, strings with leadings
             * digits are converted to integers up to the first non-digit.
             * variable- and lfun-closures are converted into their variable
             * resp.function index.
             * Integers are just returned.
             */

            int n;

            switch(sp->type)
            {
            default:
                goto xbad_arg_1;

            case T_FLOAT:
              {
                double d;

                d = READ_DOUBLE(sp);
                if (d < (-DBL_MAX) || d > DBL_MAX)
                    ERRORF(("Numeric overflow: to_int(%g)\n", d));
                n = (long)d;
                break;
              }

            case T_STRING:
                n = atol(sp->u.string);
                /* TODO: make this strtol() */
                free_string_svalue(sp);
                break;

            case T_CLOSURE:
                if (sp->x.closure_type == CLOSURE_IDENTIFIER
                 || sp->x.closure_type == CLOSURE_LFUN)
                    n = sp->u.lambda->function.index;
                else
                    goto xbad_arg_1;
                free_closure(sp);
                break;

            case T_NUMBER:
                n = sp->u.number;
                break;
            }
            put_number(sp, n);
            break;
          }

        XCASE(F_TO_FLOAT);          /* --- esc to_float           --- */
          {
            /* EFUN to_float()
             *
             *   float to_float(int)
             *   float to_float(string)
             *   float to_float(float)
             *
             * Ints are expanded to floats, strings are converted up to the
             * first character that doesnt belong into a float.
             * Floats are just returned.
             */

            STORE_DOUBLE_USED
            double d;

            d = 0.0;
            switch(sp->type)
            {
            default:
                goto xbad_arg_1;

            case T_NUMBER:
                d = (double)sp->u.number;
                break;

            case T_FLOAT:
                NOOP;
                break;

            case T_STRING:
                d = atof(sp->u.string);
                /* TODO: make this strtof() or so */
                free_string_svalue(sp);
                break;
            }

            if (sp->type != T_FLOAT)
            {
                sp->type = T_FLOAT;
                STORE_DOUBLE(sp, d);
            }
            break;
          }

        XCASE(F_TO_STRING);         /* --- esc to_string       --- */
          {
            /* XEFUN to_string()
             *
             *   string to_string(mixed)
             *
             * The argument is converted to a string. Works with int, float,
             * object, arrays (to convert an array of int back into a string),
             * symbols, strings, and closures.
             *
             * Converts variable/lfun closure to the appropriate names.
             */

            char buf[1024], *s;

            s = NULL;
            buf[sizeof(buf)-1] = '\0';
            switch(sp->type)
            {
            default:
                goto xbad_arg_1;

            case T_NUMBER:
                sprintf(buf,"%ld", sp->u.number);
                if (buf[sizeof(buf)-1] != '\0')
                    FATAL("Buffer overflow in F_TO_STRING: "
                          "int number too big.\n")
                s = string_copy(buf);
                break;

            case T_FLOAT:
                sprintf(buf,"%g", READ_DOUBLE(sp));
                if (buf[sizeof(buf)-1] != '\0')
                    FATAL("Buffer overflow in F_TO_STRING: "
                          "int number too big.\n")
                s = string_copy(buf);
                break;

            case T_OBJECT:
                if (!compat_mode)
                    s = add_slash(sp->u.ob->name);
                else
                    s = string_copy(sp->u.ob->name);
                if (!s)
                    ERROR("Out of memory\n")
                free_object_svalue(sp);
                break;

            case T_POINTER:
              {
                /* Arrays of ints are considered exploded strings and
                 * converted back accordingly, ie. up to the first 0
                 * or the first non-int.
                 */

                long size;
                svalue_t *svp;
                char c, *d;

                size = (long)VEC_SIZE(sp->u.vec) + 1;
                svp = sp->u.vec->item;
                d = s = xalloc((size_t)size);
                for (;;)
                {
                    if (!--size)
                    {
                        *d++ = '\0';
                        break;
                    }
                    if (svp->type != T_NUMBER || !(c = (char)svp->u.number) )
                    {
                        *d++ = '\0';
                        d = string_copy(s);
                        xfree(s);
                        s = d;
                        break;
                    }
                    *d++ = c;
                    svp++;
                }
                free_array(sp->u.vec);
                break;
              }

            case T_CLOSURE:
              {
                /* Convert the various types of closures into a string */

                lambda_t *l = sp->u.lambda;
                object_t *ob;
                int ix;

                switch(sp->x.closure_type)
                {

                case CLOSURE_IDENTIFIER: /* Variable Closure */
                  {
                    /* We need the program resident */
                    if (O_PROG_SWAPPED(l->ob))
                    {
                        l->ob->time_of_ref = current_time;
                        if (load_ob_from_swap(l->ob) < 0)
                            ERROR("Out of memory.\n");
                    }

                    if (l->function.index != VANISHED_VARCLOSURE_INDEX)
                    {
                        /* Get the variable name */
                        put_ref_string(sp
                         , l->ob->prog->variable_names[l->function.index].name
                        );
                    }
                    else
                    {
                        /* Variable vanished in a replace_program() */
                        put_volatile_string(sp, "dangling var closure");
                    }
                    break;
                  }

                case CLOSURE_LFUN: /* Lfun closure */
                case CLOSURE_ALIEN_LFUN:
                  {
                    program_t *prog;
                    fun_hdr_p fun;
                    funflag_t flags;
                    char *function_name;
                    inherit_t *inheritp;

                    if (sp->x.closure_type == CLOSURE_LFUN)
                    {
                        ob = l->ob;
                        ix = l->function.index;
                    }
                    else
                    {
                        ob = l->function.alien.ob;
                        ix = l->function.alien.index;
                        /* TODO: ix: After a replace_program, can this index
                         * TODO:: be negative?
                         */
                    }

                    /* Get the program resident */
                    if (O_PROG_SWAPPED(ob)) {
                        ob->time_of_ref = current_time;
                        if (load_ob_from_swap(ob) < 0)
                            ERROR("Out of memory\n");
                    }

                    /* Find the true definition of the function */
                    prog = ob->prog;
                    flags = prog->functions[ix];
                    while (flags & NAME_INHERITED)
                    {
                        inheritp = &prog->inherit[flags & INHERIT_MASK];
                        ix -= inheritp->function_index_offset;
                        prog = inheritp->prog;
                        flags = prog->functions[ix];
                    }

                    /* Copy the function name pointer (a shared string) */
                    fun = prog->program + (flags & FUNSTART_MASK);
                    memcpy(&function_name, FUNCTION_NAMEP(fun)
                          , sizeof function_name
                    );
                    put_ref_string(sp, function_name);
                    break;
                  }

                case CLOSURE_UNBOUND_LAMBDA: /* Unbound-Lambda Closure */
                case CLOSURE_PRELIMINARY:    /* Preliminary Lambda Closure */
                  {
                      char *rc;

                      if (sp->x.closure_type == CLOSURE_PRELIMINARY)
                          sprintf(buf, "<prelim lambda 0x%p>", l);
                      else
                          sprintf(buf, "<free lambda 0x%p>", l);
                      rc = string_copy(buf);
                      put_malloced_string(sp, rc);
                      break;
                  }

                case CLOSURE_LAMBDA:         /* Lambda Closure */
                case CLOSURE_BOUND_LAMBDA:   /* Bound-Lambda Closure */
                  {
                      char *rc;

                      if (sp->x.closure_type == CLOSURE_BOUND_LAMBDA)
                          sprintf(buf, "<bound lambda 0x%p:", l);
                      else
                          sprintf(buf, "<lambda 0x%p:", l);

                      ob = l->ob;

                      if (!ob)
                      {
                          strcat(buf, "{null}>");
                          rc = string_copy(buf);
                      }
                      else
                      {
                          if (ob->flags & O_DESTRUCTED)
                              strcat(buf, "{dest}");
                          xallocate(rc, strlen(buf)+strlen(ob->name)+3
                                   , "string-repr of lambda closure");
                          strcat(buf, "/");
                          strcpy(rc, buf);
                          strcat(rc, ob->name);
                          strcat(rc, ">");
                      }

                      put_malloced_string(sp, rc);
                      break;
                  }

                default:
                  {
                    int type = sp->x.closure_type;
                    if (type < 0)
                    {
                        switch(type & -0x0800)
                        {
                        case CLOSURE_OPERATOR:
                          {
                            s = NULL;
                            switch(type - CLOSURE_OPERATOR)
                            {
                            case F_POP_VALUE:
                                s = ",";
                                break;

                            case F_BBRANCH_WHEN_NON_ZERO:
                                s = "do";
                                break;

                            case F_BBRANCH_WHEN_ZERO:
                                s = "while";
                                break;

                            case F_BRANCH:
                                s = "continue";
                                break;

                            case F_CSTRING0:
                                s = "default";
                                break;

                            case F_BRANCH_WHEN_ZERO:
                                s = "?";
                                break;

                            case F_BRANCH_WHEN_NON_ZERO:
                                s = "?!";
                                break;

                            case F_RANGE:
                                s = "[..]";
                                break;

                            case F_NR_RANGE:
                                s = "[..<]";
                                break;

                            case F_RR_RANGE:
                                s = "[<..<]";
                                break;

                            case F_RN_RANGE:
                                s = "[<..]";
                                break;

                            case F_MAP_INDEX:
                                s = "[,]";
                                break;

                            case F_NX_RANGE:
                                s = "[..";
                                break;

                            case F_RX_RANGE:
                                s = "[<..";
                                break;

                            }

                            if (s)
                            {
                                put_volatile_string(sp, s);
                                break;
                            }
                            type += CLOSURE_EFUN - CLOSURE_OPERATOR;
                          }
                        /* default action for operators: FALLTHROUGH */

                        case CLOSURE_EFUN:
                          {
                              char *rc;

                              sprintf(buf, "#'%s"
                                         , instrs[type - CLOSURE_EFUN].name);
                              rc = string_copy(buf);
                              put_malloced_string(sp, rc);
                              break;
                          }

                        case CLOSURE_SIMUL_EFUN:
                          {
                              char *rc;

                              sprintf(buf, "#'<sefun>%s"
                                         , simul_efunp[type - CLOSURE_SIMUL_EFUN].name);
                              rc = string_copy(buf);
                              put_malloced_string(sp, rc);
                              break;
                          }
                        }
                        break;
                    }
                    else /* type >= 0 */
                    {
                        goto xbad_arg_1;
                    } /* if (type) */
                  } /* case default */
                } /* switch(closure type) */
                break;
              }

            case T_SYMBOL:
              {
                /* Easy: the symbol value is a string */
                sp->type = T_STRING;
                sp->x.string_type = STRING_SHARED;
                break;
              }

            case T_STRING:
                break;
            }

            if (sp->type != T_STRING)
                put_malloced_string(sp, s);
            break;
          }

        XCASE(F_TO_ARRAY);          /* --- esc to_array        --- */
          {
            /* XEFUN to_array()
             *
             *   mixed *to_array(string)
             *   mixed *to_array(symbol)
             *   mixed *to_array(quotedarray)
             *   mixed *to_array(mixed *)
             *
             * Strings and symbols are converted to an int array that
             * consists of the args characters, with 0 == '\0' as last
             * character stored.
             * Quoted arrays are ``dequoted'', and arrays are left as they
             * are.
             */

            vector_t *v;
            char *s, ch;
            svalue_t *svp;

            if (sp->type == T_STRING || sp->type == T_SYMBOL)
            {
                /* Split the string into an array of ints */

                inter_sp = sp;
                inter_pc = pc;
                v = allocate_uninit_array((mp_int)svalue_strlen(sp) + 1);
                s = sp->u.string;
                svp = v->item;
                do {
                    ch = *s++;
                    put_number(svp, ch);
                    svp++;
                } while (ch);
                free_string_svalue(sp);
                put_array(sp, v);
                break;
            }
            else if (sp->type == T_QUOTED_ARRAY)
            {
                /* Unquote it fully */
                sp->type = T_POINTER;
                break;
            }
            else if (sp->type == T_POINTER)
            {
                /* Good as it is */
                break;
            }
            else
                goto xbad_arg_1;
            break;
          }

        /* --- XEfuns: Strings --- */

        XCASE(F_STRSTR);            /* --- esc strstr          --- */
          {
            /* XEFUN strstr()
             *
             *   int strstr (string str, string str2, int pos)
             *
             * Returns the index of str2 in str searching from position pos.
             * If str2 is not found in str, -1 is returned. The returned
             * index is relativ to the beginning of the string.
             *
             * If pos is negativ, it counts from the end of the string.
             */

            char *p1, *p2;
            int offs;

            if (sp[-2].type != T_STRING) goto xbad_arg_1;
            if (sp[-1].type != T_STRING) goto xbad_arg_2;
            if (sp[ 0].type != T_NUMBER) goto xbad_arg_3;

            p1 = sp[-2].u.string;
            if ( 0 != (offs = sp->u.number) )
            {
                /* Set p1 to the offset */

                if (offs < 0)
                {
                    offs += svalue_strlen(sp-2);
                    if (offs < 0)
                        offs = 0;
                }

                /* The loop is necessary because the allocated
                 * length might not be the real length.
                 * TODO: Lars sighs deeply.
                 */
                if (offs)
                {
                    do {
                        if (!*p1++)
                        {
                            p1--;
                            break;
                        }
                    } while (--offs);
                }
            }

            /* Now do the search starting at p1 */
            p2 = strstr(p1, sp[-1].u.string);
            p1 = sp[-2].u.string;
            sp--;
            pop_stack();
            free_string_svalue(sp);
            put_number(sp, p2 ? (p2 - p1) : -1);
            break;
          }

        /* --- XEfuns: Arrays and Mappings --- */

        XCASE(F_M_ALLOCATE);        /* --- esc m_allocate      --- */
          {
            /* XEFUN m_allocate()
             *
             *   mapping m_allocate(int size, int width)
             *
             * Reserve memory for a mapping.
             *
             * size is the number of entries (i.e. keys) to reserve, width is
             * the number of data items per entry. If the optional width is
             * omitted, 1 is used as default.
             */

            if ( sp[-1].type != T_NUMBER || sp[-1].u.number < 0)
                goto xbad_arg_1;
            if ( sp->type != T_NUMBER || sp->u.number < 0)
                goto xbad_arg_2;
            sp--;

            if (max_mapping_size && sp->u.number > max_mapping_size)
                ERRORF(("Illegal mapping size: %ld\n", sp->u.number));

            if (!(sp->u.map = allocate_mapping(sp->u.number, sp[1].u.number)))
            {
                sp++;
                /* sp points to a number-typed svalue, so freeing won't
                 * be a problem.
                 */
                ERROR("Out of memory\n")
            }
            sp->type = T_MAPPING;
            break;
          }

        XCASE(F_M_CONTAINS);     /* --- esc m_contains <nargs> --- */
          {
            /* XEFUN m_contains()
             *
             *   int m_contains(mixed &data1, ..., &dataN, map, key)
             *
             * If the mapping contains the key map, the corresponding values
             * are assigned to the data arguments, which massed be passed by
             * reference, and 1 is returned. If key is not in map, 0 is
             * returned and the data args are left unchanged.
             * It is possible to use this function for a 0-value mapping, in
             * which case it has the same effect as member(E).
             */

            svalue_t *item;
            int i;

            GET_NUM_ARG

            /* Test the arguments */
            for (i = -num_arg; ++i < -1; )
                if (sp[i].type != T_LVALUE)
                    bad_arg_pc(num_arg + i, code + 0x100, sp, pc);
            if (sp[-1].type != T_MAPPING ||
                sp[-1].u.map->num_values != num_arg -2)
                    bad_arg_pc(num_arg + i, code + 0x100, sp, pc);

            item = get_map_value(sp[-1].u.map, sp);
            if (item == &const0)
            {
                /* Not found */
                pop_n_elems(num_arg-1);
                free_svalue(sp);
                put_number(sp, 0);
                break;
            }

            free_svalue(sp--); /* free key */

            /* Copy the elements */
            for (i = -num_arg + 1; ++i < 0; )
            {
                /* get_map_lvalue() may return destructed objects. */
                /* TODO: May this cause problems elsewhere, too? */
                if (destructed_object_ref(item))
                {
                    assign_svalue(sp[i].u.lvalue, &const0);
                    item++;
                }
                else
                    /* mapping must not have been freed yet */
                    assign_svalue(sp[i].u.lvalue, item++);
                free_svalue(&sp[i]);
            }

            free_svalue(sp--); /* free mapping */
            sp += 3 - num_arg;
            put_number(sp, 1);
            break;
          }

        /* --- XEfuns: Functions and Closures --- */

        XCASE(F_CALLER_STACK);      /* --- esc caller_stack    --- */
          {
            /* XEFUN caller_stack()
             *
             *   object *caller_stack()
             *   object *caller_stack(int add_interactive)
             *
             * Returns an array of the previous_object()s who caused the
             * call_other() to this_object().  previous_object(i) equals
             * caller_stack()[i].

             * If you pass the optional argument <add_interactive> (as true
             * value), this_interactive() (or 0 if not existing) is appended
             * to the array.
             */

            int depth, i;
            Bool done;
            struct control_stack *p;
            vector_t *v;
            svalue_t *svp;

            TYPE_TEST1(sp, T_NUMBER)

            /* Determine the depth of the call stack */
            p = csp;
            for (depth = 0, done = MY_FALSE; ; depth++)
            {
                do {
                    if (p == CONTROL_STACK)
                    {
                        done = MY_TRUE;
                        break;
                    }
                } while ( !(--p)[1].extern_call );
                if (done)
                    break;
            }

            /* Allocate and fill in the result array */
            v = allocate_uninit_array(depth + (sp->u.number ? 1 : 0));
            p = csp;
            for (i = 0, svp = v->item, done = MY_FALSE; i < depth; i++, svp++)
            {
                object_t *prev;
                do {
                    if (p == CONTROL_STACK)
                    {
                        done = MY_TRUE;
                        break;
                    }
                } while ( !(--p)[1].extern_call);

                /* Break if end of stack */
                if (done)
                    break;

                /* Get 'the' calling object */
                if (p[1].extern_call & CS_PRETEND)
                    prev = p[1].pretend_to_be;
                else
                    prev = p[1].ob;

                /* Enter it into the array */
                if (prev == NULL || prev->flags & O_DESTRUCTED)
                    put_number(svp, 0);
                else
                    put_ref_object(svp, prev, "caller_stack");
            }

#ifdef DEBUG
            if (i < depth)
            {
                error("Computed stack depth to %d, but found only %d objects\n"
                     , depth, i);
                /* NOTREACHED */
                break;
            }
#endif

            /* If so desired, add the interactive object */
            if (sp->u.number)
            {
                if ( current_interactive
                 && !(current_interactive->flags & O_DESTRUCTED))
                {
                    put_ref_object(svp, current_interactive, "caller_stack");
                }
                else
                    put_number(svp, 0);
            }

            /* Assign the result and return */
            put_array(sp, v);
            break;
          }

        XCASE(F_CALLER_STACK_DEPTH);  /* esc caller_stack_depth --- */
          {
            /* XEFUN caller_stack_depth()
             *
             *   int caller_stack_depth(void)
             *
             * Returns the number of previous objects on the stack. This
             * can be used for security checks.
             */

            int depth;
            Bool done;
            struct control_stack *p;

            /* Determine the depth of the call stack */
            p = csp;
            for (depth = 0, done = MY_FALSE; ; depth++)
            {
                do {
                    if (p == CONTROL_STACK)
                    {
                        done = MY_TRUE;
                        break;
                    }
                } while ( !(--p)[1].extern_call );
                if (done)
                    break;
            }

            push_number(depth);
            break;
          }

        XCASE(F_CALL_RESOLVED);   /* --- esc call_resolved <nargs> --- */
          {
            /* XEFUN call_resolved()
             *
             *   int call_resolved(mixed & result, object ob, string func, ...)
             *
             * Similar to call_other(). If ob->func() is defined and publicly
             * accessible, any of the optional extra arguments are passed to
             * ob->func(...). The result of that function call is stored in
             * result, which must be passed by reference.
             *
             * If the current object is already destructed, or the ob does not
             * exist, or ob does not define a public accessible function named
             * func, call_resolved() returns 0 as failure code, else 1 for
             * success.
             *
             * ob can also be a file_name. If a string is passed for ob, and
             * no object with that name does exist, an error occurs.
             */

            svalue_t *arg;
            object_t *ob;

            ASSIGN_EVAL_COST
            GET_NUM_ARG
            inter_pc = pc;
            inter_sp = sp;
            arg = sp - num_arg + 1;

            /* Test the arguments */
            if (arg[0].type != T_LVALUE)
                goto xbad_arg_1;

            if (arg[1].type == T_NUMBER && arg[1].u.number == 0)
                ob = NULL;
            else if (arg[1].type == T_OBJECT)
                ob = arg[1].u.ob;
            else if (arg[1].type == T_STRING)
            {
                ob = get_object(arg[1].u.string);
                if (!ob)
                    ERROR("call_resolved() failed: can't get object.\n")
            }
            else
                goto xbad_arg_2;

            if (arg[2].type != T_STRING)
                goto xbad_arg_3;

            /* No external calls may be done when this object is
             * destructed, or if the called object is destructed.
             */
            if (current_object->flags & O_DESTRUCTED
             || ob == NULL)
            {
                pop_n_elems(num_arg);
                push_number(0);
                break;
            }

            /* Handle traceing. */
            if (TRACEP(TRACE_CALL_OTHER) && TRACE_IS_INTERACTIVE())
            {
                if (!++traceing_recursion)
                {
                    inter_sp = sp;
                    do_trace("Call other ", arg[2].u.string, "\n");
                }
                traceing_recursion--;
            }

            /* Send the remaining arguments to the function.
             */
            if (!apply_low(arg[2].u.string, ob, num_arg-3, MY_FALSE))
            {
                /* Function not found */
                pop_n_elems(num_arg-1);
                free_svalue(sp);
                put_number(sp, 0);
                break;
            }

            /* The result of the function call is on the stack. But, so
             * is the function name and object that was called.
             * These have to be removed.
             */
            sp = inter_sp;
            transfer_svalue(arg, sp--);  /* Copy the function call result */
            pop_n_elems(2);        /* Remove old arguments to call_solved */
            free_svalue(sp);        /* Free the lvalue */
            put_number(sp, 1);
            break;
          }

        XCASE(F_EXTERN_CALL);       /* --- esc extern_call     --- */
          {
            /* XEFUN extern_call()
             *
             *   int extern_call();
             *
             * Returns zero, if the function that is currently being executed
             * was called by a local call, non-zero for call_other(), driver
             * applies, closure calls, etc. Currently the only return value
             * for them is 1, but later the various methods may be
             * distinguished by means of the return value.
             */


            struct control_stack * pt = csp;

            while (pt->catch_call) pt--;
            push_number((pt->extern_call & ~CS_PRETEND) ? 1 : 0);
            break;
          }

        XCASE(F_GET_EVAL_COST);     /* --- esc get_eval_cost   --- */
          {
            /* XEFUN get_eval_cost()
             *
             *   int get_eval_cost()
             *
             * Returns the remaining evaluation cost the current
             * execution (the current command) may use up.
             *
             * It starts at a driver given high value (__MAX_EVAL_COST__) and
             * is reduced with each executed statement.
             */

            push_number((max_eval_cost ? max_eval_cost : LONG_MAX) - eval_cost);
            break;
          }

        XCASE(F_PREVIOUS_OBJECT);   /* --- esc previous_object --- */
          {
            /* XEFUN previous_object()
             *
             *   object previous_object(int i)
             *
             * Follow back the last <i> call_other()s and return the calling
             * object (i.e. previous_object(2) returns the caller of the
             * caller). It must hold 0 <= i < caller_stack_depth().
             *
             * There is an important special case: in functions called by the
             * gamedriver in reaction to some external event (e.g. commands
             * added by add_action), previous_object() will return
             * this_object(), but previous_object(0) will return 0.
             */

            int i;
            struct control_stack *p;
            object_t *prev_ob;

            /* Test the arguments */
            if (sp->type != T_NUMBER)
                goto xbad_arg_1;
            i = sp->u.number;
            if (i > MAX_TRACE) {
                sp->u.number = 0;
                break;
            }

            /* Set p back to the <i>th extern call */
            p = csp;
            do {
                do {
                    if (p == CONTROL_STACK) {
                        sp->u.number = 0;
                        goto again;
                    }
                } while ( !(--p)[1].extern_call );
            } while (--i >= 0);

            /* Determine the object and push it */
            if (p[1].extern_call & CS_PRETEND)
                prev_ob = p[1].pretend_to_be;
            else
                prev_ob = p[1].ob;

            if (!prev_ob || prev_ob->flags & O_DESTRUCTED)
                sp->u.number = 0;
            else
                put_ref_object(sp, prev_ob, "previous_object");
            break;
          }

        /* --- XEfuns: Objects --- */

        XCASE(F_OBJECT_TIME);       /* --- esc object_time     --- */
          {
            /* XEFUN object_time()
             *
             *   int object_time()
             *   int object_time(object ob)
             *
             * Returns the creation time of the object.
             * Default is this_object(), if no arg is given.
             */

            mp_int load_time;

            if (sp->type != T_OBJECT)
                goto xbad_arg_1;

            load_time = sp->u.ob->load_time;

            free_object_svalue(sp);
            put_number(sp, load_time);
            break;
          }

        XCASE(F_PROGRAM_TIME);      /* --- esc program_time    --- */
          {
            /* XEFUN program_time()
             *
             *   int program_time()
             *   int program_time(object ob)
             *
             * Returns the creation (compilation) time of the object's
             * program. Default is this_object(), if no arg is given.
             */

            mp_int load_time;

            if (sp->type != T_OBJECT)
                goto xbad_arg_1;

            if (O_PROG_SWAPPED(sp->u.ob))
            {
                sp->u.ob->time_of_ref = current_time;
                if (load_ob_from_swap(sp->u.ob) < 0)
                {
                    sp--;
                    ERROR("Out of memory\n")
                }
            }
            load_time = sp->u.ob->prog->load_time;

            free_object_svalue(sp);
            put_number(sp, load_time);
            break;
          }

        XCASE(F_PROGRAM_NAME);      /* --- esc program_name    --- */
          {
            /* XEFUN program_name()
             *
             *   string program_name()
             *   string program_name(object obj)
             *
             * Returns the name of the program of <obj>, resp. the name of the
             * program of the current object if <obj> is omitted.
             *
             * The returned name is usually the name from which the blueprint
             * of <obj> was compiled (the 'load name'), but changes if an
             * object replaces its programs with the efun replace_program().
             *
             * As a special case, if <ob> is 0, the function returns 0.
             *
             * The name always ends in '.c'. It starts with a '/' unless the
             * driver is running in COMPAT mode.
             */

            char *name, *res;
            object_t *ob;

            /* If the argument is 0, return 0. */
            if (sp->type == T_NUMBER && sp->u.number == 0)
            {
                break;
            }

            TYPE_TEST1(sp, T_OBJECT)

            ob = sp->u.ob;
            if (O_PROG_SWAPPED(ob))
            {
                ob->time_of_ref = current_time;
                if (load_ob_from_swap(ob) < 0)
                {
                    ERROR("Out of memory\n");
                }
            }
            name = ob->prog->name;
            if (compat_mode)
                res = string_copy(name);
            else
                res = add_slash(name);
            if (!res)
                ERROR("Out of memory\n")
            free_object_svalue(sp);
            put_malloced_string(sp, res);
            break;
          }

                             /* --- esc query_once_interactive --- */
        XCASE(F_QUERY_ONCE_INTERACTIVE);
          {
            /* XEFUN query_once_interactive()
             *
             *   int query_once_interactive(object ob)
             *
             * True if the object is or once was interactive.
             */

            object_t *obj;

            if (sp->type != T_OBJECT) goto xbad_arg_1;

            obj = sp->u.ob;
            put_number(sp, obj->flags & O_ONCE_INTERACTIVE ? 1 : 0);
            deref_object(obj, "query_once_interactive");
            break;
          }

        /* --- XEfuns: Network IO --- */

        XCASE(F_QUERY_UDP_PORT);    /* --- esc query_udp_port  --- */
          {
            /* XEFUN query_udp_port()
             *
             *   int query_udp_port(void)
             *
             * Returns the port number that is used for the inter mud
             * protocol.
             */

            push_number(udp_port);
            break;
          }

        XCASE(F_QUERY_INPUT_PENDING); /* --- esc query_input_pending --- */
          {
            /* XEFUN query_input_pending()
             *
             *   object query_input_pending(object ob)
             *
             * If ob is interactive and currently has an input_to() pending,
             * the object that has called the input_to() is returned,
             * else 0.
             */

            object_t *ob, *cb;
            interactive_t *ip;

            TYPE_TEST1(sp, T_OBJECT)

            ob = sp->u.ob;
            if (O_SET_INTERACTIVE(ip, ob) && ip->input_to)
            {
                cb = callback_object(&(ip->input_to->fun));
                if (cb)
                    sp->u.ob = ref_object(cb, "query_input_pending");
                else
                    put_number(sp, 0);
            }
            else
            {
                put_number(sp, 0);
            }

            deref_object(ob, "query_input_pending");
            break;
          }

        XCASE(F_QUERY_IP_NAME);     /* --- esc query_ip_name   --- */
          {
            /* XEFUN query_ip_name()
             *
             *   string query_ip_name(object ob)
             *
             * Give the ip-name for user the current user or for the optional
             * argument ob. An asynchronous process 'hname' is used to find
             * out these names in parallel. If there are any failures to find
             * the ip-name, then the ip-number is returned instead.
             */

            inter_pc = pc;
            sp = query_ip_name(sp, MY_TRUE);
            break;
          }

        XCASE(F_QUERY_IP_NUMBER);   /* --- esc query_ip_number --- */
          {
            /* XEFUN query_ip_number()
             *
             *   string query_ip_number(object  ob)
             *   string query_ip_number(mixed & ob)
             *
             * Give the ip-number for the current user or the optional
             * argument ob.
             *
             * If ob is given as reference (and it must be a valid object
             * then), it will upon return be set to the struct sockaddr_in of
             * the queried object, represented by an array of integers, one
             * integer per address byte:
             *   ob[0.. 1]: sin_family
             *   ob[2.. 3]: sin_port
             *   ob[4.. 7]: sin_addr
             *   ob[8..15]: undefined.
             */

            inter_pc = pc;
            sp = query_ip_name(sp, MY_FALSE);
            break;
          }

        XCASE(F_QUERY_MUD_PORT);    /* --- esc query_mud_port  --- */
          {
            /* XEFUN query_mud_port()
             *
             *   int query_mud_port(void)
             *   int query_mud_port(object user)
             *   int query_mud_port(int num)
             *
             * Returns the port number the parser uses for user connections.
             *
             * If no argument is given, the port for this_player() is
             * returned. If this_player() is not existing or not interactive,
             * the first port number open for connections is returned.
             *
             * If an user object is given, the port used for its connection is
             * returned.
             * If a positive number is given, the <num>th port number the
             * parser uses for connections is returned (given that there are
             * that many ports).
             * If -1 is given, the number of ports open for connections is
             * returned.
             */

            inter_pc = pc;
            sp = query_ip_port(sp);
            break;
          }

        /* --- XEfuns: Driver and System --- */

        XCASE(F_GARBAGE_COLLECTION);  /* --- esc garbage_collection --- */
          {
            /* XEFUN garbage_collection()
             *
             *   void garbage_collection(void)
             *   void garbage_collection(string filename)
             *
             * Tell the parser to initiate a garbage collection after the
             * current execution ended.
             */

            GET_NUM_ARG
            if (num_arg)
            {
                if (sp->type != T_STRING)
                    goto xbad_arg_1;
#if defined(GC_SUPPORT)
                {
                    int fd;
                    char * path;

                    restore_default_gc_log();
                    path = check_valid_path( sp->u.string, current_object
                                           , "garbage_collection", MY_TRUE);
                    if (path == NULL)
                    {
                        ERRORF(("Illegal arg 1 to garbage_collection(): "
                                "No privilege to write file '%s'.\n"
                               , sp->u.string
                              ));
                        /* NOTREACHED */
                        break;
                    }
                    fd = ixopen3(path, O_CREAT|O_APPEND|O_WRONLY, 0640);
                    if (fd < 0)
                    {
                        ERRORF(("Can't open GC log file '%s': errno %d\n"
                               , path, errno));
                        /* NOTREACHED */
                        break;
                    }
                    gcollect_outfd = fd;
                }
#endif
                free_svalue(sp); sp--;
            }

            extra_jobs_to_do = MY_TRUE;
            gc_request = gcEfun;
            break;
          }

        /* --- XEfuns: Inventories */

        XCASE(F_ALL_ENVIRONMENT); /* --- esc all_environment <nargs> --- */
        {
            /* XEFUN all_environment()
             *
             *   object *all_environment()
             *   object *all_environment(object o)
             *
             * Returns an array with all environments object <o> is in. If <o>
             * is omitted, the environments of the current object is returned.
             *
             * If <o> has no environment, or if <o> is destructed, 0 is
             * returned.
             */

            GET_NUM_ARG
            if (num_arg
             && sp->type != T_OBJECT
             && (sp->type != T_NUMBER || sp->u.number != 0)
               ) goto xbad_arg_1;
            if (!num_arg || sp->type != T_NUMBER)
            {
                /* Not a destructed object given: do the call */
                inter_sp = sp;
                sp = x_all_environment(sp, num_arg);
            } /* else: the 0 from the destructed object is also the result */
            break;
        }

        /* --- Optional XEfuns --- */

#ifdef F_COPY_MAPPING
        XCASE(F_COPY_MAPPING);      /* --- esc copy_mapping    --- */
          {
            /* XEFUN copy_mapping()
             *
             *   mapping copy_mapping(mapping)
             *
             * This efun is needed to create copies of mappings instead of
             * just passing a reference, like adding/subtraction from a
             * mapping do.
             * TODO: This efun is outdated by the copy() efun.
             */

            mapping_t *m, *m2;

            TYPE_TEST1(sp, T_MAPPING)
            m = sp->u.map;
            check_map_for_destr(m);
            m2 = copy_mapping(m);
            free_mapping(m);
            sp->u.map = m2;
            break;
          }
#endif

#ifdef F_EXTRACT
          /* TODO: Get rid of efun extract() altogether */
        XCASE(F_EXTRACT2);          /* --- esc extract2        --- */
          {
            /* Compute the range sp[0]..end from string/array sp[-1]
             * and leave it on the stack. If sp[0] is negative, it is
             * counted from the end of the string/array.
             *
             * The compiler generates this if the efun extract() is called
             * with just two arguments.
             * TODO: Get rid of efun extract() .
             */

            long len, from;
            svalue_t *arg;

            arg = sp - 1;

            if (arg->type == T_STRING)
            {
                /* Slice an array */
                char *res;

                len = (long)_svalue_strlen(&arg[0]);
                if ((arg+1)->type != T_NUMBER)
                {
                    ERRORF(("Index value must be a number.\n"));
                    /* NOTREACHED */
                    return MY_FALSE; /* Flow control hint */
                }
                from = arg[1].u.number;
                sp--;
                if (from < 0) {
                    from = len + from;
                    if (from < 0)
                        from = 0;
                }
                if (from >= len) {
                    pop_stack();
                    push_volatile_string("");
                    break;
                }
                res = string_copy(arg->u.string + from);
                free_string_svalue(sp);
                put_malloced_string(sp, res);
                break;
            }

            if (arg->type != T_POINTER)
            {
                ERRORF(("Indexed value is neither string nor array.\n"));
                /* NOTREACHED */
                return MY_FALSE; /* Flow control hint */
            }

            /* Slice an array */
            {
                vector_t *v, *res;

                if ((arg+1)->type != T_NUMBER)
                {
                    ERRORF(("Index value must be a number.\n"));
                    /* NOTREACHED */
                    return MY_FALSE; /* Flow control hint */
                }
                v = arg->u.vec;
                len = (long)VEC_SIZE(v);
                from = arg[1].u.number;
                sp--;
                if (from < 0) {
                    from = len + from;
                }
                res = slice_array(v, from, len-1);
                free_array(v);
                put_array(sp,res);
                break;
            }
          }

        XCASE(F_EXTRACT1);          /* --- esc extract1        --- */
          {
            /* XEFUN extract1()
             *
             *   string extract(string arg)
             *
             * Generated by the compiler when it finds efun extract()
             * used with just the string, this efun returns the string.
             */

            if (sp->type != T_STRING)
                goto xbad_arg_1;

            break;
          }

        XCASE(F_EXTRACT);           /* --- esc extract         --- */
          {
            /* XEFUN extract()
             *
             *   string  extract(string str, int from, int to)
             *   string  extract(string str, int from)
             *   mixed * extract(mixed * arr, int from, int to)
             *   mixed * extract(mixed * arr, int from)
             *
             * Extract a substring from a string, resp. a subarray
             * from an array.
             *
             * This is the old notation for str[from..to] and supported
             * only for hysterical raisins. The distinctive point is that
             * negative values for from and to implement the from-the-end
             * indexing.
             */

            if (sp[-1].type != T_NUMBER) goto xbad_arg_2;
            if (sp[0].type != T_NUMBER) goto xbad_arg_3;

            if (sp[-2].type == T_POINTER)
            {
                /* Extract from an array */

                vector_t *v;
                mp_int end, size;

                v = sp[-2].u.vec;
                v =
                  slice_array(
                    v,
                    sp[-1].u.number,
                    (end = sp[0].u.number) >= (size = (mp_int)VEC_SIZE(v)) ?
                      size - 1 :
                      end
                  );
                pop_n_elems(3);
                if (v)
                {
                    push_referenced_vector(v);
                }
                else
                {
                    push_number(0);
                }
            }
            else if (sp[-2].type == T_STRING)
            {
                /* Extract from a string */

                long len, from, to;
                char *res;

                len = (long)_svalue_strlen(&sp[-2]);
                from = sp[-1].u.number;
                if (from < 0) {
                    from = len + from;
                    if (from < 0)
                        from = 0;
                }
                to = sp[0].u.number;
                if (to < 0)
                    to = len + to;
                if (to >= len)
                    to = len-1;
                if (to < from) {
                    pop_n_elems(3);
                    push_volatile_string("");
                    break;
                }
                if (to == len-1) {
                    res = string_copy(sp[-2].u.string + from);
                    pop_n_elems(3);
                    push_malloced_string(res);
                    break;
                }
                res = xalloc((size_t)(to - from + 2));
                strncpy(res, sp[-2].u.string + from, (size_t)(to - from + 1));
                res[to - from + 1] = '\0';
                pop_n_elems(3);
                push_malloced_string(res);
            }
            else
            {
                goto xbad_arg_1;
            }
            break;
          }
#endif

#ifdef F_SWAP
        XCASE(F_SWAP);              /* --- esc swap            --- */
          {
            /* XEFUN swap()
             *
             *   void swap(object obj)
             *
             * Swap out an object. This efun is only used for system internal
             * debugging and can cause a crash.
             */

            object_t *ob;

            if (sp->type != T_OBJECT) goto xbad_arg_1;
            ob = sp->u.ob;
            if (ob != current_object) /* should also check csp */
            {
                if (!O_PROG_SWAPPED(ob))
                    (void)swap_program(ob);
                if (!O_VAR_SWAPPED(ob))
                    (void)swap_variables(ob);
            }
            pop_stack();
            break;
          }
#endif

        } /* switch(code) */
        break;
      } /* end of F_ESCAPE */

    } /* end of the monumental switch */

    /* Instruction executed */

    /* Even intermediate results could exceed the stack size.
     * We better check for that.
     */
    if (sp - VALUE_STACK == SIZEOF_STACK - 1)
    {
        /* sp ist just at then end of the stack area */
        stack_overflow(sp, fp, pc);
    }
    else if ((long)(sp - VALUE_STACK) > (long)(SIZEOF_STACK - 1))
    {
        /* When we come here, we already overwrote the bounds
         * of the stack :-(
         */
        fatal("Fatal stack overflow: %ld too high\n"
             , (long)(sp - VALUE_STACK - (SIZEOF_STACK - 1))
             );
    }

#ifdef DEBUG
    if (expected_stack && expected_stack != sp)
    {
        fatal( "Bad stack after evaluation.\n"
               "sp: %lx expected: %lx\n"
               "Instruction %d(%s), num arg %d\n"
             , (long)sp, (long)expected_stack
             , instruction, get_f_name(instruction), num_arg);
    }

    if (sp < fp + csp->num_local_variables - 1)
    {
        fatal( "Bad stack after evaluation.\n"
               "sp: %lx minimum expected: %lx\n"
               "Instruction %d(%s), num arg %d\n"
             , (long)sp, (long)(fp + csp->num_local_variables - 1)
             , instruction, get_f_name(instruction), num_arg);
    }
#endif /* DEBUG */

    /* Execute the next instruction */

    goto again;

    /* Get rid of the handy but highly local macros */
#   undef GET_NUM_ARG
#   undef TYPE_TEST1
#   undef TYPE_TEST2
#   undef TYPE_TEST3
#   undef TYPE_TEST4
#   undef CASE
} /* eval_instruction() */

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

/* These macros are no longer needed:
 */
#undef push_malloced_string
#undef push_number

/*-------------------------------------------------------------------------*/
static Bool
apply_low (char *fun, object_t *ob, int num_arg, Bool b_ign_prot)

/* The low-level implementation of function calls.
 *
 * Call function <fun> in <ob>ject with <num_arg> arguments pushed
 * onto the stack (<inter_sp> points to the last one). static and protected
 * functions can't be called from the outside unless <b_ign_prot> is true.
 * apply_low() takes care of calling shadows where necessary.
 *
 * When apply_low() returns true, the call was successful, the arguments
 * one the stack have been popped and replaced with the result. But note
 * that <ob> might have been destructed during the call.
 *
 * If apply_low() returns false, the function was not found and the arguments
 * must be removed by the caller. One reason for failure can be an attempt
 * to call an inherited function '::foo' with this function.
 *
 * To speed up the calls, apply_low() maintains a cache of earlier calls, both
 * hits and misses.
 *
 * The function call will swap in the object and also unset its reset status.
 */

{
    program_t *progp;
    struct control_stack *save_csp;
    p_int ix;

    /* This object will now be used, and is thus a target for
     * reset later on (when time due).
     */
    ob->flags &= ~O_RESET_STATE;

#ifdef DEBUG
    if (num_error > 2) {
        fatal("apply_low with too many errors.\n");
        goto failure;
    }
#endif

    /* If there is a chain of objects shadowing, start with the first
     * of these.
     */
    if (ob->flags & O_SHADOW)
    {
        object_t *shadow;

        while (NULL != (shadow = O_GET_SHADOW(ob)->shadowed_by)
            && shadow != current_object)
        {
            ob = shadow;
        }
    }

retry_for_shadow:

    ob->time_of_ref = current_time;

    /* Load the object from swap */
    if (ob->flags & O_SWAPPED)
    {
        if (load_ob_from_swap(ob) < 0)
            error("Out of memory\n");
    }

    progp = ob->prog;

#ifdef DEBUG
    if (ob->flags & O_DESTRUCTED)
        fatal("apply() on destructed object '%s' function '%s'\n"
             , ob->name != NULL ? ob->name : "<null>"
             , fun != NULL ? fun : "<null>"
             );
#endif

    /* Lookup the shared string for <fun>. Function names are always
     * shared, so if there is no shared string twin for <fun>, the
     * called function does not exist anywhere. As a side effect,
     * we get the shared string for <fun> which makes the cache-lookup
     * much faster.
     */
    fun = findstring(fun);
    if (!fun)
        goto failure2;

    /* *fun is now guaranteed to be a shared string */

    /* Get the hashed index into the cache */
    ix =
      ( progp->id_number ^ (p_int)fun ^ ( (p_int)fun >> APPLY_CACHE_BITS ) )
         & (CACHE_SIZE-1);

    /* Check if we have an entry for this function call */
    if (cache[ix].id == progp->id_number
     && (cache[ix].name == fun || !strcmp(cache[ix].name, fun))
       )
    {
        /* We have found a matching entry in the cache. The contents have
         * to match, not only the pointers, because cache entries for
         * functions not existant in _this_ object <ob> are stored as
         * separately allocated copy, not as another ref to the shared
         * string. Yet they shall be found here.
         */
#ifdef APPLY_CACHE_STAT
        apply_cache_hit++;
#endif
        if (cache[ix].progp
          /* Static functions may not be called from outside. */
          && (   !cache[ix].flags
              || b_ign_prot
              || (   !(cache[ix].flags & TYPE_MOD_PROTECTED)
                  && current_object == ob))
           )
        {
            /* the cache will tell us in wich program the function is, and
             * where.
             */
            fun_hdr_p funstart;

            push_control_stack(inter_sp, inter_pc, inter_fp);
            csp->ob = current_object;
            csp->prev_ob = previous_ob;
            csp->num_local_variables = num_arg;
            csp->funstart = funstart = cache[ix].funstart;
            current_prog = cache[ix].progp;
            current_strings = current_prog->strings;
            function_index_offset = cache[ix].function_index_offset;
#ifdef DEBUG
            if (!ob->variables && cache[ix].variable_index_offset)
                fatal("%s Fatal: apply (cached) for object %p '%s' "
                      "w/o variables, but offset %d\n"
                     , time_stamp(), ob, ob->name
                     , cache[ix].variable_index_offset);
#endif
            current_variables = ob->variables;
            if (current_variables)
                current_variables += cache[ix].variable_index_offset;
            inter_sp = setup_new_frame2(funstart, inter_sp, MY_FALSE, MY_FALSE);
            previous_ob = current_object;
            current_object = ob;
            save_csp = csp;
            eval_instruction(FUNCTION_CODE(funstart), inter_sp);
#ifdef DEBUG
            if (save_csp-1 != csp)
                fatal("Bad csp after execution in apply_low\n");
#endif
            /* Arguments and local variables are now removed. One
             * resulting value is always returned on the stack.
             */
            return MY_TRUE;
        }

        /* when we come here, the cache has told us that the function isn't
         * defined in the object
         */
    }
    else
    {
        /* we have to search the function */
        char *shared_name;

#ifdef APPLY_CACHE_STAT
        apply_cache_miss++;
#endif

        shared_name = fun;
        if ( NULL != shared_name)
        {
            int fx;

            /* Yup, fun is a function _somewhere_ */

            eval_cost++;
            fx = find_function(shared_name, progp);
            if (fx >= 0)
            {
                /* Found the function - setup the control stack and
                 * create a new cache entry.
                 */

                funflag_t flags;
                fun_hdr_p funstart;

                push_control_stack(inter_sp, inter_pc, inter_fp);
                  /* if an error occurs here, it won't leave the cache in an
                   * inconsistent state.
                   */
                csp->ob = current_object;
                csp->prev_ob = previous_ob;
                if (!cache[ix].progp)
                {
                    /* The old cache entry was for an undefined function,
                     * so the name had to be malloced.
                     */
                    xfree(cache[ix].name);
                }

                cache[ix].id = progp->id_number;
                cache[ix].name = shared_name;
                csp->num_local_variables = num_arg;
                current_prog = progp;
                flags = setup_new_frame1(fx, 0, 0);
                current_strings = current_prog->strings;
                cache[ix].progp = current_prog;
                cache[ix].function_index_offset = function_index_offset;
                cache[ix].variable_index_offset = variable_index_offset;
#ifdef DEBUG
                if (!ob->variables && variable_index_offset)
                    fatal("%s Fatal: apply for object %p '%s' w/o variables, "
                          "but offset %d\n"
                         , time_stamp(), ob, ob->name, variable_index_offset);
#endif
                current_variables = ob->variables;
                if (current_variables)
                    current_variables += variable_index_offset;
                funstart = current_prog->program + (flags & FUNSTART_MASK);
                cache[ix].funstart = funstart;
                cache[ix].flags = progp->functions[fx]
                                  & (TYPE_MOD_STATIC|TYPE_MOD_PROTECTED);

                /* Static functions may not be called from outside,
                 * Protected functions not even from the inside.
                 */
                if (0 != cache[ix].flags
                  && (   (cache[ix].flags & TYPE_MOD_PROTECTED)
                      || current_object != ob)
                  && !b_ign_prot
                    )
                {
                    /* Not found */

                    previous_ob = csp->prev_ob;
                    current_object = csp->ob;
                    pop_control_stack();
                    if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing)
                    {
                        /* This is an object shadowing another. The function
                         * was not found, but can maybe be found in the object
                         * we are shadowing.
                         */
                        ob = O_GET_SHADOW(ob)->shadowing;
                        goto retry_for_shadow;
                    }
                    else
                        goto failure;
                }
                csp->funstart = funstart;
                inter_sp = setup_new_frame2(funstart, inter_sp, MY_FALSE, MY_FALSE);
                previous_ob = current_object;
                current_object = ob;
                save_csp = csp;
                eval_instruction(FUNCTION_CODE(funstart), inter_sp);
#ifdef DEBUG
                if (save_csp-1 != csp)
                    fatal("Bad csp after execution in apply_low\n");
#endif
                /* Arguments and local variables are now removed. One
                 * resulting value is always returned on the stack.
                 */
                return MY_TRUE;
            } /* end if (fx >= 0) */
        } /* end if(shared_name) */

        /* We have to mark this function as non-existant in this object. */

        if (!cache[ix].progp)
        {
            /* The old cache entry was for an undefined function, so the
               name had to be malloced */
            xfree(cache[ix].name);
        }

        cache[ix].id = progp->id_number;
        cache[ix].name = string_copy(fun);
        cache[ix].progp = NULL;
    }

    /* At this point, the function was not found in the object. But
     * maybe this object is a shadow and we find the function in the
     * shadowed object.
     */

    if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing)
    {
        ob = O_GET_SHADOW(ob)->shadowing;
        goto retry_for_shadow;
    }

failure:
    if (fun[0] == ':')
        error("Illegal function call\n");

failure2:
    /* Failure. Deallocate stack. */
    return MY_FALSE;
} /* apply_low() */

/*-------------------------------------------------------------------------*/
void
push_apply_value (void)

/* Push the current <apply_return_value> onto the stack, <apply_return_value>
 * itself free afterwards.
 */

{
    *++inter_sp = apply_return_value;
    apply_return_value.type = T_NUMBER;
}

/*-------------------------------------------------------------------------*/
void
pop_apply_value (void)

/* Pop the current value on the stack into <apply_return_value>, after
 * freeing the latter of course.
 */

{
    free_svalue(&apply_return_value);
    apply_return_value = *inter_sp--;
}

/*-------------------------------------------------------------------------*/
svalue_t *
sapply_int (char *fun, object_t *ob, int num_arg, Bool b_find_static)

/* Call function <fun> in <ob>ject with <num_arg> arguments pushed
 * onto the stack (<inter_sp> points to the last one). static and protected
 * functions can't be called from the outside unless <b_find_static> is true.
 * sapply() takes care of calling shadows where necessary.
 *
 * sapply() returns a pointer to the function result when the call was
 * successfull, or NULL on failure. The arguments are popped in any case.
 * The result pointer, if returned, points to a static area which will be
 * overwritten with the next sapply().
 *
 * The function call will swap in the object and also unset its reset status.
 *
 * interpret.h defines the macro sapply(fun,ob,num_arg) for the most
 * common call with b_find_static passed as false.
 */

{
#ifdef DEBUG
    svalue_t *expected_sp;
#endif

    /* Handle tracing */
    if (TRACEP(TRACE_APPLY) && TRACE_IS_INTERACTIVE())
    {
        if (!++traceing_recursion)
        {
            do_trace("Apply", "", "\n");
        }
        traceing_recursion--;
    }

#ifdef DEBUG
    expected_sp = inter_sp - num_arg;
#endif

    /* Do the call */
    if (!apply_low(fun, ob, num_arg, b_find_static))
    {
        inter_sp = _pop_n_elems(num_arg, inter_sp);
        return NULL;
    }
    transfer_svalue(&apply_return_value, inter_sp);
    inter_sp--;

#ifdef DEBUG
    if (expected_sp != inter_sp)
        fatal("Corrupt stack pointer.\n");
#endif

    return &apply_return_value;
} /* sapply_int() */

/*-------------------------------------------------------------------------*/
svalue_t *
apply (char *fun, object_t *ob, int num_arg)

/* Call function <fun> in <ob>ject with <num_arg> arguments pushed
 * onto the stack (<inter_sp> points to the last one). static and protected
 * functions can't be called from the outside.
 * apply() takes care of calling shadows where necessary.
 *
 * apply() returns a pointer to the function result when the call was
 * successfull, or NULL on failure. The arguments are popped in any case.
 * The result pointer, if returned, points to a static area which will be
 * overwritten with the next apply().
 *
 * The function call will swap in the object and also unset its reset status.
 *
 * The big difference between apply() and sapply() is that apply() sets
 * the tracedepth to 0 before calling the function.
 */

{
    tracedepth = 0;
    return sapply_int(fun, ob, num_arg, MY_FALSE);
}

/*-------------------------------------------------------------------------*/
static void
secure_apply_error ( svalue_t *save_sp, struct control_stack *save_csp
                   , Bool clear_costs)

/* Recover from an error during a secure apply. <save_sp> and <save_csp>
 * are the saved evaluator stack and control stack pointers, saving the
 * state from when secure_apply() was entered.
 *
 * The function pops all the arguments for the call from the stack, and
 * then calls runtime_error() in the master object with the necessary
 * information, unless it is a triple fault - in that case only a
 * debug_message() is generated.
 *
 * If <clear_costs> is TRUE, the eval costs and limits will be reset
 * before runtime_error() is called. This is used for top-level master
 * applies which should behave like normal function calls in the error
 * handling.
 */

{
    if (csp != save_csp)
    {
        /* Could be error before push.
         * We have to unroll the control stack in case it references
         * lambda closures.
         */

        while (csp > save_csp+1)
            pop_control_stack();

        previous_ob = csp->prev_ob;
        current_object = csp->ob;
        pop_control_stack();
    }

    inter_sp = _pop_n_elems (inter_sp - save_sp, inter_sp);

    if (num_error == 3)
    {
        if (!out_of_memory)
        {
            debug_message("%s Master failure: %s", time_stamp(), current_error);
            xfree(current_error);
            xfree(current_error_file);
            xfree(current_error_object_name);
            if (current_error_trace)
            {
                free_array(current_error_trace);
                current_error_trace = NULL;
            }
            if (uncaught_error_trace)
            {
                free_array(uncaught_error_trace);
                uncaught_error_trace = NULL;
            }

        }
    }
    else if (!out_of_memory)
    {
        int a;
        object_t *save_cmd;

        push_malloced_string(current_error);
        a = 1;
        if (current_error_file)
        {
            push_malloced_string(current_error_file);
            push_malloced_string(current_error_object_name);
            push_number(current_error_line_number);
            a += 3;
        }

        if (current_heart_beat)
        {
            /* Heartbeat error (unlikely though): turn off the heartbeat in
             * the object and also pass it to RUNTIME_ERROR.
             */

            object_t *culprit;

            culprit = current_heart_beat;
            current_heart_beat = NULL;
            set_heart_beat(culprit, 0);
            debug_message("%s Heart beat in %s turned off.\n"
                         , time_stamp(), culprit->name);
            inter_sp = _push_valid_ob(culprit, inter_sp);
            a++;
        }
        else
        {
            if (!current_error_file)
            {
                /* Pass dummy values */
                push_number(0);
                push_number(0);
                push_number(0);
                a += 3;
            }
            /* Normal error: push -1 instead of a culprit. */
            push_number(-1);
            a++;
        }

        if (clear_costs)
        {
            CLEAR_EVAL_COST;
            RESET_LIMITS;
        }

        save_cmd = command_giver;
        apply_master(STR_RUNTIME, a);
        command_giver = save_cmd;
    }
    num_error--;

} /* secure_apply_error() */

/*-------------------------------------------------------------------------*/
svalue_t *
secure_apply (char *fun, object_t *ob, int num_arg)

/* Call function <fun> in <ob>ject with <num_arg> arguments pushed
 * onto the stack (<inter_sp> points to the last one). static and protected
 * functions can't be called from the outside.
 * secure_apply() takes care of calling shadows where necessary.
 *
 * secure_apply() returns a pointer to the function result when the call was
 * successfull, or NULL on failure. The arguments are popped in any case.
 * The result pointer, if returned, points to a static area which will be
 * overwritten with the next secure_apply().
 *
 * The function call will swap in the object and also unset its reset status.
 *
 * Errors during the execution are caught (this is the big difference
 * to sapply()/apply()) and cause secure_apply() to return NULL.
 */

{
    struct error_recovery_info error_recovery_info;
    svalue_t *save_sp;
    struct control_stack *save_csp;
    svalue_t *result;

    if (ob->flags & O_DESTRUCTED)
        return NULL;

    error_recovery_info.rt.last = rt_context;
    error_recovery_info.rt.type = ERROR_RECOVERY_APPLY;
    rt_context = (rt_context_t *)&error_recovery_info;
    save_sp = inter_sp;
    save_csp = csp;
    if (setjmp(error_recovery_info.con.text))
    {
        secure_apply_error(save_sp - num_arg, save_csp, MY_FALSE);
        result = NULL;
    }
    else
    {
        result = sapply(fun, ob, num_arg);
    }
    rt_context = error_recovery_info.rt.last;
    return result;
} /* secure_apply() */

/*-------------------------------------------------------------------------*/
svalue_t *
apply_master_ob (char *fun, int num_arg, Bool external)

/* Aliases:
 *   apply_master(fun, num_arg) == apply_master_ob(fun, num_arg, FALSE)
 *   callback_master(fun, num_arg) == apply_master_ob(fun, num_arg, TRUE)
 *
 * Call function <fun> in the master object with <num_arg> arguments pushed
 * onto the stack (<inter_sp> points to the last one). static and protected
 * functions can be called from the outside. The function takes care
 * of calling shadows where necessary.
 *
 * If <external> is TRUE, it means that this call is due to some external
 * event (like an ERQ message) instead of being caused by a running program.
 * The effect of this flag is that the error handling is like for a normal
 * function call (clearing the eval costs before calling runtime_error()).
 *
 * apply_master_object() returns a pointer to the function result when the
 * call was successfull, or NULL on failure. The arguments are popped in
 * any case.
 * The result pointer, if returned, points to a static area which will be
 * overwritten with the next apply_master_object().
 *
 * The function makes sure that there is a master object to be called. If
 * necessary, a new one is compiled or, failing that, an old one is
 * reactivated.
 *
 * Errors during the execution are caught and case the function to
 * return NULL.
 *
 * The function operates on an execution tick reserve of MASTER_RESERVED_COST
 * which is used then the normal evaluation cost is already too high.
 */

{
    static int eval_cost_reserve = MASTER_RESERVED_COST;
      /* Available eval_cost reserver. If needed, the reserve is halved
       * for the duration of the apply to establish a protection against
       * an endless recursion of master calls.
       */

    volatile Bool reserve_used = MY_FALSE;

    struct error_recovery_info error_recovery_info;
    svalue_t *save_sp;
    struct control_stack *save_csp;
    svalue_t *result;

    /* Get the master object. */
    assert_master_ob_loaded();

    /* Tap into the eval_cost reserve if the end is near */
    if (   (max_eval_cost && eval_cost > max_eval_cost - MASTER_RESERVED_COST)
        && eval_cost_reserve > 1)
    {
        eval_cost -= eval_cost_reserve;
        assigned_eval_cost -= eval_cost_reserve;
        eval_cost_reserve >>= 1;
        reserve_used = MY_TRUE;
    }

    /* Setup the the error recovery and call the function */
    error_recovery_info.rt.last = rt_context;
    error_recovery_info.rt.type = ERROR_RECOVERY_APPLY;
    rt_context = (rt_context_t *)&error_recovery_info;
    save_sp = inter_sp;
    save_csp = csp;
    if (setjmp(error_recovery_info.con.text))
    {
        secure_apply_error(save_sp - num_arg, save_csp, external);
        printf("%s Error in master_ob->%s()\n", time_stamp(), fun);
        debug_message("%s Error in master_ob->%s()\n", time_stamp(), fun);
        result = NULL;
    }
    else
    {
        result = sapply_int(fun, master_ob, num_arg, MY_TRUE);
    }

    /* Free the reserve if we used it */
    if (reserve_used)
    {
        eval_cost_reserve <<= 1;
        assigned_eval_cost = eval_cost += eval_cost_reserve;
    }
    rt_context = error_recovery_info.rt.last;

    return result;
} /* apply_master_ob() */

/*-------------------------------------------------------------------------*/
void
assert_master_ob_loaded (void)

/* Make sure that there is a master object <master_ob>.
 * If necessary, a new master is compiled, or, failing that, an old
 * destructed one is reactivated. If everything fails, the driver exits.
 *
 * Note that the function may be called recursively:
 *  - While calling a master function from yyparse() (e.g. log_error()),
 *    the master self-destructs and then causes an error.
 *  - Another possibility is that some driver hook invokes some
 *    function that uses apply_master_ob().
 *  - The master object might have been reloaded without noticing that
 *    it is the master. This could happen when there already was a call to
 *    assert_master_ob_loaded(), clearing master_ob, and the master
 *    inherits itself. Partial working self-inheritance is possible if
 *    the H_INCLUDE_DIRS hook does something strange.
 */

{
    static Bool inside = MY_FALSE;
      /* Flag to notice recursive calls */

    static object_t *destructed_master_ob = NULL;
      /* Old, destructed master object */

    int i;

    if (!master_ob || master_ob->flags & O_DESTRUCTED)
    {
        /* The master object has been destructed. Free our reference,
         * and load a new one.
         */
        if (inside || !master_ob)
        {
            object_t *ob;
            object_t *prev;
            Bool newly_removed = MY_FALSE;
              /* TRUE if the old master was on the list of newly
               * destructed objects. That is important to know
               * because then it still has all its variables.
               */

            /* A recursive call while loading the master, or there
             * was no master to begin with.
             * If there is a destructed master, reactivate that
             * one, else stop the driver.
             */

            if (!destructed_master_ob)
            {
                fprintf(stderr, "%s Failed to load master object '%s'.\n"
                              , time_stamp(), master_name);
                add_message("Failed to load master object '%s'!\n"
                           , master_name);
                exit(1);
            }

            /* If we come here, we had a destructed master and failed
             * to load a new one. Now try to reactivate the
             * old one again.
             *
             * We don't have to reactivate any destructed inherits, though:
             * as long as the master references their programs, that's all
             * we need.
             */

            /* First, make sure that there is no half-done object
             * using the masters name.
             */
            if ( NULL != (ob = find_object(master_name)) )
            {
                destruct(ob);
            }

            /* Get the destructed master */
            ob = destructed_master_ob;
            destructed_master_ob = NULL;

            /* Remove the destructed master from the list
             * of newly destructed objects or destructed objects.
             */
            if (newly_destructed_objs != NULL)
            {
                if (ob == newly_destructed_objs)
                {
                    newly_destructed_objs = ob->next_all;
                    newly_removed = MY_TRUE;
                    num_newly_destructed--;
                }
                else
                {
                    for ( prev = newly_destructed_objs
                        ; prev && prev->next_all != ob
                        ; prev = prev->next_all
                        ) NOOP;
                    if (prev)
                    {
                        prev->next_all = ob->next_all;
                        newly_removed = MY_TRUE;
                        num_newly_destructed--;
                    }
                }
            }
            if (!newly_removed && destructed_objs != NULL)
            {
                if (ob == destructed_objs)
                {
                    destructed_objs = ob->next_all;
                    if (destructed_objs)
                        destructed_objs->prev_all = NULL;
                    num_destructed--;
                }
                else
                {
                    for ( prev = destructed_objs
                        ; prev && prev->next_all != ob
                        ; prev = prev->next_all
                        ) NOOP;
                    if (prev)
                    {
                        prev->next_all = ob->next_all;
                        if (prev->next_all)
                            prev->next_all->prev_all = prev;
                        num_destructed--;
                    }
                }
            }
            ob->flags &= ~O_DESTRUCTED;

            /* Restore the old masters variable space.
             * Remember: as long as the objects are in the 'newly destructed'
             * list, they still have all variables.
             */
            if (!newly_removed && ob->prog->num_variables)
            {
                int save_privilege = malloc_privilege;
                int j;
                svalue_t *v;

                malloc_privilege = MALLOC_SYSTEM;
                ob->variables = v = (svalue_t *)
                    xalloc(sizeof *v * ob->prog->num_variables);
                malloc_privilege = save_privilege;
                for (j = ob->prog->num_variables; --j >= 0; )
                    *v++ = const0;
            }

            /* Reenter the object into the various lists */
            enter_object_hash(ob);
            ob->next_all = obj_list;
            ob->prev_all = NULL;
            if (obj_list)
                obj_list->prev_all = ob;
            obj_list = ob;
            if (!obj_list_end)
                obj_list_end = ob;
            num_listed_objs++;
            ob->super = NULL;
            ob->contains = NULL;
            ob->next_inv = NULL;

            /* Reactivate the old master */
            master_ob = ref_object(ob, "assert_master_ob_loaded");
            if (current_object == &dummy_current_object_for_loads)
                current_object = master_ob;
            push_number(newly_removed);
            sapply_int(STR_REACTIVATE, ob, 1, MY_TRUE);
            push_number(2 - (newly_removed ? 1 : 0));
            sapply_int(STR_INAUGURATE, ob, 1, MY_TRUE);
            fprintf(stderr, "%s Old master reactivated.\n", time_stamp());
            inside = MY_FALSE;
            return;

        } /* if (inside || !master_obj) */

        /* A normal call to assert_master_ob_loaded: just load a new one */

        fprintf(stderr, "%s assert_master_ob_loaded: Reloading master '%s'\n"
               , time_stamp(), master_name);
        destructed_master_ob = master_ob;

        /* Clear the pointer, in case the load failed.
         */
        master_ob = NULL;
        inside = MY_TRUE;
        if (!current_object)
        {
            current_object = &dummy_current_object_for_loads;
        }

#ifdef USE_FREE_CLOSURE_HOOK
        /* don't free the closure hooks now since they might be
         * still in use - the backend will take care of them.
         */
        free_closure_hooks(driver_hook, NUM_DRIVER_HOOKS);
        for (i = NUM_DRIVER_HOOKS; i--;)
            driver_hook[i] = const0;
#else
        /* Free the closure hooks.
         */
        for (i = NUM_DRIVER_HOOKS; i--;)
        {
            /* The object reference in bound closures is not counted! */
            if (driver_hook[i].type == T_CLOSURE &&
                driver_hook[i].x.closure_type == CLOSURE_LAMBDA)
            {
                driver_hook[i].x.closure_type = CLOSURE_UNBOUND_LAMBDA;
            }

            assign_svalue(driver_hook+i, &const0);
        }
#endif

        init_telopts();

        master_ob = get_object(master_name);
        if (current_object == &dummy_current_object_for_loads)
        {
            /* This might be due to the above assignment, or to setting
             * it in the backend.
             */
            current_object = master_ob;
        }

        initialize_master_uid();
        push_number(3);
        apply_master(STR_INAUGURATE, 1);
        assert_master_ob_loaded();
          /* ...in case inaugurate_master() destructed this object again */
        inside = MY_FALSE;
        ref_object(master_ob, "assert_master_ob_loaded");

        if (destructed_master_ob)
            free_object(destructed_master_ob, "assert_master_ob_loaded");

        fprintf(stderr, "%s Reloading done.\n", time_stamp());
    }

    /* Master exists. Nothing to see here, move along... */

} /* assert_master_ob_loaded() */

/*-------------------------------------------------------------------------*/
void
call_lambda (svalue_t *lsvp, int num_arg)

/* Call the closure <lsvp> with <num_arg> arguments on the stack. On
 * success, the arguments are replaced with the result, else an error()
 * is generated.
 */

{
#  define CLEAN_CSP \
        previous_ob = csp->prev_ob; \
        current_object = csp->ob; \
        pop_control_stack();
  /* Macro to undo all the call preparations in case the closure
   * can't be called after all.
   */

    svalue_t *sp;
    lambda_t *l = lsvp->u.lambda;

    sp = inter_sp;

    /* Basic setup for the new control frame.
     * If the closure can't be called, all this has to be undone
     * using the macro CLEAN_CSP.
     */
    push_control_stack(sp, inter_pc, inter_fp);
    csp->ob = current_object;
    csp->prev_ob = previous_ob;
    csp->num_local_variables = num_arg;
    previous_ob = current_object;

    switch(lsvp->x.closure_type)
    {

    case CLOSURE_LFUN:  /* --- lfun closure --- */
      {
        funflag_t flags;
        fun_hdr_p funstart;

        /* Can't call from a destructed object */
        if (l->ob->flags & O_DESTRUCTED)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Object '%s' the closure was bound to has been "
                  "destructed\n", l->ob->name);
            /* NOTREACHED */
            return;
        }

        /* Reference the object the lfun is bound to */
        l->ob->time_of_ref = current_time;
        l->ob->flags &= ~O_RESET_STATE;

        current_object = l->ob;

        /* Make the object resident */
        if (current_object->flags & O_SWAPPED
         && load_ob_from_swap(current_object) < 0)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Out of memory\n");
            /* NOTREACHED */
            return;
        }

#ifdef DEBUG
        if (l->function.index >= current_object->prog->num_functions)
            fatal("Calling non-existing lfun closure #%hu in program '%s' "
                  "with %hu functions.\n"
                 , l->function.index
                 , current_object->prog->name
                 , current_object->prog->num_functions
                );
#endif

        /* Ok, object and program are there */

        current_prog = current_object->prog;
        /* inter_sp == sp */
        flags = setup_new_frame(l->function.index);
        funstart = current_prog->program + (flags & FUNSTART_MASK);
        csp->funstart = funstart;
        eval_instruction(FUNCTION_CODE(funstart), inter_sp);
        /* The result is on the stack (inter_sp) */
        return;
      }

    case CLOSURE_ALIEN_LFUN:  /* --- alien lfun closure --- */
      {
        funflag_t flags;
        fun_hdr_p funstart;

        /* Can't call from a destructed object */
        if (l->ob->flags & O_DESTRUCTED)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Object '%s' the closure was bound to has been "
                  "destructed\n", l->ob->name);
            /* NOTREACHED */
            return;
        }

        /* Reference the bound and the originating object */
        l->ob->time_of_ref = current_time;
        l->function.alien.ob->time_of_ref = current_time;
        l->function.alien.ob->flags &= ~O_RESET_STATE;

        current_object = l->ob;

        /* Can't call a function in a destructed object */
        if (l->function.alien.ob->flags & O_DESTRUCTED)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Object '%s' holding the closure has been "
                  "destructed\n", l->function.alien.ob->name);
            /* NOTREACHED */
            return;
        }

        /* Make the objects resident */
        if ( (   current_object->flags & O_SWAPPED
              && load_ob_from_swap(current_object) < 0)
         ||  (   l->function.alien.ob->flags & O_SWAPPED
              && load_ob_from_swap(l->function.alien.ob) < 0)
           )
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Out of memory\n");
            /* NOTREACHED */
            return;
        }

#ifdef DEBUG
        if (l->function.alien.index >= l->function.alien.ob->prog->num_functions)
            fatal("Calling non-existing lfun closure #%hu in program '%s' "
                  "with %hu functions.\n"
                 , l->function.alien.index
                 , l->function.alien.ob->prog->name
                 , l->function.alien.ob->prog->num_functions
                );
#endif

        /* If the object creating the closure wasn't the one in which
         * it will be executed, we need to record the fact in a second
         * 'dummy' control frame. If we didn't, major security holes
         * open up.
         */

        if (l->ob != l->function.alien.ob)
        {
            csp->extern_call = MY_TRUE;
            csp->funstart = NULL;
            push_control_stack(sp, 0, inter_fp);
            csp->ob = current_object;
            csp->prev_ob = previous_ob;
            csp->num_local_variables = num_arg;
            previous_ob = current_object;
        }

        /* Finish the setup of the control frame.
         * This is a real inter-object call.
         */
        csp->extern_call = MY_TRUE;
        current_object = l->function.alien.ob;
        current_prog = current_object->prog;
        /* inter_sp == sp */
        flags = setup_new_frame(l->function.alien.index);
        funstart = current_prog->program + (flags & FUNSTART_MASK);
        csp->funstart = funstart;
        eval_instruction(FUNCTION_CODE(funstart), inter_sp);

        /* If necessary, remove the second control frame */
        if (l->ob != l->function.alien.ob)
        {
            current_object = csp->ob;
            previous_ob = csp->prev_ob;
            pop_control_stack();
        }

        /* The result is on the stack (inter_sp) */
        return;
      }

    case CLOSURE_IDENTIFIER:  /* --- variable closure --- */
      {
        short i; /* the signed variant of lambda_t->function.index */

        CLEAN_CSP  /* no call will be done */
        if (num_arg)
            error("Arguments passed to variable closure.\n");

        /* Don't use variables in a destructed object */
        if (l->ob->flags & O_DESTRUCTED)
        {
            error("Object '%s' the closure was bound to has been destructed\n"
                 , l->ob->name);
            /* NOTREACHED */
            return;
        }

        /* Make the object resident */
        if (   (l->ob->flags & O_SWAPPED)
             && load_ob_from_swap(l->ob) < 0
           )
        {
            error("Out of memory.\n");
            /* NOTREACHED */
            return;
        }

        /* Do we have the variable? */
        if ( (i = (short)l->function.index) < 0)
        {
            error("Variable not inherited\n");
            /* NOTREACHED */
            return;
        }

        l->ob->time_of_ref = current_time;
#ifdef DEBUG
        if (!l->ob->variables)
            fatal("%s Fatal: call_lambda on variable for object %p '%s' "
                  "w/o variables, index %d\n"
                 , time_stamp(), l->ob, l->ob->name, i);
#endif
        assign_svalue_no_free(++sp, &l->ob->variables[i]);
        inter_sp = sp;
        return;
      }

    case CLOSURE_BOUND_LAMBDA:  /* --- bound lambda closure --- */
      {
        lambda_t *l2;

        /* Deref the closure and then treat the resulting unbound
         * lambda like a normal lambda
         */
        l2 = l->function.lambda;
        l2->ob = l->ob;
        l = l2;
      }
      /* FALLTHROUGH */

    case CLOSURE_LAMBDA:
      {
        fun_hdr_p funstart;

        /* Can't call from a destructed object */
        if (l->ob->flags & O_DESTRUCTED)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Object '%s' the closure was bound to has been "
                  "destructed\n", l->ob->name);
            /* NOTREACHED */
            return;
        }

        current_object = l->ob;

        /* Make the object resident */
        if (current_object->flags & O_SWAPPED
         && load_ob_from_swap(current_object) < 0)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Out of memory\n");
            /* NOTREACHED */
            return;
        }

        /* Reference the object */
        current_object->time_of_ref = current_time;
        current_object->flags &= ~O_RESET_STATE;

        /* Finish the setup */

        current_prog = current_object->prog;
        current_lambda = *lsvp; addref_closure(lsvp, "call_lambda");
        variable_index_offset = 0;
        function_index_offset = 0;
        funstart = l->function.code + 1;
        csp->funstart = funstart;
        sp = setup_new_frame2(funstart, sp, MY_FALSE, MY_TRUE);
        current_variables = current_object->variables;
        current_strings = current_prog->strings;
        eval_instruction(FUNCTION_CODE(funstart), sp);
        /* The result is on the stack (inter_sp) */
        return;
      }

    case CLOSURE_UNBOUND_LAMBDA:
    case CLOSURE_PRELIMINARY:
        /* no valid current_object ==> pop the control stack */
        /* inter_sp == sp */
        CLEAN_CSP
        break;

    default: /* --- efun-, simul efun-, operator closure */
      {
        int i;  /* the closure type */

        current_object = lsvp->u.ob;
        /* Can't call from a destructed object */
        if (current_object->flags & O_DESTRUCTED)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Object '%s' the closure was bound to has been "
                  "destructed\n", current_object->name);
            /* NOTREACHED */
            return;
        }

        /* Make the object resident */
        if (current_object->flags & O_SWAPPED
         && load_ob_from_swap(current_object) < 0)
        {
            /* inter_sp == sp */
            CLEAN_CSP
            error("Out of memory\n");
            /* NOTREACHED */
            return;
        }

        /* Reference the object */
        current_object->time_of_ref = current_time;

        i = lsvp->x.closure_type;
        if (i < CLOSURE_SIMUL_EFUN)
        {
            /* It's an operator or efun */

            if (i == CLOSURE_EFUN + F_UNDEF)
            {
                /* The closure was discovered to be bound to a destructed
                 * object and thus disabled.
                 */
                CLEAN_CSP
                error("Object the closure was bound to has been destructed\n");
                /* NOTREACHED */
                return;
            }

            i -= CLOSURE_EFUN;
              /* Efuns have now a positive value, operators a negative one.
               */

            if (i >= 0
             || instrs[i -= CLOSURE_OPERATOR-CLOSURE_EFUN].min_arg)
            {
                /* To call an operator or efun, we have to construct
                 * a small piece of program with this instruction.
                 */
                bytecode_t code[5];  /* the code fragment */
                bytecode_p p;        /* the code pointer */

                int min, max, def;

                min = instrs[i].min_arg;
                max = instrs[i].max_arg;
                p = code;

                /* Fix up the number of arguments passed */
                if (num_arg < min)
                {
                    /* Add some arguments */

                    int f;

                    if (num_arg == min-1
                     && 0 != (def = instrs[i].Default) && def != -1)
                    {
                        /* We lack one argument for which a default
                         * is provided.
                         */
                        *p++ = (bytecode_t)(def);
                        max--;
                        min--;
                    }
                    else
                    {
                        /* Maybe there is a fitting replacement efun */
                        f = proxy_efun(i, num_arg);
                        if (f >= 0)
                            /* Yup, use that one */
                            i = f;
                        else
                        {
                            /* Nope. */
                            csp->extern_call = MY_TRUE;
                            inter_pc = csp->funstart = EFUN_FUNSTART;
                            csp->instruction = i;
                            error("Too few arguments to %s\n", instrs[i].name);
                        }
                    }
                }
                else if (num_arg > 0xff || (num_arg > max && max != -1))
                {
                    csp->extern_call = MY_TRUE;
                    inter_pc = csp->funstart = EFUN_FUNSTART;
                    csp->instruction = i;
                    error("Too many arguments to %s\n", instrs[i].name);
                }

                /* Store the instruction code */
                if (i > 0xff)
                    *p++ = (bytecode_t)(i >> F_ESCAPE_BITS);
                *p++ = (bytecode_t)i;

                /* Store the <nargs> code, if necessary */
                if (min != max)
                    *p++ = (bytecode_t)num_arg;

                /* And finally the return instruction */
                if ( instrs[i].ret_type == TYPE_VOID )
                    *p++ = F_RETURN0;
                else
                    *p++ = F_RETURN;

                csp->instruction = i;
                csp->funstart = EFUN_FUNSTART;
                csp->num_local_variables = 0;
                inter_fp = sp - num_arg + 1;
                eval_instruction(code, sp);
                /* The result is on the stack (inter_sp) */
                return;
            }
            else
            {
                /* It is an operator or syntactic marker: fall through
                 * to uncallable closure type.
                 */
                break;
            }
        }
        else
        {
            /* simul_efun */
            object_t *ob;

            /* Mark the call as sefun closure */
            inter_pc = csp->funstart = SIMUL_EFUN_FUNSTART;

            /* Get the simul_efun object */
            if ( !(ob = simul_efun_object) )
            {
                /* inter_sp == sp */
                if ( !(ob = get_simul_efun_object()) ) {
                    csp->extern_call = MY_TRUE;
                    error("Couldn't load simul_efun object\n");
                    /* NOTREACHED */
                    return;
                }
            }
            call_simul_efun(i - CLOSURE_SIMUL_EFUN, ob, num_arg);
            CLEAN_CSP
        }
        /* The result is on the stack (inter_sp) */
        return;
      }

    }
    error("Uncallable closure\n");
    /* NOTREACHED */
    return;

#   undef CLEAN_CSP
} /* call_lambda() */

/*-------------------------------------------------------------------------*/
svalue_t *
secure_call_lambda (svalue_t *closure, int num_arg)

/* Call the closure <closure> with <num_arg> arguments on the stack.
 * On success, the functions returns a pointer to the result in the
 * global apply_return_value, on failure it returns NULL. The arguments are
 * removed in either case.
 *
 * This error recovery is the difference to call_lambda().
 */

{
    struct error_recovery_info error_recovery_info;
    svalue_t *save_sp;
    struct control_stack *save_csp;
    svalue_t *result;

    error_recovery_info.rt.last = rt_context;
    error_recovery_info.rt.type = ERROR_RECOVERY_APPLY;
    rt_context = (rt_context_t *)&error_recovery_info;
    save_sp = inter_sp;
    save_csp = csp;

    if (setjmp(error_recovery_info.con.text))
    {
        secure_apply_error(save_sp - num_arg, save_csp, MY_FALSE);
        result = NULL;
    }
    else
    {
        call_lambda(closure, num_arg);
        transfer_svalue((result = &apply_return_value), inter_sp);
        inter_sp--;
    }
    rt_context = error_recovery_info.rt.last;
    return result;
} /* secure_call_lambda() */

/*-------------------------------------------------------------------------*/
static void
call_simul_efun (int code, object_t *ob, int num_arg)

/* Call the simul_efun <code> in the sefun object <ob> with <num_arg>
 * arguments on the stack. If it can't be found in the <ob>ject, the
 * function queries the auxiliary sefun objects in <simul_efun_vector>.
 *
 * The function is looked up in the objects by name because its original
 * entry in the simul_efun_table[] has been marked as "discarded".
 *
 * Leave the result on the stack on return.
 */

{
    char *function_name;

    function_name = simul_efunp[code].name;

    /* First, try calling the function in the given object */
    if (!apply_low(function_name, ob, num_arg, MY_FALSE))
    {
        /* Function not found: try the alternative sefun objects */
        if (simul_efun_vector)
        {
            long i;
            svalue_t *v;

            i = (long)VEC_SIZE(simul_efun_vector);
            for (v = simul_efun_vector->item+1; MY_TRUE; v++)
            {
                if (--i <= 0 || v->type != T_STRING)
                {
                    error("Calling a vanished simul_efun\n");
                    return;
                }
                if ( !(ob = get_object(v->u.string)) )
                    continue;
                if (apply_low(function_name, ob, num_arg, MY_FALSE))
                    return;
            }
            return;
        }
        error("Calling a vanished simul_efun\n");
        return;
    }
    /*
     * The result of the function call is on the stack.
     */
} /* call_simul_efun() */

/*-------------------------------------------------------------------------*/
char *
function_exists (char *fun, object_t *ob)

/* Search for the function <fun> in the object <ob>. If existing, return
 * the name of the program, if not return NULL.
 *
 * Visibility rules apply: static and protected functions can't be
 * found from the outside.
 */

{
    char *shared_name;
    fun_hdr_p funstart;
    program_t *progp;
    int ix;
    funflag_t flags;

#ifdef DEBUG
    if (ob->flags & O_DESTRUCTED)
        fatal("function_exists() on destructed object\n");
#endif

    /* Make the program resident */
    if (O_PROG_SWAPPED(ob))
    {
        ob->time_of_ref = current_time;
        if (load_ob_from_swap(ob) < 0)
            error("Out of memory\n");
    }

    shared_name = findstring(fun);
    progp = ob->prog;

    /* Check if the function exists at all */
    if ( (ix = find_function(shared_name, progp)) < 0)
        return NULL;

    /* Is it visible for the caller? */
    flags = progp->functions[ix];

    if (flags & TYPE_MOD_PRIVATE
     || (flags & TYPE_MOD_STATIC && current_object != ob))
        return NULL;

    /* Resolve inheritance */
    while (flags & NAME_INHERITED)
    {
        inherit_t *inheritp;

        inheritp = &progp->inherit[flags & INHERIT_MASK];
        ix -= inheritp->function_index_offset;
        progp = inheritp->prog;
        flags = progp->functions[ix];
    }

    funstart = progp->program  + (flags & FUNSTART_MASK);

    /* And after all this, the function may be undefined */
    if (FUNCTION_CODE(funstart)[0] == F_ESCAPE
     && FUNCTION_CODE(funstart)[1] == F_UNDEF  - 0x100)
    {
        return NULL;
    }

    /* We got it. */
    return progp->name;
} /* function_exists() */

/*-------------------------------------------------------------------------*/
void
call_function (program_t *progp, int fx)

/* Call the function <fx> in program <progp> for the current_object.
 * This is done with no frame set up. No arguments are passed,
 * returned values are removed.
 *
 * Right now this function is used just for heartbeats, and the
 * way of calling prevents shadows from being called.
 */

{
    funflag_t flags;
    fun_hdr_p funstart;

    push_control_stack(inter_sp, inter_pc, inter_fp);
    csp->ob = current_object;
    csp->prev_ob = previous_ob;
#ifdef DEBUG
    if (csp != CONTROL_STACK)
        fatal("call_function with bad csp\n");
#endif
    csp->num_local_variables = 0;
    current_prog = progp;
    flags = setup_new_frame(fx);
    funstart = current_prog->program + (flags & FUNSTART_MASK);
    csp->funstart = funstart;
    previous_ob = current_object;
    tracedepth = 0;
    eval_instruction(FUNCTION_CODE(funstart), inter_sp);
    free_svalue(inter_sp--);  /* Throw away the returned result */
} /* call_function() */

/*-------------------------------------------------------------------------*/
int
get_line_number (bytecode_p p, program_t *progp, char **namep)

/* Look up the line number for address <p> within the program <progp>.
 * Result is the line number, and *<namep> is set to the name of the
 * source resp. include file.
 *
 * If the code was generated from an included file, and if the name lengths
 * allow it, the returned name is "<program name> (<include filename>)".
 * In this case, the returned *<namep> points to a static buffer.
 *
 * TODO: (an old comment which might no longer be true): This can be done
 * TODO:: much more efficiently, but that change has low priority.)
 */
{
    /* Datastructure to keep track of included files */
    struct incinfo
    {
        char *name;             /* Name of parent file */
        struct incinfo *super;  /* Pointer to parent entry */
        int super_line;         /* Line number within parent file */
    };

    int offset;            /* (Remaining) program offset to resolve */
    int i;                 /* Current line number */
    include_t *includes;   /* Pointer to the next include info */
    struct incinfo *inctop = NULL;  /* The include information stack. */
    int relocated_from = 0;
    int relocated_to = -1;
    Bool used_system_mem;
      /* TRUE if the line numbers needed SYSTEM privilege to be swapped in,
       * because this means that afterwards they need to be deallocated
       * again.
       */

    if (!progp || !p)
    {
        *namep = "UNDEFINED";
        return 0;
    }

    used_system_mem = MY_FALSE;

    /* Get the line numbers */
    if (!progp->line_numbers)
    {
        if (!load_line_numbers_from_swap(progp))
        {
            /* Uhhmm, out of memory - try to pull some rank */

            int save_privilege;
            Bool rc;

            used_system_mem = MY_TRUE;
            save_privilege = malloc_privilege;
            malloc_privilege = MALLOC_SYSTEM;
            rc = load_line_numbers_from_swap(progp);
            malloc_privilege = save_privilege;
            if (!rc)
            {
                *namep = "UNDEFINED";
                return 0;
            }
        }
    }

    /* Get the offset within the program */
    offset = (int)(p - progp->program);
    if (p < progp->program || p > PROGRAM_END(*progp))
    {
        printf("%s get_line_number(): Illegal offset %d in object %s\n"
              , time_stamp(), offset, progp->name);
        debug_message("%s get_line_number(): Illegal offset %d in object %s\n"
                     , time_stamp(), offset, progp->name);
        *namep = "UNDEFINED";
        return 0;
    }

    includes = progp->includes;

    /* Decode the line number information until the line number
     * for offset is found. We do this by reading the line byte codes,
     * counting up the line number <i> while decrementing the <offset>.
     * If the offset becomes <= 0, we found the line.
     */
    for (i = 0, p = progp->line_numbers->line_numbers; ; )
    {
        int o;

        o = GET_CODE(p);

        if (o <= 63)  /* 0x00..0x3F */
        {
            if (o >= LI_MAXOFFSET)  /* 0x3b..0x3f */
            {
                if (o != LI_MAXOFFSET)
                {
                    switch (o)
                    {

                    case LI_BACK:
                      {
                        unsigned int off;

                        p++;
                        off = GET_CODE(p);
                        i -= off+1;
                        break;
                      }

                    case LI_INCLUDE:
                      {
                        /* Included file: push the information */

                        struct incinfo *inc_new;

                        /* Find the next include which generated code.
                         * We know that there is one.
                         */
                        while (includes->depth < 0) includes++;

                        i++;
                        inc_new = xalloc(sizeof *inc_new);
                        /* TODO: What if this fails? */
                        inc_new->name = includes->filename;
                        includes++;
                        inc_new->super = inctop;
                        inc_new->super_line = i;
                        inctop = inc_new;
                        i = 0;
                        break;
                      }

                    case LI_INCLUDE_END:
                      {
                        /* End of include: retrieve old position */

                        struct incinfo *inc_old;

                        inc_old = inctop;
                        i = inc_old->super_line;
                        inctop = inc_old->super;
                        xfree(inc_old );
                        break;
                      }

                    case LI_L_RELOCATED:
                      {
                        int h, l;

                        p++;
                        h = GET_CODE(p);
                        p++;
                        l = GET_CODE(p);
                        i -= 2;
                        relocated_to = i;
                        relocated_from = relocated_to - ((h << 8) + l);
                        p++; /* skip trailing LI_L_RELOCATED */
                        break;
                      }
                    }
                }
                else /* 0x3c */
                {
                    offset -= o;
                }
            }
            else  /* 0x00..0x3b */
            {
                offset -= o;
                i++;
                if (offset <= 0)
                    break;
            }
        }
        else if (o <= 127)  /* 0x40..0x7f */
        {
            /* Simple entry: count offset and lines */
            offset -= (o&7) + 1;
            i += (o>>3) - 6;
            if (offset <= 0)
                break;
        }
        else if (o >= 256-LI_MAXEMPTY)  /* 0xE0 .. 0xFF */
        {
            i += 256-o;
        }
        else /* 0x80 .. 0xDF */
        {
            i -= 2;
            relocated_from = (relocated_to = i) - (o - LI_RELOCATED);
        }

        /* Get the next line number bytecode */
        p++;
    } /* line number search */

    if (i == relocated_to + 1)
        i = relocated_from + 1;
        /* Perform the announced relocation */

    /* Here, i is the line number, and if inctop is not NULL, the
     * code originates from the included file pointed to by inctop.
     * In either case, set *<namep> to the pointer to the name
     * of the file.
     */

    if (inctop)
    {
        /* The code was included */

        static char namebuf[80];

        *namep = inctop->name;
        if (strlen(*namep) + strlen(progp->name) < sizeof(namebuf) - 3)
        {
            sprintf(namebuf, "%s (%s)", progp->name, *namep);
            *namep = namebuf;
        }
        else
            *namep = "<program names too long>";

        /* Free the include stack structures */
        do {
            struct incinfo *inc_old;

            inc_old = inctop;
            inctop = inc_old->super;
            xfree(inc_old);
        } while (inctop);
    }
    else
    {
        /* Normal code */

        *namep = progp->name;
    }

    if (used_system_mem)
    {
        /* We used SYSTEM priviledged memory - now we have to return it.
         */
        total_prog_block_size -= progp->line_numbers->size;
        total_bytes_unswapped -= progp->line_numbers->size;
        xfree(progp->line_numbers);
        progp->line_numbers = NULL;
        reallocate_reserved_areas();
    }

    /* Return the line number */
    return i;
} /* get_line_number() */

/*-------------------------------------------------------------------------*/
int
get_line_number_if_any (char **name)

/* Look up the line number for the current execution address.
 * Result is the line number, and *<namep> is set to the name of the
 * source resp. include file.
 *
 * The function recognizes sefun and lambda closures, the latter return
 * the approximate position offset of the offending instruction within
 * the closure.
 *
 * *<namep> may point to a static buffer.
 */

{
    if (csp >= &CONTROL_STACK[0] && csp->funstart == SIMUL_EFUN_FUNSTART)
    {
        *name = "<simul_efun closure>";
        return 0;
    }

    if (csp >= &CONTROL_STACK[0] && csp->funstart == EFUN_FUNSTART)
    {
        static char buf[256];
        char *iname;

        iname = instrs[csp->instruction].name;
        if (iname)
        {
            buf[sizeof buf - 1] = '\0';
            buf[0] = '#';
            buf[1] = '\'';
            strcpy(buf+2, iname);
            if (buf[sizeof buf - 1] != '\0')
                fatal("interpret:get_line_number_if_any(): "
                      "buffer overflow.\n");
            iname = buf;
        }
        else
            iname = "<efun closure>";

        *name = iname;
        return 0;
    }

    if (current_prog)
    {
        if (csp->funstart < current_prog->program
         || csp->funstart > PROGRAM_END(*current_prog))
        {
            static char name_buffer[24];

            sprintf(name_buffer, "<lambda 0x%6lx>", (long)csp->funstart);
            *name = name_buffer;
            return inter_pc - csp->funstart - 2;
        }
        return get_line_number(inter_pc, current_prog, name);
    }

    *name = "";
    return 0;
}

/*-------------------------------------------------------------------------*/
char *
collect_trace (strbuf_t * sbuf, vector_t ** rvec )

/* Collect the traceback for the current (resp. last) function call, starting
 * from the first frame.
 *
 * If <sbuf> is not NULL, traceback is written in readable form into the
 * stringbuffer <sbuf>.
 *
 * If <rvec> is not NULL, the traceback is returned in a newly created array
 * which pointer is put into *<rvec>. For the format of the array, see
 * efun debug_info().
 *
 * If a heart_beat() is involved, return a pointer to the name of the object
 * that had it, otherwise return NULL.
 */

{
    struct control_stack *p;  /* Control frame under inspection */
    char *ret = NULL;
    bytecode_p pc = inter_pc;
    int line = 0;
    char *name;
    char *file;
    object_t *ob = NULL;
    bytecode_p last_catch = NULL;  /* Last found catch */

    /* Temporary structure to hold the tracedata before it is condensed
     * into the result array.
     */
    struct traceentry {
        vector_t          * vec;
        struct traceentry * next;
    } *first_entry, *last_entry;
    size_t num_entries;

#define NEW_ENTRY(var, type, progname) \
        struct traceentry * var; \
        var = alloca(sizeof(*var)); \
        if (!var) \
            error("Stack overflow in collect_trace()"); \
        var->vec = allocate_array(TRACE_MAX); \
        var->next = NULL; \
        if (!first_entry) \
            first_entry = last_entry = var; \
        else { \
            last_entry->next = var; \
            last_entry = var; \
        } \
        num_entries++; \
        put_number(var->vec->item+TRACE_TYPE, type); \
        put_malloced_string(var->vec->item+TRACE_PROGRAM, string_copy(progname)); \
        put_malloced_string(entry->vec->item+TRACE_OBJECT, string_copy(ob->name));

#define PUT_LOC(entry, val) \
        put_number(entry->vec->item+TRACE_LOC, (p_int)(val))

    first_entry = last_entry = NULL;
    num_entries = 0;

    if (!current_prog)
    {
        if (sbuf)
           strbuf_addf(sbuf, "%s\n", STR_NO_PROG_TRACE);
        if (rvec)
        {
            vector_t * vec;

            vec = allocate_array(1);
            put_ref_string(vec->item, STR_NO_PROG_TRACE);
            *rvec = vec;
        }
        return NULL;
    }

    if (csp < &CONTROL_STACK[0])
    {
        if (sbuf)
           strbuf_addf(sbuf, "%s\n", STR_NO_TRACE);
        if (rvec)
        {
            vector_t * vec;

            vec = allocate_array(1);
            put_ref_string(vec->item, STR_NO_TRACE);
            *rvec = vec;
        }
        return NULL;
    }

    /* Loop through the call stack.
     * The organisation of the control stack results in the information
     * for this frame (p[0]) being stored in the next (p[1]).
     * Confused now? Good.
     */
    p = &CONTROL_STACK[0];
    do {
        bytecode_p  dump_pc;  /* the frame's pc */
        program_t  *prog;     /* the frame's program */

        if (p->extern_call)
        {
            /* Find the next extern_call and set <ob> to the
             * then-current object for all the coming frames.
             */
            struct control_stack *q = p;
            for (;;) {
                if (++q > csp)
                {
                    ob = current_object;
                    break;
                }
                if (q->extern_call)
                {
                    ob = q->ob;
                    break;
                }
            }
            last_catch = NULL;
        }

        /* Retrieve pc and program from the stack */
        if (p == csp)
        {
            dump_pc = pc;
            prog = current_prog;
        }
        else
        {
            dump_pc = p[1].pc;
            prog = p[1].prog;
        }

        /* Use some heuristics first to see if it could possibly be a CATCH.
         * The pc should point at a F_END_CATCH instruction, or at a LBRANCH
         * to that instruction.
         */
        if (p > &CONTROL_STACK[0] && p->funstart == p[-1].funstart)
        {
            bytecode_p pc2 = p->pc;

            if (!pc2)
                goto not_catch;  /* shouldn't happen... */

            if (GET_CODE(pc2) == F_LBRANCH)
            {
                short offset;
                pc2++;
                GET_SHORT(offset, pc2);
                if (offset <= 0)
                    goto not_catch;
                pc2 += offset;
            }

#if F_END_CATCH >= 0x100
            if (pc2 - FUNCTION_CODE(p->funstart) < 2)
                goto not_catch;

            if (GET_CODE(pc2-2) != F_ESCAPE
             || GET_CODE(pc2-1) != F_END_CATCH-0x100)
            {
                goto not_catch;
            }
#else
            if (pc2 - FUNCTION_CODE(p->funstart) < 1)
                goto not_catch;

            if (GET_CODE(pc2-1) != F_END_CATCH)
            {
                goto not_catch;
            }
#endif
            if (last_catch == pc2)
                goto not_catch;
            last_catch = pc2;
            name = "CATCH";
            file = NULL;
            line = 0;
            goto name_computed;
        }

not_catch:  /* The frame does not point at a catch here */

        /* Efun symbol? */
        if (!prog || !dump_pc)
        {
            /* TODO: See comments in call_lambda(): this code
             * TODO:: should never be reached.
             */
            if (sbuf)
                strbuf_addf(sbuf, "<function symbol> in '%20s' ('%20s')\n"
                           , ob->prog->name, ob->name);
            if (rvec)
            {
                NEW_ENTRY(entry, TRACE_TYPE_SYMBOL, ob->prog->name);
            }
            continue;
        }

        /* simul_efun closure? */
        if (p[0].funstart == SIMUL_EFUN_FUNSTART)
        {
            if (sbuf)
                strbuf_addf( sbuf
                           , "<simul_efun closure> bound to '%20s' ('%20s')\n"
                           , ob->prog->name, ob->name);
            if (rvec)
            {
                NEW_ENTRY(entry, TRACE_TYPE_SEFUN, ob->prog->name);
            }
            continue;
        }

        /* efun closure? */
        if (p[0].funstart == EFUN_FUNSTART)
        {
            char * iname;

            iname = instrs[p[0].instruction].name;
            if (iname)
            {
                if (sbuf)
                    strbuf_addf(sbuf, "#\'%-14s for '%20s' ('%20s')\n"
                               , iname, ob->prog->name, ob->name);
                if (rvec)
                {
                    NEW_ENTRY(entry, TRACE_TYPE_EFUN, ob->prog->name);
                    put_volatile_string(entry->vec->item+TRACE_NAME, iname);
                }
            }
            else
            {
                if (sbuf)
                    strbuf_addf( sbuf, "<efun closure %d> for '%20s' ('%20s')\n"
                               , p[0].instruction, ob->prog->name, ob->name);
                if (rvec)
                {
                    NEW_ENTRY(entry, TRACE_TYPE_EFUN, ob->prog->name);
                    put_number(entry->vec->item+TRACE_NAME, p[0].instruction);
                }
            }
            continue;
        }

        /* Lambda closure? */
        if (p[0].funstart < prog->program
         || p[0].funstart > PROGRAM_END(*prog))
        {
            if (sbuf)
                strbuf_addf( sbuf
                           , "<lambda 0x%6lx> in '%20s' ('%20s') offset %ld\n"
                           , (long)p[0].funstart, ob->prog->name, ob->name
                           , (long)(FUNCTION_FROM_CODE(dump_pc) - p[0].funstart)
                           );
            if (rvec)
            {
                NEW_ENTRY(entry, TRACE_TYPE_LAMBDA, ob->prog->name);
                put_number(entry->vec->item+TRACE_NAME, (p_int)p[0].funstart);
                PUT_LOC(entry, (FUNCTION_FROM_CODE(dump_pc) - p[0].funstart));
            }
            continue;
        }

        /* Nothing of the above: a normal program */
        line = get_line_number(dump_pc, prog, &file);
        memcpy(&name, FUNCTION_NAMEP(p[0].funstart), sizeof name);

name_computed: /* Jump target from the catch detection */

        /* Print the name and line */

        if (strcmp(name, "heart_beat") == 0 && p != csp)
            ret = p->extern_call ? (p->ob ? p->ob->name : NULL) : ob->name;

        if (sbuf)
        {
            if (file != NULL)
                strbuf_addf(sbuf, "'%15s' in '%20s' ('%20s') line %d\n"
                           , name, file, ob->name, line);
            else
                strbuf_addf(sbuf, "'%15s' in %22s ('%20s')\n"
                           , name, "", ob->name);
        }

        if (rvec)
        {
            NEW_ENTRY(entry, TRACE_TYPE_LFUN, file != NULL ? file : "");
            put_malloced_string(entry->vec->item+TRACE_NAME, string_copy(name));
            PUT_LOC(entry, line);
        }
    } while (++p <= csp);

    /* Condense the singular entries into the result array */
    if (rvec)
    {
        vector_t * vec;
        size_t ix;

        vec = allocate_array(num_entries+1);

        if (ret)
            put_malloced_string(vec->item, string_copy(ret));

        for (ix = 1; first_entry != NULL; ix++, first_entry = first_entry->next)
        {
            put_array(vec->item+ix, first_entry->vec);
        }

        *rvec = vec;
    }

    /* Done */
    return ret;

#undef NEW_ENTRY
#undef PUT_LOC

} /* collect_trace() */

/*-------------------------------------------------------------------------*/
char *
dump_trace (Bool how, vector_t ** rvec)

/* Write out a traceback, starting from the first frame. If a heart_beat()
 * is involved, return the name of the object that had it.
 *
 * If <how> is FALSE (the normal case), the trace is written with
 * debug_message() only. If <how> is TRUE (used for internal errors), the
 * trace is also written to stdout.
 *
 * If TRACE_CODE is defined and <how> is true, the last executed
 * instructions are printed, too.
 *
 * If <rvec> is not NULL, the traceback is returned in a newly created array
 * which pointer is put into *<rvec>. For the format of the array, see
 * efun debug_info().
 */

{
    strbuf_t sbuf;
    char *hb_obj_name;

    strbuf_zero(&sbuf);
    hb_obj_name = collect_trace(&sbuf, rvec);

    /* Print the last instructions if required */
#ifdef TRACE_CODE
    if (how) {
        /* TODO: This number of instructions should be a runtime arg */
#ifdef DEBUG
        (void)last_instructions(200, MY_TRUE, NULL);
        if (inter_pc)
            printf("%6lx: %3d %3d %3d %3d %3d %3d %3d %3d\n"
                  , (long)inter_pc
                  , inter_pc[0], inter_pc[1], inter_pc[2], inter_pc[3]
                  , inter_pc[4], inter_pc[5], inter_pc[6], inter_pc[7] );
        else
            printf("No program counter.\n");
#else  /* DEBUG */
        last_instructions(20, MY_TRUE, NULL);
#endif /* DEBUG */
    }
#endif /* TRACE_CODE */

    /* Print the trace */
    if (how)
        fputs(sbuf.buf, stdout);
    debug_message("%s", sbuf.buf);

    /* Cleanup and return */
    strbuf_free(&sbuf);

    return hb_obj_name;
} /* dump_trace() */

/*-------------------------------------------------------------------------*/
void
invalidate_apply_low_cache (void)

/* Called in the (unlikely) case that all programs had to be renumbered,
 * this invalidates the call cache.
 */

{
    int i;

    for (i = 0; i < CACHE_SIZE; i++)
    {
        cache[i].id = 0;
        if (!cache[i].progp && cache[i].name)
            xfree(cache[i].name);
    }
}

/*-------------------------------------------------------------------------*/
size_t
interpreter_overhead (void)

/* Return the amount of memory allocated for the interpreter.
 * Right now this is the apply cache overhead.
 */

{
    size_t sum, ix;

    sum = 0;

    for (ix = 0; ix < CACHE_SIZE; ix++)
    {
        if (!cache[ix].progp && cache[ix].name)
        {
            sum += strlen(cache[ix].name)+1;
        }

    }

    return sum;
} /* interpreter_overhead() */


#ifdef GC_SUPPORT

/*-------------------------------------------------------------------------*/
void
clear_interpreter_refs (void)

/* GC Support: Clear the interpreter references.
 */

{
#ifdef TRACE_CODE
    {
        int i;

        for (i = TOTAL_TRACE_LENGTH; --i >= 0; )
        {
            object_t *ob;

            if (NULL != (ob = previous_objects[i])
             && ob->flags & O_DESTRUCTED
             && ob->ref
               )
            {
                ob->ref = 0;
                ob->prog->ref = 0;
                clear_inherit_ref(ob->prog);
            }
        }
    }
#endif
} /* clear_interpreter_refs() */

/*-------------------------------------------------------------------------*/
void
count_interpreter_refs (void)

/* GC Support: Count/mark all interpreter held structures.
 */

{
    int i;

    for (i = CACHE_SIZE; --i>= 0; ) {
        if (!cache[i].progp)
            note_malloced_block_ref(cache[i].name);
    }
#ifdef TRACE_CODE
    for (i = TOTAL_TRACE_LENGTH; --i >= 0; )
    {
        object_t *ob;

        if ( NULL != (ob = previous_objects[i]) )
        {
            if (ob->flags & O_DESTRUCTED)
            {
                previous_objects[i] = NULL;
                previous_instruction[i] = 0;
                reference_destructed_object(ob);
            }
            else
            {
                ob->ref++;
            }
        }
    }
#endif
}
/*-------------------------------------------------------------------------*/

#endif /* GC_SUPPORT */
/*=========================================================================*/

/*                            D E B U G G I N G                            */

/*-------------------------------------------------------------------------*/
#ifdef OPCPROF
Bool
opcdump (char * fname)

/* Print the usage statistics for the opcodes into the file <fname>.
 * Return TRUE on success, FALSE if <fname> can't be written.
 */

{
    int i;
    FILE *f;

    fname = check_valid_path(fname, current_object, "opcdump", MY_TRUE);
    if (!fname)
        return MY_FALSE;
    f = fopen(fname, "w");
    if (!f)
        return MY_FALSE;
    FCOUNT_WRITE(fname);


    for(i = 0; i < MAXOPC; i++)
    {
        if (opcount[i])
#ifdef VERBOSE_OPCPROF
            fprintf(f,"%d: \"%-16s\" %6d\n",i, get_f_name(i), opcount[i]);
#else
            fprintf(f,"%d: %d\n", i, opcount[i]);
#endif
    }
    fclose(f);

    return MY_TRUE;
}
#endif


#ifdef TRACE_CODE

/*-------------------------------------------------------------------------*/
static char *
get_arg (int a)

/* Return the argument for the instruction at previous_pc[<a>] as a string.
 * If there is no argument, return "".
 *
 * Helper function for last_instructions().
 */

{
    static char buff[10];
    bytecode_p from, to;
    int b;

    b = (a+1) % TOTAL_TRACE_LENGTH;
    from = previous_pc[a];
    to = previous_pc[b];

    if (to - from < 2)
        return "";

    if (to - from == 2)
    {
        sprintf(buff, "%d", GET_CODE(from+1));
        return buff;
    }

    if (to - from == 3)
    {
        short arg;

        GET_SHORT(arg, from+1);
        sprintf(buff, "%d", arg);
        return buff;
    }

    if (to - from == 5)
    {
        int32 arg;

        GET_INT32(arg, from+1);
        sprintf(buff, "%ld", (long)arg);
        return buff;
    }

    return "";
}

/*-------------------------------------------------------------------------*/
static void
last_instr_output (char *str, svalue_t **svpp)

/* <svpp> == NULL: print string <str>
 * <svpp> != NULL: store a copy of <str> as string-svalue to *<svpp>, then
 *                 increment *<svpp>
 *
 * Helper function to last_instructions() to either print strings for
 * a tracedump, or to push them onto the evaluator stack for the efun
 * last_instructions().
 */

{
    if (svpp)
    {
        if ( !(str = string_copy(str)) )
            error("Out of memory\n");
        put_malloced_string((*svpp), str);
        (*svpp)++;
    }
    else
    {
        printf("%s\n", str);
    }
}

/*-------------------------------------------------------------------------*/
static Bool
program_referenced (program_t *prog, program_t *prog2)

/* Return TRUE if <prog2> inherits <prog>.
 *
 * Auxiliary function to last_instructions().
 */

{
    inherit_t *inh;
    int i;

    if (prog == prog2)
        return MY_TRUE;

    /* If a prog2 is swapped out, it can't have prog inherited
     * and swapped in.
     */
    if (P_PROG_SWAPPED(prog2))
        return MY_FALSE;

    /* Recursively test the inherits */
    for (i = prog2->num_inherited, inh = prog2->inherit; --i >= 0; inh++)
    {
        if (program_referenced(prog, inh->prog))
            return MY_TRUE;
    }

    return MY_FALSE;
}

/*-------------------------------------------------------------------------*/
static Bool
program_exists (program_t *prog, object_t *guess)

/* Test if <prog> exists - either by itself or as inherited program.
 * Start testing with the program of <guess>, if it is not there,
 * test all objects in the list.
 *
 * Auxiliary function to last_instructions().
 */

{
    if (program_referenced(prog, guess->prog))
        return MY_TRUE;

    for (guess = obj_list; guess; guess = guess->next_all)
    {
#ifdef DEBUG
        if (guess->flags & O_DESTRUCTED)  /* TODO: Can't happen */
            continue;
#endif
        if (program_referenced(prog, guess->prog))
            return MY_TRUE;
    }

    return MY_FALSE;
}

/*-------------------------------------------------------------------------*/
int
last_instructions (int length, Bool verbose, svalue_t **svpp)

/* 'Print' a dump of the <length> last instructions. If <svpp> is NULL,
 * all the data is printed, else *<svpp> points to the evaluator stack
 * and all the 'printed' lines are pushed onto the stack using *<svpp>
 * as pointer.
 *
 * If <verbose> is true, more information is printed.
 *
 * Return the index for the last executed instruction.
 *
 * This function is called from dump_trace() and f_last_instructions().
 */

{
    int i;
    object_t *old_obj;
    char old_file[160], buf[400];
    int old_line, line = 0;

    old_obj = NULL;
    old_file[0] = old_file[sizeof old_file - 1] = '\0';
    old_line = 0;
    i = (last - length + TOTAL_TRACE_LENGTH) % TOTAL_TRACE_LENGTH;

    /* Walk through the instructions.
     * Instructions with value 0 are not used yet, or have been
     * removed while cleaning up destructed objects.
     */
    do {
        i = (i + 1) % TOTAL_TRACE_LENGTH;
        if (previous_instruction[i] != 0)
        {
            if (verbose)
            {
                char *file;
                program_t *ppr;
                bytecode_p ppc;

                ppr = previous_programs[i];
                ppc = previous_pc[i]+1;
                if (!program_exists(ppr, previous_objects[i]))
                {
                    file = "program deallocated";
                    line = 0;
                }
                else if (ppc < ppr->program || ppc > PROGRAM_END(*ppr))
                {
                    file = "<lambda ?>";
                    line = 0;
                }
                else
                {
                    line = get_line_number(ppc, ppr, &file);
                }

                if (previous_objects[i] != old_obj || strcmp(file, old_file))
                {
                    sprintf(buf, "%.170s %.160s line %d",
                      previous_objects[i]->name, file, line
                    );
                    last_instr_output(buf, svpp);
                    old_obj = previous_objects[i];
                    xstrncpy(old_file, file, sizeof old_file - 1);
                }
            }
            sprintf(buf, "%6lx: %3d %8s %-26s (%ld:%3ld)"
                   , (long)previous_pc[i]
                   , previous_instruction[i] /* instrs.h has these numbers */
                   , get_arg(i)
                   , get_f_name(previous_instruction[i])
                   , (long) (stack_size[i] + 1)
                   , (long) (abs_stack_size[i])
            );
            if (verbose && line != old_line)
                sprintf(buf + strlen(buf), "\tline %d", old_line = line);
            last_instr_output(buf, svpp);
        }
    } while (i != last);

    return last;
} /* last_instructions() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_last_instructions (svalue_t *sp)

/* TEFUN last_instructions()
 *
 *   string *last_instructions (int length, int verbose)
 *
 * Return an array showing the 'length' last executed
 * instructions in disassembled form. If 'verbose' is non-zero
 * (the default), line number information are also included.
 * Each string is built as this:
 *
 *   Opcode-Address: Opcode Operand Mnemonic (Stackdepth) Linenumber
 *
 * The Stackdepth information consists of two numbers <rel>:<abs>:
 * <rel> is the relative stack usage in this function, <abs> is the
 * absolute stack usage.
 *
 * The linenumber information is appended if requested and a new
 * source line is reached. Also, calls between objects produce a
 *
 *   Objectname Programname Linenumber
 *
 * entry in the resulting array (in verbose mode only).
 *
 * There is a preconfigured upper limit for the backtrace.
 */

{
    vector_t *v, *v2;
    mp_int num_instr, size;
    svalue_t *svp;

    /* Test the arguments */
    if (sp[-1].type != T_NUMBER || (num_instr = sp[-1].u.number) <= 0)
        bad_xefun_arg(1, sp);
    if (sp->type != T_NUMBER)
        bad_xefun_arg(2, sp);

    sp--;
    inter_sp = sp; /* Out of memory possible */
    if (num_instr > TOTAL_TRACE_LENGTH)
        num_instr = TOTAL_TRACE_LENGTH;

    /* Allocate the result vector */
    size = sp[1].u.number ? num_instr << 1 : num_instr;
    v = allocate_array(size);

    /* Enter the vector into the stack for now, so that it will be
     * freed when an out of memory error occurs.
     */
    put_array(sp, v);
    svp = v->item;
    last_instructions(num_instr, sp[1].u.number != 0, &svp);

    /* If we allocated the vector to big, get a shorter one and copy
     * the data.
     */
    if (svp - v->item < size)
    {
        size = svp - v->item;
        v2 = allocate_array(size);
        memcpy(v2->item, v->item, size * sizeof *svp);
        sp->u.vec = v2;
        free_empty_vector(v);
    }

    return sp;
} /* f_last_instructions() */

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

#endif /* TRACE_CODE */


#ifdef DEBUG

/*-------------------------------------------------------------------------*/
void
count_inherits (program_t *progp)

/* Check Refcounts: Increment the extra_ref of all programs inherited
 * by <progp>. If one of those programs has not been visited yet,
 * its extra_ref is set to 1 and this function is called recursively.
 *
 * If check_..._search_prog is set and equal to one of the inherited
 * programs, a notice is printed.
 */

{
    int i;
    program_t *progp2;

    /* Clones will not add to the ref count of inherited progs */
    for (i = 0; i < progp->num_inherited; i++)
    {
        progp2 = progp->inherit[i].prog;
        progp2->extra_ref++;
        if (progp2 == check_a_lot_ref_counts_search_prog)
            printf("%s Found prog, inherited by %s, new total ref %ld\n"
                  , time_stamp(), progp->name, progp2->ref);
        if (NULL == register_pointer(ptable, progp2))
            continue;
        progp2->extra_ref = 1;
        if (progp2->blueprint)
            count_extra_ref_in_object(progp2->blueprint);
        count_inherits(progp2);
    }
} /* count_inherits() */

/*-------------------------------------------------------------------------*/
static void
count_extra_ref_in_mapping_filter ( svalue_t *key, svalue_t *data
                                  , void * extra)

/* Count the extra refs for <key> and the associated <data>. <extra>
 * is a mp_int giving the number of data values.
 */

{
    count_extra_ref_in_vector(key, 1);
    count_extra_ref_in_vector(data, (size_t)extra);
}

/*-------------------------------------------------------------------------*/
static void
check_extra_ref_in_mapping_filter (svalue_t *key, svalue_t *data
                                  , void * extra)

/* Check the extra refs for <key> and the associated <data>. <extra>
 * is a mp_int giving the number of data values.
 */

{
    check_extra_ref_in_vector(key, 1);
    check_extra_ref_in_vector(data, (size_t)extra);
}

/*-------------------------------------------------------------------------*/
void
count_extra_ref_in_object (object_t *ob)

/* Count the extra refs for object <ob>. If the object has been visited
 * before, extra_ref is just incremented. Otherwise, extra_ref is
 * set to 1 and all depending refs are counted.
 *
 * If check_..._search_prog is set and matches the objects program,
 * a notice is printed.
 */

{
    Bool was_swapped = MY_FALSE;

    ob->extra_ref++;
    if ( NULL == register_pointer(ptable, ob) )
        return;

    ob->extra_ref = 1;
    if ( !O_PROG_SWAPPED(ob) )
    {
        ob->prog->extra_ref++;
        if (ob->prog == check_a_lot_ref_counts_search_prog)
            printf("%s Found program for object %s\n", time_stamp(), ob->name);
    }

    /* Clones will not add to the ref count of inherited progs */
    if (O_PROG_SWAPPED(ob))
    {
         if (load_ob_from_swap(ob) < 0)
            debug_message( "%s check-refcounts: Program for '%s' can't be "
                           "swapped in - extra refcounts may be wrong.\n"
                         , time_stamp(), ob->name);
         else
             was_swapped = MY_TRUE;
    }

    if (!O_PROG_SWAPPED(ob))
    {
        if (NULL != register_pointer(ptable, ob->prog))
        {
            ob->prog->extra_ref = 1;
            if (ob->prog->blueprint)
                count_extra_ref_in_object(ob->prog->blueprint);
            count_inherits(ob->prog);
        }
    }

    if (was_swapped)
        swap_program(ob);

    if (ob->flags & O_SHADOW)
    {
        ed_buffer_t *buf;

        if ( NULL != (buf = O_GET_SHADOW(ob)->ed_buffer) )
            count_ed_buffer_extra_refs(buf);
    }
} /* count_extra_ref_in_closure() */

/*-------------------------------------------------------------------------*/
static void
count_extra_ref_in_closure (lambda_t *l, ph_int type)

/* Count the extra refs in the closure <l> of type <type>.
 */

{
    if (CLOSURE_HAS_CODE(type))
    {
        /* We need to count the extra_refs in the constant values. */

        mp_int num_values;
        svalue_t *svp;

        svp = (svalue_t *)l;
        if ( (num_values = EXTRACT_UCHAR(l->function.code)) == 0xff)
            num_values = svp[-0x100].u.number;
        svp -= num_values;
        count_extra_ref_in_vector(svp, (size_t)num_values);
    }
    else
    {
        /* Count the referenced closures and objects */
        if (type == CLOSURE_BOUND_LAMBDA)
        {
            lambda_t *l2 = l->function.lambda;

            if (NULL != register_pointer(ptable, l2) )
                count_extra_ref_in_closure(l2, CLOSURE_UNBOUND_LAMBDA);
        }
        else if (type == CLOSURE_ALIEN_LFUN)
        {
            count_extra_ref_in_object(l->function.alien.ob);
        }
    }

    if (type != CLOSURE_UNBOUND_LAMBDA)
        count_extra_ref_in_object(l->ob);
} /* count_extra_ref_in_closure() */

/*-------------------------------------------------------------------------*/
void
count_extra_ref_in_vector (svalue_t *svp, size_t num)

/* Count the extra_refs of all <num> values starting at <svp>.
 */

{
    svalue_t *p;

    if (!svp)
        return;

    for (p = svp; p < svp+num; p++)
    {
        switch(p->type)
        {

        case T_CLOSURE:
            if (CLOSURE_MALLOCED(p->x.closure_type))
            {
                lambda_t *l;

                l = p->u.lambda;
                if ( NULL == register_pointer(ptable, l) )
                    continue;
                count_extra_ref_in_closure(l, p->x.closure_type);
                continue;
            }
            /* FALLTHROUGH */

        case T_OBJECT:
          {
            count_extra_ref_in_object(p->u.ob);
            continue;
          }

        case T_QUOTED_ARRAY:
        case T_POINTER:
            p->u.vec->extra_ref++;
            if (NULL == register_pointer(ptable, p->u.vec) )
                continue;
            p->u.vec->extra_ref = 1;
            count_extra_ref_in_vector(&p->u.vec->item[0], VEC_SIZE(p->u.vec));
            continue;

        case T_MAPPING:
            if (NULL == register_pointer(ptable, p->u.map) ) continue;
            walk_mapping(
              p->u.map,
              count_extra_ref_in_mapping_filter,
              (void *)p->u.map->num_values
            );
            continue; /* no extra ref count implemented */
        }
    }
} /* count_extra_ref_in_vector() */

/*-------------------------------------------------------------------------*/
static void
check_extra_ref_in_vector (svalue_t *svp, size_t num)

/* Check the extra_refs of the <num> values starting at <svp>
 */

{
    svalue_t *p;

    if (!svp)
        return;

    for (p = svp; p < svp+num; p++)
    {
        switch(p->type)
        {
        case T_QUOTED_ARRAY:
        case T_POINTER:
            if (NULL == register_pointer(ptable, p->u.vec) )
                continue;
            check_extra_ref_in_vector(&p->u.vec->item[0], VEC_SIZE(p->u.vec));
            p->u.vec->extra_ref = 0;
            continue;

        case T_MAPPING:
            if (NULL == register_pointer(ptable, p->u.map) ) continue;
            walk_mapping(
              p->u.map,
              check_extra_ref_in_mapping_filter,
              (void *)((p_int)p->u.map->num_values)
            );
            continue; /* no extra ref count implemented */
        }
    }
} /* check_extra_ref_in_vector() */

/*-------------------------------------------------------------------------*/
void
check_a_lot_ref_counts (program_t *search_prog)

/* Loop through every object and variable in the game and check all
 * reference counts. This will surely take some time and should be
 * used only for debugging.
 *
 * If <search_prog> is set, the function will just count the references
 * and print the information for the given program, if found.
 *
 * The function must be called after removing all destructed objects.
 *
 * TODO: No extra_refs implemented in mappings.
 * TODO: Put this code somewhere else.
 */

{
    object_t *ob;
    int i;

    check_a_lot_ref_counts_search_prog = search_prog;

    /* Pass 1: Compute the ref counts.
     *
     * The pointer table keeps track of objects already visited,
     * eliminating the need for a separate pass to clear the
     * ref counts.
     */
    ptable = new_pointer_table();
    if (!ptable)
    {
        debug_message("%s Out of memory while checking all refcounts.\n"
                     , time_stamp());
        return;
    }

    /* List of all objects.
     */
    for (ob = obj_list; ob; ob = ob->next_all)
    {
        if (ob->flags & O_DESTRUCTED)
        {
            /* This shouldn't happen */
            debug_message("%s Found destructed object '%s' where it shouldn't "
                          "be.\n", time_stamp(), ob->name);
            continue;
        }
        if (O_VAR_SWAPPED(ob))
            load_ob_from_swap(ob);
        count_extra_ref_in_vector(ob->variables, (size_t)ob->extra_num_variables);
        count_extra_ref_in_object(ob);
    }

    if (master_ob)
        master_ob->extra_ref++;

    if (d_flag > 3)
    {
        debug_message("%s obj_list evaluated\n", time_stamp());
    }

    /* The current stack.
     */
    count_extra_ref_in_vector(VALUE_STACK, (size_t)(inter_sp - VALUE_STACK + 1));
    if (d_flag > 3)
    {
        debug_message("%s stack evaluated\n", time_stamp());
    }


    /* Other variables and lists.
     */
    count_extra_ref_from_call_outs();
    count_extra_ref_from_wiz_list();
    count_simul_efun_extra_refs(ptable);
    count_comm_extra_refs();

#ifdef TRACE_CODE
    {
        int j;

        for (j = TOTAL_TRACE_LENGTH; --j >= 0; )
        {
            if ( NULL != (ob = previous_objects[j]) )
                count_extra_ref_in_object(ob);
        }
    }
#endif

    count_extra_ref_in_vector(&indexing_quickfix, 1);
    count_extra_ref_in_vector(&last_indexing_protector, 1);
    null_vector.extra_ref++;

    /* To cound the closure hooks properly, we have to fiddle the
     * types a bit.
     */
    for (i = NUM_DRIVER_HOOKS; --i >= 0; )
    {
        if (driver_hook[i].type == T_CLOSURE
         && driver_hook[i].x.closure_type == CLOSURE_LAMBDA)
        {
            driver_hook[i].x.closure_type = CLOSURE_UNBOUND_LAMBDA;
        }
    }

    count_extra_ref_in_vector(driver_hook, NUM_DRIVER_HOOKS);

    /* Undo the type fiddling.
     */
    for (i = NUM_DRIVER_HOOKS; --i >= 0; )
    {
        if (driver_hook[i].type == T_CLOSURE
         && driver_hook[i].x.closure_type == CLOSURE_UNBOUND_LAMBDA)
        {
            driver_hook[i].x.closure_type = CLOSURE_LAMBDA;
        }
    }

    /* Done with the counting */
    free_pointer_table(ptable);

    /* Was that all for this time? */
    if (search_prog)
        return;

    /* Pass 3: Check the ref counts.
     *
     * The (new) pointer table is used as before.
     */
    ptable = new_pointer_table();
    if (!ptable)
    {
        debug_message("%s Out of memory while checking all refcounts.\n"
                     , time_stamp());
        return;
    }

    for (ob = obj_list; ob; ob = ob->next_all) {
        if (ob->flags & O_DESTRUCTED)  /* shouldn't happen */
            continue;

        if (ob->ref != ob->extra_ref)
        {
             debug_message("%s Bad ref count in object %s, %ld - %ld\n"
                          , time_stamp()
                          , ob->name, ob->ref, ob->extra_ref);
        }
        else if ( !(ob->flags & O_SWAPPED) )
        {
            if (ob->prog->ref != ob->prog->extra_ref)
            {
                /* an inheriting file might be swapped */
                if (time_to_swap + 1 > 0
                 && ob->prog->ref > ob->prog->extra_ref)
                {
                    debug_message("%s high ref count in prog %s, %ld - %ld\n"
                                 , time_stamp(), ob->prog->name, ob->prog->ref
                                 , ob->prog->extra_ref);
                }
                else
                {
                    check_a_lot_ref_counts(ob->prog);
                    debug_message("%s Bad ref count in prog %s, %ld - %ld\n"
                                 , time_stamp()
                                 , ob->prog->name
                                 , ob->prog->ref, ob->prog->extra_ref);
                }
            }
        } /* !SWAPPED */
        check_extra_ref_in_vector(ob->variables, (size_t)ob->extra_num_variables);
    } /* for */

    check_extra_ref_in_vector(VALUE_STACK, (size_t)(inter_sp - VALUE_STACK + 1));

    free_pointer_table(ptable);
} /* check_a_lot_of_ref_counts() */

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

#endif /* DEBUG */


/*=========================================================================*/

/*                             E F U N S                                   */

/*-------------------------------------------------------------------------*/
/* (Re)define a couple a macros for the efuns below
 */

#undef ERROR
#define ERROR(s) {inter_sp = sp; error(s);}

#undef TYPE_TEST1
#define TYPE_TEST1(arg1, type1, instruction) {\
    if ((arg1)->type != (type1)) {\
        bad_efun_arg(1, (instruction), sp);\
    }\
}

#undef TYPE_TEST2
#define TYPE_TEST2(arg1, type2, instruction) {\
    if ((arg1)->type != (type2)) {\
        bad_efun_arg(2, (instruction), sp);\
    }\
}

/*-------------------------------------------------------------------------*/
svalue_t *
f_trace (svalue_t *sp)

/* TEFUN trace()
 *
 *   int trace(int traceflags)
 *
 * Sets the trace flags and returns the old trace flags. When
 * tracing is on, a lot of information is printed during
 * execution and too much output can crash your connection or
 * even the whole driver.
 *
 * Tracing is done on a per-connection basis: each interactive(!)
 * user may specifiy its own tracelevel and -prefix. Each gets the
 * traceoutput for just the code executed during the evaluation
 * of the commands he entered.
 *
 * The trace bits are:
 *
 *   TRACE_NOTHING     (  0): stop tracing.
 *
 *   TRACE_CALL        (  1): trace all calls to lfuns.
 *   TRACE_CALL_OTHER  (  2): trace call_others()s.
 *   TRACE_RETURN      (  4): trace function returns.
 *   TRACE_ARGS        (  8): print function arguments and results.
 *   TRACE_EXEC        ( 16): trace all executed instructions.
 *   TRACE_HEART_BEAT  ( 32): trace heartbeat code.
 *   TRACE_APPLY       ( 64): trace driver applies.
 *   TRACE_OBJNAME     (128): print the object names.
 *
 * TRACE_EXEC and TRACE_HEART_BEAT should be avoided as they cause massive
 * output! TRACE_OBJNAME should be avoided when you know what you trace.
 *
 * The master-lfun valid_trace() is called to verify the
 * usage of this efun.
 */

{
    int ot;
    interactive_t *ip;

    TYPE_TEST1(sp, T_NUMBER, F_TRACE)
    ot = -1;

    /* If the command_giver is allowed to do so... */
    if (command_giver
     && O_SET_INTERACTIVE(ip, command_giver))
    {
        svalue_t *arg;

        assign_eval_cost();
        inter_sp = _push_volatile_string("trace", sp);
        push_number(sp->u.number);
        arg = apply_master(STR_VALID_TRACE, 2);
        if (arg)
        {
            /* ... then set the new tracelevel */
            if (arg->type != T_NUMBER || arg->u.number != 0)
            {
                ot = ip->trace_level;
                trace_level |=
                  ip->trace_level = sp->u.number;
            }
        }
    }

    /* Return the old level */
    sp->u.number = ot;
    SET_TRACE_EXEC;
    return sp;
} /* f_trace() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_traceprefix (svalue_t *sp)

/* TEFUN traceprefix()
 *
 *   string traceprefix(string prefix)
 *   string traceprefix(int dummy)
 *
 * If called with a string, only objects matching this prefix will be traced.
 * The string must not contain a leading "/" because the object names are
 * stored internally without it. If called with a number, the traceprefix will
 * be ignored and all objects will be traced. Returns the last traceprefix or
 * 0 if there wasn't any.
 *
 * The master-lfun valid_trace() is called to verify the usage of this
 * efun.
 */

{
    char *old;
    interactive_t *ip;

    if (sp->type != T_STRING && sp->type != T_NUMBER)
        bad_xefun_arg(1, sp);

    old = 0;

    /* If the command_giver is allowed to do that... */
    if (command_giver
     && O_SET_INTERACTIVE(ip, command_giver))
    {
        svalue_t *arg;

        inter_sp = _push_volatile_string("traceprefix", sp);
        inter_sp++; assign_svalue_no_free(inter_sp, sp);
        assign_eval_cost();
        arg = apply_master(STR_VALID_TRACE,2);
        if (arg)
        {
            /* ... then so shall it be */
            if (arg && (arg->type != T_NUMBER || arg->u.number))
            {
                old = ip->trace_prefix;
                if (sp->type == T_STRING)
                {
                    ip->trace_prefix = make_shared_string(sp->u.string);
                }
                else
                    ip->trace_prefix = NULL;
            }
        }
    }

    free_svalue(sp);

    /* Return the old prefix */
    if (old)
        put_string(sp, old);
    else
        put_number(sp, 0);
    return sp;
} /* f_traceprefix() */

/***************************************************************************/