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 funlist.c
 *
 * \brief List-handling functions for mushcode.
 *
 *
 */
#include "copyrite.h"

#include "config.h"
#include <string.h>
#include <ctype.h>
#include "ansi.h"
#include "conf.h"
#include "case.h"
#include "externs.h"
#include "parse.h"
#include "function.h"
#include "mymalloc.h"
#include "pcre.h"
#include "match.h"
#include "command.h"
#include "attrib.h"
#include "dbdefs.h"
#include "flags.h"
#include "mushdb.h"
#include "lock.h"
#include "confmagic.h"

#define MAX_SORTSIZE (BUFFER_LEN / 2)  /**< Maximum number of elements to sort */

static char *next_token(char *str, char sep);
static char *autodetect_list(char **ptrs, int nptrs);
static char *get_list_type(char **args, int nargs,
			   int type_pos, char **ptrs, int nptrs);
static char *get_list_type_noauto(char **args, int nargs, int type_pos);
static int i_comp(const void *s1, const void *s2);
static int f_comp(const void *s1, const void *s2);
static int u_comp(const void *s1, const void *s2);
static int regrep_helper(dbref who, dbref what, dbref parent,
			 char const *name, ATTR *atr, void *args);
/** Type definition for a qsort comparison function */
typedef int (*comp_func) (const void *, const void *);
static void sane_qsort(void **array, int left, int right, comp_func compare);
static void do_itemfuns(char *buff, char **bp, char *str, char *num,
			char *word, char *sep, int flag);

char *iter_rep[MAX_ITERS];  /**< itext values */
int iter_place[MAX_ITERS];  /**< inum numbers */
int inum = 0;		    /**< iter depth */
int inum_limit = 0;	    /**< limit of iter depth */
extern const unsigned char *tables;

static char *
next_token(char *str, char sep)
{
  /* move pointer to start of the next token */

  while (*str && (*str != sep))
    str++;
  if (!*str)
    return NULL;
  str++;
  if (sep == ' ') {
    while (*str == sep)
      str++;
  }
  return str;
}

/** Convert list to array.
 * Chops up a list of words into an array of words. The list is
 * destructively modified.
 * \param r pointer to array to store words.
 * \param max maximum number of words to split out.
 * \param list list of words as a string.
 * \param sep separator character between list items.
 * \return number of words split out.
 */
int
list2arr(char *r[], int max, char *list, char sep)
{
  char *p, *lp;
  int i;
  int first;
  ansi_string *as;
  char *aptr;

  as = parse_ansi_string(list);
  aptr = as->text;

  aptr = trim_space_sep(aptr, sep);

  lp = list;
  p = split_token(&aptr, sep);
  first = 0;
  for (i = 0; p && (i < max); i++, p = split_token(&aptr, sep)) {
    r[i] = lp;
    safe_ansi_string(as, p - (as->text), strlen(p), list, &lp);
    *(lp++) = '\0';
  }
  free_ansi_string(as);
  return i;
}

/** Convert array to list.
 * Takes an array of words and concatenates them into a string,
 * using our safe string functions.
 * \param r pointer to array of words.
 * \param max maximum number of words to concatenate.
 * \param list string to fill with word list.
 * \param lp pointer into end of list.
 * \param sep string to use as separator between words.
 */
void
arr2list(char *r[], int max, char *list, char **lp, char *sep)
{
  int i;
  int seplen = 0;

  if (!max)
    return;

  if (sep && *sep)
    seplen = strlen(sep);

  safe_str(r[0], list, lp);
  for (i = 1; i < max; i++) {
    safe_strl(sep, seplen, list, lp);
    safe_str(r[i], list, lp);
  }
  **lp = '\0';
}

/* ARGSUSED */
FUNCTION(fun_munge)
{
  /* This is a function which takes three arguments. The first is
   * an obj-attr pair referencing a u-function to be called. The
   * other two arguments are lists. The first list is passed to the
   * u-function.  The second list is then rearranged to match the
   * order of the first list as returned from the u-function.
   * This rearranged list is returned by MUNGE.
   * A fourth argument (separator) is optional.
   */

  char list1[BUFFER_LEN], *lp, rlist[BUFFER_LEN], *rp;
  char **ptrs1, **ptrs2, **results;
  int i, j, nptrs1, nptrs2, nresults;
  dbref thing;
  ATTR *attrib;
  char sep, isep[2] = { '\0', '\0' }, *osep, osepd[2] = {
  '\0', '\0'};
  int first;
  char *uargs[2];

  if (!delim_check(buff, bp, nargs, args, 4, &sep))
    return;

  isep[0] = sep;
  if (nargs == 5)
    osep = args[4];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  /* find our object and attribute */
  parse_anon_attrib(executor, args[0], &thing, &attrib);
  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (!CanEvalAttr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }

  /* Copy the first list, since we need to pass it to two destructive
   * routines.
   */

  strcpy(list1, args[1]);

  /* Break up the two lists into their respective elements. */

  ptrs1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  ptrs2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  if (!ptrs1 || !ptrs2)
    mush_panic("Unable to allocate memory in fun_munge");
  nptrs1 = list2arr(ptrs1, MAX_SORTSIZE, args[1], sep);
  nptrs2 = list2arr(ptrs2, MAX_SORTSIZE, args[2], sep);

  if (nptrs1 != nptrs2) {
    safe_str(T("#-1 LISTS MUST BE OF EQUAL SIZE"), buff, bp);
    mush_free((Malloc_t) ptrs1, "ptrarray");
    mush_free((Malloc_t) ptrs2, "ptrarray");
    free_anon_attrib(attrib);
    return;
  }
  /* Call the user function */

  lp = list1;
  rp = rlist;
  uargs[0] = lp;
  uargs[1] = isep;
  do_userfn(rlist, &rp, thing, attrib, 2, uargs,
	    executor, caller, enactor, pe_info);
  *rp = '\0';

  /* Now that we have our result, put it back into array form. Search
   * through list1 until we find the element position, then copy the
   * corresponding element from list2.  Mark used elements with
   * NULL to handle duplicates
   */
  results = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  if (!results)
    mush_panic("Unable to allocate memory in fun_munge");
  nresults = list2arr(results, MAX_SORTSIZE, rlist, sep);

  first = 1;
  for (i = 0; i < nresults; i++) {
    for (j = 0; j < nptrs1; j++) {
      if (ptrs2[j] && !strcmp(results[i], ptrs1[j])) {
	if (first)
	  first = 0;
	else
	  safe_str(osep, buff, bp);
	safe_str(ptrs2[j], buff, bp);
	ptrs2[j] = NULL;
	break;
      }
    }
  }
  mush_free((Malloc_t) ptrs1, "ptrarray");
  mush_free((Malloc_t) ptrs2, "ptrarray");
  mush_free((Malloc_t) results, "ptrarray");
  free_anon_attrib(attrib);
}

/* ARGSUSED */
FUNCTION(fun_elements)
{
  /* Given a list and a list of numbers, return the corresponding
   * elements of the list. elements(ack bar eep foof yay,2 4) = bar foof
   * A separator for the first list is allowed.
   * This code modified slightly from the Tiny 2.2.1 distribution
   */
  int nwords, cur;
  char **ptrs;
  char *wordlist;
  char *s, *r, sep;
  char *osep, osepd[2] = { '\0', '\0' };

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs == 4)
    osep = args[3];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  ptrs = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  wordlist = (char *) mush_malloc(BUFFER_LEN, "string");
  if (!ptrs || !wordlist)
    mush_panic("Unable to allocate memory in fun_elements");

  /* Turn the first list into an array. */
  strcpy(wordlist, args[0]);
  nwords = list2arr(ptrs, MAX_SORTSIZE, wordlist, sep);

  s = trim_space_sep(args[1], ' ');

  /* Go through the second list, grabbing the numbers and finding the
   * corresponding elements.
   */
  r = split_token(&s, ' ');
  cur = atoi(r) - 1;
  if ((cur >= 0) && (cur < nwords) && ptrs[cur]) {
    safe_str(ptrs[cur], buff, bp);
  }
  while (s) {
    r = split_token(&s, ' ');
    cur = atoi(r) - 1;
    if ((cur >= 0) && (cur < nwords) && ptrs[cur]) {
      safe_str(osep, buff, bp);
      safe_str(ptrs[cur], buff, bp);
    }
  }
  mush_free((Malloc_t) ptrs, "ptrarray");
  mush_free((Malloc_t) wordlist, "string");
}

/* ARGSUSED */
FUNCTION(fun_matchall)
{
  /* Check each word individually, returning the word number of all
   * that match. If none match, return an empty string.
   */

  int wcount;
  char *r, *s, *b, sep;
  char *osep, osepd[2] = { '\0', '\0' };

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs == 4)
    osep = args[3];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  wcount = 1;
  s = trim_space_sep(args[0], sep);
  b = *bp;
  do {
    r = split_token(&s, sep);
    if (quick_wild(args[1], r)) {
      if (*bp != b)
	safe_str(osep, buff, bp);
      safe_integer(wcount, buff, bp);
    }
    wcount++;
  } while (s);
}

/* ARGSUSED */
FUNCTION(fun_graball)
{
  /* Check each word individually, returning all that match.
   * If none match, return an empty string.  This is to grab()
   * what matchall() is to match().
   */

  char *r, *s, *b, sep;
  char *osep, osepd[2] = { '\0', '\0' };

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs == 4)
    osep = args[3];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  s = trim_space_sep(args[0], sep);
  b = *bp;
  do {
    r = split_token(&s, sep);
    if (quick_wild(args[1], r)) {
      if (*bp != b)
	safe_str(osep, buff, bp);
      safe_str(r, buff, bp);
    }
  } while (s);
}



