pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* parse.c */

#include "copyrite.h"

#include "config.h"
#include <limits.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>

#include "conf.h"
#include "ansi.h"
#include "dbdefs.h"
#include "externs.h"
#include "function.h"
#include "globals.h"
#include "intrface.h"
#include "match.h"
#include "mushdb.h"
#include "parse.h"

#ifdef MEM_CHECK
#include "memcheck.h"
#endif
#include "mymalloc.h"
#include "confmagic.h"

extern char ccom[];		/* bsd.c */
extern char *absp[], *obj[], *poss[], *subj[];	/* fundb.c */

typedef struct debug_info Debug_Info;
struct debug_info {
  char *string;
  Debug_Info *prev;
  Debug_Info *next;
};

struct pe_info {
  int fun_invocations;
  int fun_depth;
  int nest_depth;
  Debug_Info *debug_strings;
};

/* Common error messages */
char e_int[] = "#-1 ARGUMENT MUST BE INTEGER";
char e_ints[] = "#-1 ARGUMENTS MUST BE INTEGERS";
#ifdef FLOATING_POINTS
char e_num[] = "#-1 ARGUMENT MUST BE NUMBER";
char e_nums[] = "#-1 ARGUMENTS MUST BE NUMBERS";
#endif

dbref
parse_dbref(str)
    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))
    return NOTHING;
  for (p = str + 1; isdigit(*p); p++) {
  }
  if (*p)
    return NOTHING;

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

int
parse_boolean(str)
    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.
     */
    if (!str)
      return 0;
    if (str[0] == '#' && str[1] == '-')		/* check like this so error msgs */
      return 0;			/* are false */
    if (is_strict_number(str))
      return parse_number(str) != 0;	/* avoid rounding problems */
    while (*str == ' ')
      str++;
    return *str != '\0';	/* force to 1 or 0 */
  }
}

int
is_boolean(str)
    char const *str;
{
  if (TINY_BOOLEANS)
    return is_integer(str);
  else
    return 1;
}

int
is_dbref(str)
    char const *str;
{
  if (!str || (*str != NUMBER_TOKEN))
    return 0;
  if (*(str + 1) == '-') {
    str++;
  }
  for (str++; isdigit(*str); str++) {
  }
  return !*str;
}

int
is_integer(str)
    char const *str;
{
  char const *p = str;
  int val;
  /* If we're emulating Tiny, anything is an integer */
  if (options.tiny_math)
    return 1;
  if (!str)
    return 0;
  while (isspace(*str))
    str++;
  if (!*str)
    return (NULL_EQ_ZERO ? 1 : 0);
  if ((*str == '-') && isdigit(str[1]))
    str += 2;
  while (isdigit(*str))
    str++;
  while (isspace(*str))
    str++;
  if (*str)
    return 0;
  val = parse_integer(p);
  if ((val > HUGE_INT) || (-val > HUGE_INT))
    return 0;
  return 1;
}

int
is_strict_number(str)
char const *str;
{
  char const *p = str;
  NVAL val;

  if (!str)
    return 0;
  while (isspace(*str))
    str++;
  if (!*str)
    return (NULL_EQ_ZERO ? 1 : 0);
  if (((*str == '-') || (*str == '+')) && str[1])
    str++;
  while (isdigit(*str))
    str++;
  if (*str == '.')
    str++;
  while (isdigit(*str))
    str++;
  while (isspace(*str))
    str++;
  if (*str)
    return 0;
  val = parse_number(p);
  if ((val > HUGE_NVAL) || (-val > HUGE_NVAL))
    return 0;
  return 1;
}

int
is_number(str)
    char const *str;
{
  /* If we're emulating Tiny, anything is a number */
  if (options.tiny_math)
    return 1;
  return is_strict_number(str);
}

/* Table of interesting characters for process_expression() */
static char active_table[UCHAR_MAX + 1];

/* Function to initialize table above */
void
init_process_expression()
{
  active_table['\0'] = 1;
  active_table['%'] = 1;
  active_table['{'] = 1;
  active_table['['] = 1;
  active_table['('] = 1;
  active_table['\\'] = 1;
  active_table[' '] = 1;
  active_table['}'] = 1;
  active_table[']'] = 1;
  active_table[')'] = 1;
  active_table[','] = 1;
  active_table[';'] = 1;
  active_table['='] = 1;
}

