/
umud/DOC/
umud/DOC/examples/
umud/DOC/internals/
umud/DOC/wizard/
umud/MISC/
umud/MISC/dbchk/
umud/RWHO/rwhod/
/*
	Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
*/

#ifndef	lint
static	char	RCSid[] = "$Header: /home/mjr/hacks/umud/RCS/run.c,v 1.12 91/10/22 11:29:28 mjr Exp $";
#endif

/* configure all options BEFORE including system stuff. */
#include	"config.h"


#include	<stdio.h>
#include	<ctype.h>

#include	"mud.h"
#include	"cmd.h"
#include	"match.h"
#include	"vars.h"

/*
routines to run commands, expand variables, and tokenize. if you modify
this to search different locations for macros, you'll probably want to
look at runargv(), rather than all the string-thrashing code.

if you are adding a new variable or variable type, you will probably
want to look *CAREFULLY* at vlookup.
*/


static	int	nowhereman;		/* player is nowhere */
static	char	*actor;			/* real player who started all this */
static	int	recursion_depth;

#ifdef COMMAND_STATS

static long int hotwired = 0;
static long int atsyscmd = 0;
static long int syscmd = 0;
static long int exitstaken = 0;
static long int whomac = 0;
static long int aswhomac = 0;
static long int roommac = 0;
static long int usemac = 0;
static long int invmac = 0;

cmd__cmdstats(ac,av,who,aswho)
int	ac;
char	*av[];
char	*who;
char	*aswho;
{
	char	buf[32];

	say(who,"Hotwired (i.e. : and \") ",itoa(hotwired,buf),"\n",(char *)0);
	say(who,"@<system macro/command> ",itoa(atsyscmd,buf),"\n",(char *)0);
	say(who,"<system macro/command> ",itoa(syscmd,buf),"\n",(char *)0);
	say(who,"exits taken ",itoa(exitstaken,buf),"\n",(char *)0);
	say(who,"macros on who ",itoa(whomac,buf),"\n",(char *)0);
	say(who,"macros on aswho ",itoa(aswhomac,buf),"\n",(char *)0);
	say(who,"macros on rooms ",itoa(roommac,buf),"\n",(char *)0);
	say(who,"macros on used objects ",itoa(usemac,buf),"\n",(char *)0);
	say(who,"macros on inventory ",itoa(invmac,buf),"\n",(char *)0);
}

#endif

/*
copy 's' into the expansion buffer. return nonzero if it don't fit.
*/
static	int
pastein(s,bp,lp)
char	*s;
char	**bp;
int	*lp;
{
	while(s && *s != '\0') {
		if(--(*lp) <= 0)
			return(1);
		**bp = *s++, (*bp)++;
	}
	return(0);
}




