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/
Date: Mon, 7 Feb 2000 18:24:15 +0100 (CET)
From: das mud <mud@omikron.wisc.de>
Short: Finalfrontier efuns
Type: Feature
State: New
See also: b-000207



unsere (devellop) ;)  efuns.c gezippt... 

also nach dem standard-efun teil kommen mysql funktionen und danach der
teil von righ ;)

ich weiss nicht ob er da noch was anfuegen wollte oder so.. aber du kannst
ja schonmal einen blick drauf werfen

bastian

/*---------------------------------------------------------------------------
 * Various Efuns.
 *
 *---------------------------------------------------------------------------
 * This file acts as a repository for various old and new efuns. Over the time
 * it will probably grow large enough to justify a split into several files.
 *
 * The implemented efuns, sorted by topic, are:
 *
 * Strings:
 *    tefun: make_shared_string()
 *    tefun: process_string() (optional)
 *    efun:  sscanf()
 *    efun:  terminal_colour()
 *    tefun: trim()
 *    tefun: upper_case()
 *
 * Objects:
 *    xefun: all_environment()
 *    vefun: clones()
 *    tefun: object_info()
 *    tefun: present_clone() (preliminary)
 *    tefun: present()
 *    tefun: set_is_wizard() (optional)
 *    tefun: set_modify_command()
 *    tefun: set_prompt()
 *    tefun: transfer() (optional)
 *     efun: say()
 *     efun: tell_room()
 *
 * Values:
 *    tefun: copy()
 *    tefun: deep_copy()
 *    vefun: filter()
 *
 * Others:
 *    tefun: debug_info()
 *    tefun: shutdown()
 *
 * mySQL:
 *    tefun: db_connect() (optional)
 *    tefun: db_close() (optional)
 *    tefun: db_exec() (optional)
 *    tefun: db_fetch() (optional)
 *    tefun: db_affected_rows() (optional)
 *    tefun: db_conv_string() (optional)
 *    tefun: db_handles() (optional)
 *
 *---------------------------------------------------------------------------
 */

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

#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>

#ifdef HAVE_LIBMYSQLCLIENT
#include <mysql/mysql.h>
#include <mysql/errmsg.h>
#endif

#define USES_SVALUE_STRLEN
#include "efuns.h"

#include "actions.h"
#include "array.h"
#include "backend.h"
#include "call_out.h"
#include "closure.h"
#include "comm.h"
#include "dumpstat.h"
#include "heartbeat.h"
#include "instrs.h"
#include "interpret.h"
#include "main.h"
#include "mapping.h"
#include "object.h"
#include "otable.h"
#include "ptrtable.h"
#include "rxcache.h"
#include "simulate.h"
#include "smalloc.h"
#include "stdstrings.h"
#include "stralloc.h"
#include "strfuns.h"
#include "swap.h"
#include "svalue.h"
#include "wiz_list.h"

#include "../mudlib/sys/debug_info.h"
#include "../mudlib/sys/objectinfo.h"
#include "../mudlib/sys/strings.h"

/* Forward declarations */
static void copy_svalue (svalue_t *dest, svalue_t *, struct pointer_table *);

/* Macros */

/* Typetests for xefuns/tefuns */

#define TYPE_TEST1(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_xefun_arg(1, sp);

#define TYPE_TEST2(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_xefun_arg(2, sp);

#define TYPE_TEST3(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_xefun_arg(3, sp);

/* Typetests for vararg xefuns/vefuns */

#define TYPE_TESTV1(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_xefun_vararg(1, sp);

#define TYPE_TESTV2(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_xefun_vararg(2, sp);

#define TYPE_TESTV3(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_xefun_vararg(3, sp);

/* Typetests for efuns */

#define E_TYPE_TESTV1(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_efun_arg(1, -2, sp);

#define E_TYPE_TESTV2(arg1,type1) \
  if ((arg1)->type != type1) \
      bad_efun_arg(2, -2, sp);

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

#ifdef F_SET_IS_WIZARD
Bool is_wizard_used = MY_FALSE;
  /* TODO: This flag can go when the special commands are gone. */
#endif


/*=========================================================================*/
/*                              STRINGS                                    */

/*-------------------------------------------------------------------------*/
svalue_t *
f_make_shared_string (svalue_t *sp)

/* TEFUN make_shared_string()
 *
 *    string make_shared_string(string s)
 *
 * If the passed string <s> is not shared, the efun enters it into
 * the shared string table and returns the shared version. Else the
 * passed string is returned.
 *
 * TODO: Improve the string handling of the driver so that this efun
 * TODO:: becomes unnecessary.
 */

{
    TYPE_TEST1(sp, T_STRING)

    if (sp->x.string_type != STRING_SHARED)
    {
        char *s = make_shared_string(sp->u.string);

        if (sp->x.string_type == STRING_MALLOC)
            xfree(sp->u.string);
        sp->u.string = s;
        sp->x.string_type = STRING_SHARED;
    }

    return sp;
} /* f_make_shared_string() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_trim (svalue_t *sp, int num_arg)

/* VEFUN trim()
 *
 *    string trim (string s [, int where [, string|int ch]])
 *
 * Remove all leading/trailing characters <ch> from the string <s>
 * and return the new string. <ch> may be a single character, or a string
 * of characters to be trimmed. If <ch> is not given or 0, it defaults
 * to " \t". <where> determines where to remove the characters:
 *   TRIM_LEFT:  remove the leading characters
 *   TRIM_RIGHT: remove the trailing characters
 *   TRIM_BOTH:  remove both leading and trailing characters.
 *
 * TODO: Expand this to remove interim characters as well?
 * TODO: Expand this to fold runs of embedded chs into just one?
 */

{
    svalue_t * argp;
    char *str, *end;     /* Pointer to string begin and end */
    char *left, *right;  /* Pointer to the strings left and right end */
    char def_ch[3]       /* Buffer for single characters to strip */
      = { '\t', ' ', '\0' };
    char *strip;         /* String of characters to strip */
    size_t strip_l;      /* Length of *strip */
    int  where;

    /* Get and test the arguments */
    if (num_arg > 3)
        error("Bad number of arguments to trim()\n");
    argp = sp - num_arg + 1;

    TYPE_TESTV1(argp, T_STRING)
    str = argp->u.string;

    if (num_arg > 1)
    {
        TYPE_TESTV2(argp+1, T_NUMBER)
        where = argp[1].u.number;
        if (!where)
            where = TRIM_LEFT|TRIM_RIGHT;
        if (where & ~(TRIM_LEFT|TRIM_RIGHT))
            error("Bad argument 2 to trim(): illegal value %ld\n", (long)where);
    }
    else
        where = TRIM_LEFT|TRIM_RIGHT;

    if (num_arg > 2)
    {
        if (argp[2].type == T_NUMBER)
        {
            if (argp[2].u.number <= 0 || argp[2].u.number >= 1 << CHAR_BIT)
                error("Bad argument 3 to trim(): %ld is not a character\n"
                     , argp[2].u.number);
            def_ch[0] = (char)argp[2].u.number;
            def_ch[1] = '\0';
            strip = def_ch;
            strip_l = 1;
        }
        else if (argp[2].type == T_STRING)
        {
            strip = argp[2].u.string;
            strip_l = svalue_strlen(argp+2);
        }
        else
            bad_xefun_vararg(3, sp);
    }
    else
    {
       strip = def_ch;
       strip_l = 2;
    }

    /* Get the string limits */
    end = str + strlen(str);
    if (where & TRIM_LEFT)
    {
        for (left = str
            ; *left != '\0' && strchr(strip, *left) != NULL
            ; left++
            ) NOOP;
    }
    else
        left = str;
    if (where & TRIM_RIGHT && end != left)
    {
        for (right = end; right != left && NULL != strchr(strip, right[-1])
            ; right--) NOOP;
    }
    else
        right = end;

    /* If there are things to strip, create a new string and put it
     * into the place of the old one.
     */
    if (left != str || right != end)
    {
        char * trimmed;
        size_t newlen;

        newlen = (unsigned)(right - left);
        trimmed = xalloc(newlen+1);
        if (!trimmed)
            error("Out of memory.\n");
        memcpy(trimmed, left, newlen);
        trimmed[newlen] = '\0';
        free_string_svalue(argp);
        put_malloced_string(argp, trimmed);
    }

    /* argp+2 might need to be freed, but argp+1 is always just a number.
     * And the result argp is fine as it is.
     */

    if (num_arg > 2 && argp[2].type == T_STRING)
        free_svalue(argp+2);

    return argp;
} /* f_trim() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_upper_case (svalue_t *sp)

/* TEFUN upper_case()
 *
 *    string upper_case (string s)
 *
 * Convert all characters in <s> to upper case and return the new string.
 */

{
    char *str, *s, *d, c;
    ptrdiff_t initial_len;

    TYPE_TEST1(sp, T_STRING)

    /* Find the first non-uppercase character in the string */
    for (s = sp->u.string; '\0' != (c = *s) && !islower((unsigned char)c); s++)
        NOOP;

    if ('\0' != *s)  /* there are lowercase characters */
    {
        if (STRING_MALLOC == sp->x.string_type)
        {
            /* MALLOCed strings can be changed in-place */
            for ( ; '\0' != (c = *s); s++)
            {
                if (islower((unsigned)c))
                    *s = (char)toupper(c);
            }
        }
        else
        {
            /* Other strings must be duplicated and then changed */
            str = xalloc(svalue_strlen(sp)+1);
            if (!str)
            {
                error("Out of memory.\n");
                /* NOTREACHED */
                return sp;
            }

            initial_len = s - sp->u.string;
            /* Copy the initial part */
            if (initial_len)
                memcpy(str, sp->u.string, (size_t)initial_len);

            /* Copy and change the rest */
            for (d = str + initial_len; '\0' != (c = *s++) ; )
            {
                if (islower((unsigned)c))
                    c = (char)toupper(c);
                *d++ = c;
            }

            *d = '\0';
            free_string_svalue(sp);
            put_malloced_string(sp, str);
        }
    }

    /* That's it */
    return sp;
}

/*-------------------------------------------------------------------------*/
static Bool
at_end (int i, int imax, int z, int *lens)

/* Auxilary function for e_terminal_colour().
 *
 * <z> is the position within string number <i>. <lens> is an array
 * with the lengths of all <imax> strings.
 *
 * The function returns true if there are no more characters to process
 * after <i>:<z> in all strings, else it returns false.
 */

{
    if (z + 1 != lens[i])
        return MY_FALSE;
    for (i++; i < imax; i++) {
        if (lens[i] > 0)
            return MY_FALSE;
    }
    return MY_TRUE;
}

/*-------------------------------------------------------------------------*/
char *
e_terminal_colour ( char * text, mapping_t * map
                  , int indent, int wrap
                  )

/* EFUN terminal_colour()
 *
 *   string terminal_colour (string text, mapping 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.
 *
 * The parameters wrap and indent are both optional, if only wrap is given
 * then the str will be linewrapped at the column given with wrap.  If indent
 * is given too, then all wrapped lines will be indented with the number of
 * blanks specified with indent.
 *
 * The wrapper itself ignores the length of the color macros and that what
 * they contain, it wraps the string based on the length of the other chars
 * inside. Therefor it is color-aware.
 *
 * This function is called from the evaluator and provided with the
 * proper arguments.
 *
 * Result is a pointer to the final string. If no changes were necessary,
 * this is <text> again; otherwise it is a pointer to memory allocated
 * by the function.
 *
 * TODO: This efun needs better definitions, ie. how keywords and %^ relate,
 * TODO:: (%^key%^ or %^key or...), how to escape %^s, etc.
 */

{
#define CALLOCATE(num, type) ((type *)xalloc(sizeof(type[1]) * (num) ))
  /* Allocate a block of <num> elements of <type>
   */

#define RESIZE(ptr, num, type) ((type *)rexalloc((void *)ptr, sizeof(type) * (num)))
  /* Resize the block <ptr> to hold <num> elements of <type>.
   */

#define NSTRSEGS       32
  /* Allocation increment. */

#define TC_FIRST_CHAR  '%'
#define TC_SECOND_CHAR '^'
  /* The two magic characters.
   */

#define MAX_STRING_LENGTH 200000
  /* The maximum length of the result.
   */

    char *cp;              /* Workpointer */
    char *savestr = NULL;  /* Allocated auxiliary string */
    char *instr;
      /* The input string. This may be <text> itself, or a working copy. */
    char *deststr;         /* Result string */
    char **parts;
      /* The <num> delimited parts from <instr>. This are mostly
       * pointers into *<instr>, but can also be (uncounted) pointers to
       * the string data in <map>.
       */
    int num;               /* Number of delimited parts in <instr> */
    int *lens;
      /* Length of the <num> parts. This value is negative for strings
       * 'retrieved' from the <map>ping when wrapping is required. This
       * is necessary to determine which parts[] to exempt from the
       * wrapping calculation.
       */
    int k;                 /* Index within a string */
    int col;               /* Current print column */
    int j;                 /* Accumulated total length of result */
    int j_extra;           /* Temporary extra length of result before fmt'ing */
    int start;             /* Col of first non-blank character */
    int space;             /* Col of last space char */
    int i;
    Bool maybe_at_end;     /* TRUE if the next text might start a new line */
    Bool no_keys;          /* TRUE if no delimiter in the string */

    instr = text;

    /* Find the first occurance of the magic character pair.
     * If found, duplicate the input string into instr and
     * let cp point into that copy at the delimiter.
     * If not found, cp will be NULL.
     */
    cp = text;
    do {
        cp = strchr(cp, TC_FIRST_CHAR);
        if (cp)
        {
            if (cp[1] == TC_SECOND_CHAR)
            {
                savestr = string_copy(text);
                cp = savestr + (cp - text);
                instr = savestr;
                break;
            }
            cp++;
        }
    } while (cp);

    /* If the delimiter was found, split up the instr into the
     * parts and store them. If not found, just return.
     */
    no_keys = MY_FALSE;
    if (cp == NULL)
    {
        /* No delimiter found - but maybe we need to wrap */
        if (wrap)
        {
            /* Yup, just fake one delimited part which just happens
             * to not match anything in the mapping.
             */
            num = 1;
            parts = CALLOCATE(1, char *);
            parts[0] = instr;
            savestr = NULL;  /* should be NULL anyway */
            no_keys = MY_TRUE;
        }
        else
        {
            /* no delimiter in string and no wrapping, so return the original.
             */
            return text;
        }
    }
    else
    {
        /* There are delimiters in the string. Find them all, let the
         * pointers in *<parts> point to the strings delimited by
         * them, and let those parts end with a '\0'.
         * This means modifying the *<instr>, but it is already
         * a copy.
         */

        /* cp here points to the first delimiter found */

        parts = CALLOCATE( NSTRSEGS, char * );
        if (!parts)
        {
            error("Out of memory.\n");
            /* NOTREACHED */
            return NULL;
        }

        /* The string by definition starts with a non-keyword,
         * which might be empty.
         * Initialize our variables accordingly.
         */
        num = 1;
        parts[0] = instr;
        *cp = '\0';

        /* Search and find the other delimited segments.
         * Loop variant: cp points to the last delimiter found,
         * its first character replaced by \0, or cp points to NULL (exit
         * condition)
         * Loop invariant: instr points to the begin of the last delimited
         * segment.
         */
        while (cp)
        {
            /* Skip the delimiter found last and search the next */
            cp += 2;
            instr = cp;
            do
            {
                cp = strchr(cp,TC_FIRST_CHAR);
                if (cp) {
                    if (cp[1] == TC_SECOND_CHAR)
                        break;
                    cp++;
                }
            } while (cp);

            if (cp)
            {
                /* Another delimiter found: put it into the parts array.
                 */
                *cp = '\0';
                parts[num] = instr;
                num++;
                if (num % NSTRSEGS == 0)
                    parts = RESIZE(parts, num + NSTRSEGS, char * );
            }
        }

        /* Trailing part, or maybe just a delimiter */
        if (*instr)
            parts[num++] = instr;
    } /* if (delimiter found or not) */

    /* Prepare the lens[] array */
    if ( num )
        lens = CALLOCATE(num, int);
    else
        lens = NULL;

    /* Do the the keyword replacement and calculate the lengths.
     * The lengths are collected in the lens[] array to save the
     * need for repeated strlens().
     */
    col = 0;
    start = -1;
    space = 0;
    maybe_at_end = MY_FALSE;
    j = 0; /* gathers the total length of the final string */
    j_extra = 0; /* gathers the extra length needed during fmt'ing */
    for (i = 0; i < num; i++)
    {
        long len;
        char * str;
        svalue_t * mdata;

        /* If parts[i] is a valid colour key, there must exist a shared
         * string for it. Is that the case, look up parts[i] in the
         * mapping and set the result in mdata, otherwise save that effort.
         * However, if i is even, parts[i] is by definition not a colour
         * key.
         */
        mdata = NULL;
        if (i % 2 && !no_keys)
        {
            if (parts[i][0] == '\0') /* Empty key - already handled */
                str = NULL;
            else
                str = findstring(parts[i]);
            if (str != NULL)
            {
                svalue_t mkey;

                put_string(&mkey, str);
                 /* The only use of mkey is to index a mapping - an
                  * operation which will not decrement the refcount
                  * for <str>. This makes it safe to not count the
                  * ref by mkey here, and saves a bit time.
                  */

                /* now look for mapping data */
                mdata = get_map_value(map, &mkey);
            }
        }
        else if (!(i % 2) && !no_keys
              && i < num -1 && parts[i+1][0] == '\0')
        {
            /* Special case: the following colour key is the empty "%^%^".
             * We interpret it as literal "%^" and add it to this part.
             * Both part[i] and part[i+1] will end with the same '\0'.
             */
            parts[i+1][-2] = TC_FIRST_CHAR;
        }

        /* If mdata found a string, use it instead of the old parts[i].
         * Note its length, making it negative where necessary.
         */
        if ( mdata && mdata->type == T_STRING )
        {
            parts[i] = mdata->u.string;
            len = (long)svalue_strlen( mdata );
            if (wrap)
                len = -len;
        }
        else
            len = (long)strlen(parts[i]);

        lens[i] = len;
        if (len > 0)
        {
            /* This part must be considered for wrapping/indentation */

            if (maybe_at_end)
            {
                /* This part may start a new line, so count in the indent */

                if (j + indent > MAX_STRING_LENGTH)
                {
                    /* This string no longer counts, so we are still in a
                     * maybe_at_end condition.  This means we will end up
                     * truncating the rest of the fragments too, since the
                     * indent will never fit.
                     */
                    lens[i] = 0;
                    len = 0;
                }
                else
                {
                    j += indent;
                    col += indent;
                    maybe_at_end = MY_FALSE;
                }
            }

            /* Add the new string to the total length */
            j += len;
            if (j > MAX_STRING_LENGTH)
            {
                /* Overflow: shorten this fragment to fit (and all
                 * the following ones will be shortened to 0 length).
                 */
                lens[i] -= j - MAX_STRING_LENGTH;
                j = MAX_STRING_LENGTH;
            }

            /* If wrapping is requested, perform the analysis */
            if (wrap)
            {
                int   z;             /* Index into the current string */
                char *p = parts[i];  /* Pointer into the current string */

                for (z = 0; z < lens[i]; z++)
                {
                    char c = p[z];   /* current character */

                    if (c == '\n')
                    {
                        /* Hard line break: start a new line */
                        col = 0;
                        start = -1;
                    }
                    else
                    {
                        /* All space characters in columns before col <start>
                         * do not count.
                         */
                        if (col > start || c != ' ')
                            col++;
                        else
                        {
                            j--;
                            j_extra++;
                        }

                        /* If space, remember the position */
                        if (c == ' ')
                            space = col;

                        if (col == wrap+1)
                        {
                            /* Wrapping necessary */

                            if (space)
                            {
                                /* Break the line at the last space */
                                col -= space;
                                space = 0;
                            }
                            else
                            {
                                /* No space within range: simply let this
                                 * one extent over the wrap margin and
                                 * restart counting.
                                 */
                                j++;
                                col = 1;
                            }

                            /* Reset the start column. */
                            start = indent;
                        }
                        else
                            continue; /* the for(z) */
                    }

                    /* If we get here, we ended a line */

                    if (col || z + 1 != lens[i])
                    {
                        /* Not at the end of the fragment: count in
                         * the indent from the new line.
                         */
                        j += indent;
                        col += indent;
                    }
                    else
                        maybe_at_end = MY_TRUE;

                    /* Guard against overflow */
                    if (j > MAX_STRING_LENGTH)
                    {
                        /* Reduce this part to fit; all the following
                         * parts will be reduced to shreds^W0.
                         */
                        lens[i] -= (j - MAX_STRING_LENGTH);
                        j = MAX_STRING_LENGTH;
                        if (lens[i] < z)
                        {
                            /* must have been ok or we wouldn't be here */
                            lens[i] = z;
                            break;
                        }
                    }
                } /* for (z = 0..lens[i]) */
            } /* if (wrap) */
        }
        else
        {
            /* This replacement does not need to be wrapped. */
            j += -len;
            if (j > MAX_STRING_LENGTH)
            {
                /* Max length exceeded: shrink the working length
                 * to something usable. All following fragments
                 * will be shrunk to length 0.
                 */
                lens[i] = -(-(lens[i]) - (j - MAX_STRING_LENGTH));
                j = MAX_STRING_LENGTH;
            }
        } /* if (len > 0) */
    } /* for (i = 0..num) */


    /* Now we have the final string in parts and length in j.
     * let's compose the result, wrapping it where necessary.
     */
    deststr = xalloc((size_t)(j+1));
    if (!deststr)
    {
        error("Out of memory.\n");
        /* NOTREACHED */
        return NULL;
    }

    cp = deststr; /* destination pointer */

    if (wrap)
    {
        /* Catenate and wrap the parts together. This will look similar
         * to the length computation above.
         */

        int space_garbage = 0;
          /* Number of characters to be ignored since the last space,
           * most of them are control codes and other junk.
           */
        size_t tmpmem_size;
        char *tmpmem;
          /* Temporary buffer for the current line */
        char *pt;
          /* Pointer into tmpmem */

        tmpmem_size = (size_t)j+j_extra+1;
          /* Actually, the allocated '+j_extra' size is never used, but
           * it makes the sanity check below simpler.
           */
        tmpmem = xalloc(tmpmem_size);
        if (!tmpmem)
        {
            error("Out of memory.\n");
            /* NOTREACHED */
            return NULL;
        }

        col = 0;
        start = -1;
        space = 0;
        pt = tmpmem;

        /* Loop over all parts */
        for (i = 0; i < num; i++)
        {
            int kind;            /* The kind of a line break */
            int len;             /* Actual length of the line */
            int l = lens[i];     /* Length of current part */
            char *p = parts[i];  /* Current part */

            if (pt - tmpmem + ((l < 0) ? -l : l) >= tmpmem_size)
            {
                error("Partial string too long (%ld+%ld >= %ld).\n"
                     , (long)(pt - tmpmem), (long)((l < 0) ? -l : l)
                     , (long)tmpmem_size);
                /* NOTREACHED */
                return NULL;
            }

            if (l < 0)
            {
                /* String retrieved from the mapping: not to be counted */
                memcpy(pt, p, (size_t)-l);
                pt += -l;
                space_garbage += -l;
                continue;
            }

            /* Loop over the current part, copying and wrapping */
            for (k = 0; k < lens[i]; k++)
            {
                int n;
                char c = p[k];  /* Current character */

                /* Copy the character into tmpmem */
                *pt++ = c;

                if (c == '\n')
                {
                    /* Start a new line */
                    col = 0;
                    kind = 0;
                    start = -1;
                }
                else
                {
                    /* All space characters in columns before col <start>
                     * do not count.
                     */
                    if (col > start || c != ' ')
                        col++;
                    else
                        pt--;

                    /* If space, remember the position */
                    if (c == ' ')
                    {
                        space = col;
                        space_garbage = 0;
                    }

                    /* Wrapping necessary? */
                    if (col == wrap+1)
                    {
                        if (space)
                        {
                            /* Break at last space */
                            col -= space;
                            space = 0;
                            kind = 1;
                        }
                        else
                        {
                            /* No space within range: simply let this
                             * one extent over the wrap margin and
                             * restart counting.
                             */
                            col = 1;
                            kind = 2;
                        }

                        /* Reset the start column */
                        start = indent;
                    }
                    else
                        continue;
                }

                /* If we get here, we ended a line, and kind tells us why:
                 *   kind == 0: hard line break
                 *           1: line wrapped at suitable space
                 *           2: line extended over the limit with no space
                 */

                len = (kind == 1 ? col + space_garbage : col);

                /* Determine the length of the _previous_ (and therefore
                 * wrapped) line and copy it from tmpmem into deststr.
                 */
                n = (pt - tmpmem) - len;
                memcpy(cp, tmpmem, (size_t)n);
                cp += n;

                if (kind == 1)
                {
                    /* replace the space with the newline */
                    cp[-1] = '\n';
                }
                if (kind == 2)
                {
                    /* need to insert a newline */
                    *cp++ = '\n';
                }

                /* Remove the previous line from tmpmem */
                move_memory(tmpmem, tmpmem + n, (size_t)len);
                pt = tmpmem + len;

                if (len > space_garbage || !at_end(i, num, k, lens))
                {
                    /* There will be data coming next: insert the
                     * indentation.
                     */
                    memset(cp, ' ', (size_t)indent);
                    cp += indent;
                    col += indent;
                }
            }
        } /* for(i = 0..num) */

        memcpy(cp, tmpmem, (size_t)(pt - tmpmem));
        cp += pt - tmpmem;
        xfree(tmpmem);
    }
    else
    {
        /* No wrapping: just catenate the parts (and all lens[] entries
         * are positive here)
         */
        for (i = 0; i < num; i++)
        {
            memcpy(cp, parts[i], (size_t)lens[i]);
            cp += lens[i];
        }
    }

    /* Terminate the string */
    *cp = '\0';

    if ( lens )
      xfree(lens);
    if ( parts )
      xfree(parts);
    if (savestr)
      xfree(savestr);

    /* now we have what we want */
#ifdef DEBUG
    if (cp - deststr != j) {
      fatal("Length miscalculated in terminal_colour()\n"
            "    Expected: %i Was: %ld\n"
            "    String: %s\n    Indent: %i Wrap: %i\n"
           , j, (long)(cp - deststr), text, indent, wrap);
    }
#endif
    return deststr;

#undef CALLOCATE
#undef RESIZE
#undef NSTRSEGS
#undef TC_FIRST_CHAR
#undef TC_SECOND_CHAR
} /* e_terminal_colour() */