#ifdef WIN32
#pragma warning( disable : 4761)	/* NJG: disable warning re conversion */
#endif
/* Function and other substitution evaluation. */
void
process_expression(buff, bp, str, executor, caller, enactor,
		   eflags, tflags, pe_info)
    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;

  if (!buff || !bp || !str || !*str)
    return;
  if (Halted(executor))
    eflags = PE_NOTHING;
  if (eflags & PE_COMPRESS_SPACES)
    while (**str == ' ')
      (*str)++;
  if (!*str)
    return;

  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;
    }
    debugging = Debug(executor) && Connected(Owner(executor));
    if (debugging) {
      int j;
      char *debugp;
      char const *mark;
      Debug_Info *node;

      if (!pe_info) {
	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->debug_strings = NULL;
      }
      debugstr = (char *) mush_malloc(BUFFER_LEN,
				      "process_expression.debug_source");
      debugp = debugstr;
      safe_str(unparse_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++;
    }
  }
  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 '%':			/* Percent substitutions */
      if (!(eflags & PE_EVALUATE)) {
	/* 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)++;
	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_str(unparse_dbref(executor), buff, bp);
	  break;
	case '@':		/* caller dbref */
	  safe_str(unparse_dbref(caller), buff, bp);
	  break;
	case '#':		/* enactor dbref */
	  safe_str(unparse_dbref(enactor), buff, bp);
	  break;
	case '?':		/* function limits */
	  if (pe_info) {
	    safe_str(unparse_integer(pe_info->fun_invocations), buff, bp);
	    safe_chr(' ', buff, bp);
	    safe_str(unparse_integer(pe_info->fun_depth), buff, bp);
	  } else {
	    safe_str("0 0", 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 (wenv[savec - '0'])
	    safe_str(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(ccom, buff, bp);
	  break;
	case 'L':
	case 'l':		/* enactor location dbref */
	  /* The security implications of this have
	   * already been talked to death.  Deal. */
	  safe_str(unparse_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 (!isdigit(nextc))
	    break;
	  if (renv[nextc - '0'])
	    safe_str(renv[nextc - '0'], buff, bp);
	  break;
	case 'R':
	case 'r':		/* newline */
	  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(uncompress(attrib->value), buff, bp);
	  break;
	default:		/* just copy */
	  safe_chr(savec, buff, bp);
	}

	if (isupper(savec))
	  *savepos = UPCASE(*savepos);
      }
      break;

    case '{':			/* "{}" parse group; recurse with no function check */
      if (!pe_info && eflags != PE_NOTHING) {
	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->debug_strings = NULL;
      }
      if (eflags & PE_LITERAL) {
	safe_chr('{', buff, bp);
	(*str)++;
	break;
      }
      if (!(eflags & PE_STRIP_BRACES))
	safe_chr('{', buff, bp);
      (*str)++;
      process_expression(buff, bp, str,
			 executor, caller, enactor,
			 eflags & ~(PE_STRIP_BRACES | PE_FUNCTION_CHECK),
			 PT_BRACE, pe_info);
      if (**str == '}') {
	if (!(eflags & PE_STRIP_BRACES))
	  safe_chr('}', buff, bp);
	(*str)++;
      }
      break;

    case '[':			/* "[]" parse group; recurse with mandatory function check */
      if (!pe_info && eflags != PE_NOTHING) {
	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->debug_strings = NULL;
      }
      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)++;
      process_expression(buff, bp, str,
			 executor, caller, enactor,
			 temp_eflags, PT_BRACKET, pe_info);
      if (**str == ']') {
	if (!(eflags & PE_EVALUATE))
	  safe_chr(']', buff, bp);
	(*str)++;
      }
      break;

    case '(':			/* Function call */
      if (!pe_info && eflags != PE_NOTHING) {
	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->debug_strings = NULL;
      }
      (*str)++;
      if (!(eflags & PE_EVALUATE) || !(eflags & PE_FUNCTION_CHECK)) {
	safe_chr('(', buff, bp);
	process_expression(buff, bp, str,
			   executor, caller, enactor,
			   eflags & ~PE_STRIP_BRACES, PT_PAREN, pe_info);
	if (**str == ')') {
	  safe_chr(')', buff, bp);
	  (*str)++;
	}
	break;
      } else {
	char *sargs[10];
	char **fargs;
	int args_alloced;
	int nfargs;
	int j;
	char *name;
	char *sp, *tp;
	FUN *fp;
	int temp_tflags;

	fargs = sargs;
	for (j = 0; j < 10; j++)
	  fargs[j] = NULL;
	args_alloced = 10;

	eflags &= ~PE_FUNCTION_CHECK;

	/* Get the function name */
	name = (char *) mush_malloc(BUFFER_LEN,
				    "process_expression.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("#-1 FUNCTION (", buff, bp);
	    safe_str(name, buff, bp);
	    safe_str(") NOT FOUND", buff, bp);
	    process_expression(name, &tp, str,
			       executor, caller, enactor,
			       PT_NOTHING, PT_PAREN, pe_info);
	    if (**str == ')')
	      (*str)++;
	    mush_free((Malloc_t) name, "process_expression.function_name");
	    goto exit_sequence;
	  }
	  mush_free((Malloc_t) name, "process_expression.function_name");
	  safe_chr('(', buff, bp);
	  if (**str == ' ')
	    safe_chr(*(*str)++, buff, bp);
	  process_expression(buff, bp, str,
			     executor, caller, enactor,
			     eflags, PT_PAREN, pe_info);
	  if (**str == ')') {
	    if (eflags & PE_COMPRESS_SPACES && (*str)[-1] == ' ')
	      safe_chr(' ', buff, bp);
	    safe_chr(')', buff, bp);
	    (*str)++;
	  }
	  break;
	}
	mush_free((Malloc_t) name, "process_expression.function_name");
	*bp = startpos;

	/* Get the arguments */
	temp_eflags = (eflags & ~PE_FUNCTION_MANDATORY)
	  | PE_COMPRESS_SPACES | PE_EVALUATE | PE_FUNCTION_CHECK;
	if (fp->fun != fun_gfun) {
	  switch (fp->ftype) {
	  case FN_LITERAL:
	    temp_eflags |= PE_LITERAL;
	    /* FALL THROUGH */
	  case FN_NOPARSE:
	    temp_eflags &= ~(PE_COMPRESS_SPACES | PE_EVALUATE |
			     PE_FUNCTION_CHECK);
	    break;
	  }
	}
	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;
	    nargs = (char **) mush_malloc((nfargs + 10) * sizeof(char *),
				  "process_expression.function_arglist");
	    for (j = 0; j < nfargs; j++)
	      nargs[j] = fargs[j];
	    if (fargs != sargs)
	      mush_free((Malloc_t) fargs,
			"process_expression.function_arglist");
	    fargs = nargs;
	    args_alloced += 10;
	  }
	  fargs[nfargs] = (char *) mush_malloc(BUFFER_LEN,
				 "process_expression.function_argument");
	  argp = fargs[nfargs];
	  process_expression(fargs[nfargs], &argp, str,
			     executor, caller, enactor,
			     temp_eflags, temp_tflags, pe_info);
	  *argp = '\0';
	  (*str)++;
	  nfargs++;
	} while ((*str)[-1] == ',');
	if ((*str)[-1] != ')')
	  (*str)--;

	/* 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;
	  nfargs = 0;
	}
	if ((nfargs < fp->minargs) || (nfargs > abs(fp->maxargs))) {
	  *bp = buff;
	  safe_str("#-1 FUNCTION (", buff, bp);
	  safe_str(fp->name, buff, bp);
	  safe_str(") EXPECTS ", buff, bp);
	  if (fp->minargs == abs(fp->maxargs)) {
	    safe_str(unparse_integer(fp->minargs), buff, bp);
	  } else if ((fp->minargs + 1) == abs(fp->maxargs)) {
	    safe_str(unparse_integer(fp->minargs), buff, bp);
	    safe_str(" OR ", buff, bp);
	    safe_str(unparse_integer(abs(fp->maxargs)), buff, bp);
	  } else if (fp->maxargs == INT_MAX) {
	    safe_str("AT LEAST ", buff, bp);
	    safe_str(unparse_integer(fp->minargs), buff, bp);
	  } else {
	    safe_str("BETWEEN ", buff, bp);
	    safe_str(unparse_integer(fp->minargs), buff, bp);
	    safe_str(" AND ", buff, bp);
	    safe_str(unparse_integer(abs(fp->maxargs)), buff, bp);
	  }
	  safe_str(" ARGUMENTS", buff, bp);
	} else {
	  pe_info->fun_invocations++;
	  pe_info->fun_depth++;
	  if (pe_info->fun_invocations == options.func_invk_lim) {
	    safe_str("#-1 FUNCTION INVOCATION LIMIT EXCEEDED", buff, bp);
	  } else if (pe_info->fun_invocations > options.func_invk_lim) {
	    /* nothing */
	  } else if (pe_info->fun_depth >= options.func_nest_lim) {
	    safe_str("#-1 FUNCTION RECURSION LIMIT EXCEEDED", buff, bp);
	  } else if (fp->fun != fun_gfun)
	    fp->fun(buff, bp, nfargs, fargs, executor, caller, enactor,
		    fp->name, pe_info);
	  else {
	    dbref thing;
	    ATTR *attrib;
	    thing = userfn_tab[GF_Index(fp->ftype)].thing;
	    attrib = atr_get(thing, userfn_tab[GF_Index(fp->ftype)].name);
	    if (!attrib) {
	      do_rawlog(LT_ERR,
		      "ERROR: @function (%s) without attribute (#%d/%s)",
		  fp->name, thing, userfn_tab[GF_Index(fp->ftype)].name);
	      safe_str("#-1 @FUNCTION (", buff, bp);
	      safe_str(fp->name, buff, bp);
	      safe_str(") MISSING ATTRIBUTE (", buff, bp);
	      safe_str(unparse_dbref(thing), buff, bp);
	      safe_chr('/', buff, bp);
	      safe_str(userfn_tab[GF_Index(fp->ftype)].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--;
	}

	/* Free up the space allocated for the 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");
      }
      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)) {
	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) {
	    raw_notify(Owner(executor),
		       tprintf("%s :", pe_info->debug_strings->string));
	    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;
	}
	raw_notify(Owner(executor), tprintf("%s => %s", debugstr, startpos));
      } 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 (made_info)
      mush_free((Malloc_t) pe_info, "process_expression.pe_info");
    if (realbuff) {
      **bp = '\0';
      *bp = realbp;
      safe_str(buff, realbuff, bp);
      mush_free((Malloc_t) buff, "process_expression.buffer_extension");
    }
  }
}
#ifdef WIN32
#pragma warning( default : 4761)	/* NJG: enable warning re conversion */
#endif