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 parse.c
 *
 * \brief The PennMUSH function/expression parser
 *
 * The most important function in this file is process_expression.
 *
 */

#include "copyrite.h"

#include "config.h"
#include <math.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>

#include "conf.h"
#include "externs.h"
#include "ansi.h"
#include "dbdefs.h"
#include "function.h"
#include "case.h"
#include "match.h"
#include "mushdb.h"
#include "parse.h"
#include "attrib.h"
#include "pcre.h"
#include "flags.h"
#include "log.h"
#include "mymalloc.h"
#include "confmagic.h"

extern char *absp[], *obj[], *poss[], *subj[];	/* fundb.c */
extern int inum, inum_limit;
int global_fun_invocations;
int global_fun_recursions;
extern int re_subpatterns;
extern int *re_offsets;
extern char *re_from;
extern sig_atomic_t cpu_time_limit_hit;
extern int cpu_limit_warning_sent;

/** Structure for storing DEBUG output in a linked list */
struct debug_info {
  char *string;		/**< A DEBUG string */
  Debug_Info *prev;	/**< Previous node in the linked list */
  Debug_Info *next;	/**< Next node in the linked list */
};

FUNCTION_PROTO(fun_gfun);

#ifndef DOXYGEN_SHOULD_SKIP_THIS

/* Common error messages */
char e_int[] = "#-1 ARGUMENT MUST BE INTEGER";
char e_ints[] = "#-1 ARGUMENTS MUST BE INTEGERS";
char e_uint[] = "#-1 ARGUMENT MUST BE POSITIVE INTEGER";
char e_uints[] = "#-1 ARGUMENTS MUST BE POSITIVE INTEGERS";
char e_num[] = "#-1 ARGUMENT MUST BE NUMBER";
char e_nums[] = "#-1 ARGUMENTS MUST BE NUMBERS";
char e_invoke[] = "#-1 FUNCTION INVOCATION LIMIT EXCEEDED";
char e_call[] = "#-1 CALL LIMIT EXCEEDED";
char e_perm[] = "#-1 PERMISSION DENIED";
char e_atrperm[] = "#-1 NO PERMISSION TO GET ATTRIBUTE";
char e_match[] = "#-1 NO MATCH";
char e_notvis[] = "#-1 NO SUCH OBJECT VISIBLE";
char e_disabled[] = "#-1 FUNCTION DISABLED";
char e_range[] = "#-1 OUT OF RANGE";

#endif

#if 0
static void dummy_errors(void);

static void
dummy_errors()
{
  /* Just to make sure the error messages are in the translation
     tables. */
  char *temp;
  temp = T("#-1 ARGUMENT MUST BE INTEGER");
  temp = T("#-1 ARGUMENTS MUST BE INTEGERS");
  temp = T("#-1 ARGUMENT MUST BE POSITIVE INTEGER");
  temp = T("#-1 ARGUMENTS MUST BE POSITIVE INTEGERS");
  temp = T("#-1 ARGUMENT MUST BE NUMBER");
  temp = T("#-1 ARGUMENTS MUST BE NUMBERS");
  temp = T("#-1 FUNCTION INVOCATION LIMIT EXCEEDED");
  temp = T("#-1 CALL LIMIT EXCEEDED");
  temp = T("#-1 PERMISSION DENIED");
  temp = T("#-1 NO PERMISSION TO GET ATTRIBUTE");
  temp = T("#-1 NO MATCH");
  temp = T("#-1 NO SUCH OBJECT VISIBLE");
  temp = T("#-1 FUNCTION DISABLED");
  temp = T("#-1 OUT OF RANGE");
}

#endif

/** Given a string, parse out a dbref.
 * \param str string to parse.
 * \return dbref contained in the string, or NOTHING if not a valid dbref.
 */
dbref
parse_dbref(char const *str)
{
  /* Make sure string is strictly in format "#nnn".
   * Otherwise, possesives will be fouled up.
   */
  char const *p;
  dbref num;

  if (!str || (*str != NUMBER_TOKEN) || !*(str + 1))
    return NOTHING;
  for (p = str + 1; isdigit((unsigned char) *p); p++) {
  }
  if (*p)
    return NOTHING;

  num = atoi(str + 1);
  if (!GoodObject(num))
    return NOTHING;
  return num;
}

/** Version of parse_dbref() that doesn't do GoodObject checks */
dbref
qparse_dbref(const char *s)
{

  if (!s || (*s != NUMBER_TOKEN) || !*(s + 1))
    return NOTHING;
  return parse_integer(s + 1);
}


/** Given a string, parse out an object id or dbref.
 * \param str string to parse.
 * \return dbref of object referenced by string, or NOTHING if not a valid
 * string or not an existing dbref.
 */
dbref
parse_objid(char const *str)
{
  char *p;
  if ((p = strchr(str, ':'))) {
    char tbuf1[BUFFER_LEN];
    dbref it;
    /* A unique id, probably */
    strncpy(tbuf1, str, (p - str));
    tbuf1[p - str] = '\0';
    it = parse_dbref(tbuf1);
    if (GoodObject(it)) {
      time_t matchtime;
      p++;
      if (!is_strict_integer(p))
	return NOTHING;
      matchtime = parse_integer(p);
      return (CreTime(it) == matchtime) ? it : NOTHING;
    } else
      return NOTHING;
  } else
    return parse_dbref(str);
}