#ifdef F_PROCESS_STRING
/*-------------------------------------------------------------------------*/
static char *
process_value (char *str)

/* Helper function for process_string(): take a function call in <str>
 * in the form "function[:objectname]{|arg}" and try to call it.
 * If the function exists and returns a string, the result is a pointer
 * to the string, which must be copied immediately.
 * If the function can't be called, or does not return a string, the
 * result is NULL.
 */

{
    svalue_t *ret;     /* Return value from the function call */
    char     *func;    /* Copy of the <str> string for local modifications */
    char     *func2;   /* Shared string with the function name from <func> */
    char     *obj;     /* NULL or points to the object part in <func> */
    char     *arg;     /* NULL or points to the first arg in <func> */
    char     *narg;    /* Next argument while pushing them */
    int       numargs; /* Number of arguments to the call */
    object_t *ob;

    /* Simple check if the argument is valid */
    if (strlen(str) < 1 || !isalpha((unsigned)(str[0])))
        return NULL;

    /* Copy the argument so that we can separate the various
     * parts with \0 characters.
     */
    func = string_copy(str);

    /* Find the object and the argument part */
    arg = strchr(func,'|'); if (arg) { *arg='\0'; arg++; }
    obj = strchr(func,':'); if (obj) { *obj='\0'; obj++; }

    /* Check if the function exists at all. apply() will be delighted
     * over the shared string anyway.
     */
    if ( NULL == (func2 = findstring(func)) )
    {
        xfree(func);
        return NULL;
    }


    /* Get the object */
    if (!obj)
        ob = current_object;
    else
        ob = find_object(obj);

    if (!ob)
    {
        xfree(func);
        return NULL;
    }

    /* Push all arguments as strings to the stack
     */
    for (numargs = 0; arg; arg = narg)
    {
        narg = strchr(arg,'|');
        if (narg)
            *narg = '\0';
        push_string_malloced(arg);
        numargs++;
        if (narg)
        {
            *narg = '|';
            narg++;
        }
    }

    /* We no longer need this */
    xfree(func);
    
    /* Apply the function and see if adequate answer is returned.
     */
    ret = apply(func2, ob, numargs);

    if (ret && ret->type == T_STRING)
        return ret->u.string;
        /* The svalue is stored statically in apply_return_value */

    return NULL;
} /* process_value() */

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

/* EFUN process_string()
 *
 *     string process_string(string str)
 *
 * Searches string str for occurences of a "value by function
 * call", which is @@ followed by an implicit function call. See
 * "value_by_function_call" in the principles section.
 *
 * The value should contain a string like this:
 * @@function[:filename][|arg|arg]@@
 *
 * function must return a string or else the string which should be
 * processed will be returned unchanged.
 *
 * Note that process_string() does not recurse over returned
 * replacement values. If a function returns another function
 * description, that description will not be replaced.
 *
 * Both filename and args are optional.
 *
 * TODO: OSB has a bugfix for this function.
 */

svalue_t *
f_process_string(svalue_t *sp)
{
    vector_t   *vec;           /* Arg string exploded by '@@' */
    object_t   *old_cur;       /* Old current object */
    wiz_list_t *old_eff_user;  /* Old euid */
    int         pr_start;      /* Index of the first pr-spec in vec */
    int         il;            /* Index in vec */
    Bool        changed;       /* True if there was a replacement */
    Bool        ch_last;       /* True if the last vec-entry was replaced */
    char *p0, *p1, *p2;
    char *buf;                 /* Result string(s) */
    char *str;                 /* The argument string */

    TYPE_TEST1(sp, T_STRING);
    str = sp->u.string;
    
    if (!str || !(p1 = strchr(str,'@')))
        return sp;  /* Nothing to do */

    old_eff_user = NULL;
    old_cur = current_object;

    if (!current_object)
    {
        /* This means we are called from notify_ in comm1
         * We must temporary set eff_user to backbone uid for
         * security reasons.
         */

        svalue_t *ret;

        current_object = command_giver;
        ret = apply_master_ob(STR_GET_BB_UID,0);
        if (!ret)
            return sp;

        if (ret->type != T_STRING
         && (strict_euids || ret->type != T_NUMBER || ret->u.number))
            return sp;

        if (current_object->eff_user)
        {
            old_eff_user = current_object->eff_user;
            if (ret->type == T_STRING)
                current_object->eff_user = add_name(ret->u.string);
            else
                current_object->eff_user = NULL;
        }
    }

    /* Explode the argument by the '@@' */
    /* TODO: Rewrite to use new explode_string() */
    vec = old_explode_string(str,"@@");
    if (!vec)
        return sp;
    push_referenced_vector(vec); /* automatic free in case of errors */

    pr_start = ((str[0]=='@') && (str[1]=='@')) ? 0 : 1;

    for ( ch_last = MY_FALSE, changed = MY_FALSE, il = pr_start
        ; (size_t)il < VEC_SIZE(vec)
        ; il++)
    {
        p0 = vec->item[il].u.string;

        /* Try to interpret the entry as function call.
         * If that succeeds, hold the result (freshly allocated) in p2.
         */
        p1 = strchr(p0, ' ');
        if (!p1)
        {
            /* No space, the whole entry might be a function call */
            p2 = process_value(p0);
            if (p2)
            {
                /* Yup, it is: copy the result */
                p2 = string_copy(p2);
                ch_last = MY_TRUE;
            }
        }
        else
        {
            /* There is a space: just interpret the characters before
             * as possible function call.
             */
            size_t len;

            len = (unsigned)(p1 - p0);
            buf = xalloc(len + 1);
            strncpy(buf, p0, len);
            buf[len] = '\0';
            p2 = process_value(buf);
            if (p2)
            {
                /* We got a result: join it with the remains after the
                 * space and put it into p2.
                 */
                char * tmp;

                len = strlen(p2);
                tmp = xalloc(len + strlen(p1) + 1);
                strcpy(tmp,p2);
                strcpy(tmp+len,p1);
                p2 = tmp;
            }
            xfree(buf);
        }
        
        if (!p2)
        {
            /* No replacement by function call */
            if (!ch_last)
            {
                /* ...but we have to recreate the '@@' from the original */
                p2 = xalloc(3+strlen(p0));
                strcpy(p2,"@@");
                strcpy(p2+2,p0);
            }
            else
            {
                ch_last = MY_FALSE;
            }
        }
        else
        {
            /* Mark that we have a true replacement */
            changed = MY_TRUE;
        }

        /* If we have a replacement string, put it into place. */
        if (p2)
        {
            xfree(p0);
            vec->item[il].u.string = p2;
        }
    } /* for() */

    /* If there were changes, implode the vector again */
    if (changed)
        buf = implode_string(vec, "");
    else
        buf = NULL;

    /* Clean up */
    inter_sp--;
    free_array(vec);

    if (old_eff_user)
    {
        current_object->eff_user = old_eff_user;
    }

    current_object = old_cur;

    /* Return the result */
    if (buf)
    {
        free_string_svalue(sp);
        put_malloced_string(sp, buf);
    }

    return sp;
}

#endif /* F_PROCESS_STRING */

/*-------------------------------------------------------------------------*/
/* Structures for sscanf() */

/* Flags for every argument whether to assign and/or count it
 */

struct sscanf_flags {
    int do_assign: 16;
    int count_match: 16;
};

/* Packet of information passed between the scan functions:
 */

struct sscanf_info
{
    svalue_t *arg_start;    /* first argument for the current %-spec */
    svalue_t *arg_current;  /* current argument to consider */
    svalue_t *arg_end;      /* the last argument */
    char          *fmt_end;
      /* After the match: the next character in the fmt-string to match.
       */
    char          *match_end;
      /* After the match: the next character in the in-string to match.
       * NULL for 'no match'.
       */
    mp_uint        field;        /* Numbers: parsed fieldwidth */
    mp_uint        min;          /* Numbers: parsed precision */
    mp_uint        string_max;   /* Strings: parsed fieldwidth */
    mp_uint        string_min;   /* Strings: parsed 'precision' */
    struct sscanf_flags flags;
    int            sign;         /* -1 for '%-d', 0 for '%d', '%+d' or '%u' */
    mp_int         number_of_matches;  /* Number of matches so far */
};

/*-------------------------------------------------------------------------*/
static void
sscanf_decimal (char *str, struct sscanf_info *info)

/* Parse a number from <str> according the .field and .min given in <info>,
 * and, if successfull, store it in <info>->arg_current, which is then
 * incremented.
 *
 * <info>.match_end and .fmt_end are set properly on return.
 */

{
    static svalue_t tmp_svalue = { T_NUMBER };

    mp_int i, num;
    char c;

    num = 0;

    i = (mp_int)info->min;
    if (i > 0)
    {
        /* The number must have at least i digits */
        info->field -= i;
        do
        {
            if (!lexdigit(c = *str))
            {
                if (info->fmt_end[-1] != 'd')
                {
                    info->match_end = NULL;
                }
                else
                {
                    info->match_end = str;
                    info->fmt_end = "d"+1;
                }
                return;
            }
            str++;
            num = num * 10 + c - '0';
        } while (--i);
    }

    /* There can be info->field more digits */
    i = (mp_int)info->field;
    while  (--i >= 0)
    {
        if (!lexdigit(c = *str))
            break;
        str++;
        num = num * 10 + c - '0';
    }

    info->match_end = str;

    if (info->flags.do_assign)
    {
        /* Assign the parsed number */
        if (info->arg_current >= info->arg_end)
            return;

        tmp_svalue.u.number = (p_int)((num ^ info->sign) - info->sign);
        transfer_svalue((info->arg_current++)->u.lvalue, &tmp_svalue);
    }

    info->number_of_matches += info->flags.count_match;
    return;
}

/*-------------------------------------------------------------------------*/
static char *
sscanf_match_percent (char *str, char *fmt, struct sscanf_info *info)

/* Match a %-specification, called from sscanf_match().
 *
 * <fmt> points to the first character after the '%'.
 * <str> points to the first character to match.
 *
 * Return new value for <str> if matching is to be continued, else
 * return NULL and write in info->match_end the match end if a match was
 * found,  NULL otherwise.
 *
 * If a match was found, also write info->fmt_end with a pointer to the
 * conversion character, and info->flags, info->field, info->min.
 */

{
    char c;
    mp_uint *nump; /* Pointer to parsed fieldwidth resp. precision */

    /* Initialize field with a large value that will become
     * zero when doubled. Because 10 is divisible by 2, the multiply
     * will zero it. Note that it is negative before we decrement it
     * the first time.
     */
    *(nump = &info->field) = (((mp_uint)-1 / 2)) + 1;
    info->min = 1;
    info->flags.do_assign = 1;
    info->flags.count_match = 1;

    for (;;)
    {
        switch(c = *fmt++)
        {
        case '!':
            info->flags.count_match ^= 1;
            info->flags.do_assign ^= 1;
            continue;

        case '~':
            info->flags.do_assign ^= 1;
            continue;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            *nump = *nump * 10 + c - '0';
            continue;

        case '*':
            if (info->arg_current >= info->arg_end
             || info->arg_current->u.lvalue->type != T_NUMBER)
            {
                info->match_end = NULL;
                return NULL;
            }
            *nump = (mp_uint)((info->arg_current++)->u.lvalue->u.number);
            continue;

        case '.':
            *(nump = &info->min) = 0;
            continue;

        case 'd':
            /* Skip leading whitespace */
            while(isspace((unsigned char)*str))
                str++;
            /* FALLTHROUGH */

        case 'D':
            /* Match a signed number */
            if (*str == '-')
            {
                info->sign = -1;
                str++;
            }
            else
            {
                if (*str == '+')
                    str++;
                info->sign = 0;
            }
            info->fmt_end = fmt;
            sscanf_decimal(str, info);
            return NULL;

        case 'U':
            /* Match an unsigned number */
            info->sign = 0;
            info->fmt_end = fmt;
            sscanf_decimal(str, info);
            return NULL;

        case 's':
            /* Match a string */
            /* min = (min was explicitly given) ? min : 0; */
            info->string_max = info->field;
            info->field = 0;
            info->string_min = *nump;
            info->fmt_end = fmt;
            info->match_end = str;
            return NULL;

        default:
            error("Bad type : '%%%c' in sscanf fmt string.\n", fmt[-1]);
            return 0;

        case 't':
          {
            /* Skip whitespaces */

            mp_int i;

            info->field -= (i = (mp_int)info->min);

            /* Required whitespace */
            while (--i >= 0)
            {
                if (!isspace((unsigned char)*str))
                {
                    info->match_end = NULL;
                    return NULL;
                }
                str++;
            }

            /* Optional whitespace */
            i = (mp_int)info->field;
            while (--i >= 0)
            {
                if (!isspace((unsigned char)*str))
                    break;
                str++;
            }
            info->fmt_end = fmt;
            return str;
          }
        } /* switch(*fmt) */
    } /* forever */
} /* sscanf_match_percent() */

