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 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 "ansi.h"
#include "externs.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 "confmagic.h"


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

#ifdef __APPLE__
#define LC_MESSAGES 6
#endif

HASHTAB htab_tag;  /**< Hash table of safe html tags */

#define MAX_COLS 32  /**< Maximum number of columns for align() */
static int wraplen(char *str, int 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_tag_hashtab(void);
void init_pronouns(void);

/** 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;
#ifdef HAS_SETLOCALE
  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 = tolower(*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) || (len < 0)) {
    safe_str(T(e_range), buff, bp);
    free_ansi_string(as);
    return;
  }

  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 ((size_t) len > as->len)
    safe_strl(args[0], arglens[0], buff, bp);
  else
    safe_ansi_string(as, as->len - len, as->len, buff, bp);
  free_ansi_string(as);
}

/* ARGSUSED */
FUNCTION(fun_strinsert)
{
  /* Insert a string into another */
  ansi_string *as;
  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;
  }

  as = parse_ansi_string(args[0]);

  if ((size_t) pos > as->len) {
    /* Fast special case - concatenate args[2] to args[0] */
    safe_strl(args[0], arglens[0], buff, bp);
    safe_strl(args[2], arglens[0], buff, bp);
    free_ansi_string(as);
    return;
  }

  safe_ansi_string(as, 0, pos, buff, bp);
  safe_strl(args[2], arglens[2], buff, bp);
  safe_ansi_string(as, pos, as->len, buff, bp);
  free_ansi_string(as);

}

/* ARGSUSED */
FUNCTION(fun_delete)
{
  ansi_string *as;
  int pos, 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 ((size_t) pos > as->len || num <= 0) {
    safe_strl(args[0], arglens[0], buff, bp);
    free_ansi_string(as);
    return;
  }

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

/* ARGSUSED */
FUNCTION(fun_strreplace)
{
  ansi_string *as, *anew;
  int start, len, end;

  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;
  }

  as = parse_ansi_string(args[0]);
  anew = parse_ansi_string(args[3]);

  safe_ansi_string(as, 0, start, buff, bp);
  safe_ansi_string(anew, 0, anew->len, buff, bp);

  end = start + len;

  if ((size_t) end < as->len)
    safe_ansi_string(as, end, as->len - end, buff, bp);

  free_ansi_string(as);
  free_ansi_string(anew);

}

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 = toupper(*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 = args[1][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 *t;
  size_t len;
  /* matches a wildcard pattern for an _entire_ string */

  t = remove_markup(args[0], &len);
  memcpy(tbuf, t, len);
  safe_boolean(quick_wild(remove_markup(args[1], NULL), tbuf), buff, bp);
}

/* 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;
  int p, n;

  as = parse_ansi_string(args[0]);
  populate_codes(as);

  for (p = 0, n = as->len - 1; p < n; p++, n--) {
    char *tcode;
    char t;

    tcode = as->codes[p];
    t = as->text[p];
    as->codes[p] = as->codes[n];
    as->text[p] = as->text[n];
    as->codes[n] = tcode;
    as->text[n] = t;
  }

  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.
   */

  char *str, *rep;
  char matched[UCHAR_MAX + 1];

  /* do length checks first */
  if (arglens[0] != arglens[1]) {
    safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
    return;
  }

  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 *) args[2]; p && *p; p++)
      matched[*p] = 1;
  }

  /* walk strings, copy from the appropriate string */
  for (str = args[0], rep = args[1]; *str && *rep; str++, rep++) {
    *str = matched[(unsigned char) *str] ? *rep : *str;
  }
  safe_str(args[0], buff, bp);
}

