pennmush-1.8.3p3/game/data/
pennmush-1.8.3p3/game/log/
pennmush-1.8.3p3/game/save/
pennmush-1.8.3p3/game/txt/evt/
pennmush-1.8.3p3/game/txt/nws/
pennmush-1.8.3p3/po/
pennmush-1.8.3p3/win32/msvc.net/
pennmush-1.8.3p3/win32/msvc6/
/**
 * \file funstr.c
 *
 * \brief String functions for mushcode.
 *
 *
 */
#include "copyrite.h"

#include "config.h"
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <locale.h>
#include "conf.h"
#include "externs.h"
#include "ansi.h"
#include "case.h"
#include "match.h"
#include "parse.h"
#include "pueblo.h"
#include "attrib.h"
#include "flags.h"
#include "dbdefs.h"
#include "mushdb.h"
#include "htab.h"
#include "lock.h"
#include "sort.h"
#include "confmagic.h"


#ifdef WIN32
#pragma warning( disable : 4761)        /* NJG: disable warning re conversion */
#endif

#define MAX_COLS 32  /**< Maximum number of columns for align() */
static int wraplen(char *str, size_t maxlen);
static int align_one_line(char *buff, char **bp, int ncols,
                          int cols[MAX_COLS], int calign[MAX_COLS],
                          char *ptrs[MAX_COLS], ansi_string *as[MAX_COLS],
                          int linenum, char *fieldsep, int fslen, char *linesep,
                          int lslen, char filler);
static int comp_gencomp(dbref executor, char *left, char *right, char *type);
void init_pronouns(void);

extern signed char qreg_indexes[UCHAR_MAX + 1];

/** Return an indicator of a player's gender.
 * \param player player whose gender is to be checked.
 * \retval 0 neuter.
 * \retval 1 female.
 * \retval 2 male.
 * \retval 3 plural.
 */
int
get_gender(dbref player)
{
  ATTR *a;

  a = atr_get(player, "SEX");

  if (!a)
    return 0;

  switch (*atr_value(a)) {
  case 'T':
  case 't':
  case 'P':
  case 'p':
    return 3;
  case 'M':
  case 'm':
    return 2;
  case 'F':
  case 'f':
  case 'W':
  case 'w':
    return 1;
  default:
    return 0;
  }
}

char *subj[4];  /**< Subjective pronouns */
char *poss[4];  /**< Possessive pronouns */
char *obj[4];   /**< Objective pronouns */
char *absp[4];  /**< Absolute possessive pronouns */

/** Macro to set a pronoun entry based on whether we're translating or not */
#define SET_PRONOUN(p,v,u)  p = strdup((translate) ? (v) : (u))

/** Initialize the pronoun translation strings.
 * This function sets up the values of the arrays of subjective,
 * possessive, objective, and absolute possessive pronouns with
 * locale-appropriate values.
 */
void
init_pronouns(void)
{
  int translate = 0;
#if defined(HAS_SETLOCALE) && defined(LC_MESSAGES)
  char *loc;
  if ((loc = setlocale(LC_MESSAGES, NULL))) {
    if (strcmp(loc, "C") && strncmp(loc, "en", 2))
      translate = 1;
  }
#endif
  SET_PRONOUN(subj[0], T("pronoun:neuter,subjective"), "it");
  SET_PRONOUN(subj[1], T("pronoun:feminine,subjective"), "she");
  SET_PRONOUN(subj[2], T("pronoun:masculine,subjective"), "he");
  SET_PRONOUN(subj[3], T("pronoun:plural,subjective"), "they");
  SET_PRONOUN(poss[0], T("pronoun:neuter,possessive"), "its");
  SET_PRONOUN(poss[1], T("pronoun:feminine,possessive"), "her");
  SET_PRONOUN(poss[2], T("pronoun:masculine,possessive"), "his");
  SET_PRONOUN(poss[3], T("pronoun:plural,possessive"), "their");
  SET_PRONOUN(obj[0], T("pronoun:neuter,objective"), "it");
  SET_PRONOUN(obj[1], T("pronoun:feminine,objective"), "her");
  SET_PRONOUN(obj[2], T("pronoun:masculine,objective"), "him");
  SET_PRONOUN(obj[3], T("pronoun:plural,objective"), "them");
  SET_PRONOUN(absp[0], T("pronoun:neuter,absolute possessive"), "its");
  SET_PRONOUN(absp[1], T("pronoun:feminine,absolute possessive"), "hers");
  SET_PRONOUN(absp[2], T("pronoun:masculine,absolute possessive"), "his");
  SET_PRONOUN(absp[3], T("pronoun:plural,absolute possessive "), "theirs");
}

#undef SET_PRONOUN

