/* 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 (!*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; } if (!mudconf.ansi_colors) { /* just skip over the characters */ 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'; } } }