/*
	vlookup is responsible for taking a string and returning
another string that somehow has something to do with the first.
a buffer is provided, in case string space is needed.
*/
static	char	*
vlookup(nam,who,aswho,ac,av,buf,bsiz)
char	*nam;
char	*who;
char	*aswho;
int	ac;
char	*av[];
char	*buf;
int	bsiz;
{
	if(nam == (char *)0 || *nam == '\0')
		return((char *)0);


	/* locale */
	if(!strcmp(nam,"here"))
		return(ut_loc(aswho));


	/* actor */
	if(!strcmp(nam,"actor"))
		return(run_actor());


	/* me */
	if(!strcmp(nam,"me"))
		return(aswho);


	/* self */
	if(!strcmp(nam,"self"))
		return(aswho);


	/* subv */
	if(!strcmp(nam,"subv")) {
		char	*sp;

		if((sp = ut_getatt(who,0,typ_str,nam,(char *)0)) == (char *)0)
			return("it");
		return(sp);
	}

	/* Subv */
	if(!strcmp(nam,"Subv")) {
		char	*sp;

		if((sp = ut_getatt(who,0,typ_str,nam,(char *)0)) == (char *)0)
			return("It");
		return(sp);
	}


	/* objv */
	if(!strcmp(nam,"objv")) {
		char	*sp;

		if((sp = ut_getatt(who,0,typ_str,nam,(char *)0)) == (char *)0)
			return("it");
		return(sp);
	}

	/* Objv */
	if(!strcmp(nam,"Objv")) {
		char	*sp;

		if((sp = ut_getatt(who,0,typ_str,nam,(char *)0)) == (char *)0)
			return("It");
		return(sp);
	}


	/* posv */
	if(!strcmp(nam,"posv")) {
		char	*sp;

		if((sp = ut_getatt(who,0,typ_str,nam,(char *)0)) == (char *)0)
			return("its");
		return(sp);
	}

	/* Posv */
	if(!strcmp(nam,"Posv")) {
		char	*sp;

		if((sp = ut_getatt(who,0,typ_str,nam,(char *)0)) == (char *)0)
			return("Its");
		return(sp);
	}


	/* check for $# - param count */
	if(!strcmp(nam,"#"))
		return(itoa(ac,buf));


	/* check for $1 - $ac - individual params */
	if(isdigit(*nam)) {
		int	anum;

		if((anum = atoi(nam)) >= 0 && anum < ac)
			return(av[anum]);
		return((char *)0);
	}


	/* check for $* and $+# - concatenated params */
	if(*nam == '*' || *nam == '+') {
		char	*xp = buf;
		int	xs = bsiz;
		char	*yp;
		int	lo = 0;

		/* if $+#, then figure out the low bound */
		if(*nam != '*') {
			if((lo = atoi(nam + 1)) < 0 || lo >= ac)
				return((char *)0);
		}

		/* now concatenate args */
		for(; lo < ac; lo++) {
			for(yp = av[lo]; *yp != '\0';) {
				if(--xs <= 0)
					return((char *)0);
				*xp++ = *yp++;
			}
			if(--xs <= 0)
				return((char *)0);
			*xp++ = ' ';
		}

		/* ok. */
		*(xp - 1) = '\0';
		return(buf);
	}


	/* and last but not least, we get to the hairy stuff. */
	if(*nam == '@') {
		char	ob[MAXOID];

		nam++;

		if(matchlocal(who,nam,ut_loc(who),MTCH_UNIQ|MTCH_NONLOC|MTCH_QUIET,ob)
				&& matchlocal(who,nam,ut_loc(who),MTCH_FRST|MTCH_QUIET,ob))
			return((char *)0);
		(void)strcpy(buf,ob);
		return(buf);
	}


	if(*nam == '!') {
		char	ob[MAXOID];
		int	avp;

		nam++;

		if(!isdigit(*nam))
			return((char *)0);
		if((avp = atoi(nam)) < 0 || avp >= ac)
			return((char *)0);

		if(matchlocal(who,av[avp],ut_loc(who),MTCH_UNIQ|MTCH_NONLOC|MTCH_QUIET,ob)
				&& matchlocal(who,av[avp],ut_loc(who),MTCH_FRST|MTCH_QUIET,ob))
			return((char *)0);

		(void)strcpy(buf,ob);
		return(buf);
	}

	return((char *)0);
}