/* ARGSUSED */
FUNCTION(fun_fold)
{
  /* iteratively evaluates an attribute with a list of arguments and
   * optional base case. With no base case, the first list element is
   * passed as %0, and the second as %1. The attribute is then evaluated
   * with these args. The result is then used as %0, and the next arg as
   * %1. Repeat until no elements are left in the list. The base case 
   * can provide a starting point.
   */

  dbref thing;
  ATTR *attrib;
  char const *ap;
  char *abuf, *result, *rp, *rsave;
  char *cp;
  char *tptr[2];
  char sep;
  int funccount, per;
  int pe_flags = PE_DEFAULT;

  if (!delim_check(buff, bp, nargs, args, 4, &sep))
    return;

  /* find our object and attribute */
  parse_anon_attrib(executor, args[0], &thing, &attrib);

  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (!CanEvalAttr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }

  /* Now we can go to work */
  if (AF_Debug(attrib))
    pe_flags |= PE_DEBUG;
  result = (char *) mush_malloc(BUFFER_LEN, "string");
  rsave = (char *) mush_malloc(BUFFER_LEN, "string");
  if (!result || !rsave)
    mush_panic("Unable to allocate memory in fun_fold");

  abuf = safe_atr_value(attrib);

  /* save our stack */
  tptr[0] = global_eval_context.wenv[0];
  tptr[1] = global_eval_context.wenv[1];

  cp = args[1];

  /* If we have three or more arguments, the third one is the base case */
  if (nargs >= 3) {
    global_eval_context.wenv[0] = args[2];
    global_eval_context.wenv[1] = split_token(&cp, sep);
  } else {
    global_eval_context.wenv[0] = split_token(&cp, sep);
    global_eval_context.wenv[1] = split_token(&cp, sep);
  }
  rp = result;
  ap = abuf;
  process_expression(result, &rp, &ap, thing, executor, enactor,
		     pe_flags, PT_DEFAULT, pe_info);
  *rp = '\0';
  strcpy(rsave, result);
  funccount = pe_info->fun_invocations;

  /* handle the rest of the cases */
  while (cp && *cp) {
    global_eval_context.wenv[0] = rsave;
    global_eval_context.wenv[1] = split_token(&cp, sep);
    rp = result;
    ap = abuf;
    per = process_expression(result, &rp, &ap, thing, executor, enactor,
			     pe_flags, PT_DEFAULT, pe_info);
    *rp = '\0';
    if (per || (pe_info->fun_invocations >= FUNCTION_LIMIT &&
		pe_info->fun_invocations == funccount &&
		!strcmp(rsave, result)))
      break;
    funccount = pe_info->fun_invocations;
    strcpy(rsave, result);
  }
  safe_str(rsave, buff, bp);

  /* restore the stack */
  global_eval_context.wenv[0] = tptr[0];
  global_eval_context.wenv[1] = tptr[1];

  free((Malloc_t) abuf);
  mush_free((Malloc_t) result, "string");
  mush_free((Malloc_t) rsave, "string");
  free_anon_attrib(attrib);
}

/* ARGSUSED */
FUNCTION(fun_itemize)
{
  /* Called in one of two ways:
   * itemize(<list>[,<delim>[,<conjunction>[,<punctuation>]]])
   * elist(<list>[,<conjunction> [,<delim> [,<output delim> [,<punctuation>]]]])
   * Either way, it takes the elements of list and:
   *  If there's just one, returns it.
   *  If there's two, returns <e1> <conjunction> <e2>
   *  If there's >2, returns <e1><punc> <e2><punc> ... <conjunction> <en>
   * Default <conjunction> is "and", default punctuation is ","
   */
  const char *outsep = " ";
  char sep = ' ';
  const char *lconj = "and";
  const char *punc = ",";
  char *cp;
  char *word, *nextword;
  int pos;

  if (strcmp(called_as, "ELIST") == 0) {
    /* elist ordering */
    if (!delim_check(buff, bp, nargs, args, 3, &sep))
      return;
    if (nargs > 1)
      lconj = args[1];
    if (nargs > 3)
      outsep = args[3];
    if (nargs > 4)
      punc = args[4];
  } else {
    /* itemize ordering */
    if (!delim_check(buff, bp, nargs, args, 2, &sep))
      return;
    if (nargs > 2)
      lconj = args[2];
    if (nargs > 3)
      punc = args[3];
  }
  cp = trim_space_sep(args[0], sep);
  pos = 1;
  word = split_token(&cp, sep);
  while (word) {
    nextword = split_token(&cp, sep);
    safe_itemizer(pos, !(nextword), punc, lconj, outsep, buff, bp);
    safe_str(word, buff, bp);
    pos++;
    word = nextword;
  }
}


/* ARGSUSED */
FUNCTION(fun_filter)
{
  /* take a user-def function and a list, and return only those elements
   * of the list for which the function evaluates to 1.
   */

  dbref thing;
  ATTR *attrib;
  char const *ap;
  char *abuf, result[BUFFER_LEN], *rp;
  char *cp;
  char *tptr;
  char sep;
  int first;
  int check_bool = 0;
  int funccount;
  char *osep, osepd[2] = { '\0', '\0' };
  int pe_flags = PE_DEFAULT;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs == 4)
    osep = args[3];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  if (strcmp(called_as, "FILTERBOOL") == 0)
    check_bool = 1;

  /* find our object and attribute */
  parse_anon_attrib(executor, args[0], &thing, &attrib);

  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (!CanEvalAttr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }

  if (AF_Debug(attrib))
    pe_flags |= PE_DEBUG;

  abuf = safe_atr_value(attrib);

  tptr = global_eval_context.wenv[0];

  cp = trim_space_sep(args[1], sep);
  first = 1;
  funccount = pe_info->fun_invocations;
  while (cp && *cp) {
    global_eval_context.wenv[0] = split_token(&cp, sep);
    ap = abuf;
    rp = result;
    if (process_expression(result, &rp, &ap, thing, executor, enactor,
			   pe_flags, PT_DEFAULT, pe_info))
      break;
    *rp = '\0';
    if ((check_bool == 0)
	? (*result == '1' && *(result + 1) == '\0')
	: parse_boolean(result)) {
      if (first)
	first = 0;
      else
	safe_str(osep, buff, bp);
      safe_str(global_eval_context.wenv[0], buff, bp);
    }
    /* Can't do *bp == oldbp like in all the others, because bp might not
     * move even when not full, if one of the list elements is null and
     * we have a null separator. */
    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
      break;
    funccount = pe_info->fun_invocations;
  }

  global_eval_context.wenv[0] = tptr;

  free((Malloc_t) abuf);
  free_anon_attrib(attrib);
}

/* ARGSUSED */
FUNCTION(fun_shuffle)
{
  /* given a list of words, randomize the order of words. 
   * We do this by taking each element, and swapping it with another
   * element with a greater array index (thus, words[0] can be swapped
   * with anything up to words[n], words[5] with anything between
   * itself and words[n], etc.
   * This is relatively fast - linear time - and reasonably random.
   * Will take an optional delimiter argument.
   */

  char *words[BUFFER_LEN / 2];
  int n, i, j;
  char sep;
  char *osep, osepd[2] = { '\0', '\0' };

  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;

  if (nargs == 3)
    osep = args[2];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  /* split the list up, or return if the list is empty */
  if (!*args[0])
    return;
  n = list2arr(words, BUFFER_LEN / 2, args[0], sep);

  /* shuffle it */
  for (i = 0; i < n; i++) {
    char *tmp;
    j = get_random_long(i, n - 1);
    tmp = words[j];
    words[j] = words[i];
    words[i] = tmp;
  }

  arr2list(words, n, buff, bp, osep);
}

typedef enum {
  L_NUMERIC,
  L_FLOAT,
  L_ALPHANUM,
  L_DBREF
} ltype;

static char *
autodetect_list(char *ptrs[], int nptrs)
{
  char *sort_type;
  ltype lt;
  int i;

  lt = L_NUMERIC;
  sort_type = NUMERIC_LIST;

  for (i = 0; i < nptrs; i++) {
    switch (lt) {
    case L_NUMERIC:
      if (!is_strict_integer(ptrs[i])) {
	/* If it's not an integer, see if it's a floating-point number */
	if (is_strict_number(ptrs[i])) {
	  lt = L_FLOAT;
	  sort_type = FLOAT_LIST;
	} else if (i == 0) {

	  /* If we get something non-numeric, switch to an
	   * alphanumeric guess, unless this is the first
	   * element and we have a dbref.
	   */
	  if (is_objid(ptrs[i])) {
	    lt = L_DBREF;
	    sort_type = DBREF_LIST;
	  } else
	    return ALPHANUM_LIST;
	}
      }
      break;
    case L_FLOAT:
      if (!is_strict_number(ptrs[i]))
	return ALPHANUM_LIST;
      break;
    case L_DBREF:
      if (!is_objid(ptrs[i]))
	return ALPHANUM_LIST;
      break;
    default:
      return ALPHANUM_LIST;
    }
  }
  return sort_type;
}


typedef struct sort_record s_rec;

typedef int (*qsort_func) (const void *, const void *);
typedef void (*makerecord) (s_rec *, dbref player, char *sortflags);

#define GENRECORD(x) void x(s_rec *rec,dbref player,char *sortflags); \
  void x(s_rec *rec, \
    dbref player __attribute__ ((__unused__)), \
    char *sortflags __attribute__ ((__unused__)))

/** Sorting strings by different values. We store both the string and
 * its 'key' to sort by. Sort of a hardcode munge.
 */
struct sort_record {
  char *val;	 /**< The string this is */
  dbref db;	 /**< dbref (default 0, bad is -1) */
  char *str;	 /**< string comparisons */
  int num;	 /**< integer comparisons */
  NVAL numval;	 /**< float comparisons */
  int freestr;	 /**< free str on completion */
};

/* Compare(r,x,y) {
 *   if (x->db < 0 && y->db < 0)
 *     return 0;  // Garbage is identical.
 *   if (x->db < 0)
 *     return 2;  // Garbage goes last.
 *   if (y->db < 0)
 *     return -2; // Garbage goes last.
 *   if (r < 0)
 *     return -2; // different
 *   if (r > 0)
 *     return 2;  // different
 *   if (x->db < y->db)
 *     return -1; // similar
 *   if (y->db < x-db)
 *     return 1;  // similar
 *   return 0;    // identical
 * }
 */

/* If I could, I'd let sort() toss out non-existant dbrefs
 * Instead, sort stuffs them into a jumble at the end. */

#define Compare(r,x,y) \
        ((x->db < 0 || y->db < 0) ? \
           ((x->db < 0 && y->db < 0) ? 0 : (x->db < 0 ? 2 : -2)) \
           : ((r != 0) ? (r < 0 ? -2 : 2) \
             : (x->db == y->db ? 0 : (x->db < y->db ? -1 : 1)) \
           ) \
         )

static int
s_comp(const void *s1, const void *s2)
{
  const s_rec *sr1 = (const s_rec *) s1;
  const s_rec *sr2 = (const s_rec *) s2;
  int res = 0;
  res = strcoll(sr1->str, sr2->str);
  return Compare(res, sr1, sr2);
}

static int
si_comp(const void *s1, const void *s2)
{
  const s_rec *sr1 = (const s_rec *) s1;
  const s_rec *sr2 = (const s_rec *) s2;
  int res = 0;
  res = strcasecoll(sr1->str, sr2->str);
  return Compare(res, sr1, sr2);
}

static int
i_comp(const void *s1, const void *s2)
{
  const s_rec *sr1 = (const s_rec *) s1;
  const s_rec *sr2 = (const s_rec *) s2;
  int res = 0;
  res = sr1->num - sr2->num;
  return Compare(res, sr1, sr2);
}

static int
f_comp(const void *s1, const void *s2)
{
  const s_rec *sr1 = (const s_rec *) s1;
  const s_rec *sr2 = (const s_rec *) s2;
  NVAL res = 0;
  res = sr1->numval - sr2->numval;
  return Compare(res, sr1, sr2);
}

GENRECORD(gen_alphanum)
{
  int len;
  if (strchr(rec->val, ESC_CHAR)) {
    rec->str = mush_strdup(remove_markup(rec->val, &len), "genrecord");
    rec->freestr = 1;
  } else {
    rec->str = rec->val;
  }
}

