tinymush-2.2.4/conf/
tinymush-2.2.4/scripts/
tinymush-2.2.4/vms/
/* eval.c - command evaulation and cracking */

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char RCSid[] = "$Id: eval.c,v 1.7 1995/03/21 00:00:09 ambar Exp $";
USE(RCSid);
#endif

#include "externs.h"
#include "attrs.h"
#include "functions.h"
#include "alloc.h"
#include "ansi.h"

/* ---------------------------------------------------------------------------
 * parse_to: Split a line at a character, obeying nesting.  The line is
 * destructively modified (a null is inserted where the delimiter was found)
 * dstr is modified to point to the char after the delimiter, and the function
 * return value points to the found string (space compressed if specified).
 * If we ran off the end of the string without finding the delimiter, dstr is
 * returned as NULL.
 */

static char *
parse_to_cleanup(eval, first, cstr, rstr, zstr)
    int eval, first;
    char *cstr, *rstr, *zstr;
{
    if ((mudconf.space_compress || (eval & EV_STRIP_TS)) &&
	!first && (cstr[-1] == ' '))
	zstr--;
    if ((eval & EV_STRIP_AROUND) && (*rstr == '{') && (zstr[-1] == '}')) {
	rstr++;
	if (mudconf.space_compress || (eval & EV_STRIP_LS))
	    while (*rstr && isspace(*rstr))
		rstr++;
	rstr[-1] = '\0';
	zstr--;
	if (mudconf.space_compress || (eval & EV_STRIP_TS))
	    while (zstr[-1] && isspace(zstr[-1]))
		zstr--;
	*zstr = '\0';
    }
    *zstr = '\0';
    return rstr;
}

#define NEXTCHAR \
if (cstr == zstr) { \
		    cstr++; \
		    zstr++; \
	    } else \
	    *zstr++ = *cstr++

char *
parse_to(dstr, delim, eval)
    char **dstr, delim;
    int eval;
{
#define stacklim 32
    char stack[stacklim];
    char *rstr, *cstr, *zstr;
    int sp, tp, first, bracketlev;

    if ((dstr == NULL) || (*dstr == NULL))
	return NULL;
    if (**dstr == '\0') {
	rstr = *dstr;
	*dstr = NULL;
	return rstr;
    }
    sp = 0;
    first = 1;
    rstr = *dstr;
    if (mudconf.space_compress | (eval & EV_STRIP_LS)) {
	while (*rstr && isspace(*rstr))
	    rstr++;
	*dstr = rstr;
    }
    zstr = cstr = rstr;
    while (*cstr) {
	switch (*cstr) {
	case '\\':		/* general escape */
	case '%':		/* also escapes chars */
	    first = 0;
	    if ((*cstr == '\\') && (eval & EV_STRIP_ESC))
		cstr++;
	    else
		NEXTCHAR;
	    if (*cstr)
		NEXTCHAR;
	    break;
	case ']':
	case ')':
	    first = 0;
	    for (tp = sp - 1; (tp >= 0) && (stack[tp] != *cstr); tp--);

	    /* If we hit something on the stack, unwind to it
	     * Otherwise (it's not on stack), if it's our delim
	     * we are done, and we convert the delim to a null
	     * and return a ptr to the char after the null.
	     * If it's not our delimiter, skip over it normally */

	    if (tp >= 0)
		sp = tp;
	    else if (*cstr == delim) {
		rstr = parse_to_cleanup(eval, first,
					cstr, rstr, zstr);
		*dstr = ++cstr;
		return rstr;
	    }
	    NEXTCHAR;
	    break;
	case '{':
	    first = 0;
	    bracketlev = 1;
	    if (eval & EV_STRIP) {
		cstr++;
	    } else {
		NEXTCHAR;
	    }
	    while (*cstr && (bracketlev > 0)) {
		switch (*cstr) {
		case '\\':
		case '%':
		    if (cstr[1]) {
			if ((*cstr == '\\') &&
			    (eval & EV_STRIP_ESC))
			    cstr++;
			else
			    NEXTCHAR;
		    }
		    break;
		case '{':
		    bracketlev++;
		    break;
		case '}':
		    bracketlev--;
		    break;
		}
		if (bracketlev > 0) {
		    NEXTCHAR;
		}
	    }
	    if ((eval & EV_STRIP) && (bracketlev == 0)) {
		cstr++;
	    } else if (bracketlev == 0) {
		NEXTCHAR;
	    }
	    break;
	default:
	    if ((*cstr == delim) && (sp == 0)) {
		rstr = parse_to_cleanup(eval, first,
					cstr, rstr, zstr);
		*dstr = ++cstr;
		return rstr;
	    }
	    switch (*cstr) {
	    case ' ':		/* space */
		if (mudconf.space_compress) {
		    if (first)
			rstr++;
		    else if (cstr[-1] == ' ')
			zstr--;
		}
		break;
	    case '[':
		first = 0;
		if (sp < stacklim)
		    stack[sp++] = ']';
		break;
	    case '(':
		first = 0;
		if (sp < stacklim)
		    stack[sp++] = ')';
		break;
	    default:
		first = 0;
	    }
	    NEXTCHAR;
	}
    }
    rstr = parse_to_cleanup(eval, first, cstr, rstr, zstr);
    *dstr = NULL;
    return rstr;
}

