pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file function.c
 *
 * \brief The function parser.
 *
 *
 */
#include "copyrite.h"

#include "config.h"
#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include "conf.h"
#include "externs.h"
#include "attrib.h"
#include "dbdefs.h"
#include "mushdb.h"
#include "function.h"
#include "match.h"
#include "htab.h"
#include "parse.h"
#include "lock.h"
#include "flags.h"
#include "game.h"
#include "mymalloc.h"
#include "funs.h"
#include "confmagic.h"

static void func_hash_insert(const char *name, FUN *func);
extern void local_functions(void);
static int apply_restrictions(unsigned int result, const char *restriction);

USERFN_ENTRY *userfn_tab;   /**< Table of user-defined functions */
HASHTAB htab_function;	    /**< Function hash table */
HASHTAB htab_user_function; /**< User-defined function hash table */

/* -------------------------------------------------------------------------*
 * Utilities.
 */

/** Save a copy of the q-registers.
 * \param funcname name of function calling (for memory leak testing)
 * \param preserve pointer to array to store the q-registers in.
 */
void
save_global_regs(const char *funcname, char *preserve[])
{
  int i;

  for (i = 0; i < NUMQ; i++) {
    if (!global_eval_context.renv[i][0])
      preserve[i] = NULL;
    else {
      preserve[i] = (char *) mush_malloc(BUFFER_LEN, funcname);
      strcpy(preserve[i], global_eval_context.renv[i]);
    }
  }
}

/** Restore the q-registers, freeing the storage array.
 * \param funcname name of function calling (for memory leak testing)
 * \param preserve pointer to array to restore the q-registers from.
 */
void
restore_global_regs(const char *funcname, char *preserve[])
{
  int i;
  for (i = 0; i < NUMQ; i++) {
    if (preserve[i]) {
      strcpy(global_eval_context.renv[i], preserve[i]);
      mush_free(preserve[i], funcname);
      preserve[i] = NULL;
    } else {
      global_eval_context.renv[i][0] = '\0';
    }
  }
}

/** Free the storage array for the q-registers, without restoring
 * \param funcname name of function calling (for memory leak testing)
 * \param preserve pointer to array to free q-registers from.
 */
void
free_global_regs(const char *funcname, char *preserve[])
{
  int i;
  for (i = 0; i < NUMQ; i++) {
    if (preserve[i])
      mush_free(preserve[i], funcname);
  }
}

/** Initilalize an array for the q-registers, setting all NULL.
 * \param preserve pointer to array to free q-registers from.
 */
void
init_global_regs(char *preserve[])
{
  int i;
  for (i = 0; i < NUMQ; i++) {
    preserve[i] = NULL;
  }
}

/** Restore the q-registers, without freeing the storage array.
 * \param preserve pointer to array to restore the q-registers from.
 */
void
load_global_regs(char *preserve[])
{
  int i;
  for (i = 0; i < NUMQ; i++) {
    if (preserve[i]) {
      strcpy(global_eval_context.renv[i], preserve[i]);
    } else {
      global_eval_context.renv[i][0] = '\0';
    }
  }
}

/** Save a copy of the environment (%0-%9)
 * \param funcname name of function calling (for memory leak testing)
 * \param preserve pointer to array to store %0-%9 in.
 */
void
save_global_env(const char *funcname __attribute__ ((__unused__)),
		char *preserve[])
{
  int i;
  for (i = 0; i < 10; i++)
    preserve[i] = global_eval_context.wenv[i];
}

/** Restore the environment (%0-%9)
 * \param funcname name of function calling (for memory leak testing)
 * \param preserve pointer to array to restore %0-%9 from.
 */
void
restore_global_env(const char *funcname __attribute__ ((__unused__)),
		   char *preserve[])
{
  int i;
  for (i = 0; i < 10; i++)
    global_eval_context.wenv[i] = preserve[i];
}

/** Check for a delimiter in an argument of a function call.
 * This function checks a given argument of a function call and sees
 * if it could be used as a delimiter. A delimiter must be a single
 * character. If the argument isn't present or is null, we return
 * the default delimiter, a space.
 * \param buff unused.
 * \param bp unused.
 * \param nfargs number of arguments to the function.
 * \param fargs array of function arguments.
 * \param sep_arg index of the argument to check for a delimiter.
 * \param sep pointer to separator character, used to return separator.
 * \retval 0 illegal separator argument.
 * \retval 1 successfully returned a separator (maybe the default one).
 */
int
delim_check(char *buff, char **bp, int nfargs, char *fargs[], int sep_arg,
	    char *sep)
{
  /* Find a delimiter. */

  if (nfargs >= sep_arg) {
    if (!*fargs[sep_arg - 1])
      *sep = ' ';
    else if (strlen(fargs[sep_arg - 1]) != 1) {
      safe_str(T("#-1 SEPARATOR MUST BE ONE CHARACTER"), buff, bp);
      return 0;
    } else
      *sep = *fargs[sep_arg - 1];
  } else
    *sep = ' ';

  return 1;
}

/* --------------------------------------------------------------------------
 * The actual function handlers
 */

/** An entry in the function table.
 * This structure represents a function's entry in the function table.
 */
typedef struct fun_tab {
  const char *name;	/**< Name of the function, uppercase. */
  function_func fun;	/**< Pointer to code to call for this function. */
  int minargs;	/**< Minimum args required. */
  int maxargs;	/**< Maximum args, or INT_MAX. If <0, last arg may have commas */
  int flags;	/**< Flags to control how the function is parsed. */
} FUNTAB;


/** The function table. Functions can also be added at runtime with
 * add_function().
 */