/* 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 rawstr[BUFFER_LEN];
  char *ip, *op;
  size_t i, len;
  char *c;
  ansi_string *as;

  /* No ansi allowed in find or replace lists */
  c = remove_markup(args[1], &len);
  memcpy(rawstr, c, len);

  /* do length checks first */

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

  ip = instr;
  op = outstr;

  for (i = 0; i < len; i++) {
    safe_chr(rawstr[i], instr, &ip);
    /* Handle a range of characters */
    if (i != len - 1 && rawstr[i + 1] == '-' && i != len - 2) {
      int dir, sentinel, cur;

      if (rawstr[i] < rawstr[i + 2])
	dir = 1;
      else
	dir = -1;

      sentinel = rawstr[i + 2] + dir;
      cur = rawstr[i] + dir;

      while (cur != sentinel) {
	safe_chr((char) cur, instr, &ip);
	cur += dir;
      }
      i += 2;
    }
  }

  c = remove_markup(args[2], &len);
  memcpy(rawstr, c, len);
  for (i = 0; i < len; i++) {
    safe_chr(rawstr[i], outstr, &op);
    /* Handle a range of characters */
    if (i != len - 1 && rawstr[i + 1] == '-' && i != len - 2) {
      int dir, sentinel, cur;

      if (rawstr[i] < rawstr[i + 2])
	dir = 1;
      else
	dir = -1;

      sentinel = rawstr[i + 2] + dir;
      cur = rawstr[i] + dir;

      while (cur != sentinel) {
	safe_chr((char) cur, outstr, &op);
	cur += dir;
      }
      i += 2;
    }
  }

  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]);
  populate_codes(as);
  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;

  if (!*args[0])
    return;

  as = parse_ansi_string(args[0]);
  populate_codes(as);
  n = as->len;
  for (i = 0; i < n; i++) {
    char t, *tcode;
    j = get_random_long(i, n - 1);
    t = as->text[j];
    as->text[j] = as->text[i];
    as->text[i] = t;
    tcode = as->codes[j];
    as->codes[j] = as->codes[i];
    as->codes[i] = tcode;
  }
  safe_ansi_string(as, 0, as->len, buff, bp);
  free_ansi_string(as);
}

/* 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 character) */

  size_t width, len, lsp, rsp;
  char sep;

  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;
  }
  rsp = width - len;
  lsp = rsp / 2;
  rsp -= lsp;
  if (lsp >= BUFFER_LEN)
    lsp = BUFFER_LEN - 1;
  if (rsp >= BUFFER_LEN)
    rsp = BUFFER_LEN - 1;

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

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

/* 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;
  char const *ap, *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_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(*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 *p, *q, sep;
  int trim;
  int trim_style_arg, trim_char_arg;

  /* 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. */
  p = args[0];
  if (trim != 2) {
    while (*p == sep)
      p++;
  }
  /* Cut off the trailing stuff, if appropriate. */
  if ((trim != 1) && (*p != '\0')) {
    q = args[0] + arglens[0] - 1;
    while ((q > p) && (*q == sep))
      q--;
    q[1] = '\0';
  }
  safe_str(p, buff, bp);
}

/* 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.
   */

  char *tp;
  char sep;

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

  /* get rid of trailing spaces first, so we don't have to worry about
   * them later.
   */
  tp = args[0] + arglens[0] - 1;
  while ((tp > args[0]) && (*tp == sep))
    tp--;
  tp[1] = '\0';

  for (tp = args[0]; *tp == sep; tp++)	/* skip leading spaces */
    ;

  while (*tp) {
    safe_chr(*tp, buff, bp);
    if (*tp == sep)
      while (*tp == sep)
	tp++;
    else
      tp++;
  }
}