/* ARGSUSED */
FUNCTION(fun_isword)
{
  /* is every character a letter? */
  char *p;
  if (!args[0] || !*args[0]) {
    safe_chr('0', buff, bp);
    return;
  }
  for (p = args[0]; *p; p++) {
    if (!isalpha((unsigned char) *p)) {
      safe_chr('0', buff, bp);
      return;
    }
  }
  safe_chr('1', buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_capstr)
{
  char *p;
  p = skip_leading_ansi(args[0]);
  if (!p) {
    safe_strl(args[0], arglens[0], buff, bp);
    return;
  } else if (p != args[0]) {
    char x = *p;
    *p = '\0';
    safe_strl(args[0], p - args[0], buff, bp);
    *p = x;
  }
  if (*p) {
    safe_chr(UPCASE(*p), buff, bp);
    p++;
  }
  if (*p)
    safe_str(p, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_art)
{
  /* checks a word and returns the appropriate article, "a" or "an" */
  char c;
  char *p = skip_leading_ansi(args[0]);

  if (!p) {
    safe_chr('a', buff, bp);
    return;
  }
  c = DOWNCASE(*p);
  if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
    safe_str("an", buff, bp);
  else
    safe_chr('a', buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_subj)
{
  dbref thing;

  thing = match_thing(executor, args[0]);
  if (thing == NOTHING) {
    safe_str(T(e_match), buff, bp);
    return;
  }
  safe_str(subj[get_gender(thing)], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_poss)
{
  dbref thing;

  thing = match_thing(executor, args[0]);
  if (thing == NOTHING) {
    safe_str(T(e_match), buff, bp);
    return;
  }
  safe_str(poss[get_gender(thing)], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_obj)
{
  dbref thing;

  thing = match_thing(executor, args[0]);
  if (thing == NOTHING) {
    safe_str(T(e_match), buff, bp);
    return;
  }
  safe_str(obj[get_gender(thing)], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_aposs)
{
  dbref thing;

  thing = match_thing(executor, args[0]);
  if (thing == NOTHING) {
    safe_str(T(e_match), buff, bp);
    return;
  }
  safe_str(absp[get_gender(thing)], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_alphamax)
{
  char amax[BUFFER_LEN];
  char *c;
  int j, m = 0;
  size_t len;

  c = remove_markup(args[0], &len);
  memcpy(amax, c, len);
  for (j = 1; j < nargs; j++) {
    c = remove_markup(args[j], &len);
    if (strcoll(amax, c) < 0) {
      memcpy(amax, c, len);
      m = j;
    }
  }
  safe_strl(args[m], arglens[m], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_alphamin)
{
  char amin[BUFFER_LEN];
  char *c;
  int j, m = 0;
  size_t len;

  c = remove_markup(args[0], &len);
  memcpy(amin, c, len);
  for (j = 1; j < nargs; j++) {
    c = remove_markup(args[j], &len);
    if (strcoll(amin, c) > 0) {
      memcpy(amin, c, len);
      m = j;
    }
  }
  safe_strl(args[m], arglens[m], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_strlen)
{
  safe_integer(ansi_strlen(args[0]), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_mid)
{
  ansi_string *as;
  int pos, len;

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

  as = parse_ansi_string(args[0]);
  pos = parse_integer(args[1]);
  len = parse_integer(args[2]);

  if (pos < 0) {
    safe_str(T(e_range), buff, bp);
    free_ansi_string(as);
    return;
  }

  if (len < 0) {
    pos = pos + len + 1;
    if (pos < 0)
      pos = 0;
    len = -len;
  }

  safe_ansi_string(as, pos, len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_left)
{
  int len;
  ansi_string *as;

  if (!is_integer(args[1])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  len = parse_integer(args[1]);

  if (len < 0) {
    safe_str(T(e_range), buff, bp);
    return;
  }

  as = parse_ansi_string(args[0]);
  safe_ansi_string(as, 0, len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_right)
{
  int len;
  ansi_string *as;

  if (!is_integer(args[1])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  len = parse_integer(args[1]);

  if (len < 0) {
    safe_str(T(e_range), buff, bp);
    return;
  }

  as = parse_ansi_string(args[0]);
  if (len > as->len)
    safe_strl(args[0], arglens[0], buff, bp);
  else
    safe_ansi_string(as, as->len - len, len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_strinsert)
{
  /* Insert a string into another */
  ansi_string *dst, *src;
  int pos;

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

  pos = parse_integer(args[1]);
  if (pos < 0) {
    safe_str(T(e_range), buff, bp);
    return;
  }

  dst = parse_ansi_string(args[0]);
  if (pos > dst->len) {
    safe_strl(args[0], arglens[0], buff, bp);
    safe_strl(args[2], arglens[0], buff, bp);
    free_ansi_string(dst);
    return;
  }

  src = parse_ansi_string(args[2]);

  ansi_string_insert(dst, pos, src, 0, src->len);

  safe_ansi_string(dst, 0, dst->len, buff, bp);

  free_ansi_string(dst);
  free_ansi_string(src);
}

/* ARGSUSED */
FUNCTION(fun_delete)
{
  ansi_string *as;
  int pos, pos2, num;

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

  pos = parse_integer(args[1]);
  num = parse_integer(args[2]);

  if (pos < 0) {
    safe_str(T(e_range), buff, bp);
    return;
  }

  as = parse_ansi_string(args[0]);

  if (pos > as->len || num == 0) {
    safe_strl(args[0], arglens[0], buff, bp);
    free_ansi_string(as);
    return;
  }

  if (num < 0) {
    pos2 = pos + 1;
    pos = pos2 + num;
    if (pos < 0)
      pos = 0;
  } else
    pos2 = pos + num;

  ansi_string_delete(as, pos, num);
  safe_ansi_string(as, 0, as->len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_strreplace)
{
  ansi_string *dst, *src;
  int start, len;

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

  start = parse_integer(args[1]);
  len = parse_integer(args[2]);

  if (start < 0 || len < 0) {
    safe_str(T(e_range), buff, bp);
    return;
  }

  dst = parse_ansi_string(args[0]);
  src = parse_ansi_string(args[3]);

  ansi_string_delete(dst, start, len);
  ansi_string_insert(dst, start, src, 0, src->len);
  safe_ansi_string(dst, 0, dst->len, buff, bp);
  free_ansi_string(dst);
  free_ansi_string(src);
}

static int
comp_gencomp(dbref executor, char *left, char *right, char *type)
{
  int c;
  c = gencomp(executor, left, right, type);
  return (c > 0 ? 1 : (c < 0 ? -1 : 0));
}

/* ARGSUSED */
FUNCTION(fun_comp)
{
  char type = 'A';

  if (nargs == 3 && !(args[2] && *args[2])) {
    safe_str(T("#-1 INVALID THIRD ARGUMENT"), buff, bp);
    return;
  } else if (nargs == 3) {
    type = UPCASE(*args[2]);
  }

  switch (type) {
  case 'A':                    /* Case-sensitive lexicographic */
    {
      char left[BUFFER_LEN], right[BUFFER_LEN], *l, *r;
      size_t llen, rlen;
      l = remove_markup(args[0], &llen);
      memcpy(left, l, llen);
      r = remove_markup(args[1], &rlen);
      memcpy(right, r, rlen);
      safe_integer(comp_gencomp(executor, left, right, ALPHANUM_LIST), buff,
                   bp);
      return;
    }
  case 'I':                    /* Case-insensitive lexicographic */
    {
      char left[BUFFER_LEN], right[BUFFER_LEN], *l, *r;
      size_t llen, rlen;
      l = remove_markup(args[0], &llen);
      memcpy(left, l, llen);
      r = remove_markup(args[1], &rlen);
      memcpy(right, r, rlen);
      safe_integer(comp_gencomp(executor, left, right, INSENS_ALPHANUM_LIST),
                   buff, bp);
      return;
    }
  case 'N':                    /* Integers */
    if (!is_strict_integer(args[0]) || !is_strict_integer(args[1])) {
      safe_str(T(e_ints), buff, bp);
      return;
    }
    safe_integer(comp_gencomp(executor, args[0], args[1], NUMERIC_LIST), buff,
                 bp);
    return;
  case 'F':
    if (!is_strict_number(args[0]) || !is_strict_number(args[1])) {
      safe_str(T(e_nums), buff, bp);
      return;
    }
    safe_integer(comp_gencomp(executor, args[0], args[1], FLOAT_LIST), buff,
                 bp);
    return;
  case 'D':
    {
      dbref a, b;
      a = parse_objid(args[0]);
      b = parse_objid(args[1]);
      if (a == NOTHING || b == NOTHING) {
        safe_str(T("#-1 INVALID DBREF"), buff, bp);
        return;
      }
      safe_integer(comp_gencomp(executor, args[0], args[1], DBREF_LIST), buff,
                   bp);
      return;
    }
  default:
    safe_str(T("#-1 INVALID THIRD ARGUMENT"), buff, bp);
    return;
  }
}

/* ARGSUSED */
FUNCTION(fun_pos)
{
  char tbuf[BUFFER_LEN];
  char *pos;
  size_t len;

  pos = remove_markup(args[1], &len);
  memcpy(tbuf, pos, len);
  pos = strstr(tbuf, remove_markup(args[0], NULL));
  if (pos)
    safe_integer(pos - tbuf + 1, buff, bp);
  else
    safe_str("#-1", buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_lpos)
{
  char *pos;
  char c = ' ';
  size_t n, len;
  int first = 1;

  if (args[1][0])
    c = remove_markup(args[1], &len)[0];

  pos = remove_markup(args[0], &len);
  for (n = 0; n < len; n++)
    if (pos[n] == c) {
      if (first)
        first = 0;
      else
        safe_chr(' ', buff, bp);
      safe_integer(n, buff, bp);
    }
}


/* ARGSUSED */
FUNCTION(fun_strmatch)
{
  char tbuf[BUFFER_LEN];
  char *ret[36];
  char *t;
  size_t len;
  int matches;
  int i;
  char *qregs[NUMQ];
  int nqregs;
  int qindex;
  char match_space[BUFFER_LEN * 2];
  ssize_t match_space_len = BUFFER_LEN * 2;

  /* matches a wildcard pattern for an _entire_ string */

  t = remove_markup(args[0], &len);
  memcpy(tbuf, t, len);
  memset(ret, 0, 36);
  matches = wild_match_case_r(remove_markup(args[1], NULL), tbuf, 0, ret,
                              NUMQ, match_space, match_space_len);
  safe_boolean(matches, buff, bp);

  if (nargs > 2) {
    /* Now, assign the captures if desired */
    nqregs = list2arr(qregs, NUMQ, args[2], ' ');

    for (i = 0; i < nqregs; i++) {
      if (ret[i] && qregs[i][0] && !qregs[i][1]) {
        qindex = qreg_indexes[(unsigned char) qregs[i][0]];
        if (qindex >= 0 && global_eval_context.renv[qindex]) {
          strcpy(global_eval_context.renv[qindex], ret[i]);
        }
      }
    }
  }
}

/* ARGSUSED */
FUNCTION(fun_strcat)
{
  int j;

  for (j = 0; j < nargs; j++)
    safe_strl(args[j], arglens[j], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_flip)
{
  ansi_string *as;
  as = parse_ansi_string(args[0]);
  flip_ansi_string(as);
  safe_ansi_string(as, 0, as->len, buff, bp);
  free_ansi_string(as);
}


/* ARGSUSED */
FUNCTION(fun_merge)
{
  /* given s1, s2, and a list of characters, for each character in s1,
   * if the char is in the list, replace it with the corresponding
   * char in s2.
   */

  int i, j;
  size_t len;
  char *ptr = args[0];
  char matched[UCHAR_MAX + 1];
  ansi_string *as;

  memset(matched, 0, sizeof matched);

  /* find the characters to look for */
  if (!args[2] || !*args[2])
    matched[(unsigned char) ' '] = 1;
  else {
    unsigned char *p;
    for (p = (unsigned char *) remove_markup(args[2], &len); p && *p; p++)
      matched[*p] = 1;
  }

  as = parse_ansi_string(args[1]);

  /* do length checks first */
  if (as->len != ansi_strlen(ptr)) {
    safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
    free_ansi_string(as);
    return;
  }

  /* walk strings, copy from the appropriate string */
  i = 0;
  ptr = args[0];
  while (*ptr) {
    switch (*ptr) {
    case ESC_CHAR:
      while (*ptr && *ptr != 'm') {
        safe_chr(*(ptr++), buff, bp);
      }
      safe_chr(*(ptr++), buff, bp);
      break;
    case TAG_START:
      while (*ptr && *ptr != TAG_END) {
        safe_chr(*(ptr++), buff, bp);
      }
      safe_chr(*(ptr++), buff, bp);
      break;
    default:
      if (matched[(unsigned char) *ptr]) {
        j = 0;
        while (*ptr && matched[(unsigned char) *ptr]) {
          ptr++;
          j++;
          switch (*ptr) {
          case ESC_CHAR:
            safe_ansi_string(as, i, j, buff, bp);
            i += j;
            j = 0;
            while (*ptr && *ptr != 'm')
              safe_chr(*(ptr++), buff, bp);
            break;
          case TAG_START:
            safe_ansi_string(as, i, j, buff, bp);
            i += j;
            j = 0;
            while (*ptr && *ptr != TAG_END)
              safe_chr(*(ptr++), buff, bp);
            break;
          }
        }
        if (j != 0) {
          safe_ansi_string(as, i, j, buff, bp);
          i += j;
        }
      } else {
        i++;
        safe_chr(*(ptr++), buff, bp);
      }
    }
  }
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_tr)
{
  /* given str, s1, s2, for each character in str, if the char
   * is in s1, replace it with the char at the same index in s2.
   */

  char charmap[256];
  char instr[BUFFER_LEN], outstr[BUFFER_LEN];
  char *ip, *op;
  size_t i, len;
  unsigned char cur, dest;
  char *c;
  ansi_string *as;

  /* Initialize */
  for (i = 0; i < 256; i++) {
    charmap[i] = (char) i;
  }

#define goodchr(x) (isprint(x) || (x == '\n'))
  /* Convert ranges in input string, and check that
   * we don't receive a nonprinting char such as
   * beep() */
  ip = instr;
  c = remove_markup(args[1], NULL);
  while (*c) {
    cur = (unsigned char) *c;
    if (!goodchr(cur)) {
      safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
      return;
    }
    /* Tack it onto the string */
    /* Do we have a range? */
    if (*(c + 1) && *(c + 1) == '-' && *(c + 2)) {
      dest = (unsigned char) *(c + 2);
      if (!goodchr(dest)) {
        safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
        return;
      }
      if (dest > cur) {
        for (; cur <= dest; cur++) {
          if (goodchr(cur))
            safe_chr((char) cur, instr, &ip);
        }
      } else {
        for (; cur >= dest; cur--) {
          if (goodchr(cur))
            safe_chr((char) cur, instr, &ip);
        }
      }
      c += 3;
    } else {
      safe_chr((char) cur, instr, &ip);
      c++;
    }
  }
  *ip = '\0';

  /* Convert ranges in output string, and check that
   * we don't receive a nonprinting char such as
   * beep() */
  op = outstr;
  c = remove_markup(args[2], NULL);
  while (*c) {
    cur = (unsigned char) *c;
    if (!goodchr(cur)) {
      safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
      return;
    }
    /* Tack it onto the string */
    /* Do we have a range? */
    if (*(c + 1) && *(c + 1) == '-' && *(c + 2)) {
      dest = (unsigned char) *(c + 2);
      if (!goodchr(dest)) {
        safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
        return;
      }
      if (dest > cur) {
        for (; cur <= dest; cur++) {
          if (goodchr(cur))
            safe_chr((char) cur, outstr, &op);
        }
      } else {
        for (; cur >= dest; cur--) {
          if (goodchr(cur))
            safe_chr((char) cur, outstr, &op);
        }
      }
      c += 3;
    } else {
      safe_chr((char) cur, outstr, &op);
      c++;
    }
  }
  *op = '\0';
#undef goodchr

  if ((ip - instr) != (op - outstr)) {
    safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
    return;
  }

  len = ip - instr;

  for (i = 0; i < len; i++)
    charmap[(unsigned char) instr[i]] = outstr[i];

  /* walk the string, translating characters */
  as = parse_ansi_string(args[0]);
  len = as->len;
  for (i = 0; i < len; i++) {
    as->text[i] = charmap[(unsigned char) as->text[i]];
  }
  safe_ansi_string(as, 0, as->len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_lcstr)
{
  char *p, *y;
  p = args[0];
  while (*p) {
    y = skip_leading_ansi(p);
    if (y != p) {
      char t;
      t = *y;
      *y = '\0';
      safe_str(p, buff, bp);
      *y = t;
      p = y;
    }
    if (*p) {
      safe_chr(DOWNCASE(*p), buff, bp);
      p++;
    }
  }
}

/* ARGSUSED */
FUNCTION(fun_ucstr)
{
  char *p, *y;
  p = args[0];
  while (*p) {
    y = skip_leading_ansi(p);
    if (y != p) {
      char t;
      t = *y;
      *y = '\0';
      safe_str(p, buff, bp);
      *y = t;
      p = y;
    }
    if (*p) {
      safe_chr(UPCASE(*p), buff, bp);
      p++;
    }
  }
}

/* ARGSUSED */
FUNCTION(fun_repeat)
{
  int times;
  char *ap;

  if (!is_integer(args[1])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  times = parse_integer(args[1]);
  if (times < 0) {
    safe_str(T("#-1 ARGUMENT MUST BE NON-NEGATIVE INTEGER"), buff, bp);
    return;
  }
  if (!*args[0])
    return;

  /* Special-case repeating one character */
  if (arglens[0] == 1) {
    safe_fill(args[0][0], times, buff, bp);
    return;
  }

  /* Do the repeat in O(lg n) time. */
  /* This takes advantage of the fact that we're given a BUFFER_LEN
   * buffer for args[0] that we are free to trash.  Huzzah! */
  ap = args[0] + arglens[0];
  while (times) {
    if (times & 1) {
      if (safe_strl(args[0], arglens[0], buff, bp) != 0)
        break;
    }
    safe_str(args[0], args[0], &ap);
    *ap = '\0';
    arglens[0] = strlen(args[0]);
    times = times >> 1;
  }
}

/* ARGSUSED */
FUNCTION(fun_scramble)
{
  int n, i, j;
  ansi_string *as;
  ansi_string *dst;
  int pos[BUFFER_LEN];
  char tmp[BUFFER_LEN];

  if (!*args[0])
    return;

  /* Set up the new ansi_string */
  memset(tmp, 0, BUFFER_LEN);
  dst = parse_ansi_string(tmp);

  /* Read the current one */
  as = parse_ansi_string(args[0]);

  for (i = 0; i < as->len; i++)
    pos[i] = i;

  n = as->len;
  for (i = 0; i < n; i++) {
    int t;
    j = get_random_long(0, n - 1);
    t = pos[i];
    pos[i] = pos[j];
    pos[j] = t;
  }

  for (i = 0; i < n; i++) {
    ansi_string_insert(dst, dst->len, as, pos[i], 1);
    if ((i % 100) == 99) {
      optimize_ansi_string(dst);
    }
  }
  free_ansi_string(as);

  /* Now optimize the ansi string */
  safe_ansi_string(dst, 0, dst->len, buff, bp);
  free_ansi_string(dst);
}

/* ARGSUSED */
FUNCTION(fun_ljust)
{
  /* pads a string with trailing blanks (or other fill character) */

  size_t spaces, len;
  char sep;

  if (!is_uinteger(args[1])) {
    safe_str(T(e_uint), buff, bp);
    return;
  }
  len = ansi_strlen(args[0]);
  spaces = parse_uinteger(args[1]);
  if (spaces >= BUFFER_LEN)
    spaces = BUFFER_LEN - 1;

  if (len >= spaces) {
    safe_strl(args[0], arglens[0], buff, bp);
    return;
  }
  spaces -= len;

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

  safe_strl(args[0], arglens[0], buff, bp);
  safe_fill(sep, spaces, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_rjust)
{
  /* pads a string with leading blanks (or other fill character) */

  size_t spaces, len;
  char sep;

  if (!is_uinteger(args[1])) {
    safe_str(T(e_uint), buff, bp);
    return;
  }
  len = ansi_strlen(args[0]);
  spaces = parse_uinteger(args[1]);
  if (spaces >= BUFFER_LEN)
    spaces = BUFFER_LEN - 1;

  if (len >= spaces) {
    safe_strl(args[0], arglens[0], buff, bp);
    return;
  }
  spaces -= len;

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

  safe_fill(sep, spaces, buff, bp);
  safe_strl(args[0], arglens[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_center)
{
  /* pads a string with leading blanks (or other fill string) */
  size_t width, len, lsp, rsp, filllen;
  int fillq, fillr, i;
  char fillstr[BUFFER_LEN], *fp;
  ansi_string *as;

  if (!is_uinteger(args[1])) {
    safe_str(T(e_uint), buff, bp);
    return;
  }
  width = parse_uinteger(args[1]);
  len = ansi_strlen(args[0]);
  if (len >= width) {
    safe_strl(args[0], arglens[0], buff, bp);
    return;
  }
  lsp = rsp = (width - len) / 2;
  rsp += (width - len) % 2;
  if (lsp >= BUFFER_LEN)
    lsp = rsp = BUFFER_LEN - 1;

  if ((!args[2] || !*args[2]) && (!args[3] || !*args[3])) {
    /* Fast case for default fill with spaces */
    safe_fill(' ', lsp, buff, bp);
    safe_strl(args[0], arglens[0], buff, bp);
    safe_fill(' ', rsp, buff, bp);
    return;
  }

  /* args[2] contains the possibly ansi, multi-char fill string */
  filllen = ansi_strlen(args[2]);
  if (!filllen) {
    safe_str(T("#-1 FILL ARGUMENT MAY NOT BE ZERO-LENGTH"), buff, bp);
    return;
  }
  as = parse_ansi_string(args[2]);
  fillq = lsp / filllen;
  fillr = lsp % filllen;
  fp = fillstr;
  for (i = 0; i < fillq; i++)
    safe_ansi_string(as, 0, as->len, fillstr, &fp);
  safe_ansi_string(as, 0, fillr, fillstr, &fp);
  *fp = '\0';
  free_ansi_string(as);
  safe_str(fillstr, buff, bp);
  safe_strl(args[0], arglens[0], buff, bp);
  /* If we have args[3], that's the right-side fill string */
  if (nargs > 3) {
    if (args[3] && *args[3]) {
      filllen = ansi_strlen(args[3]);
      if (!filllen) {
        safe_str(T("#-1 FILL ARGUMENT MAY NOT BE ZERO-LENGTH"), buff, bp);
        return;
      }
      as = parse_ansi_string(args[3]);
      fillq = rsp / filllen;
      fillr = rsp % filllen;
      fp = fillstr;
      for (i = 0; i < fillq; i++)
        safe_ansi_string(as, 0, as->len, fillstr, &fp);
      safe_ansi_string(as, 0, fillr, fillstr, &fp);
      *fp = '\0';
      free_ansi_string(as);
      safe_str(fillstr, buff, bp);
    } else {
      /* Null args[3], fill right side with spaces */
      safe_fill(' ', rsp, buff, bp);
    }
    return;
  }
  /* No args[3], so we flip args[2] */
  filllen = ansi_strlen(args[2]);
  as = parse_ansi_string(args[2]);
  fillq = rsp / filllen;
  fillr = rsp % filllen;
  fp = fillstr;
  for (i = 0; i < fillq; i++)
    safe_ansi_string(as, 0, as->len, fillstr, &fp);
  safe_ansi_string(as, 0, fillr, fillstr, &fp);
  *fp = '\0';
  free_ansi_string(as);
  as = parse_ansi_string(fillstr);
  flip_ansi_string(as);
  safe_ansi_string(as, 0, as->len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_foreach)
{
  /* Like map(), but it operates on a string, rather than on a list,
   * calling a user-defined function for each character in the string.
   * No delimiter is inserted between the results.
   */

  dbref thing;
  ATTR *attrib;
  const char *ap;
  char *lp;
  char *asave, cbuf[2];
  char *tptr[2];
  char place[SBUF_LEN];
  int placenr = 0;
  int funccount;
  char *oldbp;
  char start, end;
  char letters[BUFFER_LEN];
  size_t len;

  if (nargs >= 3) {
    if (!delim_check(buff, bp, nargs, args, 3, &start))
      return;
  }

  if (nargs == 4) {
    if (!delim_check(buff, bp, nargs, args, 4, &end))
      return;
  } else {
    end = '\0';
  }

  /* 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;
  }
  strcpy(place, "0");
  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;

  ap = remove_markup(args[1], &len);
  memcpy(letters, ap, len);

  lp = trim_space_sep(letters, ' ');
  if (nargs >= 3) {
    char *tmp = strchr(lp, start);

    if (!tmp) {
      safe_str(lp, buff, bp);
      free((Malloc_t) asave);
      free_anon_attrib(attrib);
      global_eval_context.wenv[1] = tptr[1];
      return;
    }
    oldbp = place;
    placenr = (tmp + 1) - lp;
    safe_integer_sbuf(placenr, place, &oldbp);
    oldbp = *bp;

    *tmp = '\0';
    safe_str(lp, buff, bp);
    lp = tmp + 1;
  }

  cbuf[1] = '\0';
  global_eval_context.wenv[0] = cbuf;
  oldbp = *bp;
  funccount = pe_info->fun_invocations;
  while (*lp && *lp != end) {
    *cbuf = *lp++;
    ap = asave;
    if (process_expression(buff, bp, &ap, thing, executor, enactor,
                           PE_DEFAULT, PT_DEFAULT, pe_info))
      break;
    if (*bp == oldbp && pe_info->fun_invocations == funccount)
      break;
    oldbp = place;
    safe_integer_sbuf(++placenr, place, &oldbp);
    *oldbp = '\0';
    oldbp = *bp;
    funccount = pe_info->fun_invocations;
  }
  if (*lp)
    safe_str(lp + 1, buff, bp);
  free((Malloc_t) asave);
  free_anon_attrib(attrib);
  global_eval_context.wenv[0] = tptr[0];
  global_eval_context.wenv[1] = tptr[1];
}

extern char escaped_chars[UCHAR_MAX + 1];

/* ARGSUSED */
FUNCTION(fun_decompose)
{
  /* This function simply returns a decompose'd version of
   * the included string, such that
   * s(decompose(str)) == str, down to the last space, tab,
   * and newline. Except for ansi. */
  safe_str(decompose_str(args[0]), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_secure)
{
  /* this function smashes all occurences of "unsafe" characters in a string.
   * "unsafe" characters are defined by the escaped_chars table.
   * these characters get replaced by spaces
   */
  unsigned char *p;

  for (p = (unsigned char *) args[0]; *p; p++)
    if (escaped_chars[*p])
      *p = ' ';

  safe_strl(args[0], arglens[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_escape)
{
  unsigned char *s;

  if (arglens[0]) {
    safe_chr('\\', buff, bp);
    for (s = (unsigned char *) args[0]; *s; s++) {
      if ((s != (unsigned char *) args[0]) && escaped_chars[*s])
        safe_chr('\\', buff, bp);
      safe_chr((char) *s, buff, bp);
    }
  }
}

/* ARGSUSED */
FUNCTION(fun_trim)
{
  /* Similar to squish() but it doesn't trim spaces in the center, and
   * takes a delimiter argument and trim style.
   */

  char sep;
  int trim;
  int trim_style_arg, trim_char_arg;
  ansi_string *as;
  int i;

  /* Alas, PennMUSH and TinyMUSH used different orders for the arguments.
   * We'll give the users an option about it
   */
  if (!strcmp(called_as, "TRIMTINY")) {
    trim_style_arg = 2;
    trim_char_arg = 3;
  } else if (!strcmp(called_as, "TRIMPENN")) {
    trim_style_arg = 3;
    trim_char_arg = 2;
  } else if (TINY_TRIM_FUN) {
    trim_style_arg = 2;
    trim_char_arg = 3;
  } else {
    trim_style_arg = 3;
    trim_char_arg = 2;
  }

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

  /* If a trim style is provided, it must be the third argument. */
  if (nargs >= trim_style_arg) {
    switch (DOWNCASE(*args[trim_style_arg - 1])) {
    case 'l':
      trim = 1;
      break;
    case 'r':
      trim = 2;
      break;
    default:
      trim = 3;
      break;
    }
  } else
    trim = 3;

  /* We will never need to check for buffer length overrunning, since
   * we will always get a smaller string. Thus, we can copy at the
   * same time we skip stuff.
   */

  /* If necessary, skip over the leading stuff. */
  as = parse_ansi_string(args[0]);
  if (trim != 2) {
    for (i = 0; i < as->len; i++) {
      if (as->text[i] != sep)
        break;
      as->text[i] = '\0';
    }
  }
  /* Cut off the trailing stuff, if appropriate. */
  if ((trim != 1)) {
    for (i = as->len - 1; i >= 0; i--) {
      if (as->text[i] != sep)
        break;
      as->text[i] = '\0';
    }
  }
  safe_ansi_string(as, 0, as->len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_lit)
{
  /* Just returns the argument, literally */
  safe_strl(args[0], arglens[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_squish)
{
  /* zaps leading and trailing spaces, and reduces other spaces to a single
   * space. This only applies to the literal space character, and not to
   * tabs, newlines, etc.
   * We do not need to check for buffer length overflows, since we're
   * never going to end up with a longer string.
   */

  int i;
  char sep;
  int insep = 1;
  ansi_string *as;

  /* Figure out the character to squish */
  if (!delim_check(buff, bp, nargs, args, 2, &sep))
    return;

  as = parse_ansi_string(args[0]);

  /* get rid of trailing spaces first, so we don't have to worry about
   * them later.
   */
  for (i = as->len - 1; i >= 0; i--) {
    if (as->text[i] == sep)
      as->text[i] = '\0';
    else
      break;
  }
  /* Now trim leading and sequences */
  for (i = 0; i < as->len; i++) {
    if (as->text[i] == sep) {
      if (insep)
        as->text[i] = '\0';
      insep = 1;
    } else {
      insep = 0;
    }
  }
  safe_ansi_string(as, 0, as->len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_space)
{
  int s;

  if (!is_uinteger(args[0])) {
    safe_str(T(e_uint), buff, bp);
    return;
  }
  s = parse_integer(args[0]);
  safe_fill(' ', s, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_beep)
{
  int k;

  /* this function prints 1 to 5 beeps. The alert character '\a' is
   * an ANSI C invention; non-ANSI-compliant implementations may ignore
   * the '\' character and just print an 'a', or do something else nasty,
   * so we define it to be something reasonable in ansi.h.
   */

  if (nargs) {
    if (!is_integer(args[0])) {
      safe_str(T(e_int), buff, bp);
      return;
    }
    k = parse_integer(args[0]);
  } else
    k = 1;

  if (!Hasprivs(executor) || (k <= 0) || (k > 5)) {
    safe_str(T(e_perm), buff, bp);
    return;
  }
  safe_fill(BEEP_CHAR, k, buff, bp);
}

FUNCTION(fun_ord)
{
  char *m;
  size_t len = 0;
  unsigned char what;
  if (!args[0] || !args[0][0]) {
    safe_str(T("#-1 FUNCTION (ORD) EXPECTS ONE CHARACTER"), buff, bp);
    return;
  }
  m = remove_markup(args[0], &len);
  what = (unsigned char) *m;

  if (len != 2)                 /* len includes trailing nul */
    safe_str(T("#-1 FUNCTION (ORD) EXPECTS ONE CHARACTER"), buff, bp);
  else if (isprint(what) || what == '\n')
    safe_integer(what, buff, bp);
  else
    safe_str(T("#-1 UNPRINTABLE CHARACTER"), buff, bp);
}

FUNCTION(fun_chr)
{
  int c;

  if (!is_integer(args[0])) {
    safe_str(T(e_uint), buff, bp);
    return;
  }
  c = parse_integer(args[0]);
  if (c < 0 || c > UCHAR_MAX)
    safe_str(T("#-1 THIS ISN'T UNICODE"), buff, bp);
  else if (isprint(c))
    safe_chr(c, buff, bp);
  else
    safe_str(T("#-1 UNPRINTABLE CHARACTER"), buff, bp);

}

FUNCTION(fun_accent)
{
  if (arglens[0] != arglens[1]) {
    safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
    return;
  }
  safe_accent(args[0], args[1], arglens[0], buff, bp);
}

FUNCTION(fun_stripaccents)
{
  int n;
  for (n = 0; n < arglens[0]; n++) {
    if (accent_table[(unsigned char) args[0][n]].base)
      safe_str(accent_table[(unsigned char) args[0][n]].base, buff, bp);
    else
      safe_chr(args[0][n], buff, bp);
  }
}

/* ARGSUSED */
FUNCTION(fun_edit)
{
  int i, j;
  char *needle;
  size_t nlen;
  char *search, *ptr;
  ansi_string *orig, *repl;

  orig = parse_ansi_string(args[0]);

  for (i = 1; i < nargs - 1; i += 2) {
    needle = remove_markup(args[i], &nlen);
    nlen--;
    repl = parse_ansi_string(args[i + 1]);
    if (strcmp(needle, "$") == 0) {
      ansi_string_insert(orig, orig->len, repl, 0, repl->len);
    } else if (strcmp(needle, "^") == 0) {
      ansi_string_insert(orig, 0, repl, 0, repl->len);
    } else if (nlen == 0) {
      /* Annoying. Stick repl between each character */
      /* Since this is inserts, we're working *backwards* */
      for (j = orig->len - 1; j > 0; j--) {
        ansi_string_insert(orig, j, repl, 0, repl->len);
      }
    } else {
      search = orig->text;
      /* Find each occurrence */
      while ((ptr = strstr(search, needle)) != NULL) {
        /* Perform the replacement */
        ansi_string_replace(orig, ptr - orig->text, nlen, repl, 0, repl->len);
        search = ptr + repl->len;
      }
    }
    free_ansi_string(repl);
    optimize_ansi_string(orig);
  }

  safe_ansi_string(orig, 0, orig->len, buff, bp);
  free_ansi_string(orig);
}

FUNCTION(fun_brackets)
{
  char *str;
  int rbrack, lbrack, rbrace, lbrace, lcurl, rcurl;

  lcurl = rcurl = rbrack = lbrack = rbrace = lbrace = 0;
  str = args[0];                /* The string to count the brackets in */
  while (*str) {
    switch (*str) {
    case '[':
      lbrack++;
      break;
    case ']':
      rbrack++;
      break;
    case '(':
      lbrace++;
      break;
    case ')':
      rbrace++;
      break;
    case '{':
      lcurl++;
      break;
    case '}':
      rcurl++;
      break;
    default:
      break;
    }
    str++;
  }
  safe_format(buff, bp, "%d %d %d %d %d %d", lbrack, rbrack,
              lbrace, rbrace, lcurl, rcurl);
}

/* Returns the length of str up to the first return character,
 * or else the last space, or else -1.
 */
static int
wraplen(char *str, size_t maxlen)
{
  size_t i, length;

  /* If the remaining text is shorter than our chunk size (maxlen),
   * try to return it all. Otherwise, scan the maximum allowable size,
   * but account for a newline character. */
  length = (strlen(str) <= maxlen) ? strlen(str) : maxlen + 1;

  /* If there's a newline in the chunk, wrap there. */
  for (i = 0; i < length; i++)
    if ((str[i] == '\n') || (str[i] == '\r'))
      return i;

  /* No newlines, but the text we can grab will fit on one line */
  if (length == strlen(str))
    return length;

  /* No return char was found, so find the last space in str. */
  while (str[maxlen] != ' ' && maxlen > 0)
    maxlen--;

  if (maxlen > 0)
    return (int) maxlen;
  else
    return -1;
}

/** The integer in string a will be stored in v, 
 * if a is not an integer then d (efault) is stored in v. 
 */
#define initint(a, v, d) \
  do \
   if (arglens[a] == 0) { \
      v = d; \
   } else { \
     if (!is_integer(args[a])) { \
        safe_str(T(e_int), buff, bp); \
        return; \
     } \
     v = parse_integer(args[a]); \
   } \
 while (0)

FUNCTION(fun_wrap)
{
/*  args[0]  =  text to be wrapped (required)
 *  args[1]  =  line width (width) (required)
 *  args[2]  =  width of first line (width1st)
 *  args[3]  =  output delimiter (btwn lines)
 */

  char *pstr;                   /* start of string */
  ansi_string *as;
  const char *pend;             /* end of string */
  size_t linewidth, width1st, width;
  int linenr = 0;
  const char *linesep;
  size_t ansiwidth;
  int ansilen;

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

  if (ansi_strlen(args[0]) == 0) {
    safe_str(args[0], buff, bp);
    return;
  }


  initint(1, width, 72);
  width1st = width;

  if (nargs > 2)
    initint(2, width1st, width);
  if (nargs > 3)
    linesep = args[3];
  else if (NEWLINE_ONE_CHAR)
    linesep = "\n";
  else
    linesep = "\r\n";

  if (width < 2 || width1st < 2) {
    safe_str(T("#-1 WIDTH TOO SMALL"), buff, bp);
    return;
  }

  as = parse_ansi_string(args[0]);
  pstr = as->text;
  pend = as->text + as->len;

  linewidth = width1st;

  while (pstr < pend) {
    if (linenr++ == 1)
      linewidth = width;
    if ((linenr > 1) && linesep && *linesep)
      safe_str(linesep, buff, bp);

    ansiwidth = strlen(pstr);
    if (ansiwidth > linewidth)
      ansiwidth = linewidth;
    ansilen = wraplen(pstr, ansiwidth);

    if (ansilen < 0) {
      /* word doesn't fit on one line, so cut it */
      safe_ansi_string(as, pstr - as->text, ansiwidth - 1, buff, bp);
      safe_chr('-', buff, bp);
      pstr += ansiwidth - 1;    /* move to start of next line */
    } else {
      /* normal line */
      safe_ansi_string(as, pstr - as->text, ansilen, buff, bp);
      if (pstr[ansilen] == '\r')
        ++ansilen;
      pstr += ansilen + 1;      /* move to start of next line */
    }
  }
  free_ansi_string(as);
}

#undef initint

#define AL_LEFT 1    /**< Align left */
#define AL_RIGHT 2   /**< Align right */
#define AL_CENTER 3  /**< Align center */
#define AL_REPEAT 4  /**< Repeat column */
#define AL_COALESCE_LEFT 8  /**< Coalesce empty column with column to left */
#define AL_COALESCE_RIGHT 16  /**< Coalesce empty column with column to right */

static int
align_one_line(char *buff, char **bp, int ncols,
               int cols[MAX_COLS], int calign[MAX_COLS], char *ptrs[MAX_COLS],
               ansi_string *as[MAX_COLS],
               int linenum, char *fieldsep, int fslen,
               char *linesep, int lslen, char filler)
{
  static char line[BUFFER_LEN];
  static char segment[BUFFER_LEN];
  char *sp;
  char *ptr, *tptr;
  char *lp;
  char *lastspace;
  int i, j;
  int len;
  int cols_done;
  int skipspace;

  lp = line;
  memset(line, filler, BUFFER_LEN);
  cols_done = 0;
  for (i = 0; i < ncols; i++) {
    /* Skip 0-width and negative columns */
    if (cols[i] <= 0) {
      cols_done++;
      continue;
    }
    /* Is the next column AL_COALESCE_LEFT and has it run out of
     * text? If so, do the coalesce now.
     */
    if ((i < (ncols - 1)) &&
        (cols[i + 1] > 0) &&
        (!(calign[i + 1] & AL_REPEAT) &&
         (calign[i + 1] & AL_COALESCE_LEFT) &&
         (!ptrs[i + 1] || !*ptrs[i + 1]))) {
      /* To coalesce left on this line, modify the left column's
       * width and set the current column width to 0 (which we can
       * teach it to skip). */
      cols[i] += cols[i + 1] + 1;
      cols[i + 1] = 0;
    }
    if (!ptrs[i] || !*ptrs[i]) {
      if (calign[i] & AL_REPEAT) {
        ptrs[i] = as[i]->text;
        /* To coalesce right on this line,
         * modify the current column's width to 0, modify the right
         * column's width, and continue on to processing the next
         * column
         */
      } else if (calign[i] & AL_COALESCE_RIGHT) {
        if (i < (ncols - 1))
          cols[i + 1] += cols[i] + 1;
        cols[i] = 0;
        cols_done++;
        continue;
      } else {
        lp += cols[i];
        if (i < (ncols - 1) && fslen)
          safe_str(fieldsep, line, &lp);
        cols_done++;
        continue;
      }
    }
    if (calign[i] & AL_REPEAT) {
      cols_done++;
    }
    for (len = 0, ptr = ptrs[i], lastspace = NULL; len < cols[i]; ptr++, len++) {
      if ((!*ptr) || (*ptr == '\n'))
        break;
      if (isspace((unsigned char) *ptr)) {
        lastspace = ptr;
      }
    }
    skipspace = 0;
    sp = segment;
    if (!*ptr) {
      if (len > 0) {
        safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
      }
      ptrs[i] = ptr;
    } else if (*ptr == '\n') {
      for (tptr = ptr;
           *tptr && tptr >= ptrs[i] && isspace((unsigned char) *tptr); tptr--) ;
      len = (tptr - ptrs[i]) + 1;
      if (len > 0) {
        safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
      }
      ptrs[i] = ptr + 1;
      ptr = tptr;
    } else if (lastspace) {
      ptr = lastspace;
      skipspace = 1;
      for (tptr = ptr;
           *tptr && tptr >= ptrs[i] && isspace((unsigned char) *tptr); tptr--) ;
      len = (tptr - ptrs[i]) + 1;
      if (len > 0) {
        safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
      }
      ptrs[i] = lastspace;
    } else {
      if (len > 0) {
        safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
      }
      ptrs[i] = ptr;
    }
    *sp = '\0';

    if ((calign[i] & 3) == AL_LEFT) {
      safe_str(segment, line, &lp);
      lp += cols[i] - len;
    } else if ((calign[i] & 3) == AL_RIGHT) {
      lp += cols[i] - len;
      safe_str(segment, line, &lp);
    } else if ((calign[i] & 3) == AL_CENTER) {
      j = cols[i] - len;
      lp += j >> 1;
      safe_str(segment, line, &lp);
      lp += (j >> 1) + (j & 1);
    }
    if ((lp - line) > BUFFER_LEN)
      lp = (line + BUFFER_LEN - 1);
    if (i < (ncols - 1) && fslen)
      safe_str(fieldsep, line, &lp);
    if (skipspace)
      for (;
           *ptrs[i] && (*ptrs[i] != '\n') && isspace((unsigned char) *ptrs[i]);
           ptrs[i]++) ;
  }

  if (cols_done == ncols)
    return 0;
  if ((lp - line) > BUFFER_LEN)
    lp = (line + BUFFER_LEN - 1);
  *lp = '\0';
  if (linenum > 0 && lslen > 0)
    safe_str(linesep, buff, bp);
  safe_str(line, buff, bp);
  return 1;
}


FUNCTION(fun_align)
{
  int nline;
  char *ptr;
  int ncols;
  int i;
  static int cols[MAX_COLS];
  static int calign[MAX_COLS];
  static ansi_string *as[MAX_COLS];
  static char *ptrs[MAX_COLS];
  char filler;
  char *fieldsep;
  int fslen;
  char *linesep;
  int lslen;
  int totallen = 0;

  filler = ' ';
  fieldsep = (char *) " ";
  linesep = (char *) "\n";

  /* Get column widths */
  ncols = 0;
  for (ptr = args[0]; *ptr; ptr++) {
    while (isspace((unsigned char) *ptr))
      ptr++;
    if (*ptr == '>') {
      calign[ncols] = AL_RIGHT;
      ptr++;
    } else if (*ptr == '-') {
      calign[ncols] = AL_CENTER;
      ptr++;
    } else if (*ptr == '<') {
      calign[ncols] = AL_LEFT;
      ptr++;
    } else if (isdigit((unsigned char) *ptr)) {
      calign[ncols] = AL_LEFT;
    } else {
      safe_str(T("#-1 INVALID ALIGN STRING"), buff, bp);
      return;
    }
    for (i = 0; *ptr && isdigit((unsigned char) *ptr); ptr++) {
      i *= 10;
      i += *ptr - '0';
    }
    if (*ptr == '.') {
      calign[ncols] |= AL_REPEAT;
      ptr++;
    }
    if (*ptr == '`') {
      calign[ncols] |= AL_COALESCE_LEFT;
      ptr++;
    }
    if (*ptr == '\'') {
      calign[ncols] |= AL_COALESCE_RIGHT;
      ptr++;
    }
    cols[ncols++] = i;
    if (!*ptr)
      break;
  }


  for (i = 0; i < ncols; i++) {
    if (cols[i] < 1) {
      safe_str(T("#-1 CANNOT HAVE COLUMN OF SIZE 0"), buff, bp);
      return;
    }
    if (cols[i] > BUFFER_LEN) {
      safe_str(T("#-1 CANNOT HAVE COLUMNS THAT LARGE"), buff, bp);
      return;
    }
    totallen += cols[i];
  }
  if (totallen > BUFFER_LEN) {
    safe_str(T("#-1 CANNOT HAVE COLUMNS THAT LARGE"), buff, bp);
    return;
  }

  if (ncols < 1) {
    safe_str(T("#-1 NOT ENOUGH COLUMNS FOR ALIGN"), buff, bp);
    return;
  }
  if (ncols > MAX_COLS) {
    safe_str(T("#-1 TOO MANY COLUMNS FOR ALIGN"), buff, bp);
    return;
  }
  if (nargs < (ncols + 1) || nargs > (ncols + 4)) {
    safe_str(T("#-1 INVALID NUMBER OF ARGUMENTS TO ALIGN"), buff, bp);
    return;
  }
  if (nargs >= (ncols + 2)) {
    if (!args[ncols + 1] || strlen(args[ncols + 1]) > 1) {
      safe_str(T("#-1 FILLER MUST BE ONE CHARACTER"), buff, bp);
      return;
    }
    if (*args[ncols + 1]) {
      filler = *(args[ncols + 1]);
    }
  }
  if (nargs >= (ncols + 3)) {
    fieldsep = args[ncols + 2];
  }
  if (nargs >= (ncols + 4)) {
    linesep = args[ncols + 3];
  }

  fslen = strlen(fieldsep);
  lslen = strlen(linesep);

  for (i = 0; i < MAX_COLS; i++) {
    as[i] = NULL;
  }
  for (i = 0; i < ncols; i++) {
    as[i] = parse_ansi_string(args[i + 1]);
    ptrs[i] = as[i]->text;
  }

  nline = 0;
  while (1) {
    if (!align_one_line(buff, bp, ncols, cols, calign, ptrs,
                        as, nline++, fieldsep, fslen, linesep, lslen, filler))
      break;
  }
  **bp = '\0';
  for (i = 0; i < ncols; i++) {
    free_ansi_string(as[i]);
    ptrs[i] = as[i]->text;
  }
  return;
}

FUNCTION(fun_speak)
{
  ufun_attrib transufun;
  ufun_attrib nullufun;
  dbref speaker;
  char *speaker_str;
  char *open, *close;
  int transform = 0, null = 0, say = 0;
  char *wenv[3];
  int funccount;
  int fragment = 0;
  char *say_string;
  char *string;
  char rbuff[BUFFER_LEN];

  speaker = match_thing(executor, args[0]);
  if (speaker == NOTHING || speaker == AMBIGUOUS) {
    safe_str(T(e_match), buff, bp);
    return;
  }
  speaker_str = unparse_dbref(speaker);

  if (!args[1] || !*args[1])
    return;

  string = args[1];

  if (nargs > 2 && *args[2] != '\0' && *args[2] != ' ')
    say_string = args[2];
  else
    say_string = (char *) "says,";

  if (nargs > 3) {
    if (args[3]) {
      /* we have a transform attr */
      transform = 1;
      if (!fetch_ufun_attrib(args[3], executor, &transufun, 1)) {
        safe_str(T(e_atrperm), buff, bp);
        return;
      }
      if (nargs > 4) {
        if (args[4]) {
          /* we have an attr to use when transform returns an empty string */
          null = 1;
          if (!fetch_ufun_attrib(args[4], executor, &nullufun, 1)) {
            safe_str(T(e_atrperm), buff, bp);
            return;
          }
        }
      }
    }
  }

  if (nargs < 6 || !args[5])
    open = (char *) "\"";
  else
    open = args[5];
  if (nargs < 7 || !args[6])
    close = open;
  else
    close = args[6];


  switch (*string) {
  case ':':
    safe_str(accented_name(speaker), buff, bp);
    string++;
    if (*string == ' ') {
      /* semipose it instead */
      while (*string == ' ')
        string++;
    } else
      safe_chr(' ', buff, bp);
    break;
  case ';':
    string++;
    safe_str(accented_name(speaker), buff, bp);
    if (*string == ' ') {
      /* pose it instead */
      safe_chr(' ', buff, bp);
      while (*string == ' ')
        string++;
    }
    break;
  case '|':
    string++;
    break;
  case '"':
    if (CHAT_STRIP_QUOTE)
      string++;
  default:
    say = 1;
    break;
  }

  if (!transform || (!say && !strstr(string, open))) {
    /* nice and easy */
    if (say)
      safe_format(buff, bp, "%s %s \"%s\"", accented_name(speaker),
                  say_string, string);
    else
      safe_str(string, buff, bp);
    return;
  }


  if (say && !strstr(string, close)) {
    /* the whole string has to be transformed */
    wenv[0] = string;
    wenv[1] = speaker_str;
    wenv[2] = unparse_integer(fragment);
    if (call_ufun(&transufun, wenv, 3, rbuff, executor, enactor, pe_info)) {
      return;
    }
    if (strlen(rbuff) > 0) {
      safe_format(buff, bp, "%s %s %s", accented_name(speaker),
                  say_string, rbuff);
      return;
    } else if (null == 1) {
      wenv[0] = speaker_str;
      wenv[1] = unparse_integer(fragment);
      if (call_ufun(&nullufun, wenv, 2, rbuff, executor, enactor, pe_info))
        return;
      safe_str(rbuff, buff, bp);
      return;
    }
  } else {
    /* only transform portions of string between open and close */
    char *speech;
    int indx;
    int finished = 0;
    int delete = 0;

    if (say) {
      safe_str(accented_name(speaker), buff, bp);
      safe_chr(' ', buff, bp);
      safe_str(say_string, buff, bp);
      safe_chr(' ', buff, bp);
    }
    funccount = pe_info->fun_invocations;
    while (!finished && ((say && fragment == 0 && (speech = string))
                         || (speech = strstr(string, open)))) {
      fragment++;
      indx = string - speech;
      if (indx < 0)
        indx *= -1;
      if (string != NULL && strlen(string) > 0 && indx > 0)
        safe_strl(string, indx, buff, bp);
      if (!say || fragment > 1)
        speech = speech + strlen(open); /* move past open char */
      /* find close-char */
      string = strstr(speech, close);
      if (!string || !(string = string + strlen(close))) {
        /* no close char, or nothing after it; we're at the end! */
        finished = 1;
      }
      delete = (string == NULL ? strlen(speech) : strlen(speech) -
                (strlen(string) + strlen(close)));
      speech = chopstr(speech, delete);
      wenv[0] = speech;
      wenv[1] = speaker_str;
      wenv[2] = unparse_integer(fragment);
      if (call_ufun(&transufun, wenv, 3, rbuff, executor, enactor, pe_info))
        break;
      if (*bp == (buff + BUFFER_LEN - 1) &&
          pe_info->fun_invocations == funccount)
        break;
      funccount = pe_info->fun_invocations;
      if ((null == 1) && (strlen(rbuff) == 0)) {
        wenv[0] = speaker_str;
        wenv[1] = unparse_integer(fragment);
        if (call_ufun(&nullufun, wenv, 2, rbuff, executor, enactor, pe_info))
          break;
      }
      if (strlen(rbuff) > 0) {
        safe_str(rbuff, buff, bp);
      }
      if (*bp == (buff + BUFFER_LEN - 1) &&
          pe_info->fun_invocations == funccount)
        break;
    }
    if (string != NULL && strlen(string) > 0) {
      safe_str(string, buff, bp);       /* remaining string (not speech, so not t) */
    }
  }
}