/*-------------------------------------------------------------------------*/
static void
sscanf_match (char *str, char *fmt, struct sscanf_info *info)

/* Find position in <str> after matching text from <fmt>, and place it in
 * info->match_end.
 * Set info->match_end to NULL for no match.
 * Set info->fmt_end to a guaranteed static '\0' when the fmt string ends.
 */

{
    char c;

    /* (Re)set the current argument */
    info->arg_current = info->arg_start;

    /* Loop over the format string, matching characters */
    for (;;)
    {
        if ( !(c = *fmt) )
        {
            info->match_end = str;
            info->fmt_end = "d"+1;
            return;
        }

        fmt++;

        if (c == '%')
        {
            c = *fmt;
            if (c != '%')
            {
                /* We have a format specifier! */
                char *new_str;

                new_str = sscanf_match_percent(str, fmt, info);
                if (!new_str)
                    return; /* Failure or string specifier */

                str = new_str;
                fmt = info->fmt_end;
                continue;
            }
            fmt++;
        }

        if (c == *str++)
        {
            continue;
        }
        else
        {
            info->match_end = NULL;
            return;
        }
    }
} /* sscanf_match() */

/*-------------------------------------------------------------------------*/
static char *
sscanf_search (char *str, char *fmt, struct sscanf_info *info)

/* sscanf() found a possible '%s' match. This function finds the start
 * of the next match in <str> and returns a pointer to it.
 * If none can be found, NULL is returned.
 */

{
    char a, b, c;
    mp_int n;

    a = *fmt;
    if (!a)
    {
        /* End of format: match all */
        info->fmt_end = "d"+1;
        info->arg_current = info->arg_start;
        return info->match_end = str + strlen(str);
    }
    fmt++;
    b = *fmt++;

    if (a == '%')
    {
        if (b != '%')
        {
            /* It's another %-spec: match it */
            for (fmt -= 2; *str; str++)
            {
                sscanf_match(str, fmt, info);
                if (info->match_end)
                    return str;
            }
            return NULL;
        }
        else
        {
            /* Double '%' stands for '%' itself */
            b = *fmt++;
        }
    }

    /* a and b are now the 'next two' characters from fmt, and they
     * don't start a %-spec.
     */

    if (b == a)
    {
        /* A run of identical characters: set n to the length */

        n = 0;
        do {
            n++;
            b = *fmt++;
        } while (b == a);

        if (a == '%')
        {
            /* n fmt-'%' represent (n/2) real '%'s */
            if (n & 1)
            {
                n >>= 1;
                fmt--;
                goto a_na_search;
            }
            n >>= 1;
        }

        if (b == '\0')
        {
            fmt--;
            goto a_na_search;
        }

        if (b == '%')
        {
            /* Since a is not '%' here, this may be the next %-spec */
            b = *fmt++;
            if (b != '%')
            {
                fmt -= 2;
                goto a_na_search;
            }
        }

        /* Search in <str> for the sequence <a>, (<n>+?)*<a>, <b>.
         * <b> is a character which starts a successfull new match.
         * To find this, the function tries a match at every possible <b>
         * it finds.
         *
         * If the <b> is found, all the characters before belong to
         * the previous %s match, if not found, the whole string
         * belongs to the match.
         */
        {
            char ch;
            mp_int i;

a_na_b_search:
            if ( !(ch = *str++) )
                return NULL;

            /* First <a> ? */
            if (ch != a)
                goto a_na_b_search;

            /* Followed by <n> <a>s? */
            i = n;
            do {
                if ( !(ch = *str++) )
                    return NULL;
                if (ch != a)
                    goto a_na_b_search;
            } while (--i);

            /* There may be more <a>s */
            do {
                if ( !(ch = *str++) )
                    return NULL;
            } while (ch == a);

            /* If followed by <b>, we may have found the next match */
            if (ch == b)
            {
                sscanf_match(str, fmt, info);
                if (info->match_end)
                    return str - n - 2;
            }

            /* Not found: start all over */
            goto a_na_b_search;
        }
        /* NOTREACHED */
    }

    if (!b)
    {
        /* Special case: the sequence is just <a> */
        n = 0;
        fmt--;

        /* Search in <str> for the sequence <a>, (<n>+?)*<a>, 'x'.
         * 'x' is a character which starts a successfull new match.
         * To find this, the function tries a match at every possible 'x'
         * it finds.
         *
         * If the 'x' is found, all the characters before belong to
         * the previous %s match, if not found, the whole string
         * belongs to the match.
         */
        {
            char ch;
            mp_int i;

a_na_search:
            if ( !(ch = *str++) )
                return NULL;

            /* First <a>? */
            if (ch != a)
                goto a_na_search;

            /* Followed by <n> <a>s? */
            if ( 0 != (i = n)) do {
                if ( !(ch = *str++) )
                    return NULL;
                if (ch != a)
                    goto a_na_search;
            } while (--i);

            /* For every other character, test if the next match starts here */
            do {
                sscanf_match(str, fmt, info);
                if (info->match_end)
                    return str - n - 1;
                if ( !(ch = *str++) )
                    return NULL;
            } while (ch == a);

            /* Not found: start all over */
            goto a_na_search;
        }
        /* NOTREACHED */
    }

    if (b == '%')
    {
        /* Special case: <a>, (<n>+?)*<a>, which we know will
         * be successfull.
         */
        b = *fmt++;
        if (b != '%')
        {
            fmt -= 2;
            n = 0;
            goto a_na_search;
            /* "goto, goto, goto - this is sooo ugly" says Tune */
        }
    }

    /* a != b && b != '%' here */

    c = *fmt;
    if (!c)
    {
        /* Special case: <a>, (0+?)*<b>, '\0' which we know will
         * be successfull because the fmt ends.
         */
        n = 0;
        goto ab_nab_search;
    }

    if (c == '%')
    {
        c = *++fmt;
        if (c != '%')
        {
            /* Special case: <a>, (0+?)*<b>, '%-spec', which we know will
             * be successfull because of the format spec.
             */
            fmt--;
            n = 0;
            goto ab_nab_search;
        }

        /* just a literal '%' */
    }

    fmt++;
    if (c == a)
    {
        c = *fmt++;
        if (c == '%')
        {
            c = *fmt;
            if (c != '%')
            {
                /* <a> (0+?)*<b> <a> '%-spec' */
                fmt -= 2 + (a == '%');
                n = 0;
                goto ab_nab_search;
            }
            fmt++;
            /* just a literal '%' */
        }

        if (c != b)
        {
            if (!c)
            {
                /* <a> (0+?)*<b> <a> '\0' */
                fmt -= 2 + (a == '%');
                n = 0;
                goto ab_nab_search;
            }

            /* Search in <str> for <a> ?*{<b> <a>} <a> <c>.
             * <c> is a character which starts a successfull new match.
             * To find this, the function tries a match at every possible <c>
             * it finds.
             *
             * If the <c> is found, all the characters before belong to
             * the previous %s match, if not found, the whole string
             * belongs to the match.
             */
            for (;;)
            {
                char ch;

                ch = *str++;
a_b_a_c_check_a:
                if (!ch)
                    return NULL;

                /* First <a>? */
                if (ch != a)
                    continue;

                ch = *str++;
a_b_a_c_check_b:

                /* Check for <b> <a> */
                if (ch != b)
                    goto a_b_a_c_check_a;

                ch = *str++;
                if (ch != a)
                    continue;
                ch = *str++;
                if (ch != c)
                    goto a_b_a_c_check_b;

                sscanf_match(str, fmt, info);
                if (info->match_end)
                    return str - 4;

                goto a_b_a_c_check_a;
            }
            /* NOTREACHED */
        }

        /* c == b */
        n = 2;

        /* Search in <str> for <a> <b> n*{<a> <b>} ?*<b> 'x'.
         * 'x' is a character which starts a successfull new match.
         * To find this, the function tries a match at every possible
         * 'x' it finds.
         *
         * If the 'x' is found, all the characters before belong to
         * the previous %s match, if not found, the whole string
         * belongs to the match.
         */
        {
            char ch;
            int i;

            goto ab_nab_search;

ab_nab_check_0:
            if (!ch)
                return NULL;
ab_nab_search:
            ch = *str++;
ab_nab_check_a:

            /* First <a> */
            if (ch != a)
                goto ab_nab_check_0;

            /* A <b> should follow, introducing the repetition */
            ch = *str++;
            if (ch != b)
                goto ab_nab_check_a;

            /* <n> times the couple <a> <b> should follow */
            if (0 != (i = n)) do
            {
                ch = *str++;
                if (ch != a)
                    goto ab_nab_check_0;
                ch = *str++;
                if (ch != b)
                    goto ab_nab_check_a;
            } while (i -= 2);

            do {
                sscanf_match(str, fmt, info);
                if (info->match_end)
                    return str - n - 2;
                ch = *str++;
                if (ch != a)
                    goto ab_nab_check_0;
                ch = *str++;
            } while (ch == b);

            goto ab_nab_check_0;
        }
        /* NOREACHED */
    }

    /* c != a */

    /* Search in <str> for <a> <b> <c> 'x'.
     * 'x' is a character which starts a successfull new match.
     * To find this, the function tries a match at every possible
     * 'x' it finds.
     *
     * If the 'x' is found, all the characters before belong to
     * the previous %s match, if not found, the whole string
     * belongs to the match.
     */
    for (;;) {
        char ch;

        ch = *str++;
a_b_c_check_a:
        if (!ch)
            return 0;
        if (ch != a)
            continue;
        ch = *str++;
        if (ch != b)
            goto a_b_c_check_a;
        ch = *str++;
        if (ch != c)
            goto a_b_c_check_a;
        sscanf_match(str, fmt, info);
        if (info->match_end)
            return str - 3;
    }

    /* NOTREACHED */
} /* sscanf_search() */

/*-------------------------------------------------------------------------*/
int
e_sscanf (int num_arg, svalue_t *sp)

/* EFUN sscanf()
 *
 *   int sscanf(string str, string fmt, mixed var1, mixed var2, ...)
 *
 * Execute the sscanf() function if <num_arg> arguments on the stack <sp>,
 * and return the number of matches.
 *
 * Parse a string str using the format fmt. fmt can contain strings seperated
 * by %d and %s. Every %d and %s corresponds to one of var1, var2, ... .
 *
 * The match operators in the format string have one of these formats:
 *   %[!|~][<size>[.<minmatch>]]<type>
 *
 * <type> may be:
 *    d: matches any number.
 *    D: matches any number.
 *    U: matches any unsigned number.
 *    s: matches any string.
 *    %: matches the % character.
 *    t: matches whitespace (spaces and tab characters), but does
 *       not store them (the simple ' ' matches just spaces and
 *       can't be given a size specification).
 *
 * <size> is the expected field size, <minmatch> the demanded minimal match
 * length (defaults are 0 for strings and 1 for numbers). Each of these both
 * may be specified numerically, or as '*' - then the value of the variable at
 * the current place in the argument list is used.
 *
 * Specifying ! will perform the match, but neither store the result nor count
 * the match.
 * Specifying ~ will perform and count the match, but not store the result.
 *
 * (You can think of '!' as negating on a wholesale basis, while '~'
 *  negates only individual bits. Thus, '%!' negates both do_assign
 *  and count_match, while '%~' only negates do_assign.)
 *
 * The difference between %d and %D/%U is that the latter will abort an
 * immediately preceeding %s as soon as possible, whereas the former will
 * attempt to make largest match to %s first.  %D/%U will still not skip
 * whitespace, use %.0t%D to skip optional whitespace.
 *
 * The number of matched arguments will be returned.
 *
 * The function sscanf is special, in that arguments are passed by reference
 * automatically.
 */

{
    char *fmt;                  /* Format description */
    char *in_string;            /* The string to be parsed. */
    svalue_t sv_tmp;
    svalue_t *arg0;        /* The first argument */
    struct sscanf_flags flags;  /* local copy of info.flags */
    struct sscanf_info info;    /* scan information packet */

    inter_sp = sp; /* we can have an error() deep inside */
    arg0 = sp - num_arg + 1;

    /* First get the string to be parsed.
     */
    E_TYPE_TESTV1(arg0, T_STRING);
    in_string = arg0[0].u.string;

    /* Now get the format description.
     */
    E_TYPE_TESTV2((arg0+1), T_STRING);
    fmt = arg0[1].u.string;

    info.arg_end = arg0 + num_arg;
    info.arg_current = arg0 + 2;

    /* Loop for every % or substring in the format. Update the
     * arg pointer continuosly. Assigning is done manually, for speed.
     */
    for (info.number_of_matches = 0; info.arg_current <= info.arg_end; )
    {
        info.arg_start = info.arg_current;
        sscanf_match(in_string, fmt, &info);
        in_string = info.match_end;

        if (!in_string) /* End of input? */
            break;

        /* Either fmt is out, or we found a string match */

match_skipped:

        fmt = info.fmt_end;
        if (fmt[-1] == 's')
        {
            mp_uint max;
            mp_int num;
            char *match;
            svalue_t *arg;

            flags = info.flags;

            /* Set match to the first possible end character of the string
             * to match.
             */
            num = (mp_int)info.string_min;
            if (num > 0)
            {
                if (num > (mp_int)strlen(in_string))
                    break;

                match = in_string + num;
            }
            else
            {
                /* num = 0 */
                match = in_string;
            }

            max = info.string_max;
            arg = info.arg_current;
            info.arg_start = arg + flags.do_assign;
            if (info.arg_start > info.arg_end)
            {
                break;
            }

            /* Search the real end of the string to match and set match
             * to it.
             */
            if (NULL != (match = sscanf_search(match, fmt, &info))
             && (mp_uint)(num = match - in_string) <= max)
            {
                /* Got the string: assign resp. skip it */

                if (flags.do_assign)
                {
                    match = xalloc((size_t)num+1);
                    if (!match)
                        error("Out of memory\n");
                    strncpy(match, in_string, (size_t)num);
                    match[num] = '\0';
                    put_malloced_string(&sv_tmp, match);
                    transfer_svalue(arg->u.lvalue, &sv_tmp);
                }

                in_string = info.match_end;
                info.number_of_matches += flags.count_match;
                info.arg_start = info.arg_current;
                goto match_skipped;
            }

            /* no match found */
            break;
        }

        if (!fmt[0]) /* End of format */
            break;
    }

    return info.number_of_matches;
}


/*=========================================================================*/
/*                              OBJECTS                                    */

/*-------------------------------------------------------------------------*/
svalue_t *
x_all_environment (svalue_t *sp, int numarg)

/* XEFUN all_environment()
 *
 *    object * all_environment(object o)
 *
 * Return an array with all environments of object <o> in 'outgoing'
 * order. If <o> has no environment, 0 is returned.
 *
 * The caller checked the correctness of the arguments.
 */

{
    object_t *o;

    /* Get the arg from the stack, if any */
    if (numarg)
    {
        o = ref_object(sp->u.ob, "all_environment");
        free_object_svalue(sp);
    }
    else
    {
        o = current_object;
        sp++;
    }


    /* Default return value: 0 */
    put_number(sp, 0);

    if (!(o->flags & O_DESTRUCTED))
    {
        mp_int num;
        object_t *env;
        vector_t *v;
        svalue_t *svp;

        /* Count the number of environments */
        for ( num = 0, env = o->super
            ; NULL != env
            ; num++, env = env->super)
            NOOP;

        if (num)
        {
            /* Get the array and fill it */
            v = allocate_uninit_array(num);
            for ( svp = v->item, env = o->super
                ; NULL != env
                ; svp++, env = env->super)
            {
                put_ref_object(svp, env, "all_environment");
            }

            /* Put the result on the stack and return */
            put_array(sp, v);
        }
    }

    if (numarg)
        free_object(o, "all_environment");
    return sp;
} /* x_all_environment() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_clones (svalue_t *sp, int num_arg)

/* VEFUN clones()
 *
 *   object* clones ()
 *   object* clones (int what)
 *   object* clones (string|object obj [, int what])
 *
 * The efuns returns an array with all clones of a certain blueprint.
 *
 * If <obj> is given, all clones of the blueprint of <obj> (which
 * may be <obj> itself) are returned, otherwise all clones of the
 * current object resp. of the current object's blueprint. If <obj>
 * is given as string, it must name an existing object.
 *
 * <what> selects how to treat clones made from earlier versions
 * of the blueprint:
 *   == 0: (default) return the clones of the current blueprint only.
 *   == 1: return the clones of the previous blueprints only.
 *   == 2: return all clones of the blueprint.
 */
 
{
    char      *name;     /* The load-name to search */
    mp_int     mintime;  /* 0 or lowest load_time for an object to qualify */
    mp_int     maxtime;  /* 0 or highest load_time for an object to qualify */
    mp_int     load_id;  /* The load_id of the reference */
    object_t **ores;     /* Table pointing to the found objects */
    size_t     found;    /* Number of objects found */
    size_t     osize;    /* Size of ores[] */
    vector_t  *res;      /* Result vector */
    svalue_t  *svp;
    object_t  *ob;

    mintime = 0;
    maxtime = 0;
    load_id = 0;

    /* Evaluate the arguments */
    {
        int what;
        object_t * reference;

        /* Defaults */
        reference = current_object;
        what = 0;

        if (num_arg == 1)
        {
            if (sp->type == T_OBJECT)
                reference = sp->u.ob;
            else if (sp->type == T_STRING) {
                reference = get_object(sp->u.string);
                if (!reference) {
                    error("Object not found: %s\n", sp->u.string);
                    /* NOTREACHED */
                    return sp;
                }
            }
            else if (sp->type == T_NUMBER) {
                what = sp->u.number;
                if (what < 0 || what > 2) {
                    bad_xefun_vararg(1, sp);
                    /* NOTREACHED */
                    return sp;
                }
            }
            else {
                bad_xefun_vararg(1, sp);
                /* NOTREACHED */
                return sp;
            }
        }
        else if (num_arg == 2)
        {
            if (sp->type == T_NUMBER) {
                what = sp->u.number;
                if (what < 0 || what > 2)
                {
                    bad_xefun_vararg(2, sp);
                    /* NOTREACHED */
                    return sp;
                }
            }
            else {
                bad_xefun_vararg(2, sp);
                /* NOTREACHED */
                return sp;
            }

            free_svalue(sp--); inter_sp = sp;
            
            if (sp->type == T_OBJECT)
                reference = sp->u.ob;
            else if (sp->type == T_STRING) {
                reference = get_object(sp->u.string);
                if (!reference)
                {
                    error("Object not found: %s\n", sp->u.string);
                    /* NOTREACHED */
                    return sp;
                }
            }
            else {
                bad_xefun_vararg(1, sp);
                /* NOTREACHED */
                return sp;
            }
        }

        name = reference->load_name;

        /* If we received a clone as reference, we have
         * to find the blueprint.
         */
        if (reference->flags & O_CLONE)
            reference = get_object(reference->load_name);

        /* Encode the 'what' parameter into the two
         * time bounds: during the search we just have to
         * compare the load_times against these bounds.
         */
        if (!reference)
        {
            if (!what)
            {
                /* We know that there is nothing to find,
                 * therefore return immediately.
                 */
                res = allocate_array(0);
                if (!num_arg)
                    sp++;
                else
                    free_svalue(sp);
                put_array(sp, res);
                return sp;
            }

            /* otherwise we can return all we find */
        }
        else if (!what)
        {
            /* Just the new objects */
            mintime = reference->load_time;
            load_id = reference->load_id;
        }
        else if (what == 1)
        {
            /* Just the old objects */
            maxtime = reference->load_time;
            load_id = reference->load_id;
        }

    } /* evaluation of arguments */

    /* Prepare the table with the object pointers */
    osize = 256;
    found = 0;
    ores = xalloc(sizeof(*ores) * osize);
    if (!ores)
    {
        error("Out of memory.\n");
        /* NOTREACHED */
        return sp;
    }

    /* Loop through the object list */
    for (ob = obj_list; ob; ob = ob->next_all)
    {
        if ((ob->flags & (O_DESTRUCTED|O_CLONE)) == O_CLONE
         && ob->load_name == name
         && (!mintime || ob->load_time > mintime
                      || (ob->load_time == mintime && ob->load_id >= load_id)
            )
         && (!maxtime || ob->load_time < maxtime
                      || (ob->load_time == maxtime && ob->load_id < load_id)
            )
           )
        {
            /* Got one */
            if (found == osize)
            {
                /* Need to extend the array */
                osize += 256;
                ores = rexalloc(ores, sizeof(*ores) * osize);
                if (!ores)
                {
                    error("Out of memory.\n");
                    /* NOTREACHED */
                    return sp;
                }
            }
            ores[found++] = ob;
        }
    }

    /* Create the result and put it onto the stack */
    if (max_array_size && found > max_array_size)
    {
        xfree(ores);
        error("Illegal array size: %ld\n", (long)found);
        /* NOTREACHED */
        return sp;
    }
    res = allocate_uninit_array(found);
    if (!res)
    {
        xfree(ores);
        error("Out of memory.\n");
        /* NOTREACHED */
        return sp;
    }

    osize = found;
    for (found = 0, svp = res->item; found < osize; found++, svp++)
    {
        put_ref_object(svp, ores[found], "clones");
    }

    if (!num_arg)
        sp++;
    else
        free_svalue(sp);
    put_array(sp, res);

    xfree(ores);
    
    return sp;
} /* f_clones() */

