/* 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"
/* ---------------------------------------------------------------------------
* 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;
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;
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':
safe_str(mudstate.curr_cmd, 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--;
*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';
}
}
}