/* 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);
}

/** Initialize the html tag hash table with all the safe tags from HTML 4.0 */
void
init_tag_hashtab(void)
{
  static char dummy = 1;
  int i = 0;
  static const char *safetags[] = { "A", "B", "I", "U", "STRONG", "EM",
    "ADDRESS", "BLOCKQUOTE", "CENTER", "DEL", "DIV",
    "H1", "H2", "H3", "H4", "H5", "H6", "HR", "INS",
    "P", "PRE", "DIR", "DL", "DT", "DD", "LI", "MENU", "OL", "UL",
    "TABLE", "CAPTION", "COLGROUP", "COL", "THEAD", "TFOOT",
    "TBODY", "TR", "TD", "TH",
    "BR", "FONT", "IMG", "SPAN", "SUB", "SUP",
    "ABBR", "ACRONYM", "CITE", "CODE", "DFN", "KBD", "SAMP", "VAR",
    "BIG", "S", "SMALL", "STRIKE", "TT",
    NULL
  };
  hashinit(&htab_tag, 64, 1);
  while (safetags[i]) {
    hashadd(safetags[i], (void *) &dummy, &htab_tag);
    i++;
  }
}

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

  if (len != 2)			/* len includes trailing nul */
    safe_str(T("#-1 FUNCTION EXPECTS ONE CHARACTER"), buff, bp);
  else if (isprint((unsigned char) *m))
    safe_integer((unsigned char) *m, 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_html)
{
  if (!Wizard(executor))
    safe_str(T(e_perm), buff, bp);
  else
    safe_tag(args[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_tag)
{
  int i;
  if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
    safe_str("#-1", buff, bp);
  else {
    safe_chr(TAG_START, buff, bp);
    safe_strl(args[0], arglens[0], buff, bp);
    for (i = 1; i < nargs; i++) {
      if (ok_tag_attribute(executor, args[i])) {
	safe_chr(' ', buff, bp);
	safe_strl(args[i], arglens[i], buff, bp);
      }
    }
    safe_chr(TAG_END, buff, bp);
  }
}

/* ARGSUSED */
FUNCTION(fun_endtag)
{
  if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
    safe_str("#-1", buff, bp);
  else
    safe_tag_cancel(args[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_tagwrap)
{
  if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
    safe_str("#-1", buff, bp);
  else {
    if (nargs == 2)
      safe_tag_wrap(args[0], NULL, args[1], buff, bp, executor);
    else
      safe_tag_wrap(args[0], args[1], args[2], buff, bp, executor);
  }
}

#define COL_FLASH       (1)	/**< ANSI flash attribute bit */
#define COL_HILITE      (2)	/**< ANSI hilite attribute bit */
#define COL_INVERT      (4)	/**< ANSI inverse attribute bit */
#define COL_UNDERSCORE  (8)	/**< ANSI underscore attribute bit */

#define VAL_FLASH       (5)	/**< ANSI flag attribute value */
#define VAL_HILITE      (1)	/**< ANSI hilite attribute value */
#define VAL_INVERT      (7)	/**< ANSI inverse attribute value */
#define VAL_UNDERSCORE  (4)	/**< ANSI underscore attribute value */

#define COL_BLACK       (30)	/**< ANSI color black */
#define COL_RED         (31)	/**< ANSI color red */
#define COL_GREEN       (32)	/**< ANSI color green */
#define COL_YELLOW      (33)	/**< ANSI color yellow */
#define COL_BLUE        (34)	/**< ANSI color blue */
#define COL_MAGENTA     (35)	/**< ANSI color magenta */
#define COL_CYAN        (36)	/**< ANSI color cyan */
#define COL_WHITE       (37)	/**< ANSI color white */

/** The ansi attributes associated with a character. */
typedef struct {
  char flags;		/**< Ansi text attributes */
  char fore;		/**< Ansi foreground color */
  char back;		/**< Ansi background color */
} ansi_data;

static void dump_ansi_codes(ansi_data * ad, char *buff, char **bp);

/** If we're adding y to x, do we need to add z as well? */
#define EDGE_UP(x,y,z)  (((y) & (z)) && !((x) & (z)))

static void
dump_ansi_codes(ansi_data * ad, char *buff, char **bp)
{
  static ansi_data old_ad = { 0, 0, 0 };
  int f = 0;

  if ((old_ad.fore && !ad->fore)
      || (old_ad.back && !ad->back)
      || ((old_ad.flags & ad->flags) != old_ad.flags)) {
    safe_str(ANSI_NORMAL, buff, bp);
    old_ad.flags = 0;
    old_ad.fore = 0;
    old_ad.back = 0;
  }

  if ((old_ad.fore == ad->fore)
      && (old_ad.back == ad->back)
      && (old_ad.flags == ad->flags))
    /* If nothing has changed, don't bother doing anything.
     * This stops the entirely pointless \e[m being generated. */
    return;

  safe_str(ANSI_BEGIN, buff, bp);

  if (EDGE_UP(old_ad.flags, ad->flags, COL_FLASH)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(VAL_FLASH, buff, bp);
  }

  if (EDGE_UP(old_ad.flags, ad->flags, COL_HILITE)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(VAL_HILITE, buff, bp);
  }

  if (EDGE_UP(old_ad.flags, ad->flags, COL_INVERT)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(VAL_INVERT, buff, bp);
  }

  if (EDGE_UP(old_ad.flags, ad->flags, COL_UNDERSCORE)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(VAL_UNDERSCORE, buff, bp);
  }

  if (ad->fore != old_ad.fore) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ad->fore, buff, bp);
  }

  if (ad->back != old_ad.back) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ad->back + 10, buff, bp);
  }

  safe_str(ANSI_END, buff, bp);

  old_ad = *ad;

}