/*-------------------------------------------------------------------------*/
static object_t *
object_present_in (char *str, object_t *ob)

/* <ob> is the first object in an environment: test all the objects there
 * if they match the id <str>.
 * <str> may be of the form "<id> <num>" - then the <num>th object with
 * this <id> is returned, it it is found.
 */

{
    svalue_t *ret;
    char *p;
    int   count = 0; /* >0: return the <count>th object */
    int   length;
    char *item;

    length = strlen(str);
    item = xalloc(length + 1);
    if (!item)
        error("Out of memory\n");
    strcpy(item, str);
    push_malloced_string(item); /* free on error */

    /* Check if there is a number in the string */
    p = item + length - 1;
    if (*p >= '0' && *p <= '9')
    {
        while(p > item && *p >= '0' && *p <= '9')
            p--;

        if (p > item && *p == ' ')
        {
            count = atoi(p+1) - 1;
            *p = '\0';
        }
    }

    /* Now look for the object */
    for (; ob; ob = ob->next_inv)
    {
        push_volatile_string(item);
        ret = sapply(STR_ID, ob, 1);
        if (ob->flags & O_DESTRUCTED)
        {
            xfree(item);
            inter_sp--;
            return NULL;
        }

        if (ret == NULL || (ret->type == T_NUMBER && ret->u.number == 0))
            continue;

        if (count-- > 0)
            continue;
        xfree(item);
        inter_sp--;
        return ob;
    }
    xfree(item);
    inter_sp--;

    /* Not found */
    return NULL;
} /* object_present_in() */

/*-------------------------------------------------------------------------*/
object_t *
e_object_present (svalue_t *v, object_t *ob)

/* 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.
 * TODO: Make this a nice efuns.c-Efun and also implement
 * TODO:: deep_present() and present_clone() (see bugs/f-something)
 */

{
    svalue_t *ret;
    object_t *ret_ob;
    Bool specific = MY_FALSE;

    /* Search where? */
    if (!ob)
        ob = current_object;
    else
        specific = MY_TRUE;

    if (ob->flags & O_DESTRUCTED)
        return NULL;

    if (v->type == T_OBJECT)
    {
        /* Oooh, that's easy. */

        if (specific)
        {
            if (v->u.ob->super == ob)
                return v->u.ob;
            else
                return NULL;
        }
        if (v->u.ob->super == ob
         || (v->u.ob->super == ob->super && ob->super != 0))
            return v->u.ob->super;
        return NULL;
    }

    /* Always search in the object's inventory */
    ret_ob = object_present_in(v->u.string, ob->contains);
    if (ret_ob)
        return ret_ob;

    if (specific)
        return NULL;

    /* Search in the environment of <ob> if it was not specified */
    if (!specific && ob->super)
    {
        /* Is it _the_ environment? */
        push_volatile_string(v->u.string);
        ret = sapply(STR_ID, ob->super, 1);
        if (ob->super->flags & O_DESTRUCTED)
            return NULL;
        if (ret && !(ret->type == T_NUMBER && ret->u.number == 0))
            return ob->super;

        /* No, search the other objects here. */
        return object_present_in(v->u.string, ob->super->contains);
    }

    /* Not found */
    return NULL;
} /* e_object_present() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_object_info (svalue_t *sp)

/* TEFUN object_info()
 *
 *    mixed * object_info(object o, int type)
 *
 * Return an array with information about the object <o>. The
 * type of information returned is determined by <type>.
 */

{
    vector_t *v;
    object_t *o, *o2, *prev;
    program_t *prog;
    svalue_t *svp;
    int flags, pos;

    /* Test and get the arguments from the stack */
    TYPE_TEST1(sp-1, T_OBJECT)
    TYPE_TEST2(sp, T_NUMBER)

    o = sp[-1].u.ob;

    /* Depending on the <type> argument, determine the
     * data to return.
     */
    switch(sp->u.number)
    {
    default:
        error("Illegal value %ld for object_info().\n", sp->u.number);
        /* NOTREACHED */
        return sp;

    /* --- The basic information from the object structure */
    case OINFO_BASIC:
        v = allocate_array(OIB_MAX);
        svp = v->item;

        flags = o->flags;

        svp[OIB_HEART_BEAT].u.number = (flags & O_HEART_BEAT) ? 1 : 0;
#ifdef O_IS_WIZARD
        svp[OIB_IS_WIZARD].u.number  = (flags & O_IS_WIZARD) ? 1 : 0;
#else
        svp[OIB_IS_WIZARD].u.number  = 0;
#endif
        svp[OIB_ENABLE_COMMANDS].u.number
                                     = (flags & O_ENABLE_COMMANDS) ? 1 : 0;
        svp[OIB_CLONE].u.number      = (flags & O_CLONE) ? 1 : 0;
        svp[OIB_DESTRUCTED].u.number = (flags & O_DESTRUCTED) ? 1 : 0;
        svp[OIB_SWAPPED].u.number    = (flags & O_SWAPPED) ? 1 : 0;
        svp[OIB_ONCE_INTERACTIVE].u.number
                                     = (flags & O_ONCE_INTERACTIVE) ? 1 : 0;
        svp[OIB_RESET_STATE].u.number = (flags & O_RESET_STATE) ? 1 : 0;
        svp[OIB_WILL_CLEAN_UP].u.number
                                     = (flags & O_WILL_CLEAN_UP) ? 1 : 0;
        svp[OIB_LAMBDA_REFERENCED].u.number
                                     = (flags & O_LAMBDA_REFERENCED) ? 1 : 0;
        svp[OIB_SHADOW].u.number     = (flags & O_SHADOW) ? 1 : 0;
        svp[OIB_REPLACED].u.number   = (flags & O_REPLACED) ? 1 : 0;
#ifdef F_SET_LIGHT
        svp[OIB_TOTAL_LIGHT].u.number = o->total_light;
#else
        svp[OIB_TOTAL_LIGHT].u.number = 0;
#endif
        svp[OIB_NEXT_RESET].u.number = o->time_reset;
        svp[OIB_TIME_OF_REF].u.number = o->time_of_ref;
        svp[OIB_REF].u.number         = o->ref;
        svp[OIB_GIGATICKS].u.number   = (p_int)o->gigaticks;
        svp[OIB_TICKS].u.number       = (p_int)o->ticks;
        svp[OIB_SWAP_NUM].u.number    = O_SWAP_NUM(o);
        svp[OIB_PROG_SWAPPED].u.number = O_PROG_SWAPPED(o) ? 1 : 0;
        svp[OIB_VAR_SWAPPED].u.number = O_VAR_SWAPPED(o) ? 1 : 0;

        put_malloced_string(svp+OIB_NAME, string_copy(o->name));
        put_ref_string(svp+OIB_LOAD_NAME, o->load_name);

        o2 = o->next_all;
        if (o2)
        {
            put_ref_object(svp+OIB_NEXT_ALL, o2, "object_info(0)");
        } /* else the element was already allocated as 0 */

        o2 = o->prev_all;
        if (o2)
        {
            put_ref_object(svp+OIB_PREV_ALL, o2, "object_info(0)");
        } /* else the element was already allocated as 0 */

        break;

    /* --- Position in the object list */
    case OINFO_POSITION:
        v = allocate_array(OIP_MAX);
        svp = v->item;

        o2 = o->next_all;
        if (o2)
        {
            put_ref_object(svp+OIP_NEXT, o2, "object_info(1) next");
        } /* else the element was already allocated as 0 */

        /* Find the non-destructed predecessor of the object */
        if (obj_list == o)
        {
            pos = 0;
            prev = NULL;
        }
        else
        for (prev = NULL, o2 = obj_list, pos = 0; o2; o2 = o2->next_all)
        {
            prev = o2;
            pos++;
            if (o2->next_all == o)
                break;
        }

        if (o2) /* Found it in the list */
        {
            if (prev)
            {
                put_ref_object(svp+OIP_PREV, prev, "object_info(1) prev");
            } /* else the element was already allocated as 0 */
        }
        else /* Not found (this shouldn't happen) */
            pos = -1;

        svp[OIP_POS].u.number = pos;

        break;

    /* --- Memory information */
    case OINFO_MEMORY:
        v = allocate_array(OIM_MAX);
        svp = v->item;

        if ((o->flags & O_SWAPPED) && load_ob_from_swap(o) < 0)
            error("Out of memory.\n");

        prog = o->prog;

        svp[OIM_REF].u.number = prog->ref;

        put_malloced_string(svp+OIM_NAME, string_copy(prog->name));

        svp[OIM_PROG_SIZE].u.number
                               = (long)(PROGRAM_END(*prog) - prog->program);
          /* Program size */
        svp[OIM_NUM_FUNCTIONS].u.number = prog->num_functions;
        svp[OIM_SIZE_FUNCTIONS].u.number
                           = (p_int)(prog->num_functions * sizeof(uint32)
                             + prog->num_function_names * sizeof(short));
          /* Number of function names and the memory usage */
        svp[OIM_NUM_VARIABLES].u.number = prog->num_variables;
        svp[OIM_SIZE_VARIABLES].u.number
                     = (p_int)(prog->num_variables * sizeof(variable_t));
          /* Number of variables and the memory usage */
        svp[OIM_NUM_STRINGS].u.number = prog->num_strings;
        svp[OIM_SIZE_STRINGS].u.number
                                 = (p_int)(prog->num_strings * sizeof(char*));
          /* Number of strings and the memory usage */
        {
            int i = prog->num_inherited;
            int cnt = 0;
            inherit_t *inheritp;

            for (inheritp = prog->inherit; i--; inheritp++)
            {
                if (inheritp->inherit_type == INHERIT_TYPE_NORMAL)
                    cnt++;
            }
            svp[OIM_NUM_INHERITED].u.number = cnt;
        }
        svp[OIM_SIZE_INHERITED].u.number
                     = (p_int)(prog->num_inherited * sizeof(inherit_t));
          /* Number of inherites and the memory usage */
        svp[OIM_TOTAL_SIZE].u.number = prog->total_size;

        svp[OIM_DATA_SIZE].u.number = data_size(o);
        break;
    }

    free_svalue(sp);
    sp--;
    free_svalue(sp);

    /* Assign the result */
    put_array(sp, v);

    return sp;
}

/*-------------------------------------------------------------------------*/
svalue_t *
f_present_clone (svalue_t *sp)

/* TEFUN present_clone()
 *
 *    object present_clone(string str, object env)
 *    object present_clone(object obj, object env)
 *
 * Search in the inventory of <env> for the first object with the
 * same blueprint as object <obj>, resp. for the first object with
 * the loadname <str>, and return that object.
 *
 * If not found, 0 is returned.
 */

{
    char * name;         /* the shared loadname to look for */
    object_t *obj;  /* the object under scrutiny */

    /* Test and get the arguments from the stack */
    TYPE_TEST2(sp, T_OBJECT)

    if (sp[-1].type == T_STRING)
    {
        size_t len;
        long i;
        char * end;
        char * name0;  /* Intermediate name */

        name0 = sp[-1].u.string;

        /* Normalize the given string and check if it is
         * in the shared string table. If not, we know that
         * there is no blueprint with that name
         */

        /* First, slash of a trailing '#<num>' */

        len = svalue_strlen(sp-1);
        i = (signed)len;
        end = name0 + len;

        while (--i > 0)
        {
            char c;

            c = *--end;
            if (c < '0' || c > '9' )
            {
                /* Not a digit: maybe a '#' */
                if ('#' == c && len - i > 1)
                {
                    name0 = alloca((size_t)i + 1);
                    if (!name0)
                        error("Out of stack memory.\n");
                    strncpy(name0, sp[-1].u.string, (size_t)i);
                    name0[i] = '\0';
                }

                break; /* in any case */
            }
        }

        /* Now make the name sane */
#ifndef COMPAT_MODE
        name = (char *)make_name_sane(name0, MY_TRUE);
#else
        name = (char *)make_name_sane(name0, MY_FALSE);
#endif
        if (name)
            name = findstring(name);
        else
            name = findstring(name0);

    }
    else if (sp[-1].type == T_OBJECT)
    {
        name = sp[-1].u.ob->load_name;
    }
    else
        bad_xefun_arg(1, sp);

    obj = NULL;
    if (name)
    {
        /* We have a name, now look for the object */
        for (obj = sp->u.ob->contains; obj != NULL; obj = obj->next_inv)
        {
            if (!(obj->flags & O_DESTRUCTED) && name == obj->load_name)
                break;
        }
    }

    /* Assign the result */
    sp = pop_n_elems(2, sp) + 1;
    if (obj != NULL)
        put_ref_object(sp, obj, "present_clone");
    else
        put_number(sp, 0);

    return sp;
} /* f_present_clone() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_to_object (svalue_t *sp)

/* TEFUN to_object()
 *
 *   object to_object(string arg)
 *   object to_object(closure arg)
 *   object to_object(object arg)
 *
 * The argument is converted into an object, if possible.  For strings, the
 * object with a matching file_name() is returned, or 0 if there is none, as
 * find_object() does.  For (bound!) closures, the object holding the closure
 * is returned.
 * Objects and the number 0 return themselves.
 */

{
    int n;
    object_t *o;

    switch(sp->type)
    {
    case T_NUMBER:
        if (!sp->u.number)
            return sp;
        /* FALLTHROUGH */
    default:
        bad_xefun_arg(1, sp);

    case T_CLOSURE:
        n = sp->x.closure_type;
        o = sp->u.ob;
        if (n == CLOSURE_EFUN + F_UNDEF)
            o = NULL;
        else if (CLOSURE_MALLOCED(n))
        {
            if (n == CLOSURE_UNBOUND_LAMBDA)
                bad_xefun_arg(1, sp);
            o = sp->u.lambda->ob;
        }
        if (o && o->flags & O_DESTRUCTED)
            o = NULL;
        free_closure(sp);
        break;

    case T_OBJECT:
        return sp;

    case T_STRING:
        o = find_object(sp->u.string);
        free_svalue(sp);
        break;
    }

    if (o)
        put_ref_object(sp, o, "to_object");
    else
        put_number(sp, 0);

    return sp;
} /* f_to_object() */

/*-------------------------------------------------------------------------*/
#ifdef F_TRANSFER

svalue_t *
f_transfer (svalue_t *sp)

/* TEFUN transfer()
 *
 *   int transfer(object item, object dest)
 *
 * This efun is for backward compatibility only. It is only
 * available in compat mode.
 *
 * Move the object "item" to the object "dest". All kinds of
 * tests are done, and a number is returned specifying the
 * result:
 *
 *     0: Success.
 *     1: To heavy for destination.
 *     2: Can't be dropped.
 *     3: Can't take it out of it's container.
 *     4: The object can't be inserted into bags etc.
 *     5: The destination doesn't allow insertions of objects.
 *     6: The object can't be picked up.
 *
 * If an object is transfered to a newly created object, make
 * sure that the new object first is transfered to it's
 * destination.
 *
 * The efun calls add_weight(), drop(), get(), prevent_insert(),
 * add_weight(), and can_put_and_get() where needed.
 */

{
    object_t *ob, *to;
    svalue_t *v_weight, *ret;
    int       weight;
    object_t *from;
    int       result;

    /* Get and test the arguments */
    if (sp[-1].type != T_OBJECT)
    {
        bad_xefun_arg(1, sp);
    }
    ob = sp[-1].u.ob;

    if (sp->type == T_OBJECT)
        to = sp->u.ob;
    else if (sp->type == T_STRING)
    {
        to = get_object(sp->u.string);
        if (!to)
            error("Object %s not found.\n", sp->u.string);
        free_string_svalue(sp);
        put_ref_object(sp, to, "transfer"); /* for move_object() below */
    }
    else
        bad_xefun_arg(2, sp);
        
    from = ob->super;
    result = 0; /* Default: success result */

    /* Perform the transfer step by step */
    switch(0){default:

        /* Get the weight of the object
         */
        weight = 0;
        v_weight = sapply(STR_QUERY_WEIGHT, ob, 0);
        if (v_weight && v_weight->type == T_NUMBER)
            weight = v_weight->u.number;

        if (ob->flags & O_DESTRUCTED)
        {
            result = 3;
            break;
        }

        /* If the original place of the object is a living object,
         * then we must call drop() to check that the object can be dropped.
         */
        if (from && (from->flags & O_ENABLE_COMMANDS))
        {
            ret = sapply(STR_DROP, ob, 0);
            if (ret && (ret->type != T_NUMBER || ret->u.number != 0))
            {
                result = 2;
                break;
            }

            /* This should not happen, but we can not trust LPC hackers. :-) */
            if (ob->flags & O_DESTRUCTED)
            {
                result = 2;
                break;
            }
        }

        /* If 'from' is not a room and not a player, check that we may
         * remove things out of it.
         */
        if (from && from->super && !(from->flags & O_ENABLE_COMMANDS))
        {
            ret = sapply(STR_CANPUTGET, from, 0);
            if (!ret || (ret->type != T_NUMBER && ret->u.number != 1)
             || (from->flags & O_DESTRUCTED))
            {
                result = 3;
                break;
            }
        }

        /* If the destination is not a room, and not a player,
         * Then we must test 'prevent_insert', and 'can_put_and_get'.
         */
        if (to->super && !(to->flags & O_ENABLE_COMMANDS))
        {
            ret = sapply(STR_PREVENT_INSERT, ob, 0);
            if (ret && (ret->type != T_NUMBER || ret->u.number != 0))
            {
                result = 4;
                break;
            }

            ret = sapply(STR_CANPUTGET, to, 0);
            if (!ret || (ret->type != T_NUMBER && ret->type != 0)
             || (to->flags & O_DESTRUCTED) || (ob->flags & O_DESTRUCTED))
            {
                result = 5;
                break;
            }
        }

        /* If the destination is a player, check that he can pick it up.
         */
        if (to->flags & O_ENABLE_COMMANDS)
        {
            ret = sapply(STR_GET, ob, 0);
            if (!ret || (ret->type == T_NUMBER && ret->u.number == 0)
             || (ob->flags & O_DESTRUCTED))
            {
                result = 6;
                break;
            }

            /* If it is not a room, correct the total weight in
             * the destination.
             */
            if (to->super && weight)
            {
                /* Check if the destination can carry that much.
                 */
                push_number(weight);
                ret = sapply(STR_ADD_WEIGHT, to, 1);
                if (ret && ret->type == T_NUMBER && ret->u.number == 0)
                {
                    result = 1;
                    break;
                }

                if (to->flags & O_DESTRUCTED)
                {
                    result = 1;
                    break;
                }
            }

            /* If it is not a room, correct the weight in
             * the 'from' object.
             */
            if (from && from->super && weight)
            {
                push_number(-weight);
                (void)sapply(STR_ADD_WEIGHT, from, 1);
            }
        }

        /* When we come here, the move is ok */
    } /* pseudo-switch() */
        
    if (result)
    {
        /* All the applys might have changed these */
        free_svalue(sp);
        free_svalue(sp-1);
    }
    else
    {
        /* The move is ok: do it (and use up both arguments) */
        inter_sp = sp;
        move_object();
    }

    put_number(sp-1, result);
    return sp-1;
} /* f_transfer() */

