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