/** Given a string, parse out a boolean value.
 * The meaning of boolean is fuzzy. To TinyMUSH, any string that begins with
 * a non-zero number is true, and everything else is false.
 * To PennMUSH, negative dbrefs are false, non-negative dbrefs are true,
 * 0 is false, all other numbers are true, empty or blank strings are false,
 * and all other strings are true. 
 * \param str string to parse.
 * \retval 1 str represents a true value.
 * \retval 0 str represents a false value.
 */
int
parse_boolean(char const *str)
{
  if (TINY_BOOLEANS) {
    return (atoi(str) ? 1 : 0);
  } else {
    /* Turn a string into a boolean value.
     * All negative dbrefs are false, all non-negative dbrefs are true.
     * Zero is false, all other numbers are true.
     * Empty (or space only) strings are false, all other strings are true.
     */
    /* Null strings are false */
    if (!str || !*str)
      return 0;
    /* Negative dbrefs are false - actually anything starting #-,
     * which will also cover our error messages. */
    if (*str == '#' && *(str + 1) && (*(str + 1) == '-'))
      return 0;
    /* Non-zero numbers are true, zero is false */
    if (is_strict_number(str))
      return parse_number(str) != 0;	/* avoid rounding problems */
    /* Skip blanks */
    while (*str == ' ')
      str++;
    /* If there's any non-blanks left, it's true */
    return *str != '\0';	/* force to 1 or 0 */
  }
}

/** Is a string a boolean value?
 * To TinyMUSH, any integer is a boolean value. To PennMUSH, any
 * string at all is boolean.
 * \param str string to check.
 * \retval 1 string is a valid boolean.
 * \retval 0 string is not a valid boolean.
 */
int
is_boolean(char const *str)
{
  if (TINY_BOOLEANS)
    return is_integer(str);
  else
    return 1;
}

/** Is a string a dbref?
 * A dbref is a string starting with a #, optionally followed by a -,
 * and then followed by at least one digit, and nothing else.
 * \param str string to check.
 * \retval 1 string is a dbref.
 * \retval 0 string is not a dbref.
 */
int
is_dbref(char const *str)
{
  if (!str || (*str != NUMBER_TOKEN) || !*(str + 1))
    return 0;
  if (*(str + 1) == '-') {
    str++;
  }
  for (str++; isdigit((unsigned char) *str); str++) {
  }
  return !*str;
}

/** Is a string an objid?
 * An objid is a string starting with a #, optionally followed by a -,
 * and then followed by at least one digit, then optionally followed 
 * by a : and at least one digit, and nothing else.
 * In regex: ^#-?\d+(:\d+)?$
 * \param str string to check.
 * \retval 1 string is a dbref.
 * \retval 0 string is not a dbref.
 */
int
is_objid(char const *str)
{
  static pcre *re = NULL;
  const char *errptr;
  int erroffset;

  if (!str)
    return 0;
  if (!re)
    re = pcre_compile("^#-?\\d+(?::\\d+)?$", 0, &errptr, &erroffset, NULL);
  return (pcre_exec(re, NULL, str, strlen(str), 0, 0, NULL, 0) >= 0);
}

/** Is string an integer?
 * To TinyMUSH, any string is an integer. To PennMUSH, a string that
 * passes strtol is an integer, and a blank string is an integer
 * if NULL_EQ_ZERO is turned on.
 * \param str string to check.
 * \retval 1 string is an integer.
 * \retval 0 string is not an integer.
 */
int
is_integer(char const *str)
{
  char *end;

  /* If we're emulating Tiny, anything is an integer */
  if (TINY_MATH)
    return 1;
  if (!str)
    return 0;
  while (isspace((unsigned char) *str))
    str++;
  if (*str == '\0')
    return NULL_EQ_ZERO;
  errno = 0;
  strtol(str, &end, 10);
  if (errno == ERANGE || *end != '\0')
    return 0;
  return 1;
}

/** Is string an uinteger?
 * To TinyMUSH, any string is an uinteger. To PennMUSH, a string that
 * passes strtoul is an uinteger, and a blank string is an uinteger
 * if NULL_EQ_ZERO is turned on.
 * \param str string to check.
 * \retval 1 string is an uinteger.
 * \retval 0 string is not an uinteger.
 */
int
is_uinteger(char const *str)
{
  char *end;

  /* If we're emulating Tiny, anything is an integer */
  if (TINY_MATH)
    return 1;
  if (!str)
    return 0;
  /* strtoul() accepts negative numbers, so we still have to do this check */
  while (isspace((unsigned char) *str))
    str++;
  if (*str == '\0')
    return NULL_EQ_ZERO;
  if (!(isdigit((unsigned char) *str) || *str == '+'))
    return 0;
  errno = 0;
  strtoul(str, &end, 10);
  if (errno == ERANGE || *end != '\0')
    return 0;
  return 1;
}

/** Is string a number by the strict definition?
 * A strict number is a non-null string that passes strtod.
 * \param str string to check.
 * \retval 1 string is a strict number.
 * \retval 0 string is not a strict number.
 */
int
is_strict_number(char const *str)
{
  char *end;
  NVAL val;
  if (!str)
    return 0;
  errno = 0;
  val = strtod(str, &end);
  if (errno == ERANGE || *end != '\0')
    return 0;
  return end > str;
}