GENRECORD(gen_dbref)
{
  rec->num = qparse_dbref(rec->val);
}

GENRECORD(gen_num)
{
  rec->num = parse_integer(rec->val);
}

GENRECORD(gen_float)
{
  rec->numval = parse_number(rec->val);
}

#define RealGoodObject(x) (GoodObject(x) && !IsGarbage(x))

GENRECORD(gen_db_name)
{
  rec->str = (char *) "";
  if (RealGoodObject(rec->db))
    rec->str = (char *) Name(rec->db);
}

GENRECORD(gen_db_idle)
{
  rec->num = -1;
  if (RealGoodObject(rec->db)) {
    if (Priv_Who(player))
      rec->num = least_idle_time_priv(rec->db);
    else
      rec->num = least_idle_time(rec->db);
  }
}

GENRECORD(gen_db_conn)
{
  rec->num = -1;
  if (RealGoodObject(rec->db)) {
    if (Priv_Who(player))
      rec->num = most_conn_time_priv(rec->db);
    else
      rec->num = most_conn_time(rec->db);
  }
}

GENRECORD(gen_db_ctime)
{
  if (RealGoodObject(rec->db))
    rec->num = CreTime(rec->db);
}

GENRECORD(gen_db_owner)
{
  if (RealGoodObject(rec->db))
    rec->num = Owner(rec->db);
}

GENRECORD(gen_db_loc)
{
  rec->num = -1;
  if (RealGoodObject(rec->db) && Can_Locate(player, rec->db)) {
    rec->num = Location(rec->db);
  }
}

GENRECORD(gen_db_attr)
{
  /* Eek, I hate dealing with memory issues. */

  static char *defstr = (char *) "";
  const char *ptr;

  rec->str = defstr;
  if (RealGoodObject(rec->db) && sortflags && *sortflags &&
      (ptr = do_get_attrib(player, rec->db, sortflags)) != NULL) {
    rec->str = mush_strdup(ptr, "genrecord");
    rec->freestr = 1;
  }
}

typedef struct _list_type_list_ {
  char *name;
  makerecord make_record;
  qsort_func sorter;
  int isdbs;
} list_type_list;

char ALPHANUM_LIST[] = "A";
char INSENS_ALPHANUM_LIST[] = "I";
char DBREF_LIST[] = "D";
char NUMERIC_LIST[] = "N";
char FLOAT_LIST[] = "F";
char DBREF_NAME_LIST[] = "NAME";
char DBREF_NAMEI_LIST[] = "NAMEI";
char DBREF_IDLE_LIST[] = "IDLE";
char DBREF_CONN_LIST[] = "CONN";
char DBREF_CTIME_LIST[] = "CTIME";
char DBREF_OWNER_LIST[] = "OWNER";
char DBREF_LOCATION_LIST[] = "LOC";
char DBREF_ATTR_LIST[] = "ATTR";
char DBREF_ATTRI_LIST[] = "ATTRI";
char *UNKNOWN_LIST = NULL;

list_type_list ltypelist[] = {
  /* List type name,            recordmaker,    comparer, dbrefs? */
  {ALPHANUM_LIST, gen_alphanum, s_comp, 0},
  {INSENS_ALPHANUM_LIST, gen_alphanum, si_comp, 0},
  {DBREF_LIST, gen_dbref, i_comp, 0},
  {NUMERIC_LIST, gen_num, i_comp, 0},
  {FLOAT_LIST, gen_float, f_comp, 0},
  {DBREF_NAME_LIST, gen_db_name, s_comp, 1},
  {DBREF_NAMEI_LIST, gen_db_name, si_comp, 1},
  {DBREF_IDLE_LIST, gen_db_idle, i_comp, 1},
  {DBREF_CONN_LIST, gen_db_conn, i_comp, 1},
  {DBREF_CTIME_LIST, gen_db_ctime, i_comp, 1},
  {DBREF_OWNER_LIST, gen_db_owner, i_comp, 1},
  {DBREF_LOCATION_LIST, gen_db_loc, i_comp, 1},
  {DBREF_ATTR_LIST, gen_db_attr, s_comp, 1},
  {DBREF_ATTRI_LIST, gen_db_attr, si_comp, 1},
  /* This stops the loop, so is default */
  {NULL, gen_alphanum, s_comp, 0}
};

char *
get_list_type(char *args[], int nargs, int type_pos, char *ptrs[], int nptrs)
{
  static char stype[BUFFER_LEN];
  int i;
  char *str;
  if (nargs >= type_pos) {
    str = args[type_pos - 1];
    if (*str) {
      strcpy(stype, str);
      str = strchr(stype, ':');
      if (str)
	*(str++) = '\0';
      for (i = 0; ltypelist[i].name && strcasecmp(ltypelist[i].name, stype);
	   i++) ;
      /* return ltypelist[i].name; */
      if (ltypelist[i].name) {
	return args[type_pos - 1];
      }
    }
  }
  return autodetect_list(ptrs, nptrs);
}

char *
get_list_type_noauto(char *args[], int nargs, int type_pos)
{
  static char stype[BUFFER_LEN];
  int i;
  char *str;
  if (nargs >= type_pos) {
    str = args[type_pos - 1];
    if (*str) {
      strcpy(stype, str);
      str = strchr(stype, ':');
      if (str)
	*(str++) = '\0';
      for (i = 0; ltypelist[i].name && strcasecmp(ltypelist[i].name, stype);
	   i++) ;
      /* return ltypelist[i].name; */
      return args[type_pos - 1];
    }
  }
  return UNKNOWN_LIST;
}


static dbref ucomp_executor, ucomp_caller, ucomp_enactor;
static char ucomp_buff[BUFFER_LEN];
static PE_Info *ucomp_pe_info;

static int
u_comp(const void *s1, const void *s2)
{
  char result[BUFFER_LEN], *rp;
  char const *tbuf;
  int n;

  /* Our two arguments are passed as %0 and %1 to the sortby u-function. */

  /* Note that this function is for use in conjunction with our own
   * sane_qsort routine, NOT with the standard library qsort!
   */
  global_eval_context.wenv[0] = (char *) s1;
  global_eval_context.wenv[1] = (char *) s2;

  /* Run the u-function, which should return a number. */

  tbuf = ucomp_buff;
  rp = result;
  if (process_expression(result, &rp, &tbuf,
			 ucomp_executor, ucomp_caller, ucomp_enactor,
			 PE_DEFAULT, PT_DEFAULT, ucomp_pe_info))
    return 0;
  n = parse_integer(result);

  return n;
}

/** A generic comparer routine to compare two values of any sort type.
 * \param
 */
int
gencomp(dbref player, char *a, char *b, char *sort_type)
{
  char *ptr;
  int i, len;
  int result;
  s_rec s1, s2;
  ptr = NULL;
  if (!sort_type) {
    /* Advance i to the default */
    for (i = 0; ltypelist[i].name; i++) ;
  } else if ((ptr = strchr(sort_type, ':'))) {
    len = ptr - sort_type;
    ptr += 1;
    if (!*ptr)
      ptr = NULL;
    for (i = 0;
	 ltypelist[i].name && strncasecmp(ltypelist[i].name, sort_type, len);
	 i++) ;
  } else {
    for (i = 0; ltypelist[i].name && strcasecmp(ltypelist[i].name, sort_type);
	 i++) ;
  }
  s1.freestr = s2.freestr = 0;
  if (ltypelist[i].isdbs) {
    s1.db = parse_dbref(a);
    s2.db = parse_dbref(b);
    if (!RealGoodObject(s1.db))
      s1.db = NOTHING;
    if (!RealGoodObject(s2.db))
      s2.db = NOTHING;
  } else {
    s1.db = s2.db = 0;
  }

  s1.val = a;
  s2.val = b;
  ltypelist[i].make_record(&s1, player, ptr);
  ltypelist[i].make_record(&s2, player, ptr);
  result = ltypelist[i].sorter((const void *) &s1, (const void *) &s2);
  if (s1.freestr)
    mush_free(s1.str, "genrecord");
  if (s2.freestr)
    mush_free(s2.str, "genrecord");
  return result;
}

/** A generic sort routine to sort several different
 * types of arrays, in place.
 * \param player the player executing the sort.
 * \param s the array to sort.
 * \param n number of elements in array s
 * \param sort_type the string that describes the sort type.
 */

void
do_gensort(dbref player, char *s[], int n, char *sort_type)
{
  char *ptr;
  static char stype[BUFFER_LEN];
  int i, sorti;
  s_rec *sp;
  ptr = NULL;
  if (!sort_type || !*sort_type) {
    /* Advance sorti to the default */
    for (sorti = 0; ltypelist[sorti].name; sorti++) ;
  } else if (strchr(sort_type, ':') != NULL) {
    strcpy(stype, sort_type);
    ptr = strchr(stype, ':');
    *(ptr++) = '\0';
    if (!*ptr)
      ptr = NULL;
    for (sorti = 0;
	 ltypelist[sorti].name && strcasecmp(ltypelist[sorti].name, stype);
	 sorti++) ;
  } else {
    for (sorti = 0;
	 ltypelist[sorti].name && strcasecmp(ltypelist[sorti].name, sort_type);
	 sorti++) ;
  }
  sp = (s_rec *) mush_malloc(n * sizeof(s_rec), "do_gensort");
  for (i = 0; i < n; i++) {
    sp[i].val = s[i];
    sp[i].freestr = 0;
    sp[i].db = 0;
    sp[i].str = NULL;
    if (ltypelist[sorti].isdbs) {
      sp[i].db = parse_objid(s[i]);
      if (!RealGoodObject(sp[i].db))
	sp[i].db = NOTHING;
    }
    ltypelist[sorti].make_record(&(sp[i]), player, ptr);
  }
  qsort((void *) sp, n, sizeof(s_rec), ltypelist[sorti].sorter);

  for (i = 0; i < n; i++) {
    s[i] = sp[i].val;
    if (sp[i].freestr)
      mush_free(sp[i].str, "genrecord");
  }
  mush_free((Malloc_t) sp, "do_gensort");
}

/* ARGSUSED */
FUNCTION(fun_sort)
{
  char *ptrs[MAX_SORTSIZE];
  int nptrs;
  char *sort_type;
  char sep;
  char outsep[BUFFER_LEN];

  if (!nargs || !*args[0])
    return;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs < 4) {
    outsep[0] = sep;
    outsep[1] = '\0';
  } else
    strcpy(outsep, args[3]);

  nptrs = list2arr(ptrs, MAX_SORTSIZE, args[0], sep);
  sort_type = get_list_type(args, nargs, 2, ptrs, nptrs);
  do_gensort(executor, ptrs, nptrs, sort_type);
  arr2list(ptrs, nptrs, buff, bp, outsep);
}

