/*
* eval.c - command evaluation and cracking
*/
/*
* $Id: eval.c,v 1.3 2005/08/08 09:43:07 murrayma Exp $
*/
#include "copyright.h"
#include "config.h"
#include "db.h"
#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)) &&
!(eval & EV_NO_COMPRESS) && !first && (cstr[-1] == ' '))
zstr--;
if ((eval & EV_STRIP_AROUND) && (*rstr == '{') && (zstr[-1] == '}')) {
rstr++;
if (mudconf.space_compress && (!(eval & EV_NO_COMPRESS) ||
(eval & EV_STRIP_LS)))
while (*rstr && isspace(*rstr))
rstr++;
rstr[-1] = '\0';
zstr--;
if (mudconf.space_compress && (!(eval & EV_NO_COMPRESS) ||
(eval & EV_STRIP_TS)))
while (zstr[-1] && isspace(zstr[-1]))
zstr--;
*zstr = '\0';
}
*zstr = '\0';
return rstr;
}
/* We can't change this to just '*zstr++ = *cstr++', because of the inherent
problems with copying a memory location to itself. */
#define NEXTCHAR \
do {if (cstr == zstr) { \
cstr++; \
zstr++; \
} else \
*zstr++ = *cstr++;} while (0)
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)) &&
!(eval & EV_NO_COMPRESS)) {
while (*rstr && isspace(*rstr))
rstr++;
*dstr = rstr;
}
zstr = cstr = rstr;
while (*cstr) {
switch (*cstr) {
case '\\': /*
* general escape
*/
case '%': /*
* also escapes chars
*/
if ((*cstr == '\\') && (eval & EV_STRIP_ESC))
cstr++;
else
NEXTCHAR;
if (*cstr)
NEXTCHAR;
first = 0;
break;
case ']':
case ')':
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;
}
first = 0;
NEXTCHAR;
break;
case '{':
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;
}
first = 0;
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 && !(eval & EV_NO_COMPRESS)) {
if (first)
rstr++;
else if (cstr[-1] == ' ')
zstr--;
}
break;
case '[':
if (cstr != rstr && cstr[-1] == ESC_CHAR) {
first = 0;
break;
}
if (sp < stacklim)
stack[sp++] = ']';
first = 0;
break;
case '(':
if (sp < stacklim)
stack[sp++] = ')';
first = 0;
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, *bp, *str;
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;
peval = (eval & ~EV_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) {
bp = fargs[arg] = alloc_lbuf("parse_arglist");
str = tstr;
exec(fargs[arg], &bp, 0, player, cause, eval | EV_FCHECK, &str,
cargs, ncargs);
*bp = '\0';
} else {
fargs[arg] = alloc_lbuf("parse_arglist");
StringCopy(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;
}
}
/*
* ---------------------------------------------------------------------------
* * 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 tcache_init(void)
{
tcache_head = NULL;
tcache_top = 1;
tcache_count = 0;
}
int tcache_empty(void)
{
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");
StringCopy(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;
}
void exec(buff, bufc, tflags, player, cause, eval, dstr, cargs, ncargs)
char *buff, **bufc;
int tflags;
dbref player, cause;
int eval, ncargs;
char **dstr;
char *cargs[];
{
#define NFARGS 30
char *fargs[NFARGS];
char *preserve[MAX_GLOBAL_REGS];
char *tstr, *tbuf, *tbufc, *savepos, *atr_gotten, *start, *oldp,
*savestr;
char savec, ch, *str;
char *realbuff = NULL, *realbp = NULL;
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;
at_space = 1;
gender = -1;
alldone = 0;
ansi = 0;
is_trace = Trace(player) && !(eval & EV_NOTRACE);
is_top = 0;
/* Extend the buffer if we need to. */
if (((*bufc) - buff) > (LBUF_SIZE - SBUF_SIZE)) {
realbuff = buff;
realbp = *bufc;
buff = (char *) malloc(LBUF_SIZE);
*bufc = buff;
}
oldp = start = *bufc;
/*
* 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");
StringCopy(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) ||
(eval & EV_NO_COMPRESS)) {
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)++;
if (eval & EV_NOFCHECK) {
safe_chr('[', buff, bufc);
*dstr = tstr;
break;
}
tbuf = parse_to(dstr, ']', 0);
if (*dstr == NULL) {
safe_chr('[', buff, bufc);
*dstr = tstr;
} else {
str = tbuf;
exec(buff, bufc, 0, player, cause,
(eval | EV_FCHECK | EV_FMAND), &str, cargs, ncargs);
(*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++;
}
str = tbuf;
exec(buff, bufc, 0, player, cause,
(eval & ~(EV_STRIP | EV_FCHECK)), &str, cargs, ncargs);
if (!(eval & EV_STRIP)) {
safe_chr('}', buff, bufc);
}
(*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 '|': /* piped command output */
safe_str(mudstate.pout, buff, bufc);
break;
case '%': /*
* Percent - a literal %
*/
safe_chr('%', buff, bufc);
break;
case 'c':
case 'C':
(*dstr)++;
if (!**dstr)
(*dstr)--;
ansi = 1;
switch (**dstr) {
case 'h': /*
* hilite
*/
safe_str(ANSI_HILITE, buff, bufc);
break;
case 'i': /*
* inverse
*/
safe_str(ANSI_INVERSE, buff, bufc);
break;
case 'f': /*
* flash
*/
safe_str(ANSI_BLINK, buff, bufc);
break;
case 'u': /* underline */
safe_str(ANSI_UNDER, buff, bufc);
break;
case 'n': /*
* normal
*/
safe_str(ANSI_NORMAL, buff, bufc);
ansi = 0;
break;
case 'x': /*
* black fg
*/
safe_str(ANSI_BLACK, buff, bufc);
break;
case 'r': /*
* red fg
*/
safe_str(ANSI_RED, buff, bufc);
break;
case 'g': /*
* green fg
*/
safe_str(ANSI_GREEN, buff, bufc);
break;
case 'y': /*
* yellow fg
*/
safe_str(ANSI_YELLOW, buff, bufc);
break;
case 'b': /*
* blue fg
*/
safe_str(ANSI_BLUE, buff, bufc);
break;
case 'm': /*
* magenta fg
*/
safe_str(ANSI_MAGENTA, buff, bufc);
break;
case 'c': /*
* cyan fg
*/
safe_str(ANSI_CYAN, buff, bufc);
break;
case 'w': /*
* white fg
*/
safe_str(ANSI_WHITE, buff, bufc);
break;
case 'X': /*
* black bg
*/
safe_str(ANSI_BBLACK, buff, bufc);
break;
case 'R': /*
* red bg
*/
safe_str(ANSI_BRED, buff, bufc);
break;
case 'G': /*
* green bg
*/
safe_str(ANSI_BGREEN, buff, bufc);
break;
case 'Y': /*
* yellow bg
*/
safe_str(ANSI_BYELLOW, buff, bufc);
break;
case 'B': /*
* blue bg
*/
safe_str(ANSI_BBLUE, buff, bufc);
break;
case 'M': /*
* magenta bg
*/
safe_str(ANSI_BMAGENTA, buff, bufc);
break;
case 'C': /*
* cyan bg
*/
safe_str(ANSI_BCYAN, buff, bufc);
break;
case 'W': /*
* white bg
*/
safe_str(ANSI_BWHITE, buff, bufc);
break;
default:
safe_chr(**dstr, 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_str(atr_gotten, buff, bufc);
free_lbuf(atr_gotten);
break;
case 'Q':
case 'q':
(*dstr)++;
i = (**dstr - '0');
if ((i >= 0) && (i <= 9) && mudstate.global_regs[i]) {
safe_str(mudstate.global_regs[i], buff, bufc);
}
if (!**dstr)
(*dstr)--;
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");
sprintf(tbuf, "#%d", cause);
safe_str(tbuf, buff, bufc);
free_sbuf(tbuf);
break;
case '!': /*
* Executor DB number
*/
tbuf = alloc_sbuf("exec.executor");
sprintf(tbuf, "#%d", 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':
if (!(eval & EV_NO_LOCATION)) {
tbuf = alloc_sbuf("exec.exloc");
sprintf(tbuf, "#%d", where_is(cause));
safe_str(tbuf, buff, bufc);
free_sbuf(tbuf);
}
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(oldp, tbuf, &tbufc);
*tbufc = '\0';
if (mudconf.space_compress) {
while ((--tbufc >= tbuf) && isspace(*tbufc));
tbufc++;
*tbufc = '\0';
}
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 = oldp;
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)) {
safe_str("#-1 PERMISSION DENIED", buff, &oldp);
*bufc = oldp;
} else {
tstr = atr_get(ufp->obj, ufp->atr, &aowner, &aflags);
if (ufp->flags & FN_PRIV)
i = ufp->obj;
else
i = player;
str = tstr;
if (ufp->flags & FN_PRES) {
for (j = 0; j < MAX_GLOBAL_REGS; j++) {
if (!mudstate.global_regs[j])
preserve[j] = NULL;
else {
preserve[j] = alloc_lbuf("eval_regs");
StringCopy(preserve[j],
mudstate.global_regs[j]);
}
}
}
exec(buff, &oldp, 0, i, cause, feval, &str, fargs,
nfargs);
*bufc = oldp;
if (ufp->flags & FN_PRES) {
for (j = 0; j < MAX_GLOBAL_REGS; j++) {
if (preserve[j]) {
if (!mudstate.global_regs[j])
mudstate.global_regs[j] =
alloc_lbuf("eval_regs");
StringCopy(mudstate.global_regs[j],
preserve[j]);
free_lbuf(preserve[j]);
} else {
if (mudstate.global_regs[j])
*(mudstate.global_regs[i]) = '\0';
}
}
}
free_lbuf(tstr);
}
/*
* Return the space allocated for the args
*/
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) {
safe_str("#-1 FUNCTION RECURSION LIMIT EXCEEDED", buff,
bufc);
} else if (mudstate.func_invk_ctr == mudconf.func_invk_lim) {
safe_str("#-1 FUNCTION INVOCATION LIMIT EXCEEDED",
buff, bufc);
} else if (!check_access(player, fp->perms)) {
safe_str("#-1 PERMISSION DENIED", buff, &oldp);
*bufc = oldp;
} else if (mudstate.func_invk_ctr < mudconf.func_invk_lim) {
fp->fun(buff, &oldp, player, cause, fargs, nfargs,
cargs, ncargs);
*bufc = oldp;
} else {
**bufc = '\0';
}
mudstate.func_nest_lev--;
} else {
*bufc = oldp;
tstr = alloc_sbuf("exec.funcargs");
sprintf(tstr, "%d", 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 && !(eval & EV_NO_COMPRESS)
&& (start != *bufc))
(*bufc)--;
/*
* The ansi() function knows how to take care of itself. However,
* if the player used a %c sub in the string, and hasn't yet
* terminated the color with a %cn yet, we'll have to do it for
* them.
*/
if (ansi == 1)
safe_str(ANSI_NORMAL, buff, bufc);
**bufc = '\0';
/*
* Report trace information
*/
if (realbuff) {
**bufc = '\0';
*bufc = realbp;
safe_str(buff, realbuff, bufc);
free(buff);
buff = realbuff;
}
if (is_trace) {
tcache_add(savestr, start);
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);
}
}
}