/** Is string an integer by the strict definition?
 * A strict integer is a non-null string that passes strtol.
 * \param str string to check.
 * \retval 1 string is a strict integer.
 * \retval 0 string is not a strict integer.
 */
int
is_strict_integer(char const *str)
{
  char *end;
  int val;
  if (!str)
    return 0;
  errno = 0;
  val = strtol(str, &end, 10);
  if (errno == ERANGE || *end != '\0')
    return 0;
  return end > str;
}

/** Is string a number?
 * To TinyMUSH, any string is a number. To PennMUSH, a strict number is
 * a number, and a blank string is a number if NULL_EQ_ZERO is turned on.
 * \param str string to check.
 * \retval 1 string is a number.
 * \retval 0 string is not a number.
 */
int
is_number(char const *str)
{
  /* If we're emulating Tiny, anything is a number */
  if (TINY_MATH)
    return 1;
  while (isspace((unsigned char) *str))
    str++;
  if (*str == '\0')
    return NULL_EQ_ZERO;
  return is_strict_number(str);
}

/* Table of interesting characters for process_expression() */
extern char active_table[UCHAR_MAX + 1];
/* Indexes of valid q-regs into the renv array. -1 is error. */
extern signed char qreg_indexes[UCHAR_MAX + 1];

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


/** Function and other substitution evaluation.
 * This is the PennMUSH function/expression parser. Big stuff.
 *
 * All results are returned in buff, at the point *bp. bp is likely
 * not equal to buff, so make no assumptions about writing at the
 * start of the buffer.  *bp must be updated to point at the next
 * place to be filled (ala safe_str() and safe_chr()).  Be very
 * careful about not overflowing buff; use of safe_str() and safe_chr()
 * for all writes into buff is highly recommended.
 * 
 * nargs is the count of the number of arguments passed to the function,
 * and args is an array of pointers to them.  args will have at least
 * nargs elements, or 10 elements, whichever is greater.  The first ten
 * elements are initialized to NULL for ease of porting functions from
 * the old style, but relying on such is considered bad form.
 * The argument strings are stored in BUFFER_LEN buffers, but reliance
 * on that size is also considered bad form.  The argument strings may
 * be modified, but modifying the pointers to the argument strings will
 * cause crashes. 
 *
 * executor corresponds to %!, the object invoking the function.
 * caller   corresponds to %@, the last object to do a U() or similar.
 * enactor  corresponds to %#, the object that started the whole mess.
 * Note that fun_ufun() and similar must swap around these parameters
 * in calling process_expression(); no checks are made in the parser
 * itself to maintain these values.
 *
 * called_as contains a pointer to the name of the function called
 * (taken from the function table).  This may be used to distinguish
 * multiple functions which use the same C function for implementation.
 *
 * pe_info holds context information used by the parser.  It should
 * be passed untouched to process_expression(), if it is called.
 * pe_info should be treated as a black box; its structure and contents
 * may change without notice.
 *
 * Normally, p_e() returns 0. It returns 1 upon hitting the CPU time limit.
 *
 * \param buff buffer to store returns of parsing.
 * \param bp pointer to pointer into buff marking insert position.
 * \param str string to parse.
 * \param executor dbref of the object invoking the function.
 * \param caller dbref of  the last object to use u()
 * \param enactor dbref of the enactor.
 * \param eflags flags to control what is evaluated.
 * \param tflags flags to control what terminates an expression.
 * \param pe_info pointer to parser context data.
 * \retval 0 success.
 * \retval 1 CPU time limit exceeded.
 */