/* ---------------------------------------------------------------------------
 * parse_arglist: Parse a line into an argument list contained in lbufs.
 * A pointer is returned to whatever follows the final delimiter.
 * If the arglist is unterminated, a NULL is returned.  The original arglist
 * is destructively modified.
 */

char *
parse_arglist(player, cause, dstr, delim, eval,
	      fargs, nfargs, cargs, ncargs)
    dbref player, cause, eval, nfargs, ncargs;
    char *dstr, delim, *fargs[], *cargs[];
{
    char *rstr, *tstr;
    int arg, peval;

    for (arg = 0; arg < nfargs; arg++)
	fargs[arg] = NULL;
    if (dstr == NULL)
	return NULL;
    rstr = parse_to(&dstr, delim, 0);
    arg = 0;

    if (eval & EV_EVAL) {
	peval = 0;
	if (eval & EV_STRIP_LS)
	    peval |= EV_STRIP_LS;
	if (eval & EV_STRIP_TS)
	    peval |= EV_STRIP_TS;
	if (eval & EV_STRIP_ESC)
	    peval |= EV_STRIP_ESC;
	if (eval & EV_TOP)
	    peval |= EV_TOP;
	if (eval & EV_NOTRACE)
	    peval |= EV_NOTRACE;
    } else {
	peval = eval;
    }

    while ((arg < nfargs) && rstr) {
	if (arg < (nfargs - 1))
	    tstr = parse_to(&rstr, ',', peval);
	else
	    tstr = parse_to(&rstr, '\0', peval);
	if (eval & EV_EVAL) {
	    fargs[arg] = exec(player, cause, eval | EV_FCHECK, tstr,
			      cargs, ncargs);
	} else {
	    fargs[arg] = alloc_lbuf("parse_arglist");
	    strcpy(fargs[arg], tstr);
	}
	arg++;
    }
    return dstr;
}

/* ---------------------------------------------------------------------------
 * exec: Process a command line, evaluating function calls and %-substitutions.
 */

int 
get_gender(player)
    dbref player;
{
    char first, *atr_gotten;
    dbref aowner;
    int aflags;

    atr_gotten = atr_pget(player, A_SEX, &aowner, &aflags);
    first = *atr_gotten;
    free_lbuf(atr_gotten);
    switch (first) {
    case 'P':
    case 'p':
	return 4;
    case 'M':
    case 'm':
	return 3;
    case 'F':
    case 'f':
    case 'W':
    case 'w':
	return 2;
    default:
	return 1;
    }
/*NOTREACHED*/
    return 0;
}

/* ---------------------------------------------------------------------------
 * Trace cache routines.
 */

typedef struct tcache_ent TCENT;
  struct tcache_ent {
      char *orig;
      char *result;
      struct tcache_ent *next;
  }         *tcache_head;
int tcache_top, tcache_count;

void 
NDECL(tcache_init)
{
    tcache_head = NULL;
    tcache_top = 1;
    tcache_count = 0;
}

int 
NDECL(tcache_empty)
{
    if (tcache_top) {
	tcache_top = 0;
	tcache_count = 0;
	return 1;
    }
    return 0;
}