#endif /* F_TRANSFER */

/*-------------------------------------------------------------------------*/
void
e_say (svalue_t *v, vector_t *avoid)

/* Implementation of the EFUN say() (see interpret.c for the
 * full manpage).
 * <v> is the value to say, <avoid> the array of objects to exclude.
 * If the first element of <avoid> is not an object, the function
 * will store its command_giver object into it.
 */
 
{
    static svalue_t ltmp = { T_POINTER };
    static svalue_t stmp = { T_OBJECT };

    object_t *ob;
    object_t *save_command_giver = command_giver;
    object_t *origin;
    char buff[256];
    char *message;
#define INITIAL_MAX_RECIPIENTS 48
    int max_recipients = INITIAL_MAX_RECIPIENTS;
      /* Current size of the recipients table.
       */
    object_t *first_recipients[INITIAL_MAX_RECIPIENTS];
      /* Initial table of recipients.
       */
    object_t **recipients = first_recipients;
      /* Pointer to the current table of recipients.
       * The end is marked with a NULL entry.
       */
    object_t **curr_recipient = first_recipients;
      /* Next recipient to enter.
       */
    object_t **last_recipients =
                 &first_recipients[INITIAL_MAX_RECIPIENTS-1];
      /* Last entry in the current table.
       */
    object_t *save_again;

    /* Determine the command_giver to use */
    if (current_object->flags & O_ENABLE_COMMANDS)
    {
        command_giver = current_object;
    }
    else if (current_object->flags & O_SHADOW
          && O_GET_SHADOW(current_object)->shadowing)
    {
        command_giver = O_GET_SHADOW(current_object)->shadowing;
    }

    /* Determine the originating object */
    if (command_giver)
    {
        interactive_t *ip;

        if (O_SET_INTERACTIVE(ip, command_giver))
        {
            trace_level |= ip->trace_level;
        }
        origin = command_giver;

        /* Save the commandgiver to avoid, if needed */
        if (avoid->item[0].type == T_NUMBER)
        {
            put_ref_object(avoid->item, command_giver, "say");
        }
    }
    else
        origin = current_object;

    /* Sort the avoid vector for fast lookups
     */
    ltmp.u.vec = avoid;
    avoid = order_alist(&ltmp, 1, 1);
    push_referenced_vector(avoid); /* in case of errors... */
    avoid = avoid->item[0].u.vec;

    /* Collect the list of propable recipients.
     * First, look in the environment.
     */
    if ( NULL != (ob = origin->super) )
    {
        interactive_t *ip;

        /* The environment itself? */
        if (ob->flags & O_ENABLE_COMMANDS
         || O_SET_INTERACTIVE(ip, ob))
        {
            *curr_recipient++ = ob;
        }

        for (ob = ob->contains; ob; ob = ob->next_inv)
        {
            if (ob->flags & O_ENABLE_COMMANDS
             || O_SET_INTERACTIVE(ip,ob))
            {
                if (curr_recipient >= last_recipients)
                {
                    /* Increase the table */
                    max_recipients *= 2;
                    curr_recipient = alloca(max_recipients * sizeof(object_t *));
                    memcpy( curr_recipient, recipients
                           , max_recipients * sizeof(object_t *) / 2);
                    recipients = curr_recipient;
                    last_recipients = &recipients[max_recipients-1];
                    curr_recipient += (max_recipients / 2) - 1;
                }
                *curr_recipient++ = ob;
            }
        } /* for() */
    } /* if(environment) */

    /* Now check this environment */
    for (ob = origin->contains; ob; ob = ob->next_inv)
    {
        interactive_t *ip;

        if (ob->flags & O_ENABLE_COMMANDS
         || O_SET_INTERACTIVE(ip, ob))
        {
            if (curr_recipient >= last_recipients)
            {
                /* Increase the table */
                max_recipients *= 2;
                curr_recipient = alloca(max_recipients * sizeof(object_t *));
                memcpy( curr_recipient, recipients
                      , max_recipients * sizeof(object_t *) / 2);
                recipients = curr_recipient;
                last_recipients = &recipients[max_recipients-1];
                curr_recipient += (max_recipients / 2) - 1;
            }
            *curr_recipient++ = ob;
        }
    }

    *curr_recipient = NULL;  /* Mark the end of the list */

    /* Construct the message. */

    switch(v->type)
    {
    case T_STRING:
        message = v->u.string;
        break;

    case T_OBJECT:
        strncpy(buff, v->u.ob->name, sizeof buff);
        buff[sizeof buff - 1] = '\0';
        message = buff;
        break;

    case T_NUMBER:
        sprintf(buff, "%ld", v->u.number);
        message = buff;
        break;

    case T_POINTER:
        /* say()'s evil twin: send <v> to all recipients' catch_msg() lfun */
        
        for (curr_recipient = recipients; NULL != (ob = *curr_recipient++) ; )
        {
            if (ob->flags & O_DESTRUCTED)
                continue;
            stmp.u.ob = ob;
            if (assoc(&stmp, avoid) >= 0)
                continue;
            push_vector(v->u.vec);
            push_object(origin);
            sapply(STR_CATCH_MSG, ob, 2);
        }
        pop_stack(); /* free avoid alist */
        command_giver = check_object(save_command_giver);
        return;

    default:
        error("Invalid argument %d to say()\n", v->type);
    }

    /* Now send the message to all recipients */
    
    for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
    {
        interactive_t *ip;

        if (ob->flags & O_DESTRUCTED)
            continue;
        stmp.u.ob = ob;
        if (assoc(&stmp, avoid) >= 0)
            continue;
        if (!(O_SET_INTERACTIVE(ip, ob)))
        {
            tell_npc(ob, message);
            continue;
        }
        save_again = command_giver;
        command_giver = ob;
        add_message("%s", message);
        command_giver = save_again;
    }

    pop_stack(); /* free avoid alist */
    command_giver = check_object(save_command_giver);
} /* e_say() */

/*-------------------------------------------------------------------------*/
void
e_tell_room (object_t *room, svalue_t *v, vector_t *avoid)

/* Implementation of the EFUN tell_room() (see interpret.c for
 * the full manpage).
 *
 * Value <v> is sent to all living objects in <room>, except those
 * in <avoid>. <avoid> has to be in order_alist() order.
 */

{
    object_t *ob;
    object_t *save_command_giver;
    int num_recipients = 0;
    object_t *some_recipients[20];
    object_t **recipients;
    object_t **curr_recipient;
    char buff[256], *message;
    static svalue_t stmp = { T_OBJECT, } ;

    /* Like in say(), collect the possible recipients.
     * First count how many there are.
     */
    
    for (ob = room->contains; ob; ob = ob->next_inv)
    {
        interactive_t *ip;

        if ( ob->flags & O_ENABLE_COMMANDS
         ||  O_SET_INTERACTIVE(ip, ob))
        {
            num_recipients++;
        }
    }

    /* Allocate the table */
    if (num_recipients < 20)
        recipients = some_recipients;
    else
        recipients = 
          alloca( (num_recipients+1) * sizeof(object_t *) );

    /* Now fill the table */
    curr_recipient = recipients;
    for (ob = room->contains; ob; ob = ob->next_inv)
    {
        interactive_t *ip;

        if ( ob->flags & O_ENABLE_COMMANDS
         ||  O_SET_INTERACTIVE(ip, ob))
        {
            *curr_recipient++ = ob;
        }
    }

    *curr_recipient = NULL; /* Mark the end of the table */

    /* Construct the message */
    switch(v->type)
    {
    case T_STRING:
        message = v->u.string;
        break;

    case T_OBJECT:
        strncpy(buff, v->u.ob->name, sizeof buff);
        buff[sizeof buff - 1] = '\0';
        message = buff;
        break;

    case T_NUMBER:
        sprintf(buff, "%ld", v->u.number);
        message = buff;
        break;

    case T_POINTER:
      {
        /* say()s evil brother: send <v> to all recipients'
         * catch_msg() lfun
         */
        object_t *origin = command_giver;

        if (!origin)
            origin = current_object;

        for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
        {
            if (ob->flags & O_DESTRUCTED)
                continue;
            stmp.u.ob = ob;
            if (assoc(&stmp, avoid) >= 0)
                continue;
            push_vector(v->u.vec);
            push_object(origin);
            sapply(STR_CATCH_MSG, ob, 2);
        }
        return;
      }

    default:
        error("Invalid argument %d to tell_room()\n", v->type);
    }

    /* Now send the message to all recipients */
    
    for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
    {
        interactive_t *ip;

        if (ob->flags & O_DESTRUCTED) continue;
        stmp.u.ob = ob;
        if (assoc(&stmp, avoid) >= 0) continue;
        if (!(O_SET_INTERACTIVE(ip, ob)))
        {
            tell_npc(ob, message);
            continue;
        }
        save_command_giver = command_giver;
        command_giver = ob;
        add_message("%s", message);
        command_giver = save_command_giver;
    }
} /* e_tell_room() */

/*-------------------------------------------------------------------------*/
#ifdef F_SET_IS_WIZARD

svalue_t *
f_set_is_wizard (svalue_t *sp)

/* TEFUN set_is_wizard()
 *
 *   int set_is_wizard(object ob, int n)
 *
 * Change object ob's wizardhood flag.  If n is 0, it is cleared, if n is, it
 * is set, if n is -1 the current status is reported. The return value is
 * always the old value of the flag. Using this function sets a flag in the
 * parser, that affects permissions for dumpallobj etc, which are by default
 * free for every user.
 */

{
    int i;
    unsigned short *flagp;

    TYPE_TEST1(sp-1, T_OBJECT)
    TYPE_TEST2(sp,   T_NUMBER)

    flagp = &sp[-1].u.ob->flags;
    i = (*flagp & O_IS_WIZARD) != 0;

    switch (sp->u.number)
    {
        default: bad_xefun_arg(2, sp);
        case  0: *flagp &= ~O_IS_WIZARD; is_wizard_used = MY_TRUE; break;
        case  1: *flagp |=  O_IS_WIZARD; is_wizard_used = MY_TRUE; break;
        case -1: break; /* only report status */
    }
    sp--;
    free_object_svalue(sp);
    put_number(sp, i);
    return sp;
} /* f_set_is_wizard() */

#endif /* F_SET_IS_WIZARD */

/*-------------------------------------------------------------------------*/
svalue_t *
f_set_modify_command (svalue_t *sp)

/* TEFUN set_modify_command()
 *
 *   object set_modify_command(object)
 *   object set_modify_command(string)
 *   object set_modify_command(int)
 *
 * All commands for the current object (that must obviously be interactive)
 * will be passed to ob->modify_command() before actually being executed. The
 * argument can be passed an object or a file_name.
 *
 * When set_modify_command() was called, the parser won't expand the standard
 * abbreviations n,e,s,w,nw,sw,ne,se for that user anymore, nor use any hook
 * set for this.
 *
 * 0 as argument will stop the command modification and reinstall
 *   the standard abbreviations.
 * -1 as argument will just return the object previously set.
 *
 * The return value is the object that was previously set with
 * set_modify_command(), if any.
 */

{
    object_t *old, *new;
    interactive_t *ip;

    inter_sp = sp;

    /* Make sure the current_object is interactive */

    if (!(O_SET_INTERACTIVE(ip, current_object))
     || ip->closing)
    {
        error("set_modify_command in non-interactive object\n");
    }

    /* Get the old setting */
    old = ip->modify_command;
    if (old && old->flags & O_DESTRUCTED)
    {
        free_object(old, "set_modify_command");
        old = NULL;
        ip->modify_command = NULL;
    }

    /* Set the new setting */
    new = sp->u.ob;
    switch(sp->type)
    {
    default:
bad_arg_1:
        bad_xefun_arg(1, sp);

    case T_STRING:
        new = get_object(sp->u.string);
        if (!new) goto bad_arg_1;

    case T_OBJECT:
        ip->modify_command = ref_object(new, "set_modify_command");
        break;

    case T_NUMBER:
        if (sp->u.number == 0 )
        {
            /* ref count of old is reused below, so don't free now */
            ip->modify_command = NULL;
        }
        else
        {
            if (sp->u.number != -1) goto bad_arg_1;
            if (old) ref_object(old, "set_modify_command");
        }
    }

    free_svalue(sp);

    /* Return the old setting */
    if (old)
        put_object(sp, old); /* reuse ref count */
    else
        put_number(sp, 0);

    return sp;
} /* f_set_modify_command() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_set_prompt (svalue_t *sp)

/* TEFUN set_prompt()
 *
 *       string set_prompt(mixed prompt, object ob)
 *
 * Set the prompt given by the first argument for the interactive object
 * instead of the default ``> ''. If the second argument is omitted,
 * this_player() is used as default. The first arg can be a string or a
 * closure.
 *
 * The result returned is the old prompt.
 */

{
    svalue_t *prompt;
    interactive_t *ip;

    /* Make sure the object is interactive */
    if (sp->type != T_OBJECT
     || !(O_SET_INTERACTIVE(ip, sp->u.ob))
     || ip->closing)
    {
        bad_xefun_arg(2, sp);
    }

    /* Get the address of the prompt svalue */
    prompt = query_prompt(sp->u.ob);

    free_object_svalue(sp);
    sp--;

    if (sp->type == T_STRING || sp->type == T_CLOSURE)
    {
        if (sp->type == T_STRING
         && sp->x.string_type == STRING_VOLATILE)
        {
            char *str = make_shared_string(sp->u.string);

            if (!str)
            {
                inter_sp = sp;
                error("Out of memory\n");
            }
            else
            {
                sp->u.string = str;
                sp->x.string_type = STRING_SHARED;
            }
        }

        /* Three-way exchange to set the new prompt and put
         * the old one onto the stack.
         */
        sp[1] = *prompt;
        *prompt = *sp;
        *sp = sp[1];
        if (sp->type == T_CLOSURE)
        {
            /* In case the prompt is changed from within the prompt
             * closure.
             */
            addref_closure(sp, "unset_prompt");
            free_closure_hooks(sp, 1);
        }
    }
    else if (sp->type == T_NUMBER
          && (sp->u.number == 0 || sp->u.number == -1) )
    {
        assign_svalue(sp, prompt);
    }
    else
    {
        bad_xefun_arg(1, sp);
        /* NOTREACHED */
    }

    return sp;
} /* f_set_prompt() */

/*=========================================================================*/
/*                              VALUES                                     */

/*-------------------------------------------------------------------------*/
svalue_t *
f_copy (svalue_t *sp)

/* TEFUN copy()
 *
 *    mixed copy(mixed data)
 *
 * Make a copy of <data> and return it. For everything but arrays and
 * mappings this is obviously a noop, but for arrays and mappings this
 * efuns returns a shallow value copy.
 */

{
    switch (sp->type)
    {
    default:
        NOOP
        break;

    case T_QUOTED_ARRAY:
    case T_POINTER:
      {
        vector_t *old, *new;
        size_t size, i;

        old = sp->u.vec;
        size = VEC_SIZE(old);
        if (old->ref != 1 && old != &null_vector)
        {
            new = allocate_uninit_array((int)size);
            if (!new)
                error("Out of memory.\n");
            for (i = 0; i < size; i++)
                assign_svalue_no_free(&new->item[i], &old->item[i]);
            free_array(old);
            sp->u.vec = new;
        }
        break;
      }
    case T_MAPPING:
      {
        mapping_t *old, *new;

        old = sp->u.map;
        if (old->ref != 1)
        {
            new = copy_mapping(old);
            if (!new)
                error("Out of memory.\n");
            free_mapping(old);
            sp->u.map = new;
        }
        break;
      }
    }

    return sp;
} /* f_copy() */

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

/* Data packet passed to deep_copy_mapping() during a mapping walk.
 */
struct csv_info {
    int width;                     /* width of the mapping */
    mapping_t * dest;         /* the mapping to copy into */
    struct pointer_table *ptable;  /* the pointer table to use */
};

/*-------------------------------------------------------------------------*/
static void
deep_copy_mapping (svalue_t *key, svalue_t *val, void *extra)

/* Called from copy_svalue() as part of the mapping walk to deeply copy
 * a mapping. <extra> is a (struct csv_info *).
 */

{
    struct csv_info *info = (struct csv_info *)extra;
    svalue_t newkey;
    svalue_t *newdata;
    int i;

    copy_svalue(&newkey, key, info->ptable);
    newdata = get_map_lvalue_unchecked(info->dest, &newkey);
    for (i = info->width; i-- > 0; newdata++, val++)
        copy_svalue(newdata, val, info->ptable);

    free_svalue(&newkey); /* no longer needed */
}

/*-------------------------------------------------------------------------*/
static void
copy_svalue (svalue_t *dest, svalue_t *src
            , struct pointer_table *ptable)

/* Copy the svalue <src> into the yet uninitialised svalue <dest>.
 * If <src> is an array or mapping, recurse to achieve a deep copy, using
 * <ptable> to keep track of the arrays and mappings encountered.
 *
 * The records in the pointer table store the svalue* of the created
 * copy for each registered array and mapping in the .data member.
 */

{
    switch (src->type)
    {
    default:
        assign_svalue_no_free(dest, src);
        break;

    case T_QUOTED_ARRAY:
    case T_POINTER:
      {
        struct pointer_record *rec;
        vector_t *old, *new;
        mp_int size, i;

        old = src->u.vec;

        /* No need to copy the null vector */
        if (old == &null_vector)
        {
            assign_svalue_no_free(dest, src);
            break;
        }

        /* Lookup/add this array to the pointer table */
        rec = find_add_pointer(ptable, old, MY_TRUE);

        if (rec->ref_count++ < 0) /* New array */
        {
            size = (mp_int)VEC_SIZE(old);

            /* Create a new array, assign it to dest, and store
             * it in the table, too.
             */
            new = allocate_uninit_array(size);
            put_array(dest, new);
            if (src->type == T_QUOTED_ARRAY)
                dest->x.quotes = src->x.quotes;
            rec->data = dest;

            /* Copy the values */
            for (i = 0; i < size; i++)
            {
                svalue_t * svp = &old->item[i];

                if (svp->type == T_MAPPING || svp->type == T_POINTER)
                    copy_svalue(&new->item[i], svp, ptable);
                else
                    assign_svalue_no_free(&new->item[i], svp);
            }
        }
        else /* shared array we already encountered */
        {
            assign_svalue_no_free(dest, (svalue_t *)rec->data);
        }
        break;
      }
    case T_MAPPING:
      {
        mapping_t *old, *new;
        struct pointer_record *rec;

        old = src->u.map;

        /* Lookup/add this mapping to the pointer table */
        rec = find_add_pointer(ptable, old, MY_TRUE);
        if (rec->ref_count++ < 0) /* New mapping */
        {
            mp_int size;
            struct csv_info info;

            /* Create a new array, assign it to dest, and store it
             * in the table, too.
             */
            check_map_for_destr(old);
            size = (mp_int)MAP_SIZE(old);
            info.width = old->num_values;
            new = allocate_mapping(size, info.width);
            if (!new)
                error("Out of memory.\n");
            put_mapping(dest, new);
            rec->data = dest;

            /* It is tempting to use copy_mapping() and then just
             * replacing all array/mapping references, but since this
             * can mess up the sorting order and needs a walk of the
             * mapping anyway, we do all the copying in the walk.
             */
            info.ptable = ptable;
            info.dest = new;
            walk_mapping(old, deep_copy_mapping, &info);
        }
        else /* shared mapping we already encountered */
        {
            assign_svalue_no_free(dest, (svalue_t *)rec->data);
        }
        break;
      }
    } /* switch(src->type) */
} /* copy_svalue() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_deep_copy (svalue_t *sp)

/* TEFUN deep_copy()
 *
 *    mixed deep_copy(mixed data)
 *
 * Make a copy of <data> and return it. For everything but arrays and
 * mappings this is obviously a noop, but for arrays and mappings this
 * efuns returns a deep value copy.
 *
 * Note: checking the ref-count of the array/mapping passed is of no use
 * here as it doesn't tell anything about the contained arrays/mappings.
 */