/*
	iteratively look up the components of a $-variable and when
we reach a final result, paste it in.
*/
int
vresolve(nam,who,aswho,ac,av,bp,lp)
char	*nam;
char	*who;
char	*aswho;
int	ac;
char	*av[];
char	**bp;
int	*lp;
{
	Obj	*op;
	int	wiz;
	char	*e = nam;
	char	*lkp;
	char	*nxt;
	char	buf[MUDBUF];

	if(nam == (char *)0 || *nam == '\0')
		return(0);

	if((e = index(e,'.')) != (char *)0)
		*e++ = '\0';

	/* resolve first part */
	if((lkp = vlookup(nam,who,aswho,ac,av,buf,sizeof(buf))) == (char *)0)
		return(0);

	/* happy ending? (usual case) */
	if(e == (char *)0) {
		return(pastein(lkp,bp,lp));
	}

	/* sigh. */
	wiz = ut_flagged(aswho,var_wiz);
	
	/* gets tricky. we have '.'s in there. iterate our ass off */
	while(1) {
		char	*ap;
		char	*atp;

		if((nxt = e) == (char *)0  || *nxt == '\0')
			break;

		if((atp = var_namatch(nxt)) == (char *)0) {
			atp = nxt;
		}

		if((e = index(e,'.')) != (char *)0)
			*e++ = '\0';
		if((op = cache_get(lkp)) == (Obj *)0)
			return(0);
		if((ap = objattr(op,atp,(int *)0)) == (char *)0)
			return(0);
		if(e != (char *)0 && *e != '\0' && !attistype(ap,typ_obj))
			return(0);
		if((ap = attdata(ap)) == (char *)0)
			return(0);

		/* final insult: can the guy even see it ? */
		if(!wiz && !var_ispublic(atp,who,aswho,lkp) && !ut_isobjown(aswho,lkp))
			return(0);

		lkp = ap;
	}
	return(pastein(lkp,bp,lp));
}




