/* eval.c - command evaulation and cracking */
#include "copyright.h"
#include <ctype.h>
#include "db.h"
#include "externs.h"
#include "functions.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.
*/
#define NEXTCHAR \
if (cstr == zstr) { \
cstr++; \
zstr++; \
} else \
*zstr++ = *cstr++
char *parse_to(char **dstr, char 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;
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) {
if ((mudconf.space_compress ||
(eval & EV_STRIP_TS)) &&
!first && (cstr[-1] == ' '))
zstr--;
*zstr = '\0';
*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]) {
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)) {
if ((mudconf.space_compress ||
(eval & EV_STRIP_TS)) && !first &&
(cstr[-1] == ' '))
zstr--;
*zstr = '\0';
*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;
}
}
if ((mudconf.space_compress || (eval & EV_STRIP_TS)) &&
!first && (cstr[-1] == ' '))
zstr--;
*zstr = '\0';
*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 (dbref player, dbref cause, char *dstr, char delim,
int eval, char *fargs[], int nfargs, char *cargs[], int ncargs)
{
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;
} 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 (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 'M':
case 'm':
return 3;
case 'F':
case 'f':
case 'W':
case 'w':
return 2;
default:
return 1;
}
return 0;
}
char *exec (dbref player, dbref cause, int eval, char *dstr,
char *cargs[], int ncargs)
{
#define NFARGS 30
char *fargs[NFARGS];
char *buff, *bufc, *tstr, *tbuf, *tbufc, *savepos, *atr_gotten;
char savec, ch;
dbref aowner;
int at_space, nfargs, gender, i, j, alldone, aflags;
FUN *fp;
static const char *subj[4] = {"", "it", "she", "he"};
static const char *poss[4] = {"", "its", "her", "his"};
static const char *obj[4] = {"", "it", "her", "him"};
if (dstr == NULL) return NULL;
bufc = buff = alloc_lbuf("exec.buff");
at_space = 1;
gender = -1;
alldone = 0;
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);
}
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_str(atr_gotten, buff, &bufc);
free_lbuf(atr_gotten);
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 '#': /* 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;
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;
}
eval &= ~EV_FCHECK;
/* 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 = ToUpper(*tbufc);
fp = (FUN *)hashfind(tbuf, &mudstate.func_htab);
/* Do the right thing if it doesn't exist */
if (fp == NULL) {
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);
break;
}
free_sbuf(tbuf);
/* Get the arglist and count the number of args */
/* Neg # of args means catenate subsequent args */
if (fp->nargs < 0)
nfargs = -fp->nargs;
else
nfargs = NFARGS;
tstr = dstr;
dstr = parse_arglist(player, cause, dstr+1,
')', eval|EV_EVAL, 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]);
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 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->nargs == 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 (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");
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]);
break;
default:
/* A mundane character. Just copy it */
at_space = 0;
safe_chr(*dstr, buff, &bufc);
}
dstr++;
}
*bufc = '\0';
return buff;
}