{
    struct pointer_table *ptable;

    switch (sp->type)
    {
    default:
        NOOP
        break;

    case T_POINTER:
      {
        vector_t *old;

        old = sp->u.vec;
        if (old != &null_vector)
        {
            svalue_t new;

            ptable = new_pointer_table();
            if (!ptable)
                error("Out of memory.\n");
            copy_svalue(&new, sp, ptable);
            transfer_svalue(sp, &new);
            free_pointer_table(ptable);
        }
        break;
      }
    case T_MAPPING:
      {
        mapping_t *old;
        svalue_t new;

        old = sp->u.map;
        ptable = new_pointer_table();
        if (!ptable)
            error("Out of memory.\n");
        copy_svalue(&new, sp, ptable);
        transfer_svalue(sp, &new);
        free_pointer_table(ptable);
        break;
      }
    }

    return sp;
} /* f_deep_copy() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_filter (svalue_t *sp, int num_arg)

/* VEFUN filter()
 *
 *   mixed * filter (mixed *arg, string fun, string|object ob, mixed extra...)
 *   mixed * filter (mixed *arg, closure cl, mixed extra...)
 *   mixed * filter (mixed *arg, mapping map, mixed extra...)
 *
 *  mapping filter (mapping arg, string fun, string|object ob, mixed extra...)
 *  mapping filter (mapping arg, closure cl, mixed extra...)
 *
 * Call the function <ob>-><fun>() resp. the closure <cl> for
 * every element of the array or mapping <arg>, and return
 * a result made from those elements for which the function
 * call returns TRUE.
 *
 * If <ob> is omitted, or neither an object nor a string, then
 * this_object() is used.
 */

{
    if (sp[-num_arg+1].type == T_MAPPING)
        return x_filter_mapping(sp, num_arg, MY_TRUE);
    else
        return f_filter_array(sp, num_arg);

} /* f_filter() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_map (svalue_t *sp, int num_arg)

/* VEFUN map()
 *
 *   mixed * map(mixed *arg, string func, string|object ob, mixed extra...)
 *   mixed * map(mixed *arg, closure cl, mixed extra...)
 *
 *   mapping map(mapping arg, string func, string|object ob, mixed extra...)
 *   mapping map(mapping arg, closure cl, mixed extra...)
 *
 * Call the function <ob>-><func>() resp. the closure <cl> for
 * every element of the array or mapping <arg>, and return a result
 * made up from the returned values.
 *
 * If <ob> is omitted, or neither an object nor a string, then
 * this_object() is used.
 */

{
    if (sp[-num_arg+1].type == T_MAPPING)
        return x_map_mapping(sp, num_arg, MY_TRUE);
    else
        return f_map_array(sp, num_arg);

} /* f_map() */

/*=========================================================================*/
/*                               OTHER                                     */

/*-------------------------------------------------------------------------*/
svalue_t *
f_debug_info (svalue_t *sp, int num_arg)

/* VEFUN debug_info()
 *
 *   mixed debug_info(int flag, object obj)
 *
 * Print out some driver internal debug information.
 *
 * DINFO_OBJECT (0): Information like heart_beat, enable_commands etc. of the
 *     specified object will be printed, and 0 returned.
 *
 * DINFO_MEMORY (1): Memory usage information like how many strings,
 *     variables, inherited files, object size etc. will be printed about the
 *     specified object, and 0 returned.
 *
 * DINFO_OBJLIST (2): Objects from the global object list are returned.  If
 *     the optional second arg is omitted, the first element (numbered 0)
 *     is returned. If the second arg is a number n, the n'th element of the
 *     object list returned. If the second arg is an object, it's successor
 *     in the object list is returned.
 *
 * DINFO_MALLOC: Equivalent to typing ``malloc'' at the command line.
 *     No second arg must be given. Returns 0.
 *
 * DINFO_STATUS (4): Collect the status information of the driver.  The
 *     optional second arg can be 0, "tables", "swap", "malloc" or any other
 *     argument accepted by the actual driver.  The result is a printable
 *     string with the status information, or 0 if an invalid argument was
 *     given.
 *
 * DINFO_DUMP (5): Dump the information specified by <arg2> into the
 *     filename specified by <arg3>. If <arg3> is omitted, a default file
 *     name is used. The function calls master->valid_write() to check that
 *     it can write the files. The file in question is always written anew.
 *     Result is 1 on success, or 0 if an error occured.
 *
 *     <arg2> == "objects": dump information about all objects. Default
 *       filename is '/OBJ_DUMP', the valid_write() will read 'objdump' for
 *       the function.
 *
 *     <arg2> == "opcodes": dump the usage statistics of the opcodes. Default
 *       filename is '/OPC_DUMP', the valid_write() will read 'opcdump' for
 *       the function. If the driver is compiled without OPCPROF, this call
 *       will always return 0.
 *
 * DINFO_DATA (6): Return raw information about an aspect of
 *     the driver specified by <arg2>. The result of the function
 *     is an array with the information, or 0 for unsupported values
 *     of <arg2>.
 *
 *     Allowed values for <arg2> are: DID_STATUS, DID_SWAP, DID_MALLOC.
 *
 *     <arg2> == DID_STATUS (0): Returns the "status" and "status tables"
 *        information:
 *
 *        int DID_ST_ACTIONS
 *        int DID_ST_ACTIONS_SIZE
 *            Number and size of allocated actions.
 *
 *        int DID_ST_SHADOWS
 *        int DID_ST_SHADOWS_SIZE
 *            Number and size of allocated shadows.
 *
 *        int DID_ST_OBJECTS
 *        int DID_ST_OBJECTS_SIZE
 *            Number and size of swapped-in objects.
 *
 *        int DID_ST_OBJECTS_SWAPPED
 *        int DID_ST_OBJECTS_SWAP_SIZE
 *            Number and size of swapped-out object variable blocks.
 *
 *        int DID_ST_OBJECTS_LIST
 *            Number of objects in the object list.
 *
 *        int DID_ST_OBJECTS_PROCESSED
 *            Number of listed objects processed in the last backend
 *            cycle.
 *
 *        float DID_ST_OBJECTS_AVG_PROC
 *            Average number of objects processed each cycle, expressed
 *            as fraction (0..1.0).
 *
 *        int DID_ST_OTABLE
 *            Number of objects listed in the object table.
 *
 *        int DID_ST_OTABLE_SLOTS
 *            Number of hash slots provided by the object table.
 *
 *        int DID_ST_OTABLE_SIZE
 *            Size occupied by the object table.
 *
 *        int DID_ST_HBEAT_OBJS
 *            Number of objects with a heartbeat.
 *
 *        int DID_ST_HBEAT_CALLS
 *            Number of heart_beats executed so far.
 *
 *        int DID_ST_HBEAT_SLOTS
 *        int DID_ST_HBEAT_SIZE
 *            Number of allocated entries in the heart_beat table
 *            and its size.
 *
 *        int DID_ST_HBEAT_PROCESSED
 *            Number of heart_beats called in the last backend cycle.
 *
 *        float DID_ST_HBEAT_AVG_PROC
 *            Average number of heart_beats called each cycle, expressed
 *            as fraction (0..1.0).
 *
 *        int DID_ST_CALLOUTS
 *            Number of pending call_outs.
 *
 *        int DID_ST_CALLOUT_SLOTS
 *        int DID_ST_CALLOUT_SIZE
 *            Number of allocated entries in the call_out table
 *            and its size.
 *
 *        int DID_ST_ARRAYS
 *        int DID_ST_ARRAYS_SIZE
 *            Number and size of all arrays.
 *
 *        int DID_ST_MAPPINGS
 *        int DID_ST_MAPPINGS_SIZE
 *            Number and size of all mappings.
 *
 *        int DID_ST_PROGS
 *        int DID_ST_PROGS_SIZE
 *            Number and size of all programs.
 *
 *        int DID_ST_PROGS_SWAPPED
 *        int DID_ST_PROGS_SWAP_SIZE
 *            Number and size of swapped-out programs.
 *
 *        int DID_ST_USER_RESERVE
 *        int DID_ST_MASTER_RESERVE     
 *        int DID_ST_SYSTEM_RESERVE
 *            Current sizes of the three memory reserves.
 *
 *        int DID_ST_ADD_MESSAGE
 *        int DID_ST_PACKETS
 *        int DID_ST_PACKET_SIZE
 *            Number of calls to add_message(), number and total size
 *            of sent packets.
 *            If the driver is not compiled with COMM_STAT, all three
 *            values are returned as -1.
 *
 *        int DID_ST_APPLY
 *        int DID_ST_APPLY_HITS
 *            Number of calls to apply_low(), and how many of these
 *            were cache hits.
 *            If the driver is not compiled with APPLY_CACHE_STAT, all two
 *            values are returned as -1.
 *
 *        int DID_ST_STRINGS
 *        int DID_ST_STRING_SIZE
 *            Number of distinct strings in the string table, and
 *            their size.
 *
 *        int DID_ST_STR_TABLE_SIZE
 *            Size of the string table.
 *
 *        int DID_ST_STR_REQ
 *        int DID_ST_STR_REQ_SIZE
 *            Total number of string allocations, and their size.
 *
 *        int DID_ST_STR_SEARCHES
 *        int DID_ST_STR_SEARCH_LEN
 *            Number of searches in the string table, and the
 *            accumulated search length.
 *
 *        int DID_ST_RX_CACHED
 *            Number of regular expressions cached.
 *
 *        int DID_ST_RX_TABLE
 *        int DID_ST_RX_TABLE_SIZE
 *            Number of slots in the regexp cache table, and size of the
 *            memory currently held by it and the cached expressions.
 *
 *        int DID_ST_RX_REQUESTS
 *            Number of requests for new regexps.
 *
 *        int DID_ST_RX_REQ_FOUND
 *            Number of requested regexps found in the table.
 *
 *        int DID_ST_RX_REQ_COLL
 *            Number of requested new regexps which collided with
 *            a cached one.
 *
 *
 *     <arg2> == DID_SWAP (1): Returns the "status swap" information:
 *
 *        int DID_SW_PROGS
 *        int DID_SW_PROG_SIZE
 *            Number and size of swapped-out program blocks.
 *
 *        int DID_SW_PROG_UNSWAPPED
 *        int DID_SW_PROG_U_SIZE
 *            Number and size of unswapped program blocks.
 *
 *        int DID_SW_VARS
 *        int DID_SW_VAR_SIZE
 *            Number and size of swapped-out variable blocks.
 *
 *        int DID_SW_FREE
 *        int DID_SW_FREE_SIZE
 *            Number and size of free blocks in the swap file.
 *
 *        int DID_SW_FILE_SIZE
 *            Size of the swap file.
 *
 *        int DID_SW_REUSED
 *            Total reused space in the swap file.
 *
 *        int DID_SW_SEARCHES
 *        int DID_SW_SEARCH_LEN
 *            Number and total length of searches for block to reuse
 *            in the swap file.
 *
 *        int DID_SW_F_SEARCHES
 *        int DID_SW_F_SEARCH_LEN
 *            Number and total length of searches for a block to free.
 *
 *        int DID_SW_COMPACT
 *            TRUE if the swapper is running in compact mode.
 *
 *        int DID_SW_RECYCLE_FREE
 *            TRUE if the swapper is currently recycling free block.
 *
 *
 *     <arg2> == DID_MEMORY (2): Returns the "status malloc" information:
 *
 *        string DID_MEM_NAME
 *            The name of the allocator: "sysmalloc" or "smalloc".
 *
 *        int DID_MEM_SBRK
 *        int DID_MEM_SBKR_SIZE
 *            Number and size of memory blocks requested from the
 *            operating system (smalloc only).
 *
 *        int DID_MEM_LARGE
 *        int DID_MEM_LARGE_SIZE
 *        int DID_MEM_LFREE
 *        int DID_MEM_LFREE_SIZE
 *            Number and size of large allocated resp. free blocks.
 *            (smalloc only)
 *
 *        int DID_MEM_CHUNK
 *        int DID_MEM_CHUNK_SIZE
 *            Number and size of small chunk blocks (smalloc only).
 *
 *        int DID_MEM_UNUSED
 *            Unused space in the current small chunk block
 *            (smalloc only).
 *
 *        int DID_MEM_SMALL
 *        int DID_MEM_SMALL_SIZE
 *        int DID_MEM_SFREE
 *        int DID_MEM_SFREE_SIZE
 *            Number and size of small allocated resp. free blocks
 *            (smalloc only).
 *
 *        int DID_MEM_MINC_CALLS
 *        int DID_MEM_MINC_SUCCESS
 *        int DID_MEM_MINC_SIZE
 *            Number of calls to malloc_increment(), the number
 *            of successes and the size of memory allocated this
 *            way (smalloc only).
 *
 * TODO: debug_info() and all associated routines are almost big enough
 * TODO:: to justify a file on their own.
 */

{
    svalue_t *arg;
    svalue_t res;
    object_t *ob;

    arg = sp-num_arg+1;
    inter_sp = sp;

    TYPE_TESTV1(arg, T_NUMBER)

    assign_svalue_no_free(&res, &const0);
    assign_eval_cost();
    switch ( arg[0].u.number )
    {
    case DINFO_OBJECT:  /* --- DINFO_OBJECT --- */
      {
        /* Give information about an object, deciphering it's flags, nameing
         * it's position in the list of all objects, total light and all the
         * stuff that is of interest with respect to look_for_objects_to_swap.
         */

        int flags;
        object_t *prev, *obj2;

        if (num_arg != 2)
            error("bad number of arguments to debug_info\n");
        TYPE_TESTV2(arg+1, T_OBJECT)
        ob = arg[1].u.ob;
        flags = ob->flags;
        add_message("O_HEART_BEAT      : %s\n",
          flags&O_HEART_BEAT      ?"TRUE":"FALSE");
#ifdef O_IS_WIZARD
        add_message("O_IS_WIZARD       : %s\n",
          flags&O_IS_WIZARD       ?"TRUE":"FALSE");
#endif
        add_message("O_ENABLE_COMMANDS : %s\n",
          flags&O_ENABLE_COMMANDS ?"TRUE":"FALSE");
        add_message("O_CLONE           : %s\n",
          flags&O_CLONE           ?"TRUE":"FALSE");
        add_message("O_DESTRUCTED      : %s\n",
          flags&O_DESTRUCTED      ?"TRUE":"FALSE");
        add_message("O_SWAPPED         : %s\n",
          flags&O_SWAPPED          ?"TRUE":"FALSE");
        add_message("O_ONCE_INTERACTIVE: %s\n",
          flags&O_ONCE_INTERACTIVE?"TRUE":"FALSE");
        add_message("O_RESET_STATE     : %s\n",
          flags&O_RESET_STATE     ?"TRUE":"FALSE");
        add_message("O_WILL_CLEAN_UP   : %s\n",
          flags&O_WILL_CLEAN_UP   ?"TRUE":"FALSE");
        add_message("O_REPLACED        : %s\n",
          flags&O_REPLACED        ?"TRUE":"FALSE");
#ifdef F_SET_LIGHT
        add_message("total light : %d\n", ob->total_light);
#endif
        add_message("time_reset  : %ld\n", (long)ob->time_reset);
        add_message("time_of_ref : %ld\n", (long)ob->time_of_ref);
        add_message("ref         : %ld\n", ob->ref);
#ifdef DEBUG
        add_message("extra_ref   : %ld\n", ob->extra_ref);
#endif
        if (ob->gigaticks)
            add_message("evalcost   :  %lu%09lu\n", ob->gigaticks, ob->ticks);
        else
            add_message("evalcost   :  %lu\n", ob->ticks);
        add_message("swap_num    : %ld\n", O_SWAP_NUM(ob));
        add_message("name        : '%s'\n", ob->name);
        add_message("load_name   : '%s'\n", ob->load_name);
        obj2 = ob->next_all;
        if (obj2)
            add_message("next_all    : OBJ(%s)\n",
              obj2->next_all?obj2->name:"NULL");
        prev = ob->prev_all;
        if (prev) {
            add_message("Previous object in object list: OBJ(%s)\n"
                       , prev->name);
        } else
            add_message("This object is the head of the object list.\n");
        break;
      }

    case DINFO_MEMORY:  /* --- DINFO_MEMORY --- */
      {
        /* Give information about an object's program with regard to memory
         * usage. This is meant to point out where memory can be saved in
         * program structs.
         */

        program_t *pg;

        if (num_arg != 2)
            error("bad number of arguments to debug_info\n");
        TYPE_TESTV2(arg+1, T_OBJECT)
        if ((sp->u.ob->flags & O_SWAPPED) && load_ob_from_swap(sp->u.ob) < 0)
            error("Out of memory\n");
        pg = sp->u.ob->prog;
        add_message("program ref's %3ld\n",        pg->ref);
        add_message("Name: '%s'\n",                pg->name);
        add_message("program size    %6ld\n"
          ,(long)(PROGRAM_END(*pg) - pg->program));
        add_message("num func's:  %3d (%4ld)\n", pg->num_functions
          , (long)(pg->num_functions * sizeof(uint32) +
                  pg->num_function_names * sizeof(short)));
        add_message("num vars:    %3d (%4ld)\n", pg->num_variables
          , (long)(pg->num_variables * sizeof(variable_t)));
        add_message("num strings: %3d (%4ld)\n", pg->num_strings
          , (long)(pg->num_strings   * sizeof(char *)));
        {
            int i = pg->num_inherited;
            int cnt = 0;
            inherit_t *inheritp;

            for (inheritp = pg->inherit; i--; inheritp++)
            {
                if (inheritp->inherit_type == INHERIT_TYPE_NORMAL)
                    cnt++;
            }
            add_message("num inherits %3d (%4ld)\n", cnt
                , (long)(pg->num_inherited * sizeof(inherit_t)));
        }
        add_message("total size      %6ld\n"
          ,pg->total_size);
        add_message("data size       %6ld\n"
          ,data_size(sp->u.ob));
        break;
      }

    case DINFO_OBJLIST:  /* --- DINFO_OBJLIST --- */
      {
        /* Get the first/next object in the object list */

        int i;
        ob = obj_list;
        i = 0;

        if (num_arg > 1)
        {
            if (num_arg > 2)
                error("bad number of arguments to debug_info\n");

            if (sp->type == T_NUMBER)
            {
                i = sp->u.number;
            }
            else
            {
                TYPE_TESTV2(sp, T_OBJECT)
                ob = sp->u.ob;
                i = 1;
            }
        }

        while (ob && --i >= 0) ob = ob->next_all;
        if (ob)
            put_ref_object(&res, ob, "debug_info");
        break;
      }

    case DINFO_MALLOC:  /* --- DINFO_MALLOC --- */
      {
        /* Print the malloc data */
        /* TODO: This case can go, DINFO_STATUS "malloc" is sufficient */

        strbuf_t sbuf;

        status_parse(&sbuf, "malloc");
        strbuf_send(&sbuf);
        break;
      }

    case DINFO_STATUS:  /* --- DINFO_STATUS --- */
      {
        /* Execute the 'status' command */

        strbuf_t sbuf;

        if (num_arg != 1 && num_arg != 2)
            error("bad number of arguments to debug_info\n");
        if (num_arg == 1
         || (sp->type == T_NUMBER && sp->u.number == 0)) {
            sp->u.string = "";
        } else {
            TYPE_TESTV2(arg+1, T_STRING)
        }
        if (status_parse(&sbuf, sp->u.string))
            strbuf_store(&sbuf, &res);
        else
            strbuf_free(&sbuf);
        break;
      }

    case DINFO_DUMP:  /* --- DINFO_DUMP --- */
      {
        /* Dump information into files */

        char * fname;

        if (num_arg != 2 && num_arg != 3)
            error("bad number of arguments to debug_info\n");

        TYPE_TESTV2(arg+1, T_STRING);
        if (num_arg == 2
         || (sp->type == T_NUMBER && sp->u.number == 0)) {
            fname = NULL;
        } else {
            TYPE_TESTV3(arg+2, T_STRING)
            fname = sp->u.string;
        }

        if (!strcmp(arg[1].u.string, "objects"))
        {
            res.u.number = dumpstat(fname ? fname : "/OBJ_DUMP") ? 1 : 0;
            break;
        }

        if (!strcmp(arg[1].u.string, "opcodes"))
        {
#ifdef OPCPROF
            res.u.number = opcdump(fname ? fname : "/OPC_DUMP") ? 1 : 0;
#endif
            break;
        }

        error("Bad argument '%s' to debug_info(DINFO_DUMP).\n", arg[1].u.string);
        break;
      }

    case DINFO_DATA:  /* --- DINFO_DATA --- */
      {
        /* Return information about the one or other driver interna.
         * This is basically the same information returned by DINFO_STATUS,
         * just not pre-processed into nice strings.
         */

        vector_t *v;

        if (num_arg != 2)
            error("bad number of arguments to debug_info\n");
        TYPE_TESTV2(arg+1, T_NUMBER)

        switch(arg[1].u.number)
        {
        case DID_STATUS:
            v = allocate_array(DID_STATUS_MAX);
            if (!v)
                error("Out of memory.\n");

            dinfo_data_status(v->item);
            otable_dinfo_status(v->item);
            hbeat_dinfo_status(v->item);
            callout_dinfo_status(v->item);
            string_dinfo_status(v->item);
#ifdef RXCACHE_TABLE
            rxcache_dinfo_status(v->item);
#endif
            put_array(&res, v);
            break;

        case DID_SWAP:
            v = allocate_array(DID_SWAP_MAX);
            if (!v)
                error("Out of memory.\n");
            swap_dinfo_data(v->item);
            put_array(&res, v);
            break;

        case DID_MEMORY:
            v = allocate_array(DID_MEMORY_MAX);
            if (!v)
                error("Out of memory.\n");
#if defined(MALLOC_smalloc)
            smalloc_dinfo_data(v->item);
#endif
#if defined(MALLOC_sysmalloc)
            put_volatile_string(v->item+DID_MEM_NAME, "system malloc");
#endif
            put_array(&res, v);
            break;
        }
        break;
      }

    default:
        error("Bad debug_info() request value: %ld\n", arg[0].u.number);
        /* NOTREACHED */
        break;
    }

    /* Clean up the stack and return the result */
    
    sp = pop_n_elems(num_arg, sp);

    sp++;
    *sp = res;
    return sp;
} /* f_debug_info() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_shutdown (svalue_t *sp)

/* TEFUN shutdown()
 *
 *   void shutdown()
 *
 * Shutdown the mud. Never use this efun. Instead if you have a
 * need to shutdown the mud use the shutdown command.
 * You may be asking yourself, if you're not supposed
 * to use it why is it here?  Sorry, I cannot answer
 * that.  Its top secret.
 */