/*
tokenize a command line into an argv. this is complex, but *CAN'T*
use static buffers since it can get called in recursion. this is
egregiously more complicated than I'd like it to be, but all this
string fiddling requires some hefty error checking. at least Saber-C
likes it, even if I don't  ;)
*/
static	int
enargv(bp,av,avsiz,buf,bsiz,retp,who,aswho,cac,cav)
char	*bp;
char	**av;
int	avsiz;
char	buf[];
int	bsiz;
char	**retp;
char	*who;
char	*aswho;
int	cac;
char	*cav[];
{
	char	*op;
	int	ac = 0;
	int	quot = 0;

	/* clean slate */
	av[0] = op = buf;
	*op = '\0';
	av[1] = (char *)0;

	/* preskip white/junk */
	while(*bp != '\0' && (isspace(*bp) || !isprint(*bp) || *bp == ';'))
		bp++;

	/* this is basically a BIG case statement */
	while(*bp != '\0') {


		/* drop nonprint */
		if(!isprint(*bp)) {
			bp++;
			continue;
		}


		/* set a quotation mark if on is needed */
		if(!quot && (*bp == '\"' || *bp == '\'')) {
			quot = *bp++;
			continue;
		}


		/* accept a word OR a virtual newline */
		if((isspace(*bp) || *bp == ';') && !quot) {
			if(bsiz-- <= 0) {
				if(retp != (char **)0)
					*retp = (char *)0;
				return(-1);
			}
			*op++ = '\0';

			if(++ac >= avsiz) {
				if(retp != (char **)0)
					*retp = (char *)0;
				return(-2);
			}
			av[ac] = op;
			av[ac + 1] = (char *)0;
			*op = '\0';

			while(isspace(*bp))	/* eat whitespace first! */
				bp++;

			/* if semic (virtual line break) return now */
			if(*bp == ';') {
				if(retp != (char **)0)
					*retp = ++bp;
				return(ac);
			}

			if(*bp == '\0') {
				if(retp != (char **)0)
					*retp = bp;
				return(ac);
			}

			continue;
		}


		/* end a quote */
		if(quot && *bp == quot) {
			quot = 0;
			bp++;
			continue;
		}


		/* check escapes - do *NOT* permit escaped newlines!!!! */
		if(*bp == '\\') {
			*op++ = *(++bp);

			if(*bp == '\0')
				break;
			bp++;
			continue;
		}


		/* handle '=' to tokenize to end-of-line hack */
		if(*bp == '=') {
			bp++;

			while(isspace(*bp) && *bp != '\0')
				bp++;

			/* if we are currently in mid-arg, terminate */
			if(av[ac] != (char *)0 && av[ac] != op) {
				*op++ = '\0';
				if(++ac >= avsiz) {
					if(retp != (char **)0)
						*retp = (char *)0;
					return(-2);
				}
				av[ac] = op;
			}

			while(*bp != '\0') {
				if(--bsiz < 0) {
					if(retp != (char **)0)
						*retp = (char *)0;
					return(-1);
				}
				*op++ = *bp++;
			}
			*op++ = '\0';

			if(++ac >= avsiz) {
				if(retp != (char **)0)
					*retp = (char *)0;
				return(-2);
			}
			av[ac] = (char *)0;
			if(retp != (char **)0)
				*retp = bp;
			return(ac);
		}


		/* expand $vars */
		if(quot != '\'' && *bp == '$') {
			char	vb[MAXVLEN];
			int	vl = 0;

			/* transform "$$" -> "$" */
			if(*(++bp) == '$') {
				if(--bsiz < 0) {
					if(retp != (char **)0)
						*retp = (char *)0;
					return(-1);
				}
				*op++ = *bp++;
				continue;
			}

			/* handle ${var} format */
			if(*bp == '{') {
			        bp++;
				while(*bp != '\0' && *bp != '}' && vl < sizeof(vb) - 2)
					vb[vl++] = *bp++;
				vb[vl] = '\0';
				if (*bp == '}')
					bp++;
				if(vresolve(vb,who,aswho,cac,cav,&op,&bsiz)) {
					if(retp != (char **)0)
						*retp = (char *)0;
					return(-1);
				}
				continue;
			}


			/* default format: word or suchlike junk */
			while(*bp != '\0' && vl < sizeof(vb) - 2 &&
				(isalpha(*bp) || isdigit(*bp) ||
				*bp == '.' || *bp == '_' || *bp == '+' ||
				*bp == '*' || *bp == '#'))
					vb[vl++] = *bp++;
			vb[vl] = '\0';
			if(vresolve(vb,who,aswho,cac,cav,&op,&bsiz)) {
				if(retp != (char **)0)
					*retp = (char *)0;
				return(-1);
			}
			continue;
		}


		/* default: just copy character */
		if(--bsiz < 0) {
			if(retp != (char **)0)
				*retp = (char *)0;
			return(-1);
		}
		*op++ = *bp++;
	}


	/* partially finished word at null */
	if(av[ac] != (char *)0 && av[ac][0] != '\0') {
		*op = '\0';

		if(ac++ >= avsiz) {
			if(retp != (char **)0)
				*retp = (char *)0;
			return(-2);
		}
		av[ac] = (char *)0;
	}

	if(retp != (char **)0)
		*retp = bp;
	return(ac);
}




run_tokenize(bp,av,avsiz,tobuf,tobsiz)
char	*bp;
char	**av;
int	avsiz;
char	*tobuf;
int	tobsiz;
{
	return(enargv(bp,av,avsiz,tobuf,tobsiz,(char **)0,"","",0,(char **)0));
}