int
process_expression(char *buff, char **bp, char const **str,
		   dbref executor, dbref caller, dbref enactor,
		   int eflags, int tflags, PE_Info * pe_info)
{
  int debugging = 0, made_info = 0;
  char *debugstr = NULL, *sourcestr = NULL;
  char *realbuff = NULL, *realbp = NULL;
  int gender = -1;
  char *startpos = *bp;
  int had_space = 0;
  char temp[3];
  int temp_eflags;
  int old_iter_limit;
  int qindex;
  int e_len;
  int retval = 0;
  const char *e_msg;

  if (!buff || !bp || !str || !*str)
    return 0;
  if (cpu_time_limit_hit) {
    if (!cpu_limit_warning_sent) {
      cpu_limit_warning_sent = 1;
      /* Can't just put #-1 CPU USAGE EXCEEDED in buff here, because
       * it might never get displayed.
       */
      if (!Quiet(enactor))
	notify(enactor, T("CPU usage exceeded."));
      do_rawlog(LT_TRACE,
		T
		("CPU time limit exceeded. enactor=#%d executor=#%d caller=#%d code=%s"),
		enactor, executor, caller, *str);
    }
    return 1;
  }
  if (Halted(executor))
    eflags = PE_NOTHING;
  if (eflags & PE_COMPRESS_SPACES)
    while (**str == ' ')
      (*str)++;
  if (!*str)
    return 0;

  if (!pe_info) {
    old_iter_limit = inum_limit;
    inum_limit = inum;
    made_info = 1;
    pe_info = (PE_Info *) mush_malloc(sizeof(PE_Info),
				      "process_expression.pe_info");
    pe_info->fun_invocations = 0;
    pe_info->fun_depth = 0;
    pe_info->nest_depth = 0;
    pe_info->call_depth = 0;
    pe_info->debug_strings = NULL;
  } else {
    old_iter_limit = -1;
  }

  /* If we've been asked to evaluate, log the expression if:
   * (a) the last thing we logged wasn't an expression, and
   * (b) this expression isn't a substring of the last thing we logged
   */
  if ((eflags & PE_EVALUATE) &&
      ((last_activity_type() != LA_PE) || !strstr(last_activity(), *str))) {
    log_activity(LA_PE, executor, *str);
  }

  if (eflags != PE_NOTHING) {
    if (((*bp) - buff) > (BUFFER_LEN - SBUF_LEN)) {
      realbuff = buff;
      realbp = *bp;
      buff = (char *) mush_malloc(BUFFER_LEN,
				  "process_expression.buffer_extension");
      *bp = buff;
      startpos = buff;
    }
  }

  if (CALL_LIMIT && (pe_info->call_depth++ > CALL_LIMIT)) {
    e_msg = T(e_call);
    e_len = strlen(e_msg);
    if ((buff + e_len > *bp) || strcmp(e_msg, *bp - e_len))
      safe_str(e_msg, buff, bp);
    goto exit_sequence;
  }


  if (eflags != PE_NOTHING) {
    debugging = (Debug(executor) || (eflags & PE_DEBUG))
      && (Connected(Owner(executor)) || atr_get(executor, "DEBUGFORWARDLIST"));
    if (debugging) {
      int j;
      char *debugp;
      char const *mark;
      Debug_Info *node;

      debugstr = (char *) mush_malloc(BUFFER_LEN,
				      "process_expression.debug_source");
      debugp = debugstr;
      safe_dbref(executor, debugstr, &debugp);
      safe_chr('!', debugstr, &debugp);
      for (j = 0; j <= pe_info->nest_depth; j++)
	safe_chr(' ', debugstr, &debugp);
      sourcestr = debugp;
      mark = *str;
      process_expression(debugstr, &debugp, str,
			 executor, caller, enactor,
			 PE_NOTHING, tflags, pe_info);
      *str = mark;
      if (eflags & PE_COMPRESS_SPACES)
	while ((debugp > sourcestr) && (debugp[-1] == ' '))
	  debugp--;
      *debugp = '\0';
      node = (Debug_Info *) mush_malloc(sizeof(Debug_Info),
					"process_expression.debug_node");
      node->string = debugstr;
      node->prev = pe_info->debug_strings;
      node->next = NULL;
      if (node->prev)
	node->prev->next = node;
      pe_info->debug_strings = node;
      pe_info->nest_depth++;
    }
  }

  /* Only strip command braces if the first character is a brace. */
  if (**str != '{')
    eflags &= ~PE_COMMAND_BRACES;

  for (;;) {
    /* Find the first "interesting" character */
    {
      char const *pos;
      int len, len2;
      /* Inlined strcspn() equivalent, to save on overhead and portability */
      pos = *str;
      while (!active_table[*(unsigned char const *) *str])
	(*str)++;
      /* Inlined safe_str(), since the source string
       * may not be null terminated */
      len = *str - pos;
      len2 = BUFFER_LEN - 1 - (*bp - buff);
      if (len > len2)
	len = len2;
      if (len >= 0) {
	memcpy(*bp, pos, len);
	*bp += len;
      }
    }

    switch (**str) {
      /* Possible terminators */
    case '}':
      if (tflags & PT_BRACE)
	goto exit_sequence;
      break;
    case ']':
      if (tflags & PT_BRACKET)
	goto exit_sequence;
      break;
    case ')':
      if (tflags & PT_PAREN)
	goto exit_sequence;
      break;
    case ',':
      if (tflags & PT_COMMA)
	goto exit_sequence;
      break;
    case ';':
      if (tflags & PT_SEMI)
	goto exit_sequence;
      break;
    case '=':
      if (tflags & PT_EQUALS)
	goto exit_sequence;
      break;
    case ' ':
      if (tflags & PT_SPACE)
	goto exit_sequence;
      break;
    case '\0':
      goto exit_sequence;
    }

    switch (**str) {
    case 0x1B:			/* ANSI escapes. */
      /* Skip over until the 'm' that matches the end. */
      for (; *str && **str && **str != 'm'; (*str)++)
	safe_chr(**str, buff, bp);
      if (*str && **str) {
	safe_chr(**str, buff, bp);
	(*str)++;
      }
      break;
    case '$':			/* Dollar subs for regedit() */
      if ((eflags & (PE_DOLLAR | PE_EVALUATE)) != (PE_DOLLAR | PE_EVALUATE)) {
	safe_chr('$', buff, bp);
	(*str)++;
      } else {
	char obuf[BUFFER_LEN];
	int p = 0;

	(*str)++;
	/* Check the first two characters after the $ for a number */
	if (isdigit((unsigned char) **str)) {
	  p = **str - '0';
	  (*str)++;
	  if (isdigit((unsigned char) **str)) {
	    p *= 10;
	    p += **str - '0';
	    (*str)++;
	  }
	} else
	  break;

	if (p >= re_subpatterns || re_offsets == NULL || re_from == NULL)
	  break;

	pcre_copy_substring(re_from, re_offsets, re_subpatterns, p, obuf,
			    BUFFER_LEN);
	safe_str(obuf, buff, bp);
      }
      break;
    case '%':			/* Percent substitutions */
      if (!(eflags & PE_EVALUATE) || (*bp - buff >= BUFFER_LEN - 1)) {
	/* peak -- % escapes (at least) one character */
	char savec;

	safe_chr('%', buff, bp);
	(*str)++;
	savec = **str;
	if (!savec)
	  goto exit_sequence;
	safe_chr(savec, buff, bp);
	(*str)++;
	switch (savec) {
	case 'Q':
	case 'q':
	case 'V':
	case 'v':
	case 'W':
	case 'w':
	case 'X':
	case 'x':
	  /* These sequences escape two characters */
	  savec = **str;
	  if (!savec)
	    goto exit_sequence;
	  safe_chr(savec, buff, bp);
	  (*str)++;
	}
	break;
      } else {
	char savec, nextc;
	char *savepos;
	ATTR *attrib;

	(*str)++;
	savec = **str;
	if (!savec)
	  goto exit_sequence;
	savepos = *bp;
	(*str)++;

	switch (savec) {
	case '%':		/* %% - a real % */
	  safe_chr('%', buff, bp);
	  break;
	case '!':		/* executor dbref */
	  safe_dbref(executor, buff, bp);
	  break;
	case '@':		/* caller dbref */
	  safe_dbref(caller, buff, bp);
	  break;
	case '#':		/* enactor dbref */
	  safe_dbref(enactor, buff, bp);
	  break;
	case ':':		/* enactor unique id */
	  safe_dbref(enactor, buff, bp);
	  safe_chr(':', buff, bp);
	  safe_integer(CreTime(enactor), buff, bp);
	  break;
	case '?':		/* function limits */
	  if (pe_info) {
	    safe_integer(pe_info->fun_invocations, buff, bp);
	    safe_chr(' ', buff, bp);
	    safe_integer(pe_info->fun_depth, buff, bp);
	  } else {
	    safe_str("0 0", buff, bp);
	  }
	  break;
	case '~':		/* enactor accented name */
	  safe_str(accented_name(enactor), buff, bp);
	  break;
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':		/* positional argument */
	  if (global_eval_context.wenv[savec - '0'])
	    safe_str(global_eval_context.wenv[savec - '0'], buff, bp);
	  break;
	case 'A':
	case 'a':		/* enactor absolute possessive pronoun */
	  if (gender < 0)
	    gender = get_gender(enactor);
	  safe_str(absp[gender], buff, bp);
	  break;
	case 'B':
	case 'b':		/* blank space */
	  safe_chr(' ', buff, bp);
	  break;
	case 'C':
	case 'c':		/* command line */
	  safe_str(global_eval_context.ccom, buff, bp);
	  break;
	case 'L':
	case 'l':		/* enactor location dbref */
	  /* The security implications of this have
	   * already been talked to death.  Deal. */
	  safe_dbref(Location(enactor), buff, bp);
	  break;
	case 'N':
	case 'n':		/* enactor name */
	  safe_str(Name(enactor), buff, bp);
	  break;
	case 'O':
	case 'o':		/* enactor objective pronoun */
	  if (gender < 0)
	    gender = get_gender(enactor);
	  safe_str(obj[gender], buff, bp);
	  break;
	case 'P':
	case 'p':		/* enactor possessive pronoun */
	  if (gender < 0)
	    gender = get_gender(enactor);
	  safe_str(poss[gender], buff, bp);
	  break;
	case 'Q':
	case 'q':		/* temporary storage */
	  nextc = **str;
	  if (!nextc)
	    goto exit_sequence;
	  (*str)++;
	  if ((qindex = qreg_indexes[(unsigned char) nextc]) == -1)
	    break;
	  if (global_eval_context.renv[qindex])
	    safe_str(global_eval_context.renv[qindex], buff, bp);
	  break;
	case 'R':
	case 'r':		/* newline */
	  if (NEWLINE_ONE_CHAR)
	    safe_chr('\n', buff, bp);
	  else
	    safe_str("\r\n", buff, bp);
	  break;
	case 'S':
	case 's':		/* enactor subjective pronoun */
	  if (gender < 0)
	    gender = get_gender(enactor);
	  safe_str(subj[gender], buff, bp);
	  break;
	case 'T':
	case 't':		/* tab */
	  safe_chr('\t', buff, bp);
	  break;
	case 'V':
	case 'v':
	case 'W':
	case 'w':
	case 'X':
	case 'x':		/* attribute substitution */
	  nextc = **str;
	  if (!nextc)
	    goto exit_sequence;
	  (*str)++;
	  temp[0] = UPCASE(savec);
	  temp[1] = UPCASE(nextc);
	  temp[2] = '\0';
	  attrib = atr_get(executor, temp);
	  if (attrib)
	    safe_str(atr_value(attrib), buff, bp);
	  break;
	default:		/* just copy */
	  safe_chr(savec, buff, bp);
	}

	if (isupper((unsigned char) savec))
	  *savepos = UPCASE(*savepos);
      }
      break;
    case '{':			/* "{}" parse group; recurse with no function check */
      if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
	(*str)++;
	break;
      }
      if (eflags & PE_LITERAL) {
	safe_chr('{', buff, bp);
	(*str)++;
	break;
      }
      if (!(eflags & (PE_STRIP_BRACES | PE_COMMAND_BRACES)))
	safe_chr('{', buff, bp);
      (*str)++;
      if (process_expression(buff, bp, str,
			     executor, caller, enactor,
			     eflags & PE_COMMAND_BRACES
			     ? (eflags & ~PE_COMMAND_BRACES)
			     : (eflags &
				~(PE_STRIP_BRACES | PE_FUNCTION_CHECK)),
			     PT_BRACE, pe_info)) {
	retval = 1;
	break;
      }

      if (**str == '}') {
	if (!(eflags & (PE_STRIP_BRACES | PE_COMMAND_BRACES)))
	  safe_chr('}', buff, bp);
	(*str)++;
      }
      /* Only strip one set of braces for commands */
      eflags &= ~PE_COMMAND_BRACES;
      break;
    case '[':			/* "[]" parse group; recurse with mandatory function check */
      if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
	(*str)++;
	break;
      }
      if (eflags & PE_LITERAL) {
	safe_chr('[', buff, bp);
	(*str)++;
	break;
      }
      if (!(eflags & PE_EVALUATE)) {
	safe_chr('[', buff, bp);
	temp_eflags = eflags & ~PE_STRIP_BRACES;
      } else
	temp_eflags = eflags | PE_FUNCTION_CHECK | PE_FUNCTION_MANDATORY;
      (*str)++;
      if (process_expression(buff, bp, str,
			     executor, caller, enactor,
			     temp_eflags, PT_BRACKET, pe_info)) {
	retval = 1;
	break;
      }
      if (**str == ']') {
	if (!(eflags & PE_EVALUATE))
	  safe_chr(']', buff, bp);
	(*str)++;
      }
      break;
    case '(':			/* Function call */
      if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
	(*str)++;
	break;
      }
      (*str)++;
      if (!(eflags & PE_EVALUATE) || !(eflags & PE_FUNCTION_CHECK)) {
	safe_chr('(', buff, bp);
	if (**str == ' ') {
	  safe_chr(**str, buff, bp);
	  (*str)++;
	}
	if (process_expression(buff, bp, str,
			       executor, caller, enactor,
			       eflags & ~PE_STRIP_BRACES, PT_PAREN, pe_info))
	  retval = 1;
	if (**str == ')') {
	  if (eflags & PE_COMPRESS_SPACES && (*str)[-1] == ' ')
	    safe_chr(' ', buff, bp);
	  safe_chr(')', buff, bp);
	  (*str)++;
	}
	break;
      } else {
	char *sargs[10];
	char **fargs;
	int sarglens[10];
	int *arglens;
	int args_alloced;
	int nfargs;
	int j;
	static char name[BUFFER_LEN];
	char *sp, *tp;
	FUN *fp;
	int temp_tflags;
	int denied;

	fargs = sargs;
	arglens = sarglens;
	for (j = 0; j < 10; j++) {
	  fargs[j] = NULL;
	  arglens[j] = 0;
	}
	args_alloced = 10;
	eflags &= ~PE_FUNCTION_CHECK;
	/* Get the function name */
	for (sp = startpos, tp = name; sp < *bp; sp++)
	  safe_chr(UPCASE(*sp), name, &tp);
	*tp = '\0';
	fp = func_hash_lookup(name);
	if (!fp) {
	  if (eflags & PE_FUNCTION_MANDATORY) {
	    *bp = startpos;
	    safe_str(T("#-1 FUNCTION ("), buff, bp);
	    safe_str(name, buff, bp);
	    safe_str(") NOT FOUND", buff, bp);
	    if (process_expression(name, &tp, str,
				   executor, caller, enactor,
				   PE_NOTHING, PT_PAREN, pe_info))
	      retval = 1;
	    if (**str == ')')
	      (*str)++;
	    break;
	  }
	  safe_chr('(', buff, bp);
	  if (**str == ' ') {
	    safe_chr(**str, buff, bp);
	    (*str)++;
	  }
	  if (process_expression(buff, bp, str, executor, caller, enactor,
				 eflags, PT_PAREN, pe_info)) {
	    retval = 1;
	    break;
	  }
	  if (**str == ')') {
	    if (eflags & PE_COMPRESS_SPACES && (*str)[-1] == ' ')
	      safe_chr(' ', buff, bp);
	    safe_chr(')', buff, bp);
	    (*str)++;
	  }
	  break;
	}
	*bp = startpos;

	/* Check for the invocation limit */
	if ((pe_info->fun_invocations >= FUNCTION_LIMIT) ||
	    (global_fun_invocations >= FUNCTION_LIMIT * 5)) {
	  e_msg = T(e_invoke);
	  e_len = strlen(e_msg);
	  if ((buff + e_len > *bp) || strcmp(e_msg, *bp - e_len))
	    safe_str(e_msg, buff, bp);
	  if (process_expression(name, &tp, str,
				 executor, caller, enactor,
				 PE_NOTHING, PT_PAREN, pe_info))
	    retval = 1;
	  if (**str == ')')
	    (*str)++;
	  break;
	}
	/* Check for the recursion limit */
	if ((pe_info->fun_depth + 1 >= RECURSION_LIMIT) ||
	    (global_fun_recursions + 1 >= RECURSION_LIMIT * 5)) {
	  safe_str(T("#-1 FUNCTION RECURSION LIMIT EXCEEDED"), buff, bp);
	  if (process_expression(name, &tp, str,
				 executor, caller, enactor,
				 PE_NOTHING, PT_PAREN, pe_info))
	    retval = 1;
	  if (**str == ')')
	    (*str)++;
	  break;
	}
	/* Get the arguments */
	temp_eflags = (eflags & ~PE_FUNCTION_MANDATORY)
	  | PE_COMPRESS_SPACES | PE_EVALUATE | PE_FUNCTION_CHECK;
	switch (fp->flags & FN_ARG_MASK) {
	case FN_LITERAL:
	  temp_eflags |= PE_LITERAL;
	  /* FALL THROUGH */
	case FN_NOPARSE:
	  temp_eflags &= ~(PE_COMPRESS_SPACES | PE_EVALUATE |
			   PE_FUNCTION_CHECK);
	  break;
	}
	denied = !check_func(executor, fp);
	if (denied)
	  temp_eflags &=
	    ~(PE_COMPRESS_SPACES | PE_EVALUATE | PE_FUNCTION_CHECK);
	temp_tflags = PT_COMMA | PT_PAREN;
	nfargs = 0;
	do {
	  char *argp;
	  if ((fp->maxargs < 0) && ((nfargs + 1) >= -fp->maxargs))
	    temp_tflags = PT_PAREN;
	  if (nfargs >= args_alloced) {
	    char **nargs;
	    int *narglens;
	    nargs = (char **) mush_malloc((nfargs + 10) * sizeof(char *),
					  "process_expression.function_arglist");
	    narglens = (int *) mush_malloc((nfargs + 10) * sizeof(int),
					   "process_expression.function_arglens");
	    for (j = 0; j < nfargs; j++) {
	      nargs[j] = fargs[j];
	      narglens[j] = arglens[j];
	    }
	    if (fargs != sargs)
	      mush_free((Malloc_t) fargs,
			"process_expression.function_arglist");
	    if (arglens != sarglens)
	      mush_free((Malloc_t) arglens,
			"process_expression.function_arglens");
	    fargs = nargs;
	    arglens = narglens;
	    args_alloced += 10;
	  }
	  fargs[nfargs] = (char *) mush_malloc(BUFFER_LEN,
					       "process_expression.function_argument");
	  argp = fargs[nfargs];
	  if (process_expression(fargs[nfargs], &argp, str,
				 executor, caller, enactor,
				 temp_eflags, temp_tflags, pe_info)) {
	    retval = 1;
	    nfargs++;
	    goto free_func_args;
	  }
	  *argp = '\0';
	  arglens[nfargs] = argp - fargs[nfargs];
	  (*str)++;
	  nfargs++;
	} while ((*str)[-1] == ',');
	if ((*str)[-1] != ')')
	  (*str)--;
	/* See if this function is enabled */
	/* Can't do this check earlier, because of possible side effects
	 * from the functions.  Bah. */
	if (denied) {
	  if (fp->flags & FN_DISABLED)
	    safe_str(T(e_disabled), buff, bp);
	  else
	    safe_str(T(e_perm), buff, bp);
	  goto free_func_args;
	} else {
	  /* If we have the right number of args, eval the function.
	   * Otherwise, return an error message.
	   * Special case: zero args is recognized as one null arg.
	   */
	  if ((fp->minargs == 0) && (nfargs == 1) && !*fargs[0]) {
	    mush_free((Malloc_t) fargs[0],
		      "process_expression.function_argument");
	    fargs[0] = NULL;
	    arglens[0] = 0;
	    nfargs = 0;
	  }
	  if ((nfargs < fp->minargs) || (nfargs > abs(fp->maxargs))) {
	    safe_str(T("#-1 FUNCTION ("), buff, bp);
	    safe_str(fp->name, buff, bp);
	    safe_str(") EXPECTS ", buff, bp);
	    if (fp->minargs == abs(fp->maxargs)) {
	      safe_integer(fp->minargs, buff, bp);
	    } else if ((fp->minargs + 1) == abs(fp->maxargs)) {
	      safe_integer(fp->minargs, buff, bp);
	      safe_str(" OR ", buff, bp);
	      safe_integer(abs(fp->maxargs), buff, bp);
	    } else if (fp->maxargs == INT_MAX) {
	      safe_str("AT LEAST ", buff, bp);
	      safe_integer(fp->minargs, buff, bp);
	    } else {
	      safe_str("BETWEEN ", buff, bp);
	      safe_integer(fp->minargs, buff, bp);
	      safe_str(" AND ", buff, bp);
	      safe_integer(abs(fp->maxargs), buff, bp);
	    }
	    safe_str(" ARGUMENTS BUT GOT ", buff, bp);
	    safe_integer(nfargs, buff, bp);
	  } else {
	    global_fun_recursions++;
	    pe_info->fun_depth++;
	    if (fp->flags & FN_BUILTIN) {
	      global_fun_invocations++;
	      pe_info->fun_invocations++;
	      fp->where.fun(fp, buff, bp, nfargs, fargs, arglens, executor,
			    caller, enactor, fp->name, pe_info);
	      if (fp->flags & FN_LOGARGS) {
		char logstr[BUFFER_LEN];
		char *logp;
		int logi;
		logp = logstr;
		safe_str(fp->name, logstr, &logp);
		safe_chr('(', logstr, &logp);
		for (logi = 0; logi < nfargs; logi++) {
		  safe_str(fargs[logi], logstr, &logp);
		  if (logi + 1 < nfargs)
		    safe_chr(',', logstr, &logp);
		}
		safe_chr(')', logstr, &logp);
		*logp = '\0';
		do_log(LT_CMD, executor, caller, "%s", logstr);
	      } else if (fp->flags & FN_LOGNAME)
		do_log(LT_CMD, executor, caller, "%s()", fp->name);
	    } else {
	      dbref thing;
	      ATTR *attrib;
	      global_fun_invocations++;
	      pe_info->fun_invocations++;
	      thing = userfn_tab[fp->where.offset].thing;
	      attrib = atr_get(thing, userfn_tab[fp->where.offset].name);
	      if (!attrib) {
		do_rawlog(LT_ERR,
			  T("ERROR: @function (%s) without attribute (#%d/%s)"),
			  fp->name, thing, userfn_tab[fp->where.offset].name);
		safe_str("#-1 @FUNCTION (", buff, bp);
		safe_str(fp->name, buff, bp);
		safe_str(") MISSING ATTRIBUTE (", buff, bp);
		safe_dbref(thing, buff, bp);
		safe_chr('/', buff, bp);
		safe_str(userfn_tab[fp->where.offset].name, buff, bp);
		safe_chr(')', buff, bp);
	      } else
		do_userfn(buff, bp, thing, attrib, nfargs, fargs,
			  executor, caller, enactor, pe_info);
	    }
	    pe_info->fun_depth--;
	    global_fun_recursions--;
	  }
	}
	/* Free up the space allocated for the args */
      free_func_args:
	for (j = 0; j < nfargs; j++)
	  if (fargs[j])
	    mush_free((Malloc_t) fargs[j],
		      "process_expression.function_argument");
	if (fargs != sargs)
	  mush_free((Malloc_t) fargs, "process_expression.function_arglist");
	if (arglens != sarglens)
	  mush_free((Malloc_t) arglens, "process_expression.function_arglens");
      }
      break;
      /* Space compression */
    case ' ':
      had_space = 1;
      safe_chr(' ', buff, bp);
      (*str)++;
      if (eflags & PE_COMPRESS_SPACES) {
	while (**str == ' ')
	  (*str)++;
      } else
	while (**str == ' ') {
	  safe_chr(' ', buff, bp);
	  (*str)++;
	}
      break;
      /* Escape charater */
    case '\\':
      if (!(eflags & PE_EVALUATE))
	safe_chr('\\', buff, bp);
      (*str)++;
      if (!**str)
	goto exit_sequence;
      /* FALL THROUGH */
      /* Basic character */
    default:
      safe_chr(**str, buff, bp);
      (*str)++;
      break;
    }
  }