FUNTAB flist[] = {
  {"@@", fun_atat, 1, -1, FN_NOPARSE},
  {"ABS", fun_abs, 1, 1, FN_REG},
  {"ACCENT", fun_accent, 2, 2, FN_REG},
  {"ACCNAME", fun_accname, 1, 1, FN_REG},
  {"ADD", fun_add, 2, INT_MAX, FN_REG},
  {"AFTER", fun_after, 2, 2, FN_REG},
  {"ALIGN", fun_align, 2, INT_MAX, FN_REG},
  {"ALLOF", fun_allof, 2, INT_MAX, FN_NOPARSE},
  {"ALPHAMAX", fun_alphamax, 1, INT_MAX, FN_REG},
  {"ALPHAMIN", fun_alphamin, 1, INT_MAX, FN_REG},
  {"AND", fun_and, 2, INT_MAX, FN_REG},
  {"ANDFLAGS", fun_andflags, 2, 2, FN_REG},
  {"ANDLFLAGS", fun_andlflags, 2, 2, FN_REG},
  {"ANDLPOWERS", fun_andlflags, 2, 2, FN_REG},
  {"ANDPOWERS", fun_andflags, 2, 2, FN_REG},
  {"ANSI", fun_ansi, 2, -2, FN_NOPARSE},
  {"APOSS", fun_aposs, 1, 1, FN_REG},
  {"ART", fun_art, 1, 1, FN_REG},
  {"ATRLOCK", fun_atrlock, 1, 2, FN_REG},
  {"BAND", fun_band, 1, INT_MAX, FN_REG},
  {"BASECONV", fun_baseconv, 3, 3, FN_REG},
  {"BEEP", fun_beep, 0, 1, FN_REG},
  {"BEFORE", fun_before, 2, 2, FN_REG},
  {"BNAND", fun_bnand, 2, 2, FN_REG},
  {"BNOT", fun_bnot, 1, 1, FN_REG},
  {"BOR", fun_bor, 1, INT_MAX, FN_REG},
  {"BOUND", fun_bound, 2, 3, FN_REG},
  {"BRACKETS", fun_brackets, 1, 1, FN_REG},
  {"BXOR", fun_bxor, 1, INT_MAX, FN_REG},
  {"CAND", fun_cand, 2, INT_MAX, FN_NOPARSE},
  {"CAPSTR", fun_capstr, 1, -1, FN_REG},
  {"CASE", fun_switch, 3, INT_MAX, FN_NOPARSE},
  {"CASEALL", fun_switch, 3, INT_MAX, FN_NOPARSE},
  {"CAT", fun_cat, 1, INT_MAX, FN_REG},
  {"CEMIT", fun_cemit, 2, 3, FN_REG},
  {"CFLAGS", fun_cflags, 1, 2, FN_REG},
  {"CHANNELS", fun_channels, 0, 2, FN_REG},
  {"CLOCK", fun_clock, 1, 2, FN_REG},
  {"COWNER", fun_cowner, 1, 1, FN_REG},
  {"CTITLE", fun_ctitle, 2, 2, FN_REG},
  {"CWHO", fun_cwho, 1, 1, FN_REG},
  {"CENTER", fun_center, 2, 3, FN_REG},
  {"CHILDREN", fun_lsearch, 1, 1, FN_REG},
  {"CHR", fun_chr, 1, 1, FN_REG},
  {"CHECKPASS", fun_checkpass, 2, 2, FN_REG | FN_WIZARD},
  {"CLONE", fun_clone, 1, 1, FN_REG},
  {"CMDS", fun_cmds, 1, 1, FN_REG},
  {"COMP", fun_comp, 2, 3, FN_REG},
  {"CON", fun_con, 1, 1, FN_REG},
  {"CONFIG", fun_config, 1, 1, FN_REG},
  {"CONN", fun_conn, 1, 1, FN_REG},
  {"CONTROLS", fun_controls, 2, 2, FN_REG},
  {"CONVSECS", fun_convsecs, 1, 2, FN_REG},
  {"CONVUTCSECS", fun_convsecs, 1, 1, FN_REG},
  {"CONVTIME", fun_convtime, 1, 1, FN_REG},
  {"COR", fun_cor, 2, INT_MAX, FN_NOPARSE},
  {"CREATE", fun_create, 1, 2, FN_REG},
  {"CTIME", fun_ctime, 1, 1, FN_REG},
  {"DEC", fun_dec, 1, 1, FN_REG},
  {"DECRYPT", fun_decrypt, 2, 2, FN_REG},
  {"DEFAULT", fun_default, 2, 2, FN_NOPARSE},
  {"DELETE", fun_delete, 3, 3, FN_REG},
  {"DIE", fun_die, 2, 3, FN_REG},
  {"DIG", fun_dig, 1, 3, FN_REG},
  {"DIGEST", fun_digest, 2, -2, FN_REG},
  {"DIST2D", fun_dist2d, 4, 4, FN_REG},
  {"DIST3D", fun_dist3d, 6, 6, FN_REG},
  {"DIV", fun_div, 2, 2, FN_REG},
  {"DOING", fun_doing, 1, 1, FN_REG},
  {"EDEFAULT", fun_edefault, 2, 2, FN_NOPARSE},
  {"EDIT", fun_edit, 3, INT_MAX, FN_REG},
  {"ELEMENT", fun_element, 3, 3, FN_REG},
  {"ELEMENTS", fun_elements, 2, 4, FN_REG},
  {"ELIST", fun_itemize, 1, 5, FN_REG},
  {"ELOCK", fun_elock, 2, 2, FN_REG},
  {"EMIT", fun_emit, 1, -1, FN_REG},
  {"ENCRYPT", fun_encrypt, 2, 2, FN_REG},
  {"ENTRANCES", fun_entrances, 0, 4, FN_REG},
  {"ETIMEFMT", fun_etimefmt, 2, 2, FN_REG},
  {"EQ", fun_eq, 2, 2, FN_REG},
  {"EVAL", fun_eval, 2, 2, FN_REG},
  {"ESCAPE", fun_escape, 1, -1, FN_REG},
  {"EXIT", fun_exit, 1, 1, FN_REG},
  {"EXTRACT", fun_extract, 3, 4, FN_REG},
  {"FILTER", fun_filter, 2, 4, FN_REG},
  {"FILTERBOOL", fun_filter, 2, 4, FN_REG},
  {"FINDABLE", fun_findable, 2, 2, FN_REG},
  {"FIRST", fun_first, 1, 2, FN_REG},
  {"FIRSTOF", fun_firstof, 0, INT_MAX, FN_NOPARSE},
  {"FLAGS", fun_flags, 0, 1, FN_REG},
  {"FLIP", fun_flip, 1, 1, FN_REG},
  {"FLOORDIV", fun_floordiv, 2, 2, FN_REG},
  {"FOLD", fun_fold, 2, 4, FN_REG},
  {"FOLDERSTATS", fun_folderstats, 0, 2, FN_REG},
  {"FOLLOWERS", fun_followers, 1, 1, FN_REG},
  {"FOLLOWING", fun_following, 1, 1, FN_REG},
  {"FOREACH", fun_foreach, 2, 4, FN_REG},
  {"FRACTION", fun_fraction, 1, 1, FN_REG},
  {"FUNCTIONS", fun_functions, 0, 0, FN_REG},
  {"FULLNAME", fun_fullname, 1, 1, FN_REG},
  {"GET", fun_get, 1, 1, FN_REG},
  {"GET_EVAL", fun_get_eval, 1, 1, FN_REG},
  {"GRAB", fun_grab, 2, 3, FN_REG},
  {"GRABALL", fun_graball, 2, 4, FN_REG},
  {"GREP", fun_grep, 3, 3, FN_REG},
  {"GREPI", fun_grep, 3, 3, FN_REG},
  {"GT", fun_gt, 2, 2, FN_REG},
  {"GTE", fun_gte, 2, 2, FN_REG},
  {"HASATTR", fun_hasattr, 2, 2, FN_REG},
  {"HASATTRP", fun_hasattr, 2, 2, FN_REG},
  {"HASATTRPVAL", fun_hasattr, 2, 2, FN_REG},
  {"HASATTRVAL", fun_hasattr, 2, 2, FN_REG},
  {"HASFLAG", fun_hasflag, 2, 2, FN_REG},
  {"HASPOWER", fun_haspower, 2, 2, FN_REG},
  {"HASTYPE", fun_hastype, 2, 2, FN_REG},
  {"HEIGHT", fun_height, 1, 1, FN_REG},
  {"HIDDEN", fun_hidden, 1, 1, FN_REG},
  {"HOME", fun_home, 1, 1, FN_REG},
  {"HOST", fun_hostname, 1, 1, FN_REG},
  {"HOSTNAME", fun_hostname, 1, 1, FN_REG},
  {"IDLE", fun_idlesecs, 1, 1, FN_REG},
  {"IDLESECS", fun_idlesecs, 1, 1, FN_REG},
  {"IF", fun_if, 2, 3, FN_NOPARSE},
  {"IFELSE", fun_if, 3, 3, FN_NOPARSE},
  {"ILEV", fun_ilev, 0, 0, FN_REG},
  {"INAME", fun_iname, 1, 1, FN_REG},
  {"INC", fun_inc, 1, 1, FN_REG},
  {"INDEX", fun_index, 4, 4, FN_REG},
  {"INSERT", fun_insert, 3, 4, FN_REG},
  {"INUM", fun_inum, 1, 1, FN_REG},
  {"IPADDR", fun_ipaddr, 1, 1, FN_REG},
  {"ISDAYLIGHT", fun_isdaylight, 0, 0, FN_REG},
  {"ISDBREF", fun_isdbref, 1, 1, FN_REG},
  {"ISINT", fun_isint, 1, 1, FN_REG},
  {"ISNUM", fun_isnum, 1, 1, FN_REG},
  {"ISWORD", fun_isword, 1, 1, FN_REG},
  {"ITER", fun_iter, 2, 4, FN_NOPARSE},
  {"ITEMS", fun_items, 2, 2, FN_REG},
  {"ITEMIZE", fun_itemize, 1, 4, FN_REG},
  {"ITEXT", fun_itext, 1, 1, FN_REG},
  {"LAST", fun_last, 1, 2, FN_REG},
  {"LATTR", fun_lattr, 1, 1, FN_REG},
  {"LATTRP", fun_lattr, 1, 1, FN_REG},
  {"LCON", fun_dbwalker, 1, 1, FN_REG},
  {"LCSTR", fun_lcstr, 1, -1, FN_REG},
  {"LDELETE", fun_ldelete, 2, 3, FN_REG},
  {"LEFT", fun_left, 2, 2, FN_REG},
  {"LEMIT", fun_lemit, 1, -1, FN_REG},
  {"LEXITS", fun_dbwalker, 1, 1, FN_REG},
  {"LFLAGS", fun_lflags, 0, 1, FN_REG},
  {"LINK", fun_link, 2, 2, FN_REG},
  {"LIST", fun_list, 1, 1, FN_REG},
  {"LIT", fun_lit, 1, -1, FN_LITERAL},
  {"LJUST", fun_ljust, 2, 3, FN_REG},
  {"LLOCKFLAGS", fun_lockflags, 0, 1, FN_REG},
  {"LLOCKS", fun_locks, 1, 1, FN_REG},
  {"LMATH", fun_lmath, 2, 3, FN_REG},
  {"LNUM", fun_lnum, 1, 3, FN_REG},
  {"LOC", fun_loc, 1, 1, FN_REG},
  {"LOCALIZE", fun_localize, 1, 1, FN_NOPARSE},
  {"LOCATE", fun_locate, 3, 3, FN_REG},
  {"LOCK", fun_lock, 1, 2, FN_REG},
  {"LOCKFLAGS", fun_lockflags, 0, 1, FN_REG},
  {"LOCKS", fun_locks, 1, 1, FN_REG},
  {"LPARENT", fun_lparent, 1, 1, FN_REG},
  {"LPLAYERS", fun_dbwalker, 1, 1, FN_REG},
  {"LPORTS", fun_lports, 0, 0, FN_REG},
  {"LPOS", fun_lpos, 2, 2, FN_REG},
  {"LSEARCH", fun_lsearch, 1, 5, FN_REG},
  {"LSEARCHR", fun_lsearch, 1, 5, FN_REG},
  {"LSET", fun_lset, 2, 2, FN_REG},
  {"LSTATS", fun_lstats, 0, 1, FN_REG},
  {"LT", fun_lt, 2, 2, FN_REG},
  {"LTE", fun_lte, 2, 2, FN_REG},
  {"LTHINGS", fun_dbwalker, 1, 1, FN_REG},
  {"LVCON", fun_dbwalker, 1, 1, FN_REG},
  {"LVEXITS", fun_dbwalker, 1, 1, FN_REG},
  {"LVPLAYERS", fun_dbwalker, 1, 1, FN_REG},
  {"LVTHINGS", fun_dbwalker, 1, 1, FN_REG},
  {"LWHO", fun_lwho, 0, 1, FN_REG},
  {"MAIL", fun_mail, 0, 2, FN_REG},
  {"MAILFROM", fun_mailfrom, 1, 2, FN_REG},
  {"MAILSEND", fun_mailsend, 2, 2, FN_REG},
  {"MAILSTATS", fun_mailstats, 1, 1, FN_REG},
  {"MAILDSTATS", fun_mailstats, 1, 1, FN_REG},
  {"MAILFSTATS", fun_mailstats, 1, 1, FN_REG},
  {"MAILSTATUS", fun_mailstatus, 1, 2, FN_REG},
  {"MAILSUBJECT", fun_mailsubject, 1, 2, FN_REG},
  {"MAILTIME", fun_mailtime, 1, 2, FN_REG},
  {"MALIAS", fun_malias, 0, 2, FN_REG},
  {"MAP", fun_map, 2, 4, FN_REG},
  {"MATCH", fun_match, 2, 3, FN_REG},
  {"MATCHALL", fun_matchall, 2, 4, FN_REG},
  {"MAX", fun_max, 1, INT_MAX, FN_REG},
  {"MEAN", fun_mean, 1, INT_MAX, FN_REG},
  {"MEDIAN", fun_median, 1, INT_MAX, FN_REG},
  {"MEMBER", fun_member, 2, 3, FN_REG},
  {"MERGE", fun_merge, 3, 3, FN_REG},
  {"MID", fun_mid, 3, 3, FN_REG},
  {"MIN", fun_min, 1, INT_MAX, FN_REG},
  {"MIX", fun_mix, 3, 12, FN_REG},
  {"MODULO", fun_modulo, 2, 2, FN_REG},
  {"MONEY", fun_money, 1, 1, FN_REG},
  {"MTIME", fun_mtime, 1, 1, FN_REG},
  {"MUDNAME", fun_mudname, 0, 0, FN_REG},
  {"MUL", fun_mul, 2, INT_MAX, FN_REG},
  {"MUNGE", fun_munge, 3, 5, FN_REG},
  {"MWHO", fun_lwho, 0, 0, FN_REG},
  {"NAME", fun_name, 0, 2, FN_REG},
  {"NAND", fun_nand, 1, INT_MAX, FN_REG},
  {"NATTR", fun_nattr, 1, 1, FN_REG},
  {"NATTRP", fun_nattr, 1, 1, FN_REG},
  {"NCON", fun_dbwalker, 1, 1, FN_REG},
  {"NEXITS", fun_dbwalker, 1, 1, FN_REG},
  {"NPLAYERS", fun_dbwalker, 1, 1, FN_REG},
  {"NEARBY", fun_nearby, 2, 2, FN_REG},
  {"NEQ", fun_neq, 2, 2, FN_REG},
  {"NEXT", fun_next, 1, 1, FN_REG},
  {"NMWHO", fun_nwho, 0, 0, FN_REG},
  {"NOR", fun_nor, 1, INT_MAX, FN_REG},
  {"NOT", fun_not, 1, 1, FN_REG},
  {"NSCEMIT", fun_cemit, 2, 3, FN_REG},
  {"NSEMIT", fun_emit, 1, -1, FN_REG},
  {"NSLEMIT", fun_lemit, 1, -1, FN_REG},
  {"NSOEMIT", fun_oemit, 2, -2, FN_REG},
  {"NSPEMIT", fun_pemit, 2, -2, FN_REG},
  {"NSREMIT", fun_remit, 2, -2, FN_REG},
  {"NSZEMIT", fun_zemit, 2, -2, FN_REG},
  {"NTHINGS", fun_dbwalker, 1, 1, FN_REG},
  {"NUM", fun_num, 1, 1, FN_REG},
  {"NULL", fun_null, 1, INT_MAX, FN_REG},
  {"NVCON", fun_dbwalker, 1, 1, FN_REG},
  {"NVEXITS", fun_dbwalker, 1, 1, FN_REG},
  {"NVPLAYERS", fun_dbwalker, 1, 1, FN_REG},
  {"NVTHINGS", fun_dbwalker, 1, 1, FN_REG},
  {"NWHO", fun_nwho, 0, 0, FN_REG},
  {"OBJ", fun_obj, 1, 1, FN_REG},
  {"OBJEVAL", fun_objeval, 2, -2, FN_NOPARSE},
  {"OBJID", fun_objid, 1, 1, FN_REG},
  {"OBJMEM", fun_objmem, 1, 1, FN_REG},
  {"OEMIT", fun_oemit, 2, -2, FN_REG},
  {"OPEN", fun_open, 2, 2, FN_REG},
  {"OR", fun_or, 2, INT_MAX, FN_REG},
  {"ORD", fun_ord, 1, 1, FN_REG},
  {"ORFLAGS", fun_orflags, 2, 2, FN_REG},
  {"ORLFLAGS", fun_orlflags, 2, 2, FN_REG},
  {"ORLPOWERS", fun_orlflags, 2, 2, FN_REG},
  {"ORPOWERS", fun_orflags, 2, 2, FN_REG},
  {"OWNER", fun_owner, 1, 1, FN_REG},
  {"PARENT", fun_parent, 1, 2, FN_REG},
  {"PCREATE", fun_pcreate, 1, 2, FN_REG},
  {"PEMIT", fun_pemit, 2, -2, FN_REG},
  {"PLAYERMEM", fun_playermem, 1, 1, FN_REG},
  {"PMATCH", fun_pmatch, 1, 1, FN_REG},
  {"POLL", fun_poll, 0, 0, FN_REG},
  {"PORTS", fun_ports, 1, 1, FN_REG},
  {"POS", fun_pos, 2, 2, FN_REG},
  {"POSS", fun_poss, 1, 1, FN_REG},
  {"POWERS", fun_powers, 1, 2, FN_REG},
  {"PUEBLO", fun_pueblo, 1, 1, FN_REG},
  {"QUOTA", fun_quota, 1, 1, FN_REG},
  {"R", fun_r, 1, 1, FN_REG},
  {"RAND", fun_rand, 1, 2, FN_REG},
  {"RANDWORD", fun_randword, 1, 2, FN_REG},
  {"RECV", fun_recv, 1, 1, FN_REG},
  {"REGEDIT", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
  {"REGEDITALL", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
  {"REGEDITALLI", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
  {"REGEDITI", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
  {"REGMATCH", fun_regmatch, 2, 3, FN_REG},
  {"REGMATCHI", fun_regmatch, 2, 3, FN_REG},
  {"REGRAB", fun_regrab, 2, 4, FN_REG},
  {"REGRABALL", fun_regrab, 2, 4, FN_REG},
  {"REGRABALLI", fun_regrab, 2, 3, FN_REG},
  {"REGRABI", fun_regrab, 2, 3, FN_REG},
  {"REGREP", fun_regrep, 3, 3, FN_REG},
  {"REGREPI", fun_regrep, 3, 3, FN_REG},
  {"RESWITCH", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
  {"RESWITCHALL", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
  {"RESWITCHALLI", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
  {"RESWITCHI", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
  {"REMAINDER", fun_remainder, 2, 2, FN_REG},
  {"REMIT", fun_remit, 2, -2, FN_REG},
  {"REMOVE", fun_remove, 2, 3, FN_REG},
  {"REPEAT", fun_repeat, 2, 2, FN_REG},
  {"REPLACE", fun_replace, 3, 4, FN_REG},
  {"REST", fun_rest, 1, 2, FN_REG},
  {"RESTARTS", fun_restarts, 0, 0, FN_REG},
  {"RESTARTTIME", fun_restarttime, 0, 0, FN_REG},
  {"REVERSE", fun_flip, 1, 1, FN_REG},
  {"REVWORDS", fun_revwords, 1, 3, FN_REG},
  {"RIGHT", fun_right, 2, 2, FN_REG},
  {"RJUST", fun_rjust, 2, 3, FN_REG},
  {"RLOC", fun_rloc, 2, 2, FN_REG},
  {"RNUM", fun_rnum, 2, 2, FN_REG},
  {"ROOM", fun_room, 1, 1, FN_REG},
  {"ROOT", fun_root, 2, 2, FN_REG},
  {"S", fun_s, 1, -1, FN_REG},
  {"SCAN", fun_scan, 1, -2, FN_REG},
  {"SCRAMBLE", fun_scramble, 1, -1, FN_REG},
  {"SECS", fun_secs, 0, 0, FN_REG},
  {"SECURE", fun_secure, 1, -1, FN_REG},
  {"SENT", fun_sent, 1, 1, FN_REG},
  {"SET", fun_set, 2, 2, FN_REG},
  {"SETQ", fun_setq, 2, 2, FN_REG},
  {"SETR", fun_setq, 2, 2, FN_REG},
  {"SETDIFF", fun_setdiff, 2, 5, FN_REG},
  {"SETINTER", fun_setinter, 2, 5, FN_REG},
  {"SETUNION", fun_setunion, 2, 5, FN_REG},
  {"SHA0", fun_sha0, 1, 1, FN_REG},
  {"SHL", fun_shl, 2, 2, FN_REG},
  {"SHR", fun_shr, 2, 2, FN_REG},
  {"SHUFFLE", fun_shuffle, 1, 3, FN_REG},
  {"SIGN", fun_sign, 1, 1, FN_REG},
  {"SORT", fun_sort, 1, 4, FN_REG},
  {"SORTBY", fun_sortby, 2, 4, FN_REG},
  {"SOUNDEX", fun_soundex, 1, 1, FN_REG},
  {"SOUNDSLIKE", fun_soundlike, 2, 2, FN_REG},
  {"SPACE", fun_space, 1, 1, FN_REG},
  {"SPELLNUM", fun_spellnum, 1, 1, FN_REG},
  {"SPLICE", fun_splice, 3, 4, FN_REG},
  {"SQL", fun_sql, 1, 3, FN_REG},
  {"SQLESCAPE", fun_sql_escape, 1, 1, FN_REG},
  {"SQUISH", fun_squish, 1, 2, FN_REG},
  {"SSL", fun_ssl, 1, 1, FN_REG},
  {"STARTTIME", fun_starttime, 0, 0, FN_REG},
  {"STEP", fun_step, 3, 5, FN_REG},
  {"STRCAT", fun_strcat, 1, INT_MAX, FN_REG},
  {"STRINSERT", fun_strinsert, 3, -3, FN_REG},
  {"STRIPACCENTS", fun_stripaccents, 1, 1, FN_REG},
  {"STRIPANSI", fun_stripansi, 1, -1, FN_REG},
  {"STRLEN", fun_strlen, 1, -1, FN_REG},
  {"STRMATCH", fun_strmatch, 2, 2, FN_REG},
  {"STRREPLACE", fun_strreplace, 4, 4, FN_REG},
  {"SUB", fun_sub, 2, 2, FN_REG},
  {"SUBJ", fun_subj, 1, 1, FN_REG},
  {"SWITCH", fun_switch, 3, INT_MAX, FN_NOPARSE},
  {"SWITCHALL", fun_switch, 3, INT_MAX, FN_NOPARSE},
  {"T", fun_t, 1, 1, FN_REG},
  {"TABLE", fun_table, 1, 5, FN_REG},
  {"TEL", fun_tel, 2, 4, FN_REG},
  {"TERMINFO", fun_terminfo, 1, 1, FN_REG},
  {"TEXTFILE", fun_textfile, 2, 2, FN_REG},
  {"TIME", fun_time, 0, 1, FN_REG},
  {"TIMEFMT", fun_timefmt, 1, 2, FN_REG},
  {"TIMESTRING", fun_timestring, 1, 2, FN_REG},
  {"TR", fun_tr, 3, 3, FN_REG},
  {"TRIM", fun_trim, 1, 3, FN_REG},
  {"TRIMPENN", fun_trim, 1, 3, FN_REG},
  {"TRIMTINY", fun_trim, 1, 3, FN_REG},
  {"TRUNC", fun_trunc, 1, 1, FN_REG},
  {"TYPE", fun_type, 1, 1, FN_REG},
  {"UCSTR", fun_ucstr, 1, -1, FN_REG},
  {"UDEFAULT", fun_uldefault, 2, 12, FN_NOPARSE},
  {"UFUN", fun_ufun, 1, 11, FN_REG},
  {"ULDEFAULT", fun_uldefault, 1, 12, FN_NOPARSE},
  {"ULOCAL", fun_ulocal, 1, 11, FN_REG},
  {"UTCTIME", fun_time, 0, 0, FN_REG},
  {"U", fun_ufun, 1, 11, FN_REG},
  {"V", fun_v, 1, 1, FN_REG},
  {"VALID", fun_valid, 2, 2, FN_REG},
  {"VERSION", fun_version, 0, 0, FN_REG},
  {"VISIBLE", fun_visible, 2, 2, FN_REG},
  {"WHERE", fun_where, 1, 1, FN_REG},
  {"WIDTH", fun_width, 1, 1, FN_REG},
  {"WIPE", fun_wipe, 1, 1, FN_REG},
  {"WORDPOS", fun_wordpos, 2, 3, FN_REG},
  {"WORDS", fun_words, 1, 2, FN_REG},
  {"WRAP", fun_wrap, 2, 4, FN_REG},
  {"XATTR", fun_lattr, 3, 3, FN_REG},
  {"XATTRP", fun_lattr, 3, 3, FN_REG},
  {"XCON", fun_dbwalker, 3, 3, FN_REG},
  {"XEXITS", fun_dbwalker, 3, 3, FN_REG},
  {"XMWHO", fun_xwho, 2, 2, FN_REG},
  {"XPLAYERS", fun_dbwalker, 3, 3, FN_REG},
  {"XGET", fun_xget, 2, 2, FN_REG},
  {"XOR", fun_xor, 2, INT_MAX, FN_REG},
  {"XTHINGS", fun_dbwalker, 3, 3, FN_REG},
  {"XVCON", fun_dbwalker, 3, 3, FN_REG},
  {"XVEXITS", fun_dbwalker, 3, 3, FN_REG},
  {"XVPLAYERS", fun_dbwalker, 3, 3, FN_REG},
  {"XVTHINGS", fun_dbwalker, 3, 3, FN_REG},
  {"XWHO", fun_xwho, 2, 2, FN_REG},
  {"ZEMIT", fun_zemit, 2, -2, FN_REG},
  {"ZFUN", fun_zfun, 1, 11, FN_REG},
  {"ZONE", fun_zone, 1, 2, FN_REG},
  {"ZMWHO", fun_zwho, 1, 1, FN_REG},
  {"ZWHO", fun_zwho, 1, 2, FN_REG},
  {"VADD", fun_vadd, 2, 3, FN_REG},
  {"VCROSS", fun_vcross, 2, 3, FN_REG},
  {"VSUB", fun_vsub, 2, 3, FN_REG},
  {"VMAX", fun_vmax, 2, 3, FN_REG},
  {"VMIN", fun_vmin, 2, 3, FN_REG},
  {"VMUL", fun_vmul, 2, 3, FN_REG},
  {"VDOT", fun_vdot, 2, 3, FN_REG},
  {"VMAG", fun_vmag, 1, 2, FN_REG},
  {"VDIM", fun_words, 1, 2, FN_REG},
  {"VUNIT", fun_vunit, 1, 2, FN_REG},
  {"ACOS", fun_acos, 1, 2, FN_REG},
  {"ASIN", fun_asin, 1, 2, FN_REG},
  {"ATAN", fun_atan, 1, 2, FN_REG},
  {"ATAN2", fun_atan2, 2, 3, FN_REG},
  {"CEIL", fun_ceil, 1, 1, FN_REG},
  {"COS", fun_cos, 1, 2, FN_REG},
  {"CTU", fun_ctu, 3, 3, FN_REG},
  {"E", fun_e, 0, 0, FN_REG},
  {"EXP", fun_exp, 1, 1, FN_REG},
  {"FDIV", fun_fdiv, 2, 2, FN_REG},
  {"FMOD", fun_fmod, 2, 2, FN_REG},
  {"FLOOR", fun_floor, 1, 1, FN_REG},
  {"LOG", fun_log, 1, 2, FN_REG},
  {"LN", fun_ln, 1, 1, FN_REG},
  {"PI", fun_pi, 0, 0, FN_REG},
  {"POWER", fun_power, 2, 2, FN_REG},
  {"ROUND", fun_round, 2, 2, FN_REG},
  {"SIN", fun_sin, 1, 2, FN_REG},
  {"SQRT", fun_sqrt, 1, 1, FN_REG},
  {"STDDEV", fun_stddev, 1, INT_MAX, FN_REG},
  {"TAN", fun_tan, 1, 2, FN_REG},
  {"HTML", fun_html, 1, 1, FN_REG},
  {"TAG", fun_tag, 1, INT_MAX, FN_REG},
  {"ENDTAG", fun_endtag, 1, 1, FN_REG},
  {"TAGWRAP", fun_tagwrap, 2, 3, FN_REG},
  {NULL, NULL, 0, 0, 0}
};

/** List all functions.
 * \verbatim
 * This is the mail interface to @list functions.
 * \endverbatim
 * \param player the enactor.
 * \param lc if 1, return functions in lowercase.
 */
void
do_list_functions(dbref player, int lc)
{
  /* lists all built-in functions. */
  char *b = list_functions();
  notify_format(player, "Functions: %s", lc ? strlower(b) : b);
}

/** Return a list of function names.
 * This function returns the list of function names as a string.
 * \return list of function names as a static string.
 */
char *
list_functions(void)
{
  FUN *fp;
  const char *ptrs[BUFFER_LEN / 2];
  static char buff[BUFFER_LEN];
  char *bp;
  int nptrs = 0, i;
  for (fp = (FUN *) hash_firstentry(&htab_function);
       fp; fp = (FUN *) hash_nextentry(&htab_function)) {
    if (fp->flags & FN_OVERRIDE)
      continue;
    ptrs[nptrs++] = fp->name;
  }
  fp = (FUN *) hash_firstentry(&htab_user_function);
  while (fp) {
    ptrs[nptrs++] = fp->name;
    fp = (FUN *) hash_nextentry(&htab_user_function);
  }
  /* do_gensort needs a dbref now, but only for sort types that aren't
   * used here anyway */
  do_gensort((dbref) 0, (char **) ptrs, nptrs, ALPHANUM_LIST);
  bp = buff;
  safe_str(ptrs[0], buff, &bp);
  for (i = 1; i < nptrs; i++) {
    safe_chr(' ', buff, &bp);
    safe_str(ptrs[i], buff, &bp);
  }
  *bp = '\0';
  return buff;
}

/*---------------------------------------------------------------------------
 * Hashed function table stuff
 */


/** Look up a function by name.
 * \param name name of function to look up.
 * \return pointer to function data, or NULL.
 */
FUN *
func_hash_lookup(const char *name)
{
  FUN *f;
  f = (FUN *) hashfind(strupper(name), &htab_function);
  if (!f || f->flags & FN_OVERRIDE)
    f = (FUN *) hashfind(strupper(name), &htab_user_function);
  return f;
}

static void
func_hash_insert(const char *name, FUN *func)
{
  hashadd(name, (void *) func, &htab_function);
}

/** Initialize the function hash table.
 */
void
init_func_hashtab(void)
{
  FUNTAB *ftp;

  hashinit(&htab_function, 512, sizeof(FUN));
  hashinit(&htab_user_function, 32, sizeof(FUN));
  for (ftp = flist; ftp->name; ftp++) {
    function_add(ftp->name, ftp->fun, ftp->minargs, ftp->maxargs, ftp->flags);
  }
  local_functions();
}

/** Function initization to perform after reading the config file.
 * This function performs post-config initialization. Specifically,
 * we need the max_globals value from the config file before we
 * can allocate the global user function table here.
 */
void
function_init_postconfig(void)
{
  userfn_tab =
    (USERFN_ENTRY *) mush_malloc(MAX_GLOBAL_FNS * sizeof(USERFN_ENTRY),
				 "userfn_tab");
}

/** Check permissions to run a function.
 * \param player the executor.
 * \param fp pointer to function data.
 * \retval 1 executor may use the function.
 * \retval 0 permission denied.
 */
int
check_func(dbref player, FUN *fp)
{
  if (!fp)
    return 0;
  if ((fp->flags & (~FN_ARG_MASK)) == 0)
    return 1;
  if (fp->flags & FN_DISABLED)
    return 0;
  if ((fp->flags & FN_GOD) && !God(player))
    return 0;
  if ((fp->flags & FN_WIZARD) && !Wizard(player))
    return 0;
  if ((fp->flags & FN_ADMIN) && !Hasprivs(player))
    return 0;
  if ((fp->flags & FN_NOGAGGED) && Gagged(player))
    return 0;
  if ((fp->flags & FN_NOFIXED) && Fixed(player))
    return 0;
  if ((fp->flags & FN_NOGUEST) && Guest(player))
    return 0;
  return 1;
}

/** Add an alias to a function.
 * This function adds an alias to a function in the hash table.
 * \param function name of function to alias.
 * \param alias name of the alias to add.
 * \retval 0 failure (alias exists, or function doesn't, or is a user fun).
 * \retval 1 success.
 */
int
alias_function(const char *function, const char *alias)
{
  FUN *fp;

  /* Make sure the alias doesn't exist already */
  if (func_hash_lookup(alias))
    return 0;

  /* Look up the original */
  fp = func_hash_lookup(function);
  if (!fp)
    return 0;

  /* We can't alias @functions. Just use another @function for these */
  if (!(fp->flags & FN_BUILTIN))
    return 0;

  function_add(strdup(strupper(alias)), fp->where.fun,
	       fp->minargs, fp->maxargs, fp->flags);
  return 1;
}

/** Add a function.
 * \param name name of the function to add.
 * \param fun pointer to compiled function code.
 * \param minargs minimum arguments to function.
 * \param maxargs maximum arguments to function.
 * \param ftype function evaluation flags.
 */
void
function_add(const char *name, function_func fun, int minargs, int maxargs,
	     int ftype)
{
  FUN *fp;
  fp = (FUN *) mush_malloc(sizeof(FUN), "function");
  memset(fp, 0, sizeof(FUN));
  fp->name = name;
  fp->where.fun = fun;
  fp->minargs = minargs;
  fp->maxargs = maxargs;
  fp->flags = FN_BUILTIN | ftype;
  func_hash_insert(name, fp);
}

/*-------------------------------------------------------------------------
 * Function handlers and the other good stuff. Almost all this code is
 * a modification of TinyMUSH 2.0 code.
 */

/** Strip a level of braces.
 * this is a hack which just strips a level of braces. It malloc()s memory
 * which must be free()d later.
 * \param str string to strip braces from.
 * \return newly allocated string with the first level of braces stripped.
 */
char *
strip_braces(const char *str)
{
  char *buff;
  char *bufc;

  buff = (char *) mush_malloc(BUFFER_LEN, "strip_braces.buff");
  bufc = buff;

  while (isspace((unsigned char) *str))	/* eat spaces at the beginning */
    str++;

  switch (*str) {
  case '{':
    str++;
    process_expression(buff, &bufc, &str, 0, 0, 0, PE_NOTHING, PT_BRACE, NULL);
    *bufc = '\0';
    return buff;
    break;			/* NOT REACHED */
  default:
    strcpy(buff, str);
    return buff;
  }
}

/*------------------------------------------------------------------------
 * User-defined global function handlers 
 */


static Size_t userfn_count = 0;

static int
apply_restrictions(unsigned int result, const char *restriction)
{
  int flag, clear = 0;
  char *tp;
  while (restriction && *restriction) {
    if ((tp = strchr(restriction, ' ')))
      *tp++ = '\0';
    if (*restriction == '!') {
      restriction++;
      clear = 1;
    }
    flag = 0;
    if (!strcasecmp(restriction, "nobody")) {
      flag = FN_DISABLED;
    } else if (string_prefix(restriction, "nogag")) {
      flag = FN_NOGAGGED;
    } else if (string_prefix(restriction, "nofix")) {
      flag = FN_NOFIXED;
    } else if (!strcasecmp(restriction, "noguest")) {
      flag = FN_NOGUEST;
    } else if (!strcasecmp(restriction, "admin")) {
      flag = FN_ADMIN;
    } else if (!strcasecmp(restriction, "wizard")) {
      flag = FN_WIZARD;
    } else if (!strcasecmp(restriction, "god")) {
      flag = FN_GOD;
    } else if (!strcasecmp(restriction, "nosidefx")) {
      flag = FN_NOSIDEFX;
    } else if (!strcasecmp(restriction, "logargs")) {
      flag = FN_LOGARGS;
    } else if (!strcasecmp(restriction, "logname")) {
      flag = FN_LOGNAME;
    } else if (!strcasecmp(restriction, "noparse")) {
      flag = FN_NOPARSE;
    }
    if (clear)
      result &= ~flag;
    else
      result |= flag;
    restriction = tp;
  }
  return result;
}


/** Given a function name and a restriction, apply the restriction to the
 * function in addition to whatever its usual restrictions are.
 * This is used by the configuration file startup in conf.c
 * \verbatim
 * Valid restrictions are:
 *   nobody     disable the command
 *   nogagged   can't be used by gagged players
 *   nofixed    can't be used by fixed players
 *   noguest    can't be used by guests
 *   admin      can only be used by royalty or wizards
 *   wizard     can only be used by wizards
 *   god        can only be used by god
 *   noplayer   can't be used by players, just objects/rooms/exits
 *   nosidefx   can't be used to do side-effect thingies
 * \endverbatim
 * \param name name of function to restrict.
 * \param restriction name of restriction to apply to function.
 * \retval 1 success.
 * \retval 0 failure (invalid function or restriction name).
 */
int
restrict_function(const char *name, const char *restriction)
{
  FUN *fp;

  if (!name || !*name)
    return 0;
  fp = func_hash_lookup(name);
  if (!fp)
    return 0;
  fp->flags = apply_restrictions(fp->flags, restriction);
  return 1;
}

/** Softcode interface to restrict a function.
 * \verbatim
 * This is the implementation of @function/restrict.
 * \endverbatim
 * \param player the enactor.
 * \param name name of function to restrict.
 * \param restriction name of restriction to add.
 */
void
do_function_restrict(dbref player, const char *name, const char *restriction)
{
  if (!Wizard(player)) {
    notify(player, T("Permission denied."));
    return;
  }
  if (!name) {
    notify(player, T("Restrict what function?"));
    return;
  }
  if (!restriction) {
    notify(player, T("Do what with the function?"));
    return;
  }
  if (restrict_function(name, restriction))
    notify(player, T("Restrictions modified."));
  else
    notify(player, T("Restrict attempt failed."));
}


/** Add a user-defined function.
 * \verbatim
 * This is the implementation of the @function command. If no arguments
 * are given, it lists all @functions defined. Otherwise, this adds
 * an @function.
 * \endverbatim
 * \param player the enactor.
 * \param name name of function to add.
 * \param argv array of arguments.
 */
void
do_function(dbref player, char *name, char *argv[])
{
  char tbuf1[BUFFER_LEN];
  char *bp = tbuf1;
  dbref thing;
  FUN *fp;

  /* if no arguments, just give the list of user functions, by walking
   * the function hash table, and looking up all functions marked
   * as user-defined.
   */

  if (!name || !*name) {
    if (userfn_count == 0) {
      notify(player, T("No global user-defined functions exist."));
      return;
    }
    if (Global_Funcs(player)) {
      /* if the player is privileged, display user-def'ed functions
       * with corresponding dbref number of thing and attribute name.
       */
      notify(player, T("Function Name                   Dbref #    Attrib"));
      for (fp = (FUN *) hash_firstentry(&htab_user_function);
	   fp; fp = (FUN *) hash_nextentry(&htab_user_function)) {
	notify_format(player,
		      "%-32s %6d    %s", fp->name,
		      userfn_tab[fp->where.offset].thing,
		      userfn_tab[fp->where.offset].name);
      }
    } else {
      /* just print out the list of available functions */
      safe_str(T("User functions:"), tbuf1, &bp);
      for (fp = (FUN *) hash_firstentry(&htab_user_function);
	   fp; fp = (FUN *) hash_nextentry(&htab_user_function)) {
	safe_chr(' ', tbuf1, &bp);
	safe_str(fp->name, tbuf1, &bp);
      }
      *bp = '\0';
      notify(player, tbuf1);
    }
    return;
  }
  /* otherwise, we are adding a user function. 
   * Only those with the Global_Funcs power may add stuff.
   * If you add a function that is already a user-defined function,
   * the old function gets over-written.
   */

  if (!Global_Funcs(player)) {
    notify(player, T("Permission denied."));
    return;
  }
  if (!argv[1] || !*argv[1] || !argv[2] || !*argv[2]) {
    notify(player, T("You must specify an object and an attribute."));
    return;
  }
  /* make sure the function name length is okay */
  if (strlen(name) >= SBUF_LEN) {
    notify(player, T("Function name too long."));
    return;
  }
  /* find the object. For some measure of security, the player must
   * be able to examine it.
   */
  if ((thing = noisy_match_result(player, argv[1], NOTYPE, MAT_EVERYTHING))
      == NOTHING)
    return;
  if (!Can_Examine(player, thing)) {
    notify(player, T("No permission to examine object."));
    return;
  }
  /* we don't need to check if the attribute exists. If it doesn't,
   * it's not our problem - it's the user's responsibility to make
   * sure that the attribute exists (if it doesn't, invoking the
   * function will return a #-1 NO SUCH ATTRIBUTE error).
   * We do, however, need to make sure that the user isn't trying
   * to replace a built-in function.
   */

  fp = func_hash_lookup(upcasestr(name));
  if (!fp) {
    if (userfn_count >= (Size_t) MAX_GLOBAL_FNS) {
      notify(player, T("Function table full."));
      return;
    }
    if (argv[6] && *argv[6]) {
      notify(player, T("Expected between 1 and 5 arguments."));
      return;
    }
    /* a completely new entry. First, insert it into general hash table */
    fp = (FUN *) mush_malloc(sizeof(FUN), "func_hash.FUN");
    fp->name = mush_strdup(name, "func_hash.name");
    fp->where.offset = userfn_count;
    if (argv[3] && *argv[3]) {
      fp->minargs = parse_integer(argv[3]);
      if (fp->minargs < 0)
	fp->minargs = 0;
      else if (fp->minargs > 10)
	fp->minargs = 10;
    } else
      fp->minargs = 0;

    if (argv[4] && *argv[4]) {
      fp->maxargs = parse_integer(argv[4]);
      if (fp->maxargs < -10)
	fp->maxargs = -10;
      else if (fp->maxargs > 10)
	fp->maxargs = 10;
    } else
      fp->maxargs = 10;
    if (argv[5] && *argv[5])
      fp->flags = apply_restrictions(0, argv[5]);
    else
      fp->flags = 0;
    hashadd(name, fp, &htab_user_function);

    /* now add it to the user function table */
    userfn_tab[userfn_count].thing = thing;
    userfn_tab[userfn_count].name =
      mush_strdup(upcasestr(argv[2]), "userfn_tab.name");
    userfn_tab[userfn_count].fn = mush_strdup(name, "usrfn_tab.fn");
    userfn_count++;

    notify(player, T("Function added."));
    return;
  } else {

    /* we are modifying an old entry */
    if ((fp->flags & FN_BUILTIN) && !(fp->flags & FN_OVERRIDE)) {
      notify(player, T("You cannot change that built-in function."));
      return;
    }
    if (fp->flags & FN_BUILTIN) {	/* Overriding a built in function */
      if (userfn_count >= (Size_t) MAX_GLOBAL_FNS) {
	notify(player, T("Function table full."));
	return;
      }
      fp = (FUN *) mush_malloc(sizeof(FUN), "func_hash.FUN");
      fp->name = mush_strdup(name, "func_hash.name");
      fp->where.offset = userfn_count;
      fp->flags = 0;
      userfn_count++;
      hashadd(name, fp, &htab_user_function);
    }
    userfn_tab[fp->where.offset].thing = thing;
    if (userfn_tab[fp->where.offset].name)
      mush_free((Malloc_t) userfn_tab[fp->where.offset].name,
		"userfn_tab.name");
    userfn_tab[fp->where.offset].name =
      mush_strdup(upcasestr(argv[2]), "userfn_tab.name");
    if (argv[3] && *argv[3]) {
      fp->minargs = parse_integer(argv[3]);
      if (fp->minargs < 0)
	fp->minargs = 0;
      else if (fp->minargs > 10)
	fp->minargs = 10;
    } else
      fp->minargs = 0;

    if (argv[4] && *argv[4]) {
      fp->maxargs = parse_integer(argv[4]);
      if (fp->maxargs < -10)
	fp->maxargs = -10;
      else if (fp->maxargs > 10)
	fp->maxargs = 10;
    } else
      fp->maxargs = 10;
    notify(player, T("Function updated."));
  }
}


/** Restore an overridden built-in function.
 * \verbatim
 * If a built-in function is deleted with @function/delete, it can be
 * restored with @function/restore. This implements @function/restore.
 * If a user-defined function has been added, it will be removed by
 * this function.
 * \endverbatim
 * \param player the enactor.
 * \param name name of function to restore.
 */
void
do_function_restore(dbref player, const char *name)
{
  FUN *fp;
  Size_t table_index, i;

  if (!Wizard(player)) {
    notify(player, T("Permission denied."));
    return;
  }

  if (!name || !*name) {
    notify(player, T("Restore what?"));
    return;
  }

  fp = (FUN *) hashfind(strupper(name), &htab_function);

  if (!fp) {
    notify(player, T("That's not a builtin function."));
    return;
  }

  if (!(fp->flags & FN_OVERRIDE)) {
    notify(player, T("That function isn't deleted!"));
    return;
  }

  fp->flags &= ~FN_OVERRIDE;
  notify(player, T("Restored."));

  /* Delete any @function with the same name */
  fp = (FUN *) hashfind(strupper(name), &htab_user_function);
  if (!fp)
    return;
  /* Remove it from the hash table */
  hashdelete(fp->name, &htab_user_function);
  /* Free its memory */
  table_index = fp->where.offset;
  mush_free((void *) fp->name, "func_hash.name");
  mush_free(fp, "func_hash.FUN");
  /* Fix up the user function table. Expensive, but how often will
   * we need to delete an @function anyway?
   */
  mush_free((Malloc_t) userfn_tab[table_index].name, "userfn_tab.name");
  mush_free((Malloc_t) userfn_tab[table_index].fn, "userfn_tab.fn");
  userfn_count--;
  for (i = table_index; i < userfn_count; i++) {
    fp = (FUN *) hashfind(userfn_tab[i + 1].fn, &htab_user_function);
    fp->where.offset = i;
    userfn_tab[i].thing = userfn_tab[i + 1].thing;
    userfn_tab[i].name = mush_strdup(userfn_tab[i + 1].name, "userfn_tab.name");
    mush_free((Malloc_t) userfn_tab[i + 1].name, "userfn_tab.name");
    userfn_tab[i].fn = mush_strdup(userfn_tab[i + 1].fn, "userfn_tab.fn");
    mush_free((Malloc_t) userfn_tab[i + 1].fn, "userfn_tab.fn");
  }
}

/** Delete a function.
 * \verbatim
 * This code implements @function/delete, which deletes a function -
 * either a built-in or a user-defined one.
 * \endverbatim
 * \param player the enactor.
 * \param name name of the function to delete.
 */
void
do_function_delete(dbref player, char *name)
{
  /* Deletes a user-defined function. 
   * For security, you must control the object the function uses
   * to delete the function.
   */
  Size_t table_index, i;
  FUN *fp;

  if (!Global_Funcs(player)) {
    notify(player, T("Permission denied."));
    return;
  }
  fp = func_hash_lookup(name);
  if (!fp) {
    notify(player, T("No such function."));
    return;
  }
  if (fp->flags & FN_BUILTIN) {
    if (!Wizard(player)) {
      notify(player, T("You can't delete that @function."));
      return;
    }
    fp->flags |= FN_OVERRIDE;
    notify(player, T("Function deleted."));
    return;
  }

  table_index = fp->where.offset;
  if (!controls(player, userfn_tab[table_index].thing)) {
    notify(player, T("You can't delete that @function."));
    return;
  }
  /* Remove it from the hash table */
  hashdelete(fp->name, &htab_user_function);
  /* Free its memory */
  mush_free((void *) fp->name, "func_hash.name");
  mush_free(fp, "func_hash.FUN");
  /* Fix up the user function table. Expensive, but how often will
   * we need to delete an @function anyway?
   */
  mush_free((Malloc_t) userfn_tab[table_index].name, "userfn_tab.name");
  mush_free((Malloc_t) userfn_tab[table_index].fn, "userfn_tab.fn");
  userfn_count--;
  for (i = table_index; i < userfn_count; i++) {
    fp = (FUN *) hashfind(userfn_tab[i + 1].fn, &htab_user_function);
    fp->where.offset = i;
    userfn_tab[i].thing = userfn_tab[i + 1].thing;
    userfn_tab[i].name = mush_strdup(userfn_tab[i + 1].name, "userfn_tab.name");
    mush_free((Malloc_t) userfn_tab[i + 1].name, "userfn_tab.name");
    userfn_tab[i].fn = mush_strdup(userfn_tab[i + 1].fn, "userfn_tab.fn");
    mush_free((Malloc_t) userfn_tab[i + 1].fn, "userfn_tab.fn");
  }
  notify(player, T("Function deleted."));
}

/** Enable or disable a function.
 * \verbatim
 * This implements @function/disable and @function/enable.
 * \endverbatim
 * \param player the enactor.
 * \param name name of the function to enable or disable.
 * \param toggle if 1, enable; if 0, disable.
 */
void
do_function_toggle(dbref player, char *name, int toggle)
{
  FUN *fp;

  if (!Wizard(player)) {
    notify(player, T("Permission denied."));
    return;
  }

  fp = func_hash_lookup(name);
  if (!fp) {
    notify(player, T("No such function."));
    return;
  }

  if (toggle) {
    fp->flags &= ~FN_DISABLED;
    notify(player, T("Enabled."));
  } else {
    fp->flags |= FN_DISABLED;
    notify(player, T("Disabled."));
  }
}

/** Get information about a function.
 * \verbatim
 * This implements the @function <function> command, which reports function
 * details to the enactor.
 * \endverbatim
 * \param player the enactor.
 * \param name name of the function.
 */
void
do_function_report(dbref player, char *name)
{
  FUN *fp;
  char tbuf[BUFFER_LEN];
  char *tp;
  const char *state, *state2;
  int first = 1;
  int maxargs;

  fp = func_hash_lookup(name);
  if (!fp) {
    notify(player, T("No such function."));
    return;
  }

  if (fp->flags & FN_BUILTIN)
    state2 = "";
  else
    state2 = " @function";

  if (fp->flags & FN_DISABLED)
    state = "Disabled";
  else
    state = "Enabled";

  notify_format(player, T("Name      : %s() (%s%s)"), fp->name, state, state2);

  tp = tbuf;
  tbuf[0] = '\0';
  if (fp->flags & FN_NOPARSE) {
    safe_str("Noparse", tbuf, &tp);
    if (first)
      first = 0;
  }

  if (fp->flags & FN_LITERAL) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("Literal", tbuf, &tp);
    first = 0;
  }

  if (fp->flags & FN_NOSIDEFX) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("Nosidefx", tbuf, &tp);
    first = 0;
  }

  if (fp->flags & FN_LOGARGS) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("LogArgs", tbuf, &tp);
    first = 0;
  } else if (fp->flags & FN_LOGNAME) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("LogName", tbuf, &tp);
    first = 0;
  }


  if (fp->flags & FN_NOGAGGED) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("Nogagged", tbuf, &tp);
    first = 0;
  }

  if (fp->flags & FN_NOGUEST) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("Noguest", tbuf, &tp);
    first = 0;
  }

  if (fp->flags & FN_NOFIXED) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("Nofixed", tbuf, &tp);
    first = 0;
  }

  if (fp->flags & FN_WIZARD) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("Wizard", tbuf, &tp);
    first = 0;
  }

  if (fp->flags & FN_ADMIN) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("Admin", tbuf, &tp);
    first = 0;
  }

  if (fp->flags & FN_GOD) {
    if (first == 0)
      safe_strl(", ", 2, tbuf, &tp);
    safe_str("God", tbuf, &tp);
    first = 0;
  }

  *tp = '\0';
  notify_format(player, T("Flags     : %s"), tbuf);

  if (!(fp->flags & FN_BUILTIN) && Global_Funcs(player)) {
    notify_format(player, T("Location  : #%d/%s"),
		  userfn_tab[fp->where.offset].thing,
		  userfn_tab[fp->where.offset].name);
  }

  maxargs = abs(fp->maxargs);

  tp = tbuf;

  if (fp->maxargs < 0) {
    safe_str(T("(Commas okay in last argument)"), tbuf, &tp);
    *tp = '\0';
  } else
    tbuf[0] = '\0';

  if (fp->minargs == maxargs)
    notify_format(player, T("Arguments : %d %s"), fp->minargs, tbuf);
  else if (fp->maxargs == INT_MAX)
    notify_format(player, T("Arguments : At least %d %s"), fp->minargs, tbuf);
  else
    notify_format(player,
		  T("Arguments : %d to %d %s"), fp->minargs, maxargs, tbuf);
}