/*
run an argv'd argc'd command line. here's where to insert MUD-local
command-line processing that needs to take place before commands run
*/
static	int
runargv(who,aswho,ac,av)
char	*who;
char	*aswho;
int	ac;
char	*av[];
{
	Cmd	*c;
	char	ob[MAXOID];
	char	*macp;
	char	*listp;
	char	*here;

	/* PHASE #1 - check for hard wired call to system function */
	if(av[0][0] == '@') {
		if((c = cmdlookup(av[0] + 1)) != (Cmd *)0) {
#ifdef COMMAND_STATS
			atsyscmd++;
#endif
			if(ac < c->argc ||
				(c->argc != ac && (c->flgs & CM_FIXARG))) {
				say(who,"usage: ",c->usage,"\n",(char *)0);
				return(1);
			}

			if((c->flgs & CM_PRIV) && !ut_flagged(aswho,var_wiz)) {
				say(who,"permission denied.\n",(char *)0);
				return(1);
			}

			if(nowhereman && !(c->flgs & CM_NOWHERE) && !ut_flagged(aswho,var_wiz)) {
				say(who,"You are nowhere. Go away.\n",(char *)0);
				return(1);
			}

			return((*c->func)(ac,av,who,aswho));
		}

		/* call system macro if one */
		if(!nowhereman && (macp = symlook(av[0] + 1)) != (char *)0){
#ifdef COMMAND_STATS
			atsyscmd++;
#endif
			return(run(who,who,macp,ac,av,0));
		}

		say(who,"\"",av[0],"\": unknown command.\n",(char *)0);
		return(1);
	}

	/* folks who are nowhere can't do this */
	if(nowhereman)
		goto nowherejump;

	/* PHASE #2 - look for exit in room */
	here = ut_loc(who);
	if(matchargvexit(here,av,ac,ob) != 0){
#ifdef COMMAND_STATS
		exitstaken++;
#endif
		return(player_go(ac,av,who,here,ob));
	}


	/* PHASE #3 - look for macro attached to caller */
	macp = ut_getatt(who,0,typ_cmd,av[0],(char *)0);
	if(macp != (char *)0){
#ifdef COMMAND_STATS
		whomac++;
#endif
		return(run(who,who,macp,ac,av,0));
	}


	/* PHASE #3a - look for macro attached to aswho -- for luck */
	macp = ut_getatt(aswho,0,typ_cmd,av[0],(char *)0);
	if(macp != (char *)0){
#ifdef COMMAND_STATS
		aswhomac++;
#endif
		return(run(who,aswho,macp,ac,av,0));
	}


	/* PHASE #4 - look for macro attached to room */
	macp = ut_getatt(here,0,typ_cmd,av[0],(char *)0);
	if(macp != (char *)0){
#ifdef COMMAND_STATS
		roommac++;
#endif
		return(run(who,here,macp,ac,av,0));
	}


	/* PHASE #5 - look for macro attached to object in use */
	macp = ut_getatt(who,0,typ_cmd,var_using,av[0],(char *)0);
	if(macp != (char *)0) {
		char	*up;

		up = ut_getatt(who,0,typ_obj,var_using,(char *)0);
		if(up != (char *)0){
#ifdef COMMAND_STATS
			usemac++;
#endif
			return(run(who,up,macp,ac,av,0));
		}
	}

#ifdef SEARCH_INVENTORY
	/* PHASE #5a - look for a macro on anything the player is carrying */
	listp = ut_getatt(who,0,typ_list,var_cont,(char *)0);

	listp = lstnext(listp,ob);
	while(listp != (char *)0){
		macp = ut_getatt(ob,0,typ_cmd,av[0],(char *)0);
		if(macp != (char *)0){
#ifdef COMMAND_STATS
			invmac++;
#endif
			return(run(who,ob,macp,ac,av,0));
		}
		listp = lstnext(listp,ob);
	}
#endif /* SEARCH_INVENTORY */
	
/* skip stuff for folks who are nowhere */
nowherejump:

	/* PHASE #6 - check for non-hardwired call to system functions */
	if((c = cmdlookup(av[0])) != (Cmd *)0) {
#ifdef COMMAND_STATS
			syscmd++;
#endif
		if(ac < c->argc || (c->argc != ac && (c->flgs & CM_FIXARG))) {
			say(who,"usage: ",c->usage,"\n",(char *)0);
			return(1);
		}

		if((c->flgs & CM_PRIV) && !ut_flagged(aswho,var_wiz)) {
			say(who,"permission denied.\n",(char *)0);
			return(1);
		}

		if(nowhereman && !(c->flgs & CM_NOWHERE) && !ut_flagged(aswho,var_wiz)) {
			say(who,"You are nowhere. Go away.\n",(char *)0);
			return(1);
		}

		return((*c->func)(ac,av,who,aswho));
	}

	/* PHASE #7 call system macro if one */
	if(!nowhereman && (macp = symlook(av[0])) != (char *)0){
#ifdef COMMAND_STATS
		syscmd++;
#endif
		return(run(who,who,macp,ac,av,0));
	}


	/* PHASE #8 - barf. */
	say(who,"Huh? What is \"",av[0],"\"?\n",(char *)0);
	return(1);
}