static void
sane_qsort(void *array[], int left, int right, comp_func compare)
{
  /* Andrew Molitor's qsort, which doesn't require transitivity between
   * comparisons (essential for preventing crashes due to boneheads
   * who write comparison functions where a > b doesn't mean b < a).
   */
  /* Actually, this sort doesn't require commutivity.
   * Sorting doesn't make sense without transitivity...
   */

  int i, last;
  void *tmp;

loop:
  if (left >= right)
    return;

  /* Pick something at random at swap it into the leftmost slot   */
  /* This is the pivot, we'll put it back in the right spot later */

  i = get_random_long(left, right);
  tmp = array[i];
  array[i] = array[left];
  array[left] = tmp;

  last = left;
  for (i = left + 1; i <= right; i++) {

    /* Walk the array, looking for stuff that's less than our */
    /* pivot. If it is, swap it with the next thing along     */

    if (compare(array[i], array[left]) < 0) {
      last++;
      if (last == i)
	continue;

      tmp = array[last];
      array[last] = array[i];
      array[i] = tmp;
    }
  }

  /* Now we put the pivot back, it's now in the right spot, we never */
  /* need to look at it again, trust me.                             */

  tmp = array[last];
  array[last] = array[left];
  array[left] = tmp;

  /* At this point everything underneath the 'last' index is < the */
  /* entry at 'last' and everything above it is not < it.          */

  if ((last - left) < (right - last)) {
    sane_qsort(array, left, last - 1, compare);
    left = last + 1;
    goto loop;
  } else {
    sane_qsort(array, last + 1, right, compare);
    right = last - 1;
    goto loop;
  }
}


/* ARGSUSED */
FUNCTION(fun_sortby)
{
  char *ptrs[MAX_SORTSIZE], *tptr[10];
  char *up, sep;
  int nptrs;
  dbref thing;
  ATTR *attrib;
  char *osep, osepd[2] = { '\0', '\0' };

  if (!nargs || !*args[0])
    return;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs == 4)
    osep = args[3];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  /* Find object and attribute to get sortby function from. */
  parse_anon_attrib(executor, args[0], &thing, &attrib);
  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (!CanEvalAttr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  up = ucomp_buff;
  safe_str(atr_value(attrib), ucomp_buff, &up);
  *up = '\0';

  ucomp_executor = thing;
  ucomp_caller = executor;
  ucomp_enactor = enactor;
  ucomp_pe_info = pe_info;

  save_global_env("sortby", tptr);

  /* Split up the list, sort it, reconstruct it. */
  nptrs = list2arr(ptrs, MAX_SORTSIZE, args[1], sep);
  if (nptrs > 1)		/* pointless to sort less than 2 elements */
    sane_qsort((void *) ptrs, 0, nptrs - 1, u_comp);

  arr2list(ptrs, nptrs, buff, bp, osep);

  restore_global_env("sortby", tptr);
  free_anon_attrib(attrib);
}

/* ARGSUSED */
FUNCTION(fun_setunion)
{
  char sep;
  char **a1, **a2;
  char *tempbuff;
  int n1, i, a;
  char *sort_type = ALPHANUM_LIST;
  char *osep = NULL, osepd[2] = { '\0', '\0' };

  /* if no lists, then no work */
  if (!*args[0] && !*args[1])
    return;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  tempbuff = (char *) mush_malloc((BUFFER_LEN * 2) + 4, "string");
  a1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  a2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  if (!tempbuff || !a1 || !a2)
    mush_panic("Unable to allocate memory in fun_setunion");
  /* Concat both lists, make array, sort */
  if (!*args[0])
    memcpy(tempbuff, args[1], arglens[1] + 1);
  else if (!*args[1])
    memcpy(tempbuff, args[0], arglens[0] + 1);
  else
    sprintf(tempbuff, "%s%c%s", args[0], sep, args[1]);
  n1 = list2arr(a1, MAX_SORTSIZE, tempbuff, sep);

  if (nargs < 4) {
    osepd[0] = sep;
    osep = osepd;
  } else if (nargs == 4) {
    sort_type = get_list_type_noauto(args, nargs, 4);
    if (sort_type == UNKNOWN_LIST) {
      sort_type = ALPHANUM_LIST;
      osep = args[3];
    } else {
      osepd[0] = sep;
      osep = osepd;
    }
  } else if (nargs == 5) {
    sort_type = get_list_type(args, nargs, 4, a1, n1);
    osep = args[4];
  }

  do_gensort(executor, a1, n1, sort_type);

  /* Strip the duplicates and make a2 contain the list */
  a = 0;
  for (i = 0; i < n1; i++) {
    if (((a == 0) || (gencomp(executor, a1[i], a2[a - 1], sort_type) != 0))) {
      a2[a] = a1[i];
      a++;
    }
  }

  /* Return our sorted result */
  arr2list(a2, a, buff, bp, osep);
  mush_free((Malloc_t) tempbuff, "string");
  mush_free((Malloc_t) a1, "ptrarray");
  mush_free((Malloc_t) a2, "ptrarray");
}

/* ARGSUSED */
FUNCTION(fun_setinter)
{
  char sep;
  char **a1, **a2;
  int n1, n2, x1, x2, val;
  char *sort_type = ALPHANUM_LIST;
  int osepl = 0;
  char *osep = NULL, osepd[2] = { '\0', '\0' };

  /* if no lists, then no work */
  if (!*args[0] && !*args[1])
    return;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  a1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  a2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  if (!a1 || !a2)
    mush_panic("Unable to allocate memory in fun_setunion");

  /* make arrays out of the lists */
  n1 = list2arr(a1, MAX_SORTSIZE, args[0], sep);
  n2 = list2arr(a2, MAX_SORTSIZE, args[1], sep);

  if (nargs < 4) {
    osepd[0] = sep;
    osep = osepd;
    if (sep)
      osepl = 1;
  } else if (nargs == 4) {
    sort_type = get_list_type_noauto(args, nargs, 4);
    if (sort_type == UNKNOWN_LIST) {
      sort_type = ALPHANUM_LIST;
      osep = args[3];
      osepl = arglens[3];
    } else {
      osepd[0] = sep;
      osep = osepd;
      if (sep)
	osepl = 1;
    }
  } else if (nargs == 5) {
    sort_type = get_list_type(args, nargs, 4, a1, n1);
    osep = args[4];
    osepl = arglens[4];
  }
  /* sort each array */
  do_gensort(executor, a1, n1, sort_type);
  do_gensort(executor, a2, n2, sort_type);

  /* get the first value for the intersection, removing duplicates */
  x1 = x2 = 0;
  while ((val = gencomp(executor, a1[x1], a2[x2], sort_type))) {
    if (val < 0) {
      x1++;
      if (x1 >= n1) {
	mush_free((Malloc_t) a1, "ptrarray");
	mush_free((Malloc_t) a2, "ptrarray");
	return;
      }
    } else {
      x2++;
      if (x2 >= n2) {
	mush_free((Malloc_t) a1, "ptrarray");
	mush_free((Malloc_t) a2, "ptrarray");
	return;
      }
    }
  }
  safe_str(a1[x1], buff, bp);
  while (!gencomp(executor, a1[x1], a2[x2], sort_type)) {
    x1++;
    if (x1 >= n1) {
      mush_free((Malloc_t) a1, "ptrarray");
      mush_free((Malloc_t) a2, "ptrarray");
      return;
    }
  }

  /* get values for the intersection, until at least one list is empty */
  while ((x1 < n1) && (x2 < n2)) {
    while ((val = gencomp(executor, a1[x1], a2[x2], sort_type))) {
      if (val < 0) {
	x1++;
	if (x1 >= n1) {
	  mush_free((Malloc_t) a1, "ptrarray");
	  mush_free((Malloc_t) a2, "ptrarray");
	  return;
	}
      } else {
	x2++;
	if (x2 >= n2) {
	  mush_free((Malloc_t) a1, "ptrarray");
	  mush_free((Malloc_t) a2, "ptrarray");
	  return;
	}
      }
    }
    safe_strl(osep, osepl, buff, bp);
    safe_str(a1[x1], buff, bp);
    while (!gencomp(executor, a1[x1], a2[x2], sort_type)) {
      x1++;
      if (x1 >= n1) {
	mush_free((Malloc_t) a1, "ptrarray");
	mush_free((Malloc_t) a2, "ptrarray");
	return;
      }
    }
  }
  mush_free((Malloc_t) a1, "ptrarray");
  mush_free((Malloc_t) a2, "ptrarray");
}

/* ARGSUSED */
FUNCTION(fun_setdiff)
{
  char sep;
  char **a1, **a2;
  int n1, n2, x1, x2, val;
  char *sort_type = ALPHANUM_LIST;
  int osepl = 0;
  char *osep = NULL, osepd[2] = { '\0', '\0' };

  /* if no lists, then no work */
  if (!*args[0] && !*args[1])
    return;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  a1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  a2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
  if (!a1 || !a2)
    mush_panic("Unable to allocate memory in fun_setunion");

  /* make arrays out of the lists */
  n1 = list2arr(a1, MAX_SORTSIZE, args[0], sep);
  n2 = list2arr(a2, MAX_SORTSIZE, args[1], sep);

  if (nargs < 4) {
    osepd[0] = sep;
    osep = osepd;
    if (sep)
      osepl = 1;
  } else if (nargs == 4) {
    sort_type = get_list_type_noauto(args, nargs, 4);
    if (sort_type == UNKNOWN_LIST) {
      sort_type = ALPHANUM_LIST;
      osep = args[3];
      osepl = arglens[3];
    } else {
      osepd[0] = sep;
      osep = osepd;
      if (sep)
	osepl = 1;
    }
  } else if (nargs == 5) {
    sort_type = get_list_type(args, nargs, 4, a1, n1);
    osep = args[4];
    osepl = arglens[4];
  }

  /* sort each array */
  do_gensort(executor, a1, n1, sort_type);
  do_gensort(executor, a2, n2, sort_type);

  /* get the first value for the difference, removing duplicates */
  x1 = x2 = 0;
  while ((val = gencomp(executor, a1[x1], a2[x2], sort_type)) >= 0) {
    if (val > 0) {
      x2++;
      if (x2 >= n2)
	break;
    }
    if (!val) {
      x1++;
      if (x1 >= n1) {
	mush_free((Malloc_t) a1, "ptrarray");
	mush_free((Malloc_t) a2, "ptrarray");
	return;
      }
    }
  }
  safe_str(a1[x1], buff, bp);
  do {
    x1++;
    if (x1 >= n1) {
      mush_free((Malloc_t) a1, "ptrarray");
      mush_free((Malloc_t) a2, "ptrarray");
      return;
    }
  } while (!gencomp(executor, a1[x1], a1[x1 - 1], sort_type));

  /* get values for the difference, until at least one list is empty */
  while (x2 < n2) {
    if ((val = gencomp(executor, a1[x1], a2[x2], sort_type)) < 0) {
      safe_strl(osep, osepl, buff, bp);
      safe_str(a1[x1], buff, bp);
    }
    if (val <= 0) {
      do {
	x1++;
	if (x1 >= n1) {
	  mush_free((Malloc_t) a1, "ptrarray");
	  mush_free((Malloc_t) a2, "ptrarray");
	  return;
	}
      } while (!gencomp(executor, a1[x1], a1[x1 - 1], sort_type));
    }
    if (val >= 0)
      x2++;
  }

  /* empty out remaining values, still removing duplicates */
  while (x1 < n1) {
    safe_strl(osep, osepl, buff, bp);
    safe_str(a1[x1], buff, bp);
    do {
      x1++;
    } while ((x1 < n1) && !gencomp(executor, a1[x1], a1[x1 - 1], sort_type));
  }
  mush_free((Malloc_t) a1, "ptrarray");
  mush_free((Malloc_t) a2, "ptrarray");
}