/* ARGSUSED */
FUNCTION(fun_ansi)
{
  static char tbuff[BUFFER_LEN];
  static ansi_data stack[1024] = { {0, 0, 0} }, *sp = stack;
  char const *arg0, *arg1;
  char *tbp;

  tbp = tbuff;
  arg0 = args[0];
  process_expression(tbuff, &tbp, &arg0, executor, caller, enactor,
		     PE_DEFAULT, PT_DEFAULT, pe_info);
  *tbp = '\0';

  sp[1] = sp[0];
  sp++;

  for (tbp = tbuff; *tbp; tbp++) {
    switch (*tbp) {
    case 'n':			/* normal */
      sp->flags = 0;
      sp->fore = 0;
      sp->back = 0;
      break;
    case 'f':			/* flash */
      sp->flags |= COL_FLASH;
      break;
    case 'h':			/* hilite */
      sp->flags |= COL_HILITE;
      break;
    case 'i':			/* inverse */
      sp->flags |= COL_INVERT;
      break;
    case 'u':			/* underscore */
      sp->flags |= COL_UNDERSCORE;
      break;
    case 'F':			/* flash */
      sp->flags &= ~COL_FLASH;
      break;
    case 'H':			/* hilite */
      sp->flags &= ~COL_HILITE;
      break;
    case 'I':			/* inverse */
      sp->flags &= ~COL_INVERT;
      break;
    case 'U':			/* underscore */
      sp->flags &= ~COL_UNDERSCORE;
      break;
    case 'b':			/* blue fg */
      sp->fore = COL_BLUE;
      break;
    case 'c':			/* cyan fg */
      sp->fore = COL_CYAN;
      break;
    case 'g':			/* green fg */
      sp->fore = COL_GREEN;
      break;
    case 'm':			/* magenta fg */
      sp->fore = COL_MAGENTA;
      break;
    case 'r':			/* red fg */
      sp->fore = COL_RED;
      break;
    case 'w':			/* white fg */
      sp->fore = COL_WHITE;
      break;
    case 'x':			/* black fg */
      sp->fore = COL_BLACK;
      break;
    case 'y':			/* yellow fg */
      sp->fore = COL_YELLOW;
      break;
    case 'B':			/* blue bg */
      sp->back = COL_BLUE;
      break;
    case 'C':			/* cyan bg */
      sp->back = COL_CYAN;
      break;
    case 'G':			/* green bg */
      sp->back = COL_GREEN;
      break;
    case 'M':			/* magenta bg */
      sp->back = COL_MAGENTA;
      break;
    case 'R':			/* red bg */
      sp->back = COL_RED;
      break;
    case 'W':			/* white bg */
      sp->back = COL_WHITE;
      break;
    case 'X':			/* black bg */
      sp->back = COL_BLACK;
      break;
    case 'Y':			/* yellow bg */
      sp->back = COL_YELLOW;
      break;
    }
  }

  dump_ansi_codes(sp, buff, bp);

  arg1 = args[1];
  process_expression(buff, bp, &arg1, executor, caller, enactor,
		     PE_DEFAULT, PT_DEFAULT, pe_info);

  dump_ansi_codes(--sp, buff, bp);

}