exit_sequence:
  if (eflags != PE_NOTHING) {
    if ((eflags & PE_COMPRESS_SPACES) && had_space &&
	((*str)[-1] == ' ') && ((*bp)[-1] == ' '))
      (*bp)--;
    if (debugging) {
      pe_info->nest_depth--;
      **bp = '\0';
      if (strcmp(sourcestr, startpos)) {
	static char dbuf[BUFFER_LEN];
	char *dbp;
	if (pe_info->debug_strings) {
	  while (pe_info->debug_strings->prev)
	    pe_info->debug_strings = pe_info->debug_strings->prev;
	  while (pe_info->debug_strings->next) {
	    dbp = dbuf;
	    dbuf[0] = '\0';
	    safe_format(dbuf, &dbp, "%s :", pe_info->debug_strings->string);
	    *dbp = '\0';
	    if (Connected(Owner(executor)))
	      raw_notify(Owner(executor), dbuf);
	    notify_list(executor, executor, "DEBUGFORWARDLIST", dbuf,
			NA_NOLISTEN | NA_NOPREFIX);
	    pe_info->debug_strings = pe_info->debug_strings->next;
	    mush_free((Malloc_t) pe_info->debug_strings->prev,
		      "process_expression.debug_node");
	  }
	  mush_free((Malloc_t) pe_info->debug_strings,
		    "process_expression.debug_node");
	  pe_info->debug_strings = NULL;
	}
	dbp = dbuf;
	dbuf[0] = '\0';
	safe_format(dbuf, &dbp, "%s => %s", debugstr, startpos);
	*dbp = '\0';
	if (Connected(Owner(executor)))
	  raw_notify(Owner(executor), dbuf);
	notify_list(executor, executor, "DEBUGFORWARDLIST", dbuf,
		    NA_NOLISTEN | NA_NOPREFIX);
      } else {
	Debug_Info *node;
	node = pe_info->debug_strings;
	if (node) {
	  pe_info->debug_strings = node->prev;
	  if (node->prev)
	    node->prev->next = NULL;
	  mush_free((Malloc_t) node, "process_expression.debug_node");
	}
      }
      mush_free((Malloc_t) debugstr, "process_expression.debug_source");
    }
    if (realbuff) {
      **bp = '\0';
      *bp = realbp;
      safe_str(buff, realbuff, bp);
      mush_free((Malloc_t) buff, "process_expression.buffer_extension");
    }
  }
  /* Once we cross call limit, we stay in error */
  if (pe_info && CALL_LIMIT && pe_info->call_depth <= CALL_LIMIT)
    pe_info->call_depth--;
  if (made_info)
    mush_free((Malloc_t) pe_info, "process_expression.pe_info");
  if (old_iter_limit != -1) {
    inum_limit = old_iter_limit;
  }
  return retval;
}

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