static void 
tcache_add(orig, result)
    char *orig, *result;
{
    char *tp;
    TCENT *xp;

    if (strcmp(orig, result)) {
	tcache_count++;
	if (tcache_count <= mudconf.trace_limit) {
	    xp = (TCENT *) alloc_sbuf("tcache_add.sbuf");
	    tp = alloc_lbuf("tcache_add.lbuf");
	    strcpy(tp, result);
	    xp->orig = orig;
	    xp->result = tp;
	    xp->next = tcache_head;
	    tcache_head = xp;
	} else {
	    free_lbuf(orig);
	}
    } else {
	free_lbuf(orig);
    }
}

static void 
tcache_finish(player)
    dbref player;
{
    TCENT *xp;

    while (tcache_head != NULL) {
	xp = tcache_head;
	tcache_head = xp->next;
	notify(Owner(player),
	       tprintf("%s(#%d)} '%s' -> '%s'", Name(player), player,
		       xp->orig, xp->result));
	free_lbuf(xp->orig);
	free_lbuf(xp->result);
	free_sbuf(xp);
    }
    tcache_top = 1;
    tcache_count = 0;
}

char *
exec(player, cause, eval, dstr, cargs, ncargs)
    dbref player, cause;
    int eval, ncargs;
    char *dstr, *cargs[];
{
#define	NFARGS	30
    char *fargs[NFARGS];
    char *buff, *bufc, *tstr, *tbuf, *tbufc, *savepos, *atr_gotten, *savestr;
    char savec, ch;
    dbref aowner;
    int at_space, nfargs, gender, i, j, alldone, aflags, feval;
    int is_trace, is_top, save_count;
    int ansi;
    FUN *fp;
    UFUN *ufp;

    static const char *subj[5] =
    {"", "it", "she", "he", "they"};
    static const char *poss[5] =
    {"", "its", "her", "his", "their"};
    static const char *obj[5] =
    {"", "it", "her", "him", "them"};
    static const char *absp[5] =
    {"", "its", "hers", "his", "theirs"};

    if (dstr == NULL)
	return NULL;
    bufc = buff = alloc_lbuf("exec.buff");

    at_space = 1;
    gender = -1;
    alldone = 0;
    ansi = 0;

    is_trace = Trace(player) && !(eval & EV_NOTRACE);
    is_top = 0;

    /* If we are tracing, save a copy of the starting buffer */

    savestr = NULL;
    if (is_trace) {
	is_top = tcache_empty();
	savestr = alloc_lbuf("exec.save");
	strcpy(savestr, dstr);
    }
    while (*dstr && !alldone) {
	switch (*dstr) {
	case ' ':
	    /* A space.  Add a space if not compressing or if
	     * previous char was not a space */

	    if (!(mudconf.space_compress && at_space)) {
		safe_chr(' ', buff, &bufc);
		at_space = 1;
	    }
	    break;
	case '\\':
	    /* General escape.  Add the following char without
	     * special processing */

	    at_space = 0;
	    dstr++;
	    if (*dstr) {
		safe_chr(*dstr, buff, &bufc);
	    }
	    else
		dstr--;
	    break;
	case '[':
	    /* Function start.  Evaluate the contents of the
	     * square brackets as a function.  If no closing
	     * bracket, insert the [ and continue. */

	    at_space = 0;
	    tstr = dstr++;
	    tbuf = parse_to(&dstr, ']', 0);
	    if (dstr == NULL) {
		safe_chr('[', buff, &bufc);
		dstr = tstr;
	    } else {
		tstr = exec(player, cause,
			    (eval | EV_FCHECK | EV_FMAND),
			    tbuf, cargs, ncargs);
		safe_str(tstr, buff, &bufc);
		free_lbuf(tstr);
		dstr--;
	    }
	    break;
	case '{':
	    /* Literal start.  Insert everything up to the
	     * terminating } without parsing.  If no closing
	     * brace, insert the { and continue. */

	    at_space = 0;
	    tstr = dstr++;
	    tbuf = parse_to(&dstr, '}', 0);
	    if (dstr == NULL) {
		safe_chr('{', buff, &bufc);
		dstr = tstr;
	    } else {
		if (!(eval & EV_STRIP)) {
		    safe_chr('{', buff, &bufc);
		}
		/* Preserve leading spaces (Felan) */

		if (*tbuf == ' ') {
		    safe_chr(' ', buff, &bufc);
		    tbuf++;
		}
		tstr = exec(player, cause,
			    (eval & ~(EV_STRIP | EV_FCHECK)),
			    tbuf, cargs, ncargs);
		safe_str(tstr, buff, &bufc);
		if (!(eval & EV_STRIP)) {
		    safe_chr('}', buff, &bufc);
		}
		free_lbuf(tstr);
		dstr--;
	    }
	    break;
	case '%':
	    /* Percent-replace start.  Evaluate the chars following
	     * and perform the appropriate substitution. */

	    at_space = 0;
	    dstr++;
	    savec = *dstr;
	    savepos = bufc;
	    switch (savec) {
	    case '\0':		/* Null - all done */
		dstr--;
		break;
	    case '%':		/* Percent - a literal % */
		safe_chr('%', buff, &bufc);
		break;
	    case 'r':		/* Carriage return */
	    case 'R':
		safe_str((char *) "\r\n", buff, &bufc);
		break;
	    case 't':		/* Tab */
	    case 'T':
		safe_chr('\t', buff, &bufc);
		break;
	    case 'B':		/* Blank */
	    case 'b':
		safe_chr(' ', buff, &bufc);
		break;
	    case '0':		/* Command argument number N */
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		i = (*dstr - '0');
		if ((i < ncargs) && (cargs[i] != NULL))
		    safe_str(cargs[i], buff, &bufc);
		break;
	    case 'V':		/* Variable attribute */
	    case 'v':
		dstr++;
		ch = ToUpper(*dstr);
		if (!*dstr)
		    dstr--;
		if ((ch < 'A') || (ch > 'Z'))
		    break;
		i = 100 + ch - 'A';
		atr_gotten = atr_pget(player, i, &aowner,
				      &aflags);
		safe_long_str(atr_gotten, buff, &bufc);
		free_lbuf(atr_gotten);
		break;
	    case 'Q':		/* Local registers */
	    case 'q':
		dstr++;
		if (!*dstr)
		    dstr--;
		if (!isdigit(*dstr))
		    break;
		i = (*dstr - '0');
		if (mudstate.global_regs[i]) {
		    safe_str(mudstate.global_regs[i], buff,
			     &bufc);
		}
		break;
	    case 'O':		/* Objective pronoun */
	    case 'o':
		if (gender < 0)
		    gender = get_gender(cause);
		if (!gender)
		    tbuf = Name(cause);
		else
		    tbuf = (char *) obj[gender];
		safe_str(tbuf, buff, &bufc);
		break;
	    case 'P':		/* Personal pronoun */
	    case 'p':
		if (gender < 0)
		    gender = get_gender(cause);
		if (!gender) {
		    safe_str(Name(cause), buff, &bufc);
		    safe_chr('s', buff, &bufc);
		} else {
		    safe_str((char *) poss[gender],
			     buff, &bufc);
		}
		break;
	    case 'S':		/* Subjective pronoun */
	    case 's':
		if (gender < 0)
		    gender = get_gender(cause);
		if (!gender)
		    tbuf = Name(cause);
		else
		    tbuf = (char *) subj[gender];
		safe_str(tbuf, buff, &bufc);
		break;
	    case 'A':		/* Absolute posessive */
	    case 'a':		/* idea from Empedocles */
		if (gender < 0)
		    gender = get_gender(cause);
		if (!gender) {
		    safe_str(Name(cause), buff, &bufc);
		    safe_chr('s', buff, &bufc);
		} else {
		    safe_str((char *) absp[gender],
			     buff, &bufc);
		}
		break;
	    case '#':		/* Invoker DB number */
		tbuf = alloc_sbuf("exec.invoker");
		*tbuf = '#';
		ltos(&tbuf[1], cause);
		safe_str(tbuf, buff, &bufc);
		free_sbuf(tbuf);
		break;
	    case '!':		/* Executor DB number */
		tbuf = alloc_sbuf("exec.executor");
		*tbuf = '#';
		ltos(&tbuf[1], player);
		safe_str(tbuf, buff, &bufc);
		free_sbuf(tbuf);
		break;
	    case 'N':		/* Invoker name */
	    case 'n':
		safe_str(Name(cause), buff, &bufc);
		break;
	    case 'L':		/* Invoker location db# */
	    case 'l':
		tbuf = alloc_sbuf("exec.exloc");
		*tbuf ='#';
		ltos(&tbuf[1], where_is(cause));
		safe_str(tbuf, buff, &bufc);
		free_sbuf(tbuf);
		break;
	    case 'C':
	    case 'c':		/* current command */
		safe_str(mudstate.curr_cmd, buff, &bufc);
		break;
	    case '|':		/* piped command output */
		if (mudstate.pout && *mudstate.pout)
		    safe_str(mudstate.pout, buff, &bufc);
		break;
	    case 'x':
	    case 'X':
		/* ANSI stuff */
		dstr++;
		if (!mudconf.ansi_colors) {
		    /* just skip over the characters */
		    break;
		}
		if (!*dstr) {
		    /*
		     * Note: There is an interesting bug/misfeature in
		     * the implementation of %v? and %q? -- if the second
		     * character is garbage or non-existent, it and the
		     * leading v or q gets eaten. In the interests of
		     * not changing the old behavior, this is not getting
		     * "fixed", but in this case, where moving the pointer
		     * back without existing on an error condition ends up
		     * turning things black, the behavior must by necessity
		     * be different. So we do  break out of the switch.
		     */
		    dstr--;
		    break;
		}
		ansi = 1;
		switch (*dstr) {
		    case 'h':
			safe_str(ANSI_HILITE, buff, &bufc);
			break;
		    case  'i':
			safe_str(ANSI_INVERSE, buff, &bufc);
			break;
		    case 'f':
			safe_str(ANSI_BLINK, buff, &bufc);
			break;
		    case 'n':
			safe_str(ANSI_NORMAL, buff, &bufc);
			ansi = 0;
			break;
		    case 'x':
			safe_str(ANSI_BLACK, buff, &bufc);
			break;
		    case 'r':
			safe_str(ANSI_RED, buff, &bufc);
			break;
		    case 'g':
			safe_str(ANSI_GREEN, buff, &bufc);
			break;
		    case 'y':
			safe_str(ANSI_YELLOW, buff, &bufc);
			break;
		    case 'b':
			safe_str(ANSI_BLUE, buff, &bufc);
			break;
		    case 'm':
			safe_str(ANSI_MAGENTA, buff, &bufc);
			break;
		    case 'c':
			safe_str(ANSI_CYAN, buff, &bufc);
			break;
		    case 'w':
			safe_str(ANSI_WHITE, buff, &bufc);
			break;
		    case 'X':
			safe_str(ANSI_BBLACK, buff, &bufc);
			break;
		    case 'R':
			safe_str(ANSI_BRED, buff, &bufc);
			break;
		    case 'G':
			safe_str(ANSI_BGREEN, buff, &bufc);
			break;
		    case 'Y':
			safe_str(ANSI_BYELLOW, buff, &bufc);
			break;
		    case 'B':
			safe_str(ANSI_BBLUE, buff, &bufc);
			break;
		    case 'M':
			safe_str(ANSI_BMAGENTA, buff, &bufc);
			break;
		    case 'C':
			safe_str(ANSI_BCYAN, buff, &bufc);
			break;
		    case 'W':
			safe_str(ANSI_BWHITE, buff, &bufc);
			break;
		    default:
			safe_chr(*dstr, buff, &bufc);
		}
		break;
	    default:		/* Just copy */
		safe_chr(*dstr, buff, &bufc);
	    }
	    if (isupper(savec))
		*savepos = ToUpper(*savepos);
	    break;
	case '(':
	    /* Arglist start.  See if what precedes is a function.
	     * If so, execute it if we should. */

	    at_space = 0;
	    if (!(eval & EV_FCHECK)) {
		safe_chr('(', buff, &bufc);
		break;
	    }
	    /* Load an sbuf with an uppercase version of the func
	     * name, and see if the func exists.  Trim trailing
	     * spaces from the name if configured. */

	    *bufc = '\0';
	    tbufc = tbuf = alloc_sbuf("exec.tbuf");
	    safe_sb_str(buff, tbuf, &tbufc);
	    *tbufc = '\0';
	    if (mudconf.space_compress) {
		while ((--tbufc >= tbuf) && isspace(*tbufc));
		tbufc++;
	    }
	    for (tbufc = tbuf; *tbufc; tbufc++)
		*tbufc = ToLower(*tbufc);
	    fp = (FUN *) hashfind(tbuf, &mudstate.func_htab);

	    /* If not a builtin func, check for global func */

	    ufp = NULL;
	    if (fp == NULL) {
		ufp = (UFUN *) hashfind(tbuf,
					&mudstate.ufunc_htab);
	    }
	    /* Do the right thing if it doesn't exist */

	    if (!fp && !ufp) {
		if (eval & EV_FMAND) {
		    bufc = buff;
		    safe_str((char *) "#-1 FUNCTION (",
			     buff, &bufc);
		    safe_str(tbuf, buff, &bufc);
		    safe_str((char *) ") NOT FOUND",
			     buff, &bufc);
		    alldone = 1;
		} else {
		    safe_chr('(', buff, &bufc);
		}
		free_sbuf(tbuf);
		eval &= ~EV_FCHECK;
		break;
	    }
	    free_sbuf(tbuf);

	    /* Get the arglist and count the number of args
	     * Neg # of args means catenate subsequent args
	     */

	    if (ufp)
		nfargs = NFARGS;
	    else if (fp->nargs < 0)
		nfargs = -fp->nargs;
	    else
		nfargs = NFARGS;
	    tstr = dstr;
	    if (fp && (fp->flags & FN_NO_EVAL))
		feval = (eval & ~EV_EVAL) | EV_STRIP_ESC;
	    else
		feval = eval;
	    dstr = parse_arglist(player, cause, dstr + 1,
				 ')', feval, fargs, nfargs,
				 cargs, ncargs);

	    /* If no closing delim, just insert the '(' and
	     * continue normally */

	    if (!dstr) {
		dstr = tstr;
		safe_chr(*dstr, buff, &bufc);
		for (i = 0; i < nfargs; i++)
		    if (fargs[i] != NULL)
			free_lbuf(fargs[i]);
		eval &= ~EV_FCHECK;
		break;
	    }
	    /* Count number of args returned */

	    dstr--;
	    j = 0;
	    for (i = 0; i < nfargs; i++)
		if (fargs[i] != NULL)
		    j = i + 1;
	    nfargs = j;

	    /* If it's a user-defined function, perform it now. */

	    if (ufp) {
		mudstate.func_nest_lev++;
		if (!check_access(player, ufp->perms)) {
		    strcpy(buff,
			   "#-1 PERMISSION DENIED");
		} else {
		    tstr = atr_get(ufp->obj, ufp->atr,
				   &aowner, &aflags);
		    if (ufp->flags & FN_PRIV)
			i = ufp->obj;
		    else
			i = player;
		    tbuf = exec(i, cause, feval,
				tstr, fargs, nfargs);
		    strcpy(buff, tbuf);
		    free_lbuf(tstr);
		    free_lbuf(tbuf);
		}

		/* Return the space allocated for the args */

		for (bufc = buff; *bufc; bufc++);	/* fix bufc */
		mudstate.func_nest_lev--;
		for (i = 0; i < nfargs; i++)
		    if (fargs[i] != NULL)
			free_lbuf(fargs[i]);
		eval &= ~EV_FCHECK;
		break;
	    }
	    /* If the number of args is right, perform the func.
	     * Otherwise return an error message.  Note that
	     * parse_arglist returns zero args as one null arg,
	     * so we have to handle that case specially.
	     */

	    if ((fp->nargs == 0) && (nfargs == 1)) {
		if (!*fargs[0]) {
		    free_lbuf(fargs[0]);
		    fargs[0] = NULL;
		    nfargs = 0;
		}
	    }
	    if ((nfargs == fp->nargs) ||
		(nfargs == -fp->nargs) ||
		(fp->flags & FN_VARARGS)) {

		/* Check recursion limit */

		mudstate.func_nest_lev++;
		mudstate.func_invk_ctr++;
		if (mudstate.func_nest_lev >=
		    mudconf.func_nest_lim) {
		    strcpy(buff,
			   "#-1 FUNCTION RECURSION LIMIT EXCEEDED");
		} else if (mudstate.func_invk_ctr ==
			   mudconf.func_invk_lim) {
		    strcpy(buff,
			   "#-1 FUNCTION INVOCATION LIMIT EXCEEDED");
		} else if (!check_access(player, fp->perms)) {
		    strcpy(buff,
			   "#-1 PERMISSION DENIED");
		} else if (mudstate.func_invk_ctr <
			   mudconf.func_invk_lim) {
		    fp->fun(buff, player, cause,
			    fargs, nfargs, cargs, ncargs);
		} else {
		    *bufc = '\0';
		}
		for (bufc = buff; *bufc; bufc++);	/* fix bufc */
		mudstate.func_nest_lev--;
	    } else {
		bufc = buff;
		tstr = alloc_sbuf("exec.funcargs");
		ltos(tstr, fp->nargs);
		safe_str((char *) "#-1 FUNCTION (",
			 buff, &bufc);
		safe_str((char *) fp->name, buff, &bufc);
		safe_str((char *) ") EXPECTS ",
			 buff, &bufc);
		safe_str(tstr, buff, &bufc);
		safe_str((char *) " ARGUMENTS",
			 buff, &bufc);
		free_sbuf(tstr);
	    }

	    /* Return the space allocated for the arguments */

	    for (i = 0; i < nfargs; i++)
		if (fargs[i] != NULL)
		    free_lbuf(fargs[i]);
	    eval &= ~EV_FCHECK;
	    break;
	default:
	    /* A mundane character.  Just copy it */

	    at_space = 0;
	    safe_chr(*dstr, buff, &bufc);
	}
	dstr++;
    }

    /* If we're eating spaces, and the last thing was a space,
     * eat it up. Complicated by the fact that at_space is
     * initially true. So check to see if we actually put something
     * in the buffer, too.
     */

    if (mudconf.space_compress && at_space && (bufc != buff))
	bufc--;

    /* Make sure that ansi colors don't bleed messily. If a %x substitution
     * was used but not terminated by a %xn, we need to terminate it here.
     * The ansi() function will already cope -- we don't need to worry
     * about it.
     */
    if (ansi == 1)
	safe_str(ANSI_NORMAL, buff, &bufc);

    *bufc = '\0';

    /* Report trace information */

    if (is_trace) {
	tcache_add(savestr, buff);
	save_count = tcache_count - mudconf.trace_limit;;
	if (is_top || !mudconf.trace_topdown)
	    tcache_finish(player);
	if (is_top && (save_count > 0)) {
	    tbuf = alloc_mbuf("exec.trace_diag");
	    sprintf(tbuf,
		    "%d lines of trace output discarded.",
		    save_count);
	    notify(player, tbuf);
	    free_mbuf(tbuf);
	}
    }
    return buff;
}

/* ---------------------------------------------------------------------------
 * save_global_regs, restore_global_regs:  Save and restore the global
 * registers to protect them from various sorts of munging.
 */

void save_global_regs(funcname, preserve)
     const char *funcname;
     char *preserve[];
{
    int i;

    for (i = 0; i < MAX_GLOBAL_REGS; i++) {
	if (!mudstate.global_regs[i])
	    preserve[i] = NULL;
	else {
	    preserve[i] = alloc_lbuf(funcname);
	    strcpy(preserve[i], mudstate.global_regs[i]);
	}
    }
}

void restore_global_regs(funcname, preserve)
     const char *funcname;
     char *preserve[];
{
    int i;

    for (i = 0; i < MAX_GLOBAL_REGS; i++) {
	if (preserve[i]) {
	    if (!mudstate.global_regs[i])
		mudstate.global_regs[i] = alloc_lbuf(funcname);
	    strcpy(mudstate.global_regs[i], preserve[i]);
	    free_lbuf(preserve[i]);
	} else {
	    if (mudstate.global_regs[i])
		*(mudstate.global_regs[i]) = '\0';
	}
    }
}