/* ARGSUSED */
FUNCTION(fun_stripansi)
{
  /* Strips ANSI codes away from a given string of text. Starts by
   * finding the '\x' character and stripping until it hits an 'm'.
   */

  char *cp;

  cp = remove_markup(args[0], NULL);
  safe_str(cp, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_edit)
{
  int i;
  char *f, *r, *raw;
  ansi_string *prebuf;
  char postbuf[BUFFER_LEN], lastbuf[BUFFER_LEN], *postp;
  size_t rlen, flen;

  prebuf = parse_ansi_string(args[0]);
  raw = args[0];
  for (i = 1; i < nargs - 1; i += 2) {

    postp = postbuf;
    f = args[i];		/* find this */
    r = args[i + 1];		/* replace it with this */
    flen = arglens[i];
    rlen = arglens[i + 1];

    /* Check for nothing to avoid infinite loop */
    if (!*f && !*r)
      continue;

    if (flen == 1 && *f == '$') {
      /* append */
      safe_str(raw, postbuf, &postp);
      safe_strl(r, rlen, postbuf, &postp);
    } else if (flen == 1 && *f == '^') {
      /* prepend */
      safe_strl(r, rlen, postbuf, &postp);
      safe_str(raw, postbuf, &postp);
    } else if (!*f) {
      /* insert between every character */
      size_t last;
      safe_strl(r, rlen, postbuf, &postp);
      for (last = 0; last < prebuf->len; last++) {
	safe_ansi_string(prebuf, last, 1, postbuf, &postp);
	safe_strl(r, rlen, postbuf, &postp);
      }
    } else {
      char *p;
      size_t last = 0;

      while (last < prebuf->len && (p = strstr(prebuf->text + last, f)) != NULL) {
	safe_ansi_string(prebuf, last, p - (prebuf->text + last),
			 postbuf, &postp);
	safe_strl(r, rlen, postbuf, &postp);
	last = p - prebuf->text + flen;
      }
      if (last < prebuf->len)
	safe_ansi_string(prebuf, last, prebuf->len, postbuf, &postp);
    }
    *postp = '\0';
    free_ansi_string(prebuf);
    prebuf = parse_ansi_string(postbuf);
    strcpy(lastbuf, postbuf);
    raw = lastbuf;
  }
  safe_ansi_string(prebuf, 0, prebuf->len, buff, bp);
  free_ansi_string(prebuf);
}

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 0.
 */
static int
wraplen(char *str, int maxlen)
{
  const int length = strlen(str);
  int i = 0;

  if (length <= maxlen) {
    /* Find the first return char
     * so %r will not mess with any alignment
     * functions.
     */
    while (i < length) {
      if ((str[i] == '\n') || (str[i] == '\r'))
	return i;
      i++;
    }
    return length;
  }

  /* Find the first return char
   * so %r will not mess with any alignment
   * functions.
   */
  while (i <= maxlen + 1) {
    if ((str[i] == '\n') || (str[i] == '\r'))
      return i;
    i++;
  }

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

  return (maxlen ? maxlen : -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 */
  int linewidth, width1st, width;
  int linenr = 0;
  const char *linesep;
  int ansiwidth, ansilen;

  if (!args[0] || !*args[0])
    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 = ansi_strnlen(pstr, linewidth);
    ansilen = wraplen(pstr, ansiwidth);

    if (ansilen < 0) {
      /* word doesn't fit on one line, so cut it */
      safe_ansi_string2(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_string2(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 */

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++) {
    if (!ptrs[i] || !*ptrs[i]) {
      if (calign[i] & AL_REPEAT) {
	ptrs[i] = as[i]->text;
      } 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_string2(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_string2(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_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
      }
      ptrs[i] = lastspace;
    } else {
      if (len > 0) {
	safe_ansi_string2(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;
  *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;

  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++;
    }
    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 (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;
    }
    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;
}