{
    extra_jobs_to_do = MY_TRUE;
    game_is_being_shut_down = MY_TRUE;
    return sp;
} /* f_shutdown() */

#ifdef HAVE_LIBMYSQLCLIENT
/*=========================================================================*/
/*                               mySQL                                     */

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

typedef struct db_dat_s db_dat_t;

/*--- struct db_dat_s: SQL connection handle --- */
struct db_dat_s
{
    db_dat_t  *next;
    db_dat_t  *prev;
    MYSQL     *mysql_dat;
    MYSQL_RES *mysql_result;
    MYSQL_ROW  mysql_row;
};

static db_dat_t *my_dat;
  /* List of connection handles, newest first.
   */

static void raise_db_error (db_dat_t *dat) NORETURN;

/*-------------------------------------------------------------------------*/
static db_dat_t *
allocate_new_dat(void)

/* Allocate a free handle to use for a connection to an SQL-server
 * and return it. The handle is also chained into the global list my_dat
 * at its beginning.
 */

{
    db_dat_t *tmp;

    if ( !my_dat ) /* The chained list has not been allocated */
    {
        my_dat = xalloc(sizeof(*my_dat));
	if ( !my_dat )
	{
	    error("Out of memory.\n");
	    /* NOTREACHED */
	    return NULL;
	}
        my_dat->prev = NULL;
        my_dat->next = NULL;
        my_dat->mysql_dat = NULL;
        my_dat->mysql_result = NULL;
        return my_dat;
    }

    /* The chained list exists */
    
    tmp = my_dat->prev = xalloc(sizeof(db_dat_t));
    if ( !tmp )
    {
	error("Out of memory.\n");
	return NULL;
    }
    tmp->next = my_dat; /* Put the new handle to the beginning */
    tmp->prev = NULL;
    tmp->mysql_dat = NULL;
    tmp->mysql_result = NULL;
    my_dat = tmp;

    return my_dat;
} /* allocate_new_dat() */

/*-------------------------------------------------------------------------*/
static db_dat_t *
find_dat_by_handle (unsigned int i)

/* Return the corresponding db_dat_t-structure for the handle with id <i>.
 * When the structure has been found, it is moved to the beginning
 * of the chained list as it is VERY likely that the next operation
 * will also be performed on this handle.
 * If the handle was not found, NULL is returned.
 */

{
    db_dat_t *tmp, *tmp2, *tmp0;
    unsigned int id;

    if ( !my_dat )
        return NULL;

    tmp = my_dat;
    while (   ((id = tmp->mysql_dat->thread_id) != i)
           && (tmp = tmp->next) )
	NOOP;

    if ( id != i ) // handle NOT found
        return NULL;

    /* Put the selected pointer at the beginning */
    if ( tmp == my_dat )
        return tmp;

    tmp0 = tmp->prev;
    tmp2 = tmp->next;
    tmp0->next = tmp2;
    if ( tmp2 )
        tmp2->prev = tmp0;
    my_dat->prev = tmp;
    tmp->prev = NULL;
    tmp->next = my_dat;

    /* Point my_dat at the first entry again */
    my_dat = tmp;

    return tmp;
} /* find_dat_by_handle() */

/*-------------------------------------------------------------------------*/
static unsigned int
remove_dat (db_dat_t *dat)

/* Remove the database-handle from memore and the chained list.
 * Also clean any memory allocated for SQL-use.
 * The result is the handle of the data-connection that was
 * closed, or 0 if the handle was not found.
 */

{
    db_dat_t *tmp, *tmp2;
    unsigned int i;

    if ( !dat )
        return 0;

    /* Close the database connection */
    if ( dat->mysql_dat )
    {
        if (dat->mysql_result)
            mysql_free_result(dat->mysql_result);
        i = dat->mysql_dat->thread_id;
        mysql_close(dat->mysql_dat);
    }

    /* Unlink the structure from the list */
    tmp = dat->prev;
    tmp2 = dat->next;
    if ( tmp )
        tmp->next = tmp2;
    if ( tmp2 )
    {
        if ( !tmp )
            my_dat = tmp2;
        tmp2->prev = tmp;
    }
    if ( dat == my_dat )
        my_dat = NULL;

    xfree(dat);
    return i;
} /* remove_dat() */

/*-------------------------------------------------------------------------*/
static unsigned int
remove_dat_by_handle (int i)

/* Remove the memory for the handle with the number <handle> from
 * the memory and return the handle.
 */

{
    db_dat_t *tmp;

    tmp = find_dat_by_handle(i);
    return remove_dat(tmp);
} /* remove_dat_by_handle() */

/*-------------------------------------------------------------------------*/
static void
raise_db_error (db_dat_t *dat)

/* Raise an error according to the last operation on the passed
 * SQL-connection. The connection is closed.
*/

{
    char *tmp;
    char *err_string;

    if ( !dat )
    {
       error(	"An unknown error occured during the current database-"
		"operation\n");
       /* NOTREACHED */
    } else 
    {
       
       tmp = mysql_error(dat->mysql_dat);
       err_string = alloca(strlen(tmp) + 2);
       strcpy(err_string, tmp);
       strcat(err_string, "\n");
       remove_dat(dat);
       error(err_string);
    }
    /* NOTREACHED */
} /* raise_db_error() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_affected_rows (svalue_t *sp)

/* TEFUN db_affected_rows()
 *
 *   int db_affected_rows(int handle)
 *
 * Return the number of affected rows of the last SQL-statement that
 * has been sent to the SQL-server via handle <handle>.
 * Only useful for DELETE- or UPDATE-operations.
 */

{
    db_dat_t    *dat;
    int          rows;
    unsigned int handle;

    TYPE_TEST1(sp, T_NUMBER)
    handle = (unsigned int)sp->u.number;
    if ( !(dat = find_dat_by_handle(handle)) )
        error("Illegal handle for database.\n");
    rows = mysql_affected_rows(dat->mysql_dat);
    free_svalue(sp); /* Well, it's just a number */
    put_number(sp, rows);
    return sp;
} /* f_db_affected_rows() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_insert_id (svalue_t *sp)

/* TEFUN db_insert_id()  by DaFire@FF
 *
 *   int db_insert_id(int handle)
 *
 * Gibt den Wert vom letzen AUTO_INCREMENT Feld zurueck
 */

{
    db_dat_t    *dat;
    my_ulonglong   insertid;
    unsigned int handle;

    TYPE_TEST1(sp, T_NUMBER)
    handle = (unsigned int)sp->u.number;
    if ( !(dat = find_dat_by_handle(handle)) )
        error("Illegal handle for database.\n");
    insertid = mysql_insert_id(dat->mysql_dat);
    free_svalue(sp); /* Well, it's just a number */
    put_number(sp, insertid);
    return sp;
} /* f_db_insert_id() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_conv_string (svalue_t *sp)

/* TEFUN db_conv_string()
 *
 *   string db_conv_string(string str)
 *
 * Convert the string <str> into a string that is correctly interpretated
 * for usage as a string in db_exec(), e.g. ' is replaced with \' and so
 * on.
 */

{
    char *s;
    char *buff;

    TYPE_TEST1(sp, T_STRING)
    s = sp->u.string;
    buff = alloca(strlen(s)*2 +1);
    if ( !buff )
    {
        error("Out of memory.\n");
        /* NOTREACHED */
        return sp;
    }
    mysql_escape_string(buff, s, strlen(s));

    /* Re-allocate the escaped string to its proper length */
    s = string_copy(buff);

    free_string_svalue(sp);
    put_malloced_string(sp, s);
    return sp;
} /* f_db_conv_string() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_close (svalue_t *sp)

/* TEFUN db_close()
 *
 *   int db_close(int handle)
 *
 * Close the server-connection with the handle <handle>
 * Return the handle-number on success.
 */
     
{
   p_int     handle;
   db_dat_t *dat;
   
   TYPE_TEST1(sp, T_NUMBER)
      handle = sp->u.number;
   
   if ( !(dat = find_dat_by_handle((unsigned int)handle)) )
   {
      error("Illegal handle for database.\n");
      /* NOTREACHED */
      return sp;
   }
   handle = (p_int)remove_dat(dat);
   free_svalue(sp); /* Well, it's just a number */
   put_number(sp, handle);
   return sp;
   
} /* db_close() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_connect (svalue_t *sp)

/* TEFUN db_connect()
 *
 *   int db_connect(string database, string user)
 *
 * Connect to the database <database> on the local mySQL-server.
 * The return-value is the handle for this connection.
 * If the database does not exist or the server is NOT started,
 * a runtime-error is raised.
 */

{
   char     *database;
   char     *user;
   p_int     sock;
   db_dat_t *tmp;
   
   TYPE_TEST2(sp, T_STRING);
   TYPE_TEST1(sp-1, T_STRING);
   database = sp[-1].u.string;
   user = sp->u.string;
   
   tmp = allocate_new_dat();
   if ( !tmp )
   {
      error("Out of memory.\n");
      /* NOTREACHED */
      return NULL;
   }
   
   tmp->mysql_dat = mysql_init(0);
   if ( !tmp->mysql_dat )
   {
      remove_dat(tmp);
      error("Out of memory.\n");
      /* NOTREACHED */
      return NULL;
   }
   
   /* Only connections to LOCALHOST are currently possible
       * I wouldn't dare to implement synchronous DB-access via
       * TCP (that's something for ERQ wizards :-).
       */

      /* DAFIRE@FF Datenbankanmeldung als user:mud pw:dum */
      
   if ( !mysql_real_connect(tmp->mysql_dat, "localhost", user, "dum",
                            database, 0, 0, 0))
   {
      raise_db_error(tmp);
      /* NOTREACHED */
      return sp;
   }
      
   free_string_svalue(sp);
   sp--;
   free_string_svalue(sp);
   sock = (signed)tmp->mysql_dat->thread_id;
   put_number(sp, sock);
   return sp;
} /* f_db_connect() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_exec (svalue_t *sp)

/* TEFUN db_exec()
 *
 *   int db_exec(int handle, string statement)
 *
 * Execute the SQL-statement <statement> for the connection <handle> to
 * the SQL-server. The result is the handle if all went okay. If there
 * was an error in the statement, 0 is returned.
 */

{
    char         *s;
    db_dat_t     *dat;
    unsigned int  handle;
    unsigned int  err_no;

    TYPE_TEST1(sp-1, T_NUMBER)
    TYPE_TEST2(sp, T_STRING)

    handle = (unsigned int)sp[-1].u.number;
    s = sp->u.string;

    if ( !(dat = find_dat_by_handle(handle)) )
    {
       error("Illegal handle for database.\n");
       /* NOTREACHED */
       return sp;
    }
       
    if ( mysql_query(dat->mysql_dat, s) )
    {
       /* either a REAL error occured or just an error in the SQL-statement
        */
          
       err_no = mysql_errno(dat->mysql_dat);
       if (   (err_no == CR_COMMANDS_OUT_OF_SYNC)
              || (err_no == CR_SERVER_GONE_ERROR)
              || (err_no == CR_SERVER_LOST)
              || (err_no == CR_UNKNOWN_ERROR) )
       {
          /* A REAL error occured */
          raise_db_error(dat);
          return sp;
       }
          
       /* Just an error in the SQL-statement */
       free_string_svalue(sp);
       sp--;
       free_svalue(sp); /* Only a number */
       put_number(sp, 0);
       return sp;
    }

    /* If we used a select-statement, how many columns are returned? */
    
    if ( mysql_field_count(dat->mysql_dat) )
    {
        /* Try to initiate a row-by-row transfer */
        if ( !(dat->mysql_result = mysql_use_result(dat->mysql_dat)) )
        {
            raise_db_error(dat);
            /* NOTREACHED */
            return sp;
        }
    }

    free_string_svalue(sp);
    sp--;
    free_svalue(sp); /* Only a number */
    put_number(sp, (signed)handle);
    return sp;
} /* f_db_exec() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_coldefs (svalue_t *sp)
 
/* TEFUN db_coldefs()  by dafire@ff
 *
 *   mixed db_coldefs(int handle)
 *
 */

{
   db_dat_t     *dat;
   vector_t     *v;
   int           num_fields, i;
   unsigned int  handle;
   MYSQL_FIELD  *fields;
     
   TYPE_TEST1(sp, T_NUMBER)
      handle = (unsigned int)sp->u.number;
   if ( !(dat = find_dat_by_handle(handle)) )
   {
      error("Illegal handle for database.\n");
      /* NOTREACHED */
      return sp;
   }
   if (!dat->mysql_result) 
   {
      free_svalue(sp);
      put_number(sp, 0);
      return sp;
   }
 
    
   num_fields = mysql_num_fields(dat->mysql_result);
     
   v = allocate_array(num_fields);
   if (!v)
   {
      error("Out of memory.\n");
      /* NOTREACHED */
      return sp;
   }
 
   fields = mysql_fetch_fields(dat->mysql_result);
     
   for (i = 0; i < num_fields; i++)
   {
      put_malloced_string(v->item+i, string_copy(fields[i].name));
   }
  
   free_svalue(sp); /* It's a number */
   put_array(sp, v);
   return sp;
} /* f_db_coldefs() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_fetch (svalue_t *sp)

/* TEFUN db_fetch()
 *
 *   mixed db_fetch(int handle)
 *
 * Retrieve _ONE_ line of result of the latest SQL-action to the server
 * based on the handle <handle>. If not more results are on the server,
 * 0 is returned.
 */

{
    db_dat_t     *dat;
    vector_t     *v;
    int           num_cols, i;
    unsigned int  handle;

    TYPE_TEST1(sp, T_NUMBER)
    handle = (unsigned int)sp->u.number;
    if ( !(dat = find_dat_by_handle(handle)) )
    {
        error("Illegal handle for database.\n");
        /* NOTREACHED */
        return sp;
    }

    if (!dat->mysql_result) 
    {
       free_svalue(sp);
       put_number(sp, 0);
       return sp;
    }
    if (!dat->mysql_result) 
    {
       free_svalue(sp);
       put_number(sp, 0);
       return sp;
    }
    /* Store the (next) row of the result in dat->mysql_row */

    dat->mysql_row = mysql_fetch_row(dat->mysql_result);
    if ( dat->mysql_row == NULL )
    {
        /* No more rows to fetch */
        free_svalue(sp); /* It's a number */
        put_number(sp, 0);
        return sp;
    }

    /* How many columns does every line contain? */
   
    num_cols = mysql_num_fields(dat->mysql_result);
    
    v = allocate_array(num_cols);
    if (!v)
    {
        error("Out of memory.\n");
        /* NOTREACHED */
        return sp;
    }
    
    for (i = 0; i < num_cols; i++)
       if (dat->mysql_row[i])
          put_malloced_string(v->item+i, string_copy(dat->mysql_row[i]));
       else
          put_malloced_string(v->item+i, string_copy(""));

    free_svalue(sp); /* It's a number */
    put_array(sp, v);
    return sp;
} /* f_db_exec() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_db_handles (svalue_t *sp)

/* TEFUN db_handles()
 *
 *   int *db_handles()
 *
 * Returns an array with all open handles to the SQL-server.
 * As mySQL is most of the time limited to 100 connections, you
 * should not let this number grow too big. The handles are sorted
 * in a special order: The last used handle is the first one and
 * the handle that hasn't been used for the longest time is
 * the last one. If no handles are open, an empty array is returned.
 */