/*
get a line of commands from 'who' and repeatedly break it into parameters,
or simply detect hotwired commands and call them.
the eb and ab, etc must not be static buffers, as this can be called
recursively.
*/
run(who,aswho,s,argc,argv,real_call)
char	*who;
char	*aswho;
char	*s;
int	argc;
char	*argv[];
int	real_call;
{
	char	*bp;
	char	*bp2;
	char	ab[MUDBUF * 2];		/* buffer to tokenize into */
	char	*av[MAXARG];		/* token vector */
	int	ac;
	int	uers = 0;

	/* some sanity checking and whatnot */
	if(real_call) {
		actor = who;
		recursion_depth = 0;
	} else {
		if(++recursion_depth > 20) {
			say(who,"Too many recursions.\n",(char *)0);
			return(1);
		}
	}

	/* we set 'bp' as the remaining text pointer here */
	if((bp = s) == (char *)0 || *bp == '\0')
		return(0);

	/* Preskip whitespace, obviously */

	while(isspace(*bp))
		bp++;


	/*
	check for hotwired commands, and if there are any, fake up an
	argc, argv, and just run that.
	hotwire say */
	if(*bp == '\"' && !nowhereman) {
		ac = 2;
		av[0] = "say";
		av[1] = bp + 1;
		av[2] = (char *)0;
		bp = (char *)0;
#ifdef COMMAND_STATS
		hotwired++;
#endif
		return(runargv(who,aswho,ac,av));
	}

	if(*bp == ':' && !nowhereman) {
		ac = 2;
		av[0] = "do";
		av[1] = bp + 1;
		av[2] = (char *)0;
		bp = (char *)0;
#ifdef COMMAND_STATS
		hotwired++;
#endif
		return(runargv(who,aswho,ac,av));
	}

	while(bp != (char *)0 && *bp != '\0') {
		if(real_call)
			actor = who;

		/* tokenize the line and deal with it */
		ac = enargv(bp,av,MAXARG,ab,sizeof(ab),&bp2,who,aswho,argc,argv);
		bp = bp2;
		if(ac == 0)
			continue;

		if(ac < 0) {
			switch(ac) {
			case	-1:
				say(who,"command input too large.\n",(char *)0);
				return(-1);

			case 	-2:
				say(who,"too many tokens in command.\n",(char *)0);
				return(-1);
			}
			continue;
		}

		uers += runargv(who,aswho,ac,av);
	}
	return(uers);
}





/* return the run_level of the run */
int
run_level()
{
	return(recursion_depth);
}





/* return the actor of the run */
char	*
run_actor()
{
	return(actor);
}




/*
run a command line at boot-time.
*/
int
run_boot(l)
char	*l;
{
	Cmd	*c;
	char	*av[MAXARG];
	char	ab[BUFSIZ];
	int	ac;

	ac = run_tokenize(l,av,MAXARG,ab,sizeof(ab));
	if(ac == 0)
		return(0);
	if(ac < 0) {
		logf("tokenization error in boot command.\n",(char *)0);
		return(1);
	}

	if((c = cmdlookup(av[0])) != (Cmd *)0) {
		if(ac < c->argc ||
			(c->argc != ac && (c->flgs & CM_FIXARG))) {
			logf("usage: ",c->usage,"\n",(char *)0);
			return(1);
		}
		return((*c->func)(ac,av,"wizard","wizard"));
	}

	logf("uknown command: ",av[0],"\n",(char *)0);
	return(1);
}