/* 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; }