{
    int elems;
    int i;
    db_dat_t *tmp;
    vector_t *v;

    tmp = my_dat;

    /* Maybe there's no open connection yet/anymore */
    if ( !tmp )
    {
        v = allocate_array(0);
        if (!v)
        {
            error("Out of memory.\n");
            /* NOTREACHED */
            return sp;
        }
        sp++;
        put_array(sp, v);
        return sp;
    }

    elems = 1;

    /* Count how many handles there are */
    while (tmp = tmp->next)
        elems++;

   /* Allocate an array to store all handle-ids */
    v = allocate_array(elems);
    if (!v)
    {
        error("Out of memory.\n");
        /* NOTREACHED */
        return sp;
    }

    /* Now browse through all handles again and store their ids */
    tmp = my_dat;
    for (i = 0; i < elems; i++)
    {
        put_number(v->item+i, tmp->mysql_dat->thread_id);
        tmp = tmp->next;
    }

    sp++;
    put_array(sp, v);
    return sp;
} /* f_db_handles() */

/***************************************************************************/
#endif

/* BEWARE! data is little-endian! that is: least significant data comes first
 */
typedef struct longint_s
{
   short sign;
   short len;
   unsigned short data[2];
} longint_t;

void free_longint(longint_t *a)
{
   xfree(a);
}

/* prereq: any string. arbitrary count of leading '-' are allowed (each
   reversing signedness). invalid strings are mapped to 0 */
longint_t* _to_longint(char *s)
{
   longint_t *bn;
   int len,sign,i,end,mult,j;
   unsigned int val;
   char *rs,c;
   
   if (!s)
   {
      bn=(longint_t*)xalloc(sizeof(longint_t));
      if (!bn)
      {
         error("Out of memory.\n");
         return;
      }
      
      bn->len=0;
      bn->sign=1;
      bn->data[0]=0;
      return bn;
   }
   rs=s;

   sign=1;
   
   while(s[0]=='-')
   {
      sign=-sign;
      s++;
   }

   len=strlen(s);

   if (len<0)
   {
      bn=(longint_t*)xalloc(sizeof(longint_t));
      if (!bn)
      {
         error("Out of memory.\n");
         return;
      }
      bn->len=0;
      bn->sign=1;
      bn->data[0]=0;
      return bn;
   }
   
   for(i=len;i--;)
      if (s[i]<'0'||s[i]>'9')
      {
         bn=(longint_t*)xalloc(sizeof(longint_t));
         if (!bn)
         {
            error("Out of memory.\n");
            return;
         }
         bn->len=0;
         bn->sign=1;
         bn->data[0]=0;
         return bn;
      }

   i=(int)(len*log(10)/(16*log(2)))-1;
   if (i<0) i=0;
   bn=(longint_t*)xalloc(sizeof(longint_t)+sizeof(unsigned short)*i);
   if (!bn)
   {
      error("Out of memory.\n");
      return;
   }

   bn->sign=sign;
   bn->len=0;
   bn->data[0]=0;
   i=0;
   while(i<len)
   {
      end=i+4;
      if (end>len) end=len;
      c=s[end];
      s[end]=0;
      mult=1;
      j=i;
      while(j<end) 
      {
         mult*=10;
         j++;
      }

      val=atoi(s+i);
      j=0;
      while(j<=bn->len)
      {
         val+=bn->data[j]*mult;
         bn->data[j]=val&0xffff;
         val>>=16;
         j++;
      }
      if (val)
      {
         bn->len++;
         bn->data[j]=val;
      }
      s[end]=c;
      i+=4;
   }

   if (bn->len==0 && bn->data[0]==0) bn->sign=1;
   return bn;
}

/* prereq: a,b positive, return a+b */
/* postcond: 0 is positive */
longint_t *_stradd_positive(longint_t *a, longint_t *b)
{
   longint_t *bn;
   int len=a->len+1;
   register int i;
   unsigned int v;
   
   if (b->len>a->len)
   {
      longint_t *tmp;

      tmp=a;
      a=b;
      b=tmp;
      len=a->len+1;
   }

   if (len-1<0) i=0;
   else i=len-1;
   
   bn=(longint_t*)xalloc(sizeof(longint_t)+sizeof(unsigned short)*i);
   if (!bn)
   {
      error("Out of memory.\n");
      return;
   }

   bn->sign=1;

   i=0;
   v=0;
   while(i<=b->len)
   {
      v+=a->data[i]+b->data[i];
      bn->data[i]=v&0xffff;
      v>>=16;
      i++;
   }
   while(i<=a->len)
   {
      v+=a->data[i];
      bn->data[i]=v&0xffff;
      v>>=16;
      i++;
   }
   if(v)
   {
      bn->data[i]=v;
      i++;
   }
   bn->len=i-1;
   
   return bn;
}

/* prereq: a,b positive, return signchg*(a-b) */
/* postcond: 0 may be negative */
longint_t* _strsub_positive(longint_t* a,longint_t* b,int sign)
{
   longint_t *bn;
   int len=a->len;
   register int i;
   register int v;
   int doswap;

   if (a->len==b->len)
   {
      i=a->len;
      while(i>=0 && a->data[i]==b->data[i]) i--;
      if (i>=0 && b->data[i]>a->data[i]) doswap=1;
      else doswap=0;
   }
   else doswap=0;
   
   if (doswap || b->len>a->len)
   {
      longint_t *tmp;

      tmp=a;
      a=b;
      b=tmp;
      len=a->len;
      sign=-sign;
   }

   len--;
   if (len<0) len=0;
   bn=(longint_t*)xalloc(sizeof(longint_t)+sizeof(unsigned short)*len);
   if (!bn)
   {
      error("Out of memory.\n");
      return;
   }

   bn->sign=sign;

   i=0;
   v=0;
   while(i<=b->len)
   {
      v+=a->data[i]-b->data[i];
      bn->data[i]=v&0xffff;
      v>>=16;
      i++;
   }
   while(i<=a->len)
   {
      v+=a->data[i];
      bn->data[i]=v&0xffff;
      v>>=16;
      i++;
   }

   bn->len=i-1;

   return bn;
}

/* prereq: 0 must be positive! */
inline int _strcmp(longint_t *a, longint_t *b)
{
   int i;
   
   if (a->sign!=b->sign) return a->sign;

   if (a->len > b->len) return 1*a->sign;
   else if (a->len < b->len) return -1*a->sign;

   i=a->len;
   while(i>=0 && a->data[i]==b->data[i]) i--;
   if (i<0) return 0;
   
   if (a->data[i]>b->data[i]) return 1*a->sign;
   else return -1*a->sign;
}
   

/* prereq: a->len >= b->len+ofs+1 */
/* returns abs(a)*sgn(a):=abs(a)+abs(b)*10^(9*ofs) */
inline void _stradd_inplace(longint_t *a,longint_t *b, int ofs)
{
   register int i,carry,v;

   i=0;
   carry=0;
   v=0;
   do
   {
      v+=a->data[i+ofs]+b->data[i];
      a->data[i+ofs]=v&0xffff;
      v>>=16;
      i++;
   }
   while(i<=b->len);
   while(v && i+ofs<=a->len)
   {
      v+=a->data[i+ofs];
      a->data[i+ofs]=v&0xffff;
      v>>=16;
      i++;
   }
   /* a is big enough, so no additional block needed */
}

inline void _strmul_int(longint_t *r, longint_t *a, int b)
{
   int i;
   unsigned v;
   
   if (!b)
   {
      r->data[0]=0;
      r->len=0;
      return;
   }

   v=0;
   i=0;
   while(i<=a->len)
   {
      v+=a->data[i]*b;
      r->data[i]=v&0xffff;
      v>>=16;
      i++;
   }
   if (v)
   {
      r->data[i]=v&0xffff;
      i++;
   }
   r->len=i-1;
}

/* prereq: any longints a,b */
/* returns positive 0 */
longint_t *_strmul(longint_t *a, longint_t *b)
{
   longint_t *r2, *r;
   int i;
   
   /* quick check if a==0 or b==0 */
   if (a->len==0 && a->data[0]==0
       || b->len==0 && b->data[0]==0) 
   {
      r=(longint_t*)xalloc(sizeof(longint_t));
      if (!r)
      {
         error("Out of memory.\n");
         return;
      }
      r->len=0;
      r->sign=1;
      r->data[0]=0;
      return r;
   }

   /* swap a,b. short b means less additions */
   r2=(longint_t*)xalloc(sizeof(longint_t)+sizeof(unsigned short)*a->len);
   if (!r2)
   {
      error("Out of memory.\n");
      return;
   }
   
   if (b->len==0)
   {
      _strmul_int(r2,a,b->data[0]);
      r=r2;
   }
   else
   {
      i=(a->len+b->len+1);
      r=(longint_t*)xalloc(sizeof(longint_t)+sizeof(unsigned short)*(i-1));
      if (!r)
      {
         error("Out of memory.\n");
         return;
      }
      r->len=i;
      do r->data[i]=0;
      while(i--);
      
      for(i=0;i<=b->len;i++)
      {
         register int j,carry,v;
         _strmul_int(r2,a,b->data[i]);

         v=0;
         j=0;
         while(j<=r2->len)
         {
            v+=r->data[j+i]+r2->data[j];
            r->data[j+i]=v&0xffff;
            v>>=16;
            j++;
         }
         while(v && j+i<=r->len)
         {
            v+=r->data[j+i];
            r->data[j+i]=v&0xffff;
            v>>=16;
            j++;
         }
      }
      while(r->len && !r->data[r->len]) r->len--;
      free_longint(r2);
   }
   r->sign=a->sign*b->sign;
   return r;
}
      

longint_t *_strdiv_int(longint_t *a, int b, int *rem, int shift)
{
   longint_t *r;
   div_t d;
   unsigned int v,v2;
   int i,ofs;

   ofs=shift/16;
   
   if (a->len<ofs)
   {
      *rem=0;
      r=(longint_t*)xalloc(sizeof(longint_t));
      if (!r)
      {
         error("Out of memory.\n");
         return;
      }
      r->len=0;
      r->sign=1;
      r->data[0]=0;
      return r;
   }
      
   if (a->len==0)
   {
      d=div(a->data[0]>>shift,b);
      *rem=d.rem;
      r=(longint_t*)xalloc(sizeof(longint_t));
      if (!r)
      {
         error("Out of memory.\n");
         return;
      }
      r->len=0;
      r->sign=1;
      r->data[0]=d.quot;
      return r;
   }
   
   v=a->len-ofs-1;
   if (v<0) v=0;
   r=(longint_t*)xalloc(sizeof(longint_t)+sizeof(unsigned short)*v);
   if (!r)
   {
      error("Out of memory.\n");
      return;
   }
   r->len=a->len-ofs;
   r->sign=a->sign;

   v=0;
   v2=0;
   shift=shift&15;
   i=a->len;
   while(i>=ofs)
   {
      v<<=16;
      v+=(a->data[i]>>shift)+v2;
      v2=(a->data[i]<<(16-shift))&0xffff;
      d=div(v,b);
      r->data[i-ofs]=d.quot;
      v=d.rem;
      i--;
   }
   while (r->len && !r->data[r->len]) r->len--;
   *rem=v;
   return r;
}

/* return a/b */
/* postcond: a is remainder */
longint_t *_strdiv(longint_t *a, longint_t *b)
{
   longint_t *r, *r2, *s;
   int irem,e,i,vi,j;
   unsigned int b2,v;
   
   if (a->len==0 && a->data[0]==0)
   {
      r=(longint_t*)xalloc(sizeof(longint_t));
      if (!r)
      {
         error("Out of memory.\n");
         return;
      }
      r->len=0;
      r->sign=1;
      r->data[0]=0;
      return r;
   }

   if (b->len==0 && b->data[0]==0) 
   {
      error("Division by zero!\n");
      return;
   }

   /* maybe copy a */
   
   if (b->len==0)
   {
      r=_strdiv_int(a,b->data[0],&irem,0);
      a->len=0;
      a->sign=1;
      a->data[0]=irem;
   }
   else
   {
      if (a->len<b->len)
      {
         /* return [0] */
         r=(longint_t*)xalloc(sizeof(longint_t));
         if (!r)
         {
            error("Out of memory.\n");
            return;
         }
         r->len=0;
         r->sign=1;
         r->data[0]=0;
         return r;
      }

      i=a->len-1;
      if (i<0) i=0;
      
      r=(longint_t*)xalloc(sizeof(longint_t)+sizeof(unsigned short)*i);
      if (!r)
      {
         error("Out of memory.\n");
         return;
      }
      i+=2;
      r->len=i-1;
      r->sign=1;
      while(i--) r->data[i]=0;
      
      if (b->len==0)
      {
         b2=b->data[0];
         e=0;
      }
      else
      {
         b2=b->data[b->len]<<16+b->data[b->len-1];
         e=(b->len-1)*16;
         while(b2>0xffff)
         {
            b2>>=1;
            e++;
         }
      }
      
      do
      {
         r2=_strdiv_int(a,b2,&irem,e);
         if (r2->len>0 || r2->data[0]!=0)
         {
            /* r+=r2; */
            v=0;
            j=0;
            while(j<=r2->len)
            {
               v+=r->data[j]+r2->data[j];
               r->data[j]=v&0xffff;
               v>>=16;
               j++;
            }
            while(v && j<=r->len)
            {
               v+=r->data[j];
               r->data[j]=v&0xffff;
               v>>=16;
               j++;
            }
            r->len=j-1;

            /* s=b*r2; */
            s=_strmul(b,r2);
            
            /* a-=s; */
            vi=0;
            j=0;
            while(j<=s->len)
            {
               vi+=a->data[j]+s->data[j];
               a->data[j]=vi&0xffff;
               vi>>=16;
               j++;
            }
            while(vi && j<=a->len)
            {
               vi+=a->data[j];
               a->data[j]=vi&0xffff;
               vi>>=16;
               j++;
            }
            a->len=j-1;

            free_longint(s);
         }
         free_longint(r2);
         
         if (_strcmp(a,b)>=0)
         {
            /* a-=b; */
            vi=0;
            j=0;
            while(j<=b->len)
            {
               vi+=a->data[j]+b->data[j];
               a->data[j]=vi&0xffff;
               vi>>=16;
               j++;
            }
            while(vi && j<=a->len)
            {
               vi+=a->data[j];
               a->data[j]=vi&0xffff;
               vi>>=16;
               j++;
            }
            a->len=j-1;

            /* r++ */
            v=1;
            j=0;
            while(v && j<=r->len)
            {
               v+=r->data[j];
               r->data[j]=v&0xffff;
               v>>=16;
               j++;
            }
            r->len=j-1;
         }
         else break;
      }
      while(1);
   }
   return r;
}

inline longint_t *sval_to_longint(svalue_t *sp, int argno)
{
   longint_t *a;
   int x,sgn,hi;
   
   switch(sp->type)
   {
    case T_NUMBER:
      x=sp->u.number;
      if (x<0)
      {
         sgn=-1;
         x=-x;
      }
      else sgn=1;
      if (x<=0xffff) hi=0;
      else
      {
         hi=x>>16;
         x&=0xffff;
      }
      
      a=(longint_t*)xalloc(sizeof(longint_t)
                           +(hi?sizeof(unsigned short):0));
      if (!a)
      {
         error("Out of memory.\n");
         return;
      }
      a->len=hi?1:0;
      a->sign=sgn;
      a->data[0]=x;
      if (hi) a->data[1]=hi;
      free_svalue(sp);
      break;
    case T_STRING:
      a=_to_longint(sp->u.string);
      free_string_svalue(sp);
      break;
    default:
      bad_xefun_arg(argno,sp);
   }
   return a;
}


/* this is kind of time-consuming. FREES argument!!! */
char *longint_to_string(longint_t *r)
{
   int rem;
   char *buf, *s, c;
   int len,sgn;
   longint_t *a;
      
   len=r->len*5+7+4;
   buf=(char*)xalloc(len);
   if (!buf)
   {
      error("Out of memory!\n");
      return;
   }
   
   s=buf+len-1;
   s[0]=0;

   sgn=r->sign;
   while(r->len>0 || r->data[0]!=0)
   {
      a=_strdiv_int(r,10000,&rem,0);
      free_longint(r);
      r=a;

      c=s[0];
      s-=4;
      sprintf(s,"%04d",rem);
      s[4]=c;
   }
   free_longint(r);
   
   if (s[0]==0)
   {
      s--;
      s[0]='0';
   }
   else
   {
      while(s[0]=='0') s++;
   }
   if (sgn==-1)
   {
      s--;
      s[0]='-';
   }
   
   s=string_copy(s);
   xfree(buf);
   return s;
}

 
/* TEFUN stradd()
 *
 *   string stradd(string|int a, string|int b)
 *
 * Add numbers represented by strings a and b
 */

svalue_t *
f_stradd(svalue_t *sp)
{
   longint_t *a, *b, *res;

   a=sval_to_longint(sp-1,1);
   b=sval_to_longint(sp,2);

   if (a->sign==b->sign)
   {
      res=_stradd_positive(a,b);
      res->sign=a->sign;
   }
   else
   {
      res=_strsub_positive(a,b,a->sign);
      if (res->len==0 && res->data[0]==0) res->sign=1;
   }

   free_longint(a);
   free_longint(b);

   sp--;
   put_malloced_string(sp, longint_to_string(res));
   return sp;
}

svalue_t *
f_strsub(svalue_t *sp)
{
   longint_t *a, *b, *res;

   a=sval_to_longint(sp-1,1);
   b=sval_to_longint(sp,2);

   b->sign=-b->sign;
   if (a->sign==b->sign)
   {
      res=_stradd_positive(a,b);
      res->sign=a->sign;
   }
   else
   {
      res=_strsub_positive(a,b,a->sign);
      if (res->len==0 && res->data[0]==0) res->sign=1;
   }

   free_longint(a);
   free_longint(b);

   sp--;
   put_malloced_string(sp, longint_to_string(res));
   return sp;
}

svalue_t *
f_strmul(svalue_t *sp)
{
   longint_t *a, *b, *res;

   a=sval_to_longint(sp-1,1);
   b=sval_to_longint(sp,2);

   res=_strmul(a,b);

   free_longint(a);
   free_longint(b);

   sp--;
   put_malloced_string(sp, longint_to_string(res));
   return sp;
}

svalue_t *
f_strdiv(svalue_t *sp)
{
   longint_t *a, *b, *res;
   int sgn;
   
   a=sval_to_longint(sp-1,1);
   b=sval_to_longint(sp,2);
   sgn=a->sign*b->sign;
   
   res=_strdiv(a,b);

   if (res->len==0 && res->data[0]==0) res->sign=1;
   else res->sign=sgn;

   free_longint(a);
   free_longint(b);

   sp--;
   put_malloced_string(sp, longint_to_string(res));
   return sp;
}

svalue_t *
f_strdivrem(svalue_t *sp)
{
   longint_t *a, *b, *res;
   int sgn;
   
   a=sval_to_longint(sp-2,1);
   b=sval_to_longint(sp-1,2);
   /* test sp for lvalue; not really necessary,
      but doesn't make sense, if there is no lvalue in sp */
   sgn=a->sign*b->sign;
   
   res=_strdiv(a,b);

   if (res->len==0 && res->data[0]==0) res->sign=1;
   else res->sign=sgn;

   free_longint(b);

   put_malloced_string(sp-1, longint_to_string(a));
   transfer_svalue(sp,sp-1);
   free_svalue(sp);
   
   sp-=2;
   put_malloced_string(sp, longint_to_string(res));
   return sp;
}

svalue_t *
f_strcmp(svalue_t *sp)
{
   longint_t *a, *b;
   int res;
   
   a=sval_to_longint(sp-1,1);
   b=sval_to_longint(sp,2);

   res=_strcmp(a,b);

   free_longint(a);
   free_longint(b);

   sp--;
   put_number(sp, res);
   return sp;
}