#define CACHE_SIZE 8  /**< Maximum size of the lnum cache */

/* ARGSUSED */
FUNCTION(fun_lnum)
{
  NVAL j;
  NVAL start;
  NVAL end;
  int istart, iend, k;
  char const *osep = " ";
  static NVAL cstart[CACHE_SIZE];
  static NVAL cend[CACHE_SIZE];
  static char csep[CACHE_SIZE][BUFFER_LEN];
  static char cresult[CACHE_SIZE][BUFFER_LEN];
  static int cpos;
  char *cp;

  if (!is_number(args[0])) {
    safe_str(T(e_num), buff, bp);
    return;
  }
  end = parse_number(args[0]);
  if (nargs > 1) {
    if (!is_number(args[1])) {
      safe_str(T(e_num), buff, bp);
      return;
    }
    start = end;
    end = parse_number(args[1]);
    if ((start == 0) && (end == 0)) {
      safe_str("0", buff, bp);	/* Special case - lnum(0,0) -> 0 */
      return;
    }
  } else {
    if (end == 0)
      return;			/* Special case - lnum(0) -> blank string */
    else if (end == 1) {
      safe_str("0", buff, bp);	/* Special case - lnum(1) -> 0 */
      return;
    }
    end--;
    if (end < 0) {
      safe_str(T("#-1 NUMBER OUT OF RANGE"), buff, bp);
      return;
    }
    start = 0;
  }
  if (nargs > 2) {
    osep = args[2];
  }
  for (k = 0; k < CACHE_SIZE; k++) {
    if (cstart[k] == start && cend[k] == end && !strcmp(csep[k], osep)) {
      safe_str(cresult[k], buff, bp);
      return;
    }
  }
  cpos = (cpos + 1) % CACHE_SIZE;
  cstart[cpos] = start;
  cend[cpos] = end;
  strcpy(csep[cpos], osep);
  cp = cresult[cpos];

  istart = (int) start;
  iend = (int) end;
  if (istart == start && iend == end) {
    safe_integer(istart, cresult[cpos], &cp);
    if (istart <= iend) {
      for (k = istart + 1; k <= iend; k++) {
	safe_str(osep, cresult[cpos], &cp);
	if (safe_integer(k, cresult[cpos], &cp))
	  break;
      }
    } else {
      for (k = istart - 1; k >= iend; k--) {
	safe_str(osep, cresult[cpos], &cp);
	if (safe_integer(k, cresult[cpos], &cp))
	  break;
      }
    }
  } else {
    safe_number(start, cresult[cpos], &cp);
    if (start <= end) {
      for (j = start + 1; j <= end; j++) {
	safe_str(osep, cresult[cpos], &cp);
	if (safe_number(j, cresult[cpos], &cp))
	  break;
      }
    } else {
      for (j = start - 1; j >= end; j--) {
	safe_str(osep, cresult[cpos], &cp);
	if (safe_number(j, cresult[cpos], &cp))
	  break;
      }
    }
  }
  *cp = '\0';

  safe_str(cresult[cpos], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_first)
{
  /* read first word from a string */

  char *p;
  char sep;

  if (!*args[0])
    return;

  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;

  p = trim_space_sep(args[0], sep);
  safe_str(split_token(&p, sep), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_randword)
{
  char *s, *r;
  char sep;
  int word_count, word_index;

  if (!*args[0])
    return;

  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;

  s = trim_space_sep(args[0], sep);
  word_count = do_wordcount(s, sep);
  word_index = get_random_long(0, word_count - 1);

  /* Go to the start of the token we're interested in. */
  while (word_index && s) {
    s = next_token(s, sep);
    word_index--;
  }

  if (!s || !*s)		/* ran off the end of the string */
    return;

  /* Chop off the end, and copy. No length checking needed. */
  r = s;
  if (s && *s)
    (void) split_token(&s, sep);
  safe_str(r, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_rest)
{
  char *p;
  char sep;

  if (!*args[0])
    return;

  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;

  p = trim_space_sep(args[0], sep);
  (void) split_token(&p, sep);
  safe_str(p, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_last)
{
  /* read last word from a string */

  char *p, *r;
  char sep;

  if (!*args[0])
    return;

  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;

  p = trim_space_sep(args[0], sep);
  if (!(r = strrchr(p, sep)))
    r = p;
  else
    r++;
  safe_str(r, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_grab)
{
  /* compares two strings with possible wildcards, returns the
   * word matched. Based on the 2.2 version of this function.
   */

  char *r, *s, sep;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  /* Walk the wordstring, until we find the word we want. */
  s = trim_space_sep(args[0], sep);
  do {
    r = split_token(&s, sep);
    if (quick_wild(args[1], r)) {
      safe_str(r, buff, bp);
      return;
    }
  } while (s);
}

/* ARGSUSED */
FUNCTION(fun_match)
{
  /* compares two strings with possible wildcards, returns the
   * word position of the match. Based on the 2.0 version of this
   * function.
   */

  char *s, *r;
  char sep;
  int wcount = 1;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  /* Walk the wordstring, until we find the word we want. */
  s = trim_space_sep(args[0], sep);
  do {
    r = split_token(&s, sep);
    if (quick_wild(args[1], r)) {
      safe_integer(wcount, buff, bp);
      return;
    }
    wcount++;
  } while (s);
  safe_chr('0', buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_wordpos)
{
  int charpos, i;
  char *cp, *tp, *xp;
  char sep;

  if (!is_integer(args[1])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  charpos = parse_integer(args[1]);
  cp = args[0];
  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if ((charpos <= 0) || ((size_t) charpos > strlen(cp))) {
    safe_str("#-1", buff, bp);
    return;
  }
  tp = cp + charpos - 1;
  cp = trim_space_sep(cp, sep);
  xp = split_token(&cp, sep);
  for (i = 1; xp; i++) {
    if (tp < (xp + strlen(xp)))
      break;
    xp = split_token(&cp, sep);
  }
  safe_integer(i, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_extract)
{
  char sep;
  int start, len;
  char *s, *r;

  if (!is_integer(args[1]) || !is_integer(args[2])) {
    safe_str(T(e_ints), buff, bp);
    return;
  }
  s = args[0];
  start = parse_integer(args[1]);
  len = parse_integer(args[2]);
  if (!delim_check(buff, bp, nargs, args, 4, &sep))
    return;

  if ((start < 1) || (len < 1))
    return;

  /* Go to the start of the token we're interested in. */
  start--;
  s = trim_space_sep(s, sep);
  while (start && s) {
    s = next_token(s, sep);
    start--;
  }

  if (!s || !*s)		/* ran off the end of the string */
    return;

  /* Find the end of the string that we want. */
  r = s;
  len--;
  while (len && s) {
    s = next_token(s, sep);
    len--;
  }

  /* Chop off the end, and copy. No length checking needed. */
  if (s && *s)
    (void) split_token(&s, sep);
  safe_str(r, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_cat)
{
  int i;

  safe_strl(args[0], arglens[0], buff, bp);
  for (i = 1; i < nargs; i++) {
    safe_chr(' ', buff, bp);
    safe_strl(args[i], arglens[i], buff, bp);
  }
}

/* ARGSUSED */
FUNCTION(fun_remove)
{
  char sep;

  /* zap word from string */

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;
  if (strchr(args[1], sep)) {
    safe_str(T("#-1 CAN ONLY DELETE ONE ELEMENT"), buff, bp);
    return;
  }
  safe_str(remove_word(args[0], args[1], sep), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_items)
{
  /* the equivalent of WORDS for an arbitrary separator */
  /* This differs from WORDS in its treatment of the space
   * separator.
   */

  char *s = args[0];
  char c = *args[1];
  int count = 1;

  if (c == '\0')
    c = ' ';

  while ((s = strchr(s, c))) {
    count++;
    s++;
  }

  safe_integer(count, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_element)
{
  /* the equivalent of MEMBER for an arbitrary separator */
  /* This differs from MEMBER in its use of quick_wild()
   * instead of strcmp().
   */

  char *s, *t;
  char c;
  int el;

  c = *args[2];

  if (c == '\0')
    c = ' ';
  if (strchr(args[1], c)) {
    safe_str(T("#-1 CAN ONLY TEST ONE ELEMENT"), buff, bp);
    return;
  }
  s = args[0];
  el = 1;

  do {
    t = s;
    s = seek_char(t, c);
    if (*s)
      *s++ = '\0';
    if (quick_wild(args[1], t)) {
      safe_integer(el, buff, bp);
      return;
    }
    el++;
  } while (*s);

  safe_chr('0', buff, bp);	/* no match */
}

/* ARGSUSED */
FUNCTION(fun_index)
{
  /* more or less the equivalent of EXTRACT for an arbitrary separator */
  /* This differs from EXTRACT in its handling of space separators. */

  int start, end;
  char c;
  char *s, *p;

  if (!is_integer(args[2]) || !is_integer(args[3])) {
    safe_str(T(e_ints), buff, bp);
    return;
  }
  s = args[0];
  c = *args[1];
  if (!c)
    c = ' ';

  start = parse_integer(args[2]);
  end = parse_integer(args[3]);

  if ((start < 1) || (end < 1) || (*s == '\0'))
    return;

  /* move s to the start of the item we want */
  while (--start) {
    if (!(s = strchr(s, c)))
      return;
    s++;
  }

  /* skip just spaces, not tabs or newlines, since people may MUSHcode things
   * like "%r%tPolgara %r%tDurnik %r%tJavelin"
   */
  while (*s == ' ')
    s++;
  if (!*s)
    return;

  /* now figure out where to end the string */
  p = s + 1;
  /* we may already be pointing to a separator */
  if (*s == c)
    end--;
  while (end--)
    if (!(p = strchr(p, c)))
      break;
    else
      p++;

  if (p)
    p--;
  else
    p = s + strlen(s);

  /* trim trailing spaces (just true spaces) */
  while ((p > s) && (p[-1] == ' '))
    p--;
  *p = '\0';

  safe_str(s, buff, bp);
}

/** Functions that operate on items - delete, replace, insert.
 * \param buff return buffer.
 * \param bp pointer to insertion point in buff.
 * \param str original string.
 * \param num string containing the element number to operate on.
 * \param word string to insert/delete/replace.
 * \param sep separator string.
 * \param flag operation to perform: 0 - delete, 1 - replace, 2 - insert
 */
static void
do_itemfuns(char *buff, char **bp, char *str, char *num, char *word,
	    char *sep, int flag)
{
  char c;
  int el, count, len = -1;
  char *sptr, *eptr;

  if (!is_integer(num)) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  el = parse_integer(num);

  /* figure out the separator character */
  if (sep && *sep)
    c = *sep;
  else
    c = ' ';

  /* we can't remove anything before the first position */
  if ((el < 1 && flag != 2) || el == 0) {
    safe_str(str, buff, bp);
    return;
  }
  if (el < 0) {
    sptr = str + strlen(str);
    eptr = sptr;
  } else {
    sptr = str;
    eptr = strchr(sptr, c);
  }
  count = 1;

  /* go to the correct item in the string */
  if (el < 0) {			/* if using insert() with a negative insertion param */
    /* count keeps track of the number of words from the right
     * of the string.  When count equals the correct position, then
     * sptr will point to the count'th word from the right, or
     * a null string if the  word being added will be at the end of
     * the string.
     * eptr is just a helper.  */
    for (len = strlen(str); len >= 0 && count < abs(el); len--, eptr--) {
      if (*eptr == c)
	count++;
      if (count == abs(el)) {
	sptr = eptr + 1;
	break;
      }
    }
  } else {
    /* Loop invariant: if sptr and eptr are not NULL, eptr points to
     * the count'th instance of c in str, and sptr is the beginning of
     * the count'th item. */
    while (eptr && (count < el)) {
      sptr = eptr + 1;
      eptr = strchr(sptr, c);
      count++;
    }
  }

  if ((!eptr || len < 0) && (count < abs(el))) {
    /* we've run off the end of the string without finding anything */
    safe_str(str, buff, bp);
    return;
  }
  /* now find the end of that element */
  if ((el < 0 && *eptr) || (el > 0 && sptr != str))
    sptr[-1] = '\0';

  switch (flag) {
  case 0:
    /* deletion */
    if (!eptr) {		/* last element in the string */
      if (el != 1)
	safe_str(str, buff, bp);
    } else if (sptr == str) {	/* first element in the string */
      eptr++;			/* chop leading separator */
      safe_str(eptr, buff, bp);
    } else {
      safe_str(str, buff, bp);
      safe_str(eptr, buff, bp);
    }
    break;
  case 1:
    /* replacing */
    if (!eptr) {		/* last element in string */
      if (el != 1) {
	safe_str(str, buff, bp);
	safe_chr(c, buff, bp);
      }
      safe_str(word, buff, bp);
    } else if (sptr == str) {	/* first element in string */
      safe_str(word, buff, bp);
      safe_str(eptr, buff, bp);
    } else {
      safe_str(str, buff, bp);
      safe_chr(c, buff, bp);
      safe_str(word, buff, bp);
      safe_str(eptr, buff, bp);
    }
    break;
  case 2:
    /* insertion */
    if (sptr == str) {		/* first element in string */
      safe_str(word, buff, bp);
      safe_chr(c, buff, bp);
      safe_str(str, buff, bp);
    } else {
      safe_str(str, buff, bp);
      safe_chr(c, buff, bp);
      safe_str(word, buff, bp);
      safe_chr(c, buff, bp);
      safe_str(sptr, buff, bp);
    }
    break;
  }
}


/* ARGSUSED */
FUNCTION(fun_ldelete)
{
  /* delete a word at position X of a list */

  do_itemfuns(buff, bp, args[0], args[1], NULL, args[2], 0);
}

/* ARGSUSED */
FUNCTION(fun_replace)
{
  /* replace a word at position X of a list */

  do_itemfuns(buff, bp, args[0], args[1], args[2], args[3], 1);
}

/* ARGSUSED */
FUNCTION(fun_insert)
{
  /* insert a word at position X of a list */

  do_itemfuns(buff, bp, args[0], args[1], args[2], args[3], 2);
}

/* ARGSUSED */
FUNCTION(fun_member)
{
  char *s, *t;
  char sep;
  int el;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (strchr(args[1], sep)) {
    safe_str(T("#-1 CAN ONLY TEST ONE ELEMENT"), buff, bp);
    return;
  }
  s = trim_space_sep(args[0], sep);
  el = 1;

  do {
    t = split_token(&s, sep);
    if (!strcmp(args[1], t)) {
      safe_integer(el, buff, bp);
      return;
    }
    el++;
  } while (s);

  safe_chr('0', buff, bp);	/* not found */
}

/* ARGSUSED */
FUNCTION(fun_before)
{
  char *p;

  if (!*args[1])
    p = strchr(args[0], ' ');
  else
    p = strstr(args[0], args[1]);
  if (p) {
    safe_strl(args[0], p - args[0], buff, bp);
  } else
    safe_strl(args[0], arglens[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_after)
{
  char *p;

  if (!*args[1]) {
    args[1][0] = ' ';
    args[1][1] = '\0';
    arglens[1] = 1;
  }
  p = strstr(args[0], args[1]);
  if (p)
    safe_str(p + arglens[1], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_revwords)
{
  char **words;
  int count;
  char sep;
  char *osep, osepd[2] = { '\0', '\0' };

  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;

  if (nargs == 3)
    osep = args[2];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  words = (char **) mush_malloc(sizeof(char *) * BUFFER_LEN, "wordlist");

  count = list2arr(words, BUFFER_LEN, args[0], sep);
  if (count == 0) {
    mush_free((Malloc_t) words, "wordlist");
    return;
  }

  safe_str(words[--count], buff, bp);
  while (count) {
    safe_str(osep, buff, bp);
    safe_str(words[--count], buff, bp);
  }
  mush_free((Malloc_t) words, "wordlist");
}

/* ARGSUSED */
FUNCTION(fun_words)
{
  char sep;

  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;
  safe_integer(do_wordcount(trim_space_sep(args[0], sep), sep), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_splice)
{
  /* like MERGE(), but does it for a word */

  char *s0, *s1, *s2;
  char *p0, *p1;
  char sep;

  if (!delim_check(buff, bp, nargs, args, 4, &sep))
    return;

  s0 = trim_space_sep(args[0], sep);
  s1 = trim_space_sep(args[1], sep);
  s2 = trim_space_sep(args[2], sep);

  /* length checks */
  if (!*args[2]) {
    safe_str(T("#-1 NEED A WORD"), buff, bp);
    return;
  }
  if (do_wordcount(s2, sep) != 1) {
    safe_str(T("#-1 TOO MANY WORDS"), buff, bp);
    return;
  }
  if (do_wordcount(s0, sep) != do_wordcount(s1, sep)) {
    safe_str(T("#-1 NUMBER OF WORDS MUST BE EQUAL"), buff, bp);
    return;
  }
  /* loop through the two lists */
  p0 = split_token(&s0, sep);
  p1 = split_token(&s1, sep);
  safe_str(strcmp(p0, s2) ? p0 : p1, buff, bp);
  while (s0) {
    p0 = split_token(&s0, sep);
    p1 = split_token(&s1, sep);
    safe_chr(sep, buff, bp);
    safe_str(strcmp(p0, s2) ? p0 : p1, buff, bp);
  }
}

/* ARGSUSED */
FUNCTION(fun_iter)
{
  /* Based on the TinyMUSH 2.0 code for this function. Please note that
   * arguments to this function are passed _unparsed_.
   */
  /* Actually, this code has changed so much that the above comment
   * isn't really true anymore. - Talek, 18 Oct 2000
   */

  char sep;
  char *outsep, *list;
  char *tbuf1, *tbuf2, *lp;
  char const *sp;
  int *place;
  int funccount;
  char *oldbp;
  const char *replace[2];


  if (inum >= MAX_ITERS) {
    safe_str(T("#-1 TOO MANY ITERS"), buff, bp);
    return;
  }

  if (nargs >= 3) {
    /* We have a delimiter. We've got to parse the third arg in place */
    char insep[BUFFER_LEN];
    char *isep = insep;
    const char *arg3 = args[2];
    process_expression(insep, &isep, &arg3, executor, caller, enactor,
		       PE_DEFAULT, PT_DEFAULT, pe_info);
    *isep = '\0';
    strcpy(args[2], insep);
  }
  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  outsep = (char *) mush_malloc(BUFFER_LEN, "string");
  list = (char *) mush_malloc(BUFFER_LEN, "string");
  if (!outsep || !list)
    mush_panic("Unable to allocate memory in fun_iter");
  if (nargs < 4)
    strcpy(outsep, " ");
  else {
    const char *arg4 = args[3];
    char *osep = outsep;
    process_expression(outsep, &osep, &arg4, executor, caller, enactor,
		       PE_DEFAULT, PT_DEFAULT, pe_info);
    *osep = '\0';
  }
  lp = list;
  sp = args[0];
  process_expression(list, &lp, &sp, executor, caller, enactor,
		     PE_DEFAULT, PT_DEFAULT, pe_info);
  *lp = '\0';
  lp = trim_space_sep(list, sep);
  if (!*lp) {
    mush_free((Malloc_t) outsep, "string");
    mush_free((Malloc_t) list, "string");
    return;
  }

  inum++;
  place = &iter_place[inum];
  *place = 0;
  funccount = pe_info->fun_invocations;
  oldbp = *bp;
  while (lp) {
    if (*place) {
      safe_str(outsep, buff, bp);
    }
    *place = *place + 1;
    iter_rep[inum] = tbuf1 = split_token(&lp, sep);
    replace[0] = tbuf1;
    replace[1] = unparse_integer(*place);
    tbuf2 = replace_string2(standard_tokens, replace, args[1]);
    sp = tbuf2;
    if (process_expression(buff, bp, &sp, executor, caller, enactor,
			   PE_DEFAULT, PT_DEFAULT, pe_info))
      break;
    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
      break;
    funccount = pe_info->fun_invocations;
    oldbp = *bp;
    mush_free((Malloc_t) tbuf2, "replace_string.buff");
  }
  *place = 0;
  iter_rep[inum] = NULL;
  inum--;
  mush_free((Malloc_t) outsep, "string");
  mush_free((Malloc_t) list, "string");
}

/* ARGSUSED */
FUNCTION(fun_ilev)
{
  safe_integer(inum - 1, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_itext)
{
  int i;

  if (!is_strict_integer(args[0])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  i = parse_integer(args[0]);

  if (i < 0 || i >= inum || (inum - i) <= inum_limit) {
    safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
    return;
  }

  safe_str(iter_rep[inum - i], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_inum)
{
  int i;

  if (!is_strict_integer(args[0])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  i = parse_integer(args[0]);

  if (i < 0 || i >= inum || (inum - i) <= inum_limit) {
    safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
    return;
  }

  safe_number(iter_place[inum - i], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_step)
{
  /* Like map, but passes up to 10 elements from the list at a time in %0-%9
   * If the attribute is not found, null is returned, NOT an error.
   * This function takes delimiters.
   */

  dbref thing;
  ATTR *attrib;
  char *preserve[10];
  char const *ap;
  char *asave, *lp;
  char sep;
  int n;
  int step;
  int funccount;
  char *oldbp;
  char *osep, osepd[2] = { '\0', '\0' };
  int pe_flags = PE_DEFAULT;

  if (!is_integer(args[2])) {
    safe_str(T(e_int), buff, bp);
    return;
  }

  step = parse_integer(args[2]);

  if (step < 1 || step > 10) {
    safe_str(T("#-1 STEP OUT OF RANGE"), buff, bp);
    return;
  }

  if (!delim_check(buff, bp, nargs, args, 4, &sep))
    return;

  if (nargs == 5)
    osep = args[4];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  lp = trim_space_sep(args[1], sep);
  if (!*lp)
    return;

  /* find our object and attribute */
  parse_anon_attrib(executor, args[0], &thing, &attrib);
  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (!CanEvalAttr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (AF_Debug(attrib))
    pe_flags |= PE_DEBUG;

  asave = safe_atr_value(attrib);

  /* save our stack */
  save_global_env("step", preserve);

  for (n = 0; n < step; n++) {
    global_eval_context.wenv[n] = split_token(&lp, sep);
    if (!lp) {
      n++;
      break;
    }
  }
  for (; n < 10; n++)
    global_eval_context.wenv[n] = NULL;

  ap = asave;
  process_expression(buff, bp, &ap, thing, executor, enactor,
		     pe_flags, PT_DEFAULT, pe_info);
  oldbp = *bp;
  funccount = pe_info->fun_invocations;
  while (lp) {
    safe_str(osep, buff, bp);
    for (n = 0; n < step; n++) {
      global_eval_context.wenv[n] = split_token(&lp, sep);
      if (!lp) {
	n++;
	break;
      }
    }
    for (; n < 10; n++)
      global_eval_context.wenv[n] = NULL;
    ap = asave;
    if (process_expression(buff, bp, &ap, thing, executor, enactor,
			   pe_flags, PT_DEFAULT, pe_info))
      break;
    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
      break;
    oldbp = *bp;
    funccount = pe_info->fun_invocations;
  }

  free((Malloc_t) asave);
  free_anon_attrib(attrib);
  restore_global_env("step", preserve);
}

/* ARGSUSED */
FUNCTION(fun_map)
{
  /* Like iter(), but calls an attribute with list elements as %0 instead.
   * If the attribute is not found, null is returned, NOT an error.
   * This function takes delimiters.
   */

  dbref thing;
  ATTR *attrib;
  char const *ap;
  char *asave, *lp;
  char *tptr[2];
  char place[16];
  int placenr = 1;
  char sep;
  int funccount;
  char *oldbp;
  char *osep, osepd[2] = { '\0', '\0' };
  int pe_flags = PE_DEFAULT;

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs == 4)
    osep = args[3];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  lp = trim_space_sep(args[1], sep);
  if (!*lp)
    return;

  /* find our object and attribute */
  parse_anon_attrib(executor, args[0], &thing, &attrib);
  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (!CanEvalAttr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (AF_Debug(attrib))
    pe_flags |= PE_DEBUG;

  strcpy(place, "1");
  asave = safe_atr_value(attrib);

  /* save our stack */
  tptr[0] = global_eval_context.wenv[0];
  tptr[1] = global_eval_context.wenv[1];
  global_eval_context.wenv[1] = place;

  global_eval_context.wenv[0] = split_token(&lp, sep);
  ap = asave;
  process_expression(buff, bp, &ap, thing, executor, enactor,
		     pe_flags, PT_DEFAULT, pe_info);
  oldbp = *bp;
  funccount = pe_info->fun_invocations;
  while (lp) {
    safe_str(osep, buff, bp);
    strcpy(place, unparse_integer(++placenr));
    global_eval_context.wenv[0] = split_token(&lp, sep);
    ap = asave;
    if (process_expression(buff, bp, &ap, thing, executor, enactor,
			   pe_flags, PT_DEFAULT, pe_info))
      break;
    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
      break;
    oldbp = *bp;
    funccount = pe_info->fun_invocations;
  }

  free((Malloc_t) asave);
  free_anon_attrib(attrib);
  global_eval_context.wenv[0] = tptr[0];
  global_eval_context.wenv[1] = tptr[1];
}


/* ARGSUSED */
FUNCTION(fun_mix)
{
  /* Like map(), but goes through lists, passing them as %0 and %1.. %9.
   * If the attribute is not found, null is returned, NOT an error.
   * This function takes delimiters.
   */

  dbref thing;
  ATTR *attrib;
  char const *ap;
  char *asave, *lp[10];
  char *tptr[10];
  char sep;
  int funccount;
  int n;
  int lists, words;
  char *oldbp;
  int pe_flags = PE_DEFAULT;

  if (nargs > 3) {		/* Last arg must be the delimiter */
    n = nargs;
    lists = nargs - 2;
  } else {
    n = 4;
    lists = 2;
  }

  if (!delim_check(buff, bp, nargs, args, n, &sep))
    return;

  for (n = 0; n < lists; n++)
    lp[n] = trim_space_sep(args[n + 1], sep);

  /* find our object and attribute */
  parse_anon_attrib(executor, args[0], &thing, &attrib);
  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (!CanEvalAttr(executor, thing, attrib)) {
    free_anon_attrib(attrib);
    return;
  }
  if (AF_Debug(attrib))
    pe_flags |= PE_DEBUG;

  asave = safe_atr_value(attrib);

  /* save our stack */
  save_global_env("fun_mix", tptr);

  words = 0;
  for (n = 0; n < 10; n++) {
    if ((n < lists) && lp[n] && *lp[n]) {
      global_eval_context.wenv[n] = split_token(&lp[n], sep);
      if (global_eval_context.wenv[n])
	words++;
    } else
      global_eval_context.wenv[n] = NULL;
  }
  if (words == 0) {
    restore_global_env("fun_mix", tptr);
    free((Malloc_t) asave);
    free_anon_attrib(attrib);
    return;
  }
  ap = asave;
  process_expression(buff, bp, &ap, thing, executor, enactor,
		     pe_flags, PT_DEFAULT, pe_info);
  oldbp = *bp;
  funccount = pe_info->fun_invocations;
  while (1) {
    words = 0;
    for (n = 0; n < 10; n++) {
      if ((n < lists) && lp[n] && *lp[n]) {
	global_eval_context.wenv[n] = split_token(&lp[n], sep);
	if (global_eval_context.wenv[n])
	  words++;
      } else
	global_eval_context.wenv[n] = NULL;
    }
    if (words == 0)
      break;
    safe_chr(sep, buff, bp);
    ap = asave;
    if (process_expression(buff, bp, &ap, thing, executor, enactor,
			   pe_flags, PT_DEFAULT, pe_info))
      break;
    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
      break;
    oldbp = *bp;
    funccount = pe_info->fun_invocations;
  }

  free((Malloc_t) asave);
  free_anon_attrib(attrib);
  restore_global_env("fun_mix", tptr);
}

/* ARGSUSED */
FUNCTION(fun_table)
{
  /* TABLE(list, field_width, line_length, delimiter, output sep)
   * Given a list, produce a table (a column'd list)
   * Optional parameters: field width, line length, delimiter, output sep
   * Number of columns = line length / (field width+1)
   */
  size_t line_length = 78;
  size_t field_width = 10;
  size_t col = 0;
  size_t offset, col_len;
  char sep, osep, *cp, *t;
  ansi_string *as;

  if (!delim_check(buff, bp, nargs, args, 5, &osep))
    return;
  if ((nargs == 5) && !*args[4])
    osep = '\0';

  if (!delim_check(buff, bp, nargs, args, 4, &sep))
    return;

  if (nargs > 2) {
    if (!is_integer(args[2])) {
      safe_str(T(e_ints), buff, bp);
      return;
    }
    line_length = parse_integer(args[2]);
    if (line_length < 2)
      line_length = 2;
  }
  if (nargs > 1) {
    if (!is_integer(args[1])) {
      safe_str(T(e_ints), buff, bp);
      return;
    }
    field_width = parse_integer(args[1]);
    if (field_width < 1)
      field_width = 1;
    if (field_width >= BUFFER_LEN)
      field_width = BUFFER_LEN - 1;
  }
  if (field_width >= line_length)
    field_width = line_length - 1;

  /* Split out each token, truncate/pad it to field_width, and pack
   * it onto the line. When the line would go over line_length,
   * send a return
   */

  as = parse_ansi_string(args[0]);

  cp = trim_space_sep(as->text, sep);
  if (!*cp) {
    free_ansi_string(as);
    return;
  }

  t = split_token(&cp, sep);
  offset = t - &as->text[0];
  col_len = strlen(t);
  if (col_len > field_width)
    col_len = field_width;
  safe_ansi_string(as, offset, col_len, buff, bp);
  if (safe_fill(' ', field_width - col_len, buff, bp)) {
    free_ansi_string(as);
    return;
  }
  col = field_width + (osep != '\0');
  while (cp) {
    col += field_width + (osep != '\0');
    if (col > line_length) {
      if (NEWLINE_ONE_CHAR)
	safe_str("\n", buff, bp);
      else
	safe_str("\r\n", buff, bp);
      col = field_width + !!osep;
    } else {
      if (osep)
	safe_chr(osep, buff, bp);
    }
    t = split_token(&cp, sep);
    if (!t)
      break;
    offset = t - &as->text[0];
    col_len = strlen(t);
    if (col_len > field_width)
      col_len = field_width;
    safe_ansi_string(as, offset, col_len, buff, bp);
    if (safe_fill(' ', field_width - col_len, buff, bp))
      break;
  }
  free_ansi_string(as);
}

/* In the following regexp functions, we use pcre_study to potentially
 * make pcre_exec faster. If pcre_study() can't help, it returns right
 * away, and if it can, the savings in the actual matching are usually
 * worth it.  Ideally, all compiled regexps and their study patterns
 * should be cached somewhere. Especially nice for patterns in the
 * master room. Just need to come up with a good caching algorithm to
 * use. Easiest would be a hashtable that's just cleared every
 * dbck_interval seconds. Except some benchmarking showed that compiling
 * patterns is faster than I thought it'd be, so this is low priority.
 */

/* string, regexp, replacement string. Acts like sed or perl's s///g,
 * with an ig version */
int re_subpatterns = -1;  /**< Number of subpatterns in regexp */
int *re_offsets;	  /**< Array of offsets to subpatterns */
char *re_from = NULL;	  /**< Pointer to last match position */
FUNCTION(fun_regreplace)
{
  pcre *re;
  pcre_extra *study = NULL;
  const char *errptr;
  int subpatterns;
  int offsets[99];
  int erroffset;
  const char *r, *obp;
  char *start, *oldbp;
  char tbuf[BUFFER_LEN], *tbp;
  char abuf[BUFFER_LEN], *abp;
  char prebuf[BUFFER_LEN], *prep;
  char postbuf[BUFFER_LEN], *postp;

  int flags = 0, all = 0, match_offset = 0, len, funccount;
  int i;

  if (called_as[strlen(called_as) - 1] == 'I')
    flags = PCRE_CASELESS;

  if (string_prefix(called_as, "REGEDITALL"))
    all = 1;

  abp = abuf;
  r = args[0];
  process_expression(abuf, &abp, &r, executor, caller, enactor, PE_DEFAULT,
		     PT_DEFAULT, pe_info);
  *abp = '\0';

  postp = postbuf;
  safe_str(abuf, postbuf, &postp);
  *postp = '\0';

  for (i = 1; i < nargs - 1; i += 2) {
    /* old postbuf is new prebuf */
    prep = prebuf;
    safe_str(postbuf, prebuf, &prep);
    *prep = '\0';
    postp = postbuf;
    *postp = '\0';
    tbp = tbuf;
    r = args[i];
    process_expression(tbuf, &tbp, &r, executor, caller, enactor, PE_DEFAULT,
		       PT_DEFAULT, pe_info);
    *tbp = '\0';

    if ((re = pcre_compile(tbuf, flags, &errptr, &erroffset, tables)) == NULL) {
      /* Matching error. */
      safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
      safe_str(errptr, buff, bp);
      return;
    }
    add_check("pcre");
    /* If we're going to match the pattern multiple times, let's
       study it. */
    if (all) {
      study = pcre_study(re, 0, &errptr);
      if (errptr != NULL) {
	mush_free((Malloc_t) re, "pcre");
	safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
	safe_str(errptr, buff, bp);
	return;
      }
      if (study != NULL)
	add_check("pcre.extra");
    }
    len = strlen(prebuf);
    start = prebuf;
    subpatterns = pcre_exec(re, study, prebuf, len, 0, 0, offsets, 99);

    /* Match wasn't found... we're done */
    if (subpatterns < 0) {
      safe_str(prebuf, postbuf, &postp);
      mush_free((Malloc_t) re, "pcre");
      if (study)
	mush_free((Malloc_t) study, "pcre.extra");
      continue;
    }

    funccount = pe_info->fun_invocations;
    oldbp = postp;

    do {
      /* Copy up to the start of the matched area */
      char tmp = prebuf[offsets[0]];
      prebuf[offsets[0]] = '\0';
      safe_str(start, postbuf, &postp);
      prebuf[offsets[0]] = tmp;

      /* Now copy in the replacement, putting in captured sub-expressions */
      obp = args[i + 1];
      re_from = prebuf;
      re_offsets = offsets;
      re_subpatterns = subpatterns;
      process_expression(postbuf, &postp, &obp, executor, caller, enactor,
			 PE_DEFAULT | PE_DOLLAR, PT_DEFAULT, pe_info);
      if ((*bp == (buff + BUFFER_LEN - 1))
	  && (pe_info->fun_invocations == funccount))
	break;

      oldbp = postp;
      funccount = pe_info->fun_invocations;

      start = prebuf + offsets[1];
      match_offset = offsets[1];
      /* Make sure we advance at least 1 char */
      if (offsets[0] == match_offset)
	match_offset++;
    } while (all && match_offset < len && (subpatterns =
					   pcre_exec(re, study, prebuf, len,
						     match_offset, 0, offsets,
						     99)) >= 0);


    /* Now copy everything after the matched bit */
    safe_str(start, postbuf, &postp);
    *postp = '\0';
    mush_free((Malloc_t) re, "pcre");
    if (study)
      mush_free((Malloc_t) study, "pcre.extra");

    re_offsets = NULL;
    re_subpatterns = -1;
    re_from = NULL;
  }

  safe_str(postbuf, buff, bp);
}

/** array of indexes for %q registers during regexp matching */
extern signed char qreg_indexes[UCHAR_MAX + 1];

FUNCTION(fun_regmatch)
{
/* ---------------------------------------------------------------------------
 * fun_regmatch Return 0 or 1 depending on whether or not a regular
 * expression matches a string. If a third argument is specified, dump
 * the results of a regexp pattern match into a set of r()-registers.
 *
 * regmatch(string, pattern, list of registers)
 * If the number of matches exceeds the registers, those bits are tossed
 * out.
 * If -1 is specified as a register number, the matching bit is tossed.
 * Therefore, if the list is "-1 0 3 5", the regexp $0 is tossed, and
 * the regexp $1, $2, and $3 become r(0), r(3), and r(5), respectively.
 *
 */
  int i, nqregs, curq;
  char *qregs[NUMQ];
  pcre *re;
  const char *errptr;
  int erroffset;
  int offsets[99];
  int subpatterns;
  int flags = 0;
  int qindex;

  if (strcmp(called_as, "REGMATCHI") == 0)
    flags = PCRE_CASELESS;

  if (nargs == 2) {		/* Don't care about saving sub expressions */
    safe_boolean(quick_regexp_match(args[1], args[0], flags ? 0 : 1), buff, bp);
    return;
  }

  if ((re = pcre_compile(args[1], flags, &errptr, &erroffset, tables)) == NULL) {
    /* Matching error. */
    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
    safe_str(errptr, buff, bp);
    return;
  }
  add_check("pcre");
  subpatterns = pcre_exec(re, NULL, args[0], arglens[0], 0, 0, offsets, 99);
  safe_integer(subpatterns >= 0, buff, bp);

  /* We need to parse the list of registers.  Anything that we don't parse
   * is assumed to be -1.  If we didn't match, or the match went wonky,
   * then set the register to empty.  Otherwise, fill the register with
   * the subexpression.
   */
  if (subpatterns == 0)
    subpatterns = 33;
  nqregs = list2arr(qregs, NUMQ, args[2], ' ');
  for (i = 0; i < nqregs; i++) {
    if (qregs[i] && qregs[i][0] && !qregs[i][1] &&
	((qindex = qreg_indexes[(unsigned char) qregs[i][0]]) != -1))
      curq = qindex;
    else
      curq = -1;
    if (curq < 0 || curq >= NUMQ)
      continue;
    if (subpatterns < 0)
      global_eval_context.renv[curq][0] = '\0';
    else
      pcre_copy_substring(args[0], offsets, subpatterns, i,
			  global_eval_context.renv[curq], BUFFER_LEN);
  }
  mush_free((Malloc_t) re, "pcre");
}


/** Structure to hold data for regrep */
struct regrep_data {
  pcre *re;		/**< Pointer to compiled regular expression */
  pcre_extra *study;	/**< Pointer to studied data about re */
  char *buff;		/**< Buffer to store regrep results */
  char **bp;		/**< Pointer to address of insertion point in buff */
  int first;		/**< Is this the first match or a later match? */
};

/* Like grep(), but using a regexp pattern. This same function handles
 *  both regrep and regrepi. */
FUNCTION(fun_regrep)
{
  struct regrep_data reharg;
  const char *errptr;
  int erroffset;
  int flags = 0;

  dbref it = match_thing(executor, args[0]);
  reharg.first = 0;
  if (it == NOTHING || it == AMBIGUOUS) {
    safe_str(T(e_notvis), buff, bp);
    return;
  }
  /* make sure there's an attribute and a pattern */
  if (!*args[1]) {
    safe_str(T("#-1 NO SUCH ATTRIBUTE"), buff, bp);
    return;
  }
  if (!*args[2]) {
    safe_str(T("#-1 INVALID GREP PATTERN"), buff, bp);
    return;
  }

  if (strcmp(called_as, "REGREPI") == 0)
    flags = PCRE_CASELESS;

  if ((reharg.re = pcre_compile(args[2], flags,
				&errptr, &erroffset, tables)) == NULL) {
    /* Matching error. */
    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
    safe_str(errptr, buff, bp);
    return;
  }
  add_check("pcre");

  reharg.study = pcre_study(reharg.re, 0, &errptr);
  if (errptr != NULL) {
    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
    safe_str(errptr, buff, bp);
    mush_free(reharg.re, "pcre");
    return;
  }
  if (reharg.study)
    add_check("pcre.extra");

  reharg.buff = buff;
  reharg.bp = bp;

  atr_iter_get(executor, it, args[1], 0, regrep_helper, (void *) &reharg);
  mush_free(reharg.re, "pcre");
  if (reharg.study)
    mush_free(reharg.study, "pcre.extra");
}

static int
regrep_helper(dbref who __attribute__ ((__unused__)),
	      dbref what __attribute__ ((__unused__)),
	      dbref parent __attribute__ ((__unused__)),
	      char const *name __attribute__ ((__unused__)),
	      ATTR *atr, void *args)
{
  struct regrep_data *reharg = args;
  char const *str;
  int offsets[99];

  str = atr_value(atr);
  if (pcre_exec(reharg->re, reharg->study, str, strlen(str), 0, 0, offsets, 99)
      >= 0) {
    if (reharg->first != 0)
      safe_chr(' ', reharg->buff, reharg->bp);
    else
      reharg->first = 1;
    safe_str(AL_NAME(atr), reharg->buff, reharg->bp);
    return 1;
  } else
    return 0;
}

/* Like grab, but with a regexp pattern. This same function handles
 *  regrab(), regraball(), and the case-insenstive versions. */
FUNCTION(fun_regrab)
{
  char *r, *s, *b, sep;
  pcre *re;
  pcre_extra *study;
  const char *errptr;
  int erroffset;
  int offsets[99];
  int flags = 0, all = 0;
  char *osep, osepd[2] = { '\0', '\0' };

  if (!delim_check(buff, bp, nargs, args, 3, &sep))
    return;

  if (nargs == 4)
    osep = args[3];
  else {
    osepd[0] = sep;
    osep = osepd;
  }

  s = trim_space_sep(args[0], sep);
  b = *bp;

  if (strrchr(called_as, 'I'))
    flags = PCRE_CASELESS;

  if (string_prefix(called_as, "REGRABALL"))
    all = 1;

  if ((re = pcre_compile(args[1], flags, &errptr, &erroffset, tables)) == NULL) {
    /* Matching error. */
    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
    safe_str(errptr, buff, bp);
    return;
  }
  add_check("pcre");

  study = pcre_study(re, 0, &errptr);
  if (errptr != NULL) {
    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
    safe_str(errptr, buff, bp);
    mush_free(re, "pcre");
    return;
  }
  if (study)
    add_check("pcre.extra");

  do {
    r = split_token(&s, sep);
    if (pcre_exec(re, study, r, strlen(r), 0, 0, offsets, 99) >= 0) {
      if (all && *bp != b)
	safe_str(osep, buff, bp);
      safe_str(r, buff, bp);
      if (!all)
	break;
    }
  } while (s);

  mush_free((Malloc_t) re, "pcre");
  if (study)
    mush_free((Malloc_t) study, "pcre.extra");
}