/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 * Copyright  (c) 1986 AT&T
 *	All Rights Reserved
 */

#ident	"@(#)eval.c	1.5	93/09/30 SMI"       /* SVr4.0 1.22 */

#ifdef CACA
#define _DEBUG2	1
int	_Debug=4;
#endif

#include	<stdio.h>
#include	<ctype.h>
#include	"wish.h"
#include	"eval.h"
#include	"terror.h"
#include	"message.h"
#include	"moremacros.h"
#include	"interrupt.h"

#define MAXARGS		64

/* NOTE!!! the following flags compete for bits with
 *         the EV_**** flags in inc/eval.h  Make sure
 *	   there is no overlap.
 */
#define IN_DQ		1
#define IN_SQ		2
#define IN_BQ		4
#define IN_SQUIG	8
#define FROM_BQ		16

/*
 * list of "special" characters, and flags in which they are not
 * treated as special. NOTE that EV_SQUIG  flag is opposite all 
 * others. set it in nflags if {} ARE to be treated as special.
 */
static char	spchars[] = "\"'\\`$\n \t{}|&;<>2";
/*static char	spchars[] = "\"'\\`$\n \t{}|&;<>";
abs */
static int	nflags[] = {
	FROM_BQ | IN_SQ,		/* double quote */
	FROM_BQ | IN_DQ,		/* single quote */
	FROM_BQ,			/* backslash    */
	FROM_BQ | IN_SQ,		/* back quote   */
	FROM_BQ | IN_SQ,		/* dollar sign  */
	IN_SQUIG | IN_SQ | IN_DQ,	/* new line     */
	FROM_BQ | IN_SQ | IN_DQ | EV_GROUP,		/* space        */
	FROM_BQ | IN_SQ | IN_DQ | EV_GROUP,		/* tab          */
	FROM_BQ | IN_SQUIG | IN_SQ | IN_DQ | IN_BQ | EV_SQUIG,	/* open squig   */
	FROM_BQ | IN_SQ | IN_DQ | IN_BQ | EV_SQUIG,		/* close squig  */
	FROM_BQ | IN_SQ | IN_DQ | EV_GROUP,	/* pipe symbol  */
	FROM_BQ | IN_SQ | IN_DQ | EV_GROUP,	/* ampersand    */
	FROM_BQ | IN_SQ | IN_DQ | EV_GROUP,	/* semicolon    */
	FROM_BQ | IN_SQ | IN_DQ | EV_GROUP,	/* less than    */
	FROM_BQ | IN_SQ | IN_DQ | EV_GROUP     /* greater than */
	,FROM_BQ | IN_SQ | IN_DQ | EV_GROUP 	 /* digit two    */
};

/* return code from most recently executed command */
int	EV_retcode;
int	EV_backquotes;
int	Lasttok;

extern int in_an_if;	/* (in an if statement) see evfuncs.c */


char *
special_char(c, instr)
register int c;
IOSTRUCT *instr;
{
    char	*strchr();
    int c2;

    if ((char)c == '2')
    {
	c2 = getac(instr);
	if(c2 || instr->flags & EV_USE_FP) /* the other case: EV_USE_STRING */
	   ungetac(c2, instr);	/* and c2 = EndOfString. don't unget cause */
				/* it would unget c not c2.   abs */
	if ((char)c2 != '>')
	    return(NULL);
    }
    return(strchr(spchars, c));
}
    

eval(instr, outstr, flags)
IOSTRUCT	*instr;
IOSTRUCT	*outstr;
int	flags;
{
	register int	c;
	register int	tok;
	bool	done;

	flags ^= EV_SQUIG;	/* flip flag so only have to set it
				 when special (one case) instead of
				 in all other cases.  abs */
	if (!(flags & IN_BQ))
		EV_retcode = 0;
	EV_backquotes = FALSE;
	c = getac(instr);
	/* skip leading white space */
	if (flags & (EV_TOKEN | EV_GROUP)) {
		while (isspace(c))
			c = getac(instr);
		if (c == '#') {
			/*
			 * skip everything until end of line
			 */
			while ((c = getac(instr)) && c != '\n' && c != EOF)
				;
		}
	}
	/* handler `` at beginning of line if in GROUP mode */
	if ((flags & EV_GROUP) && c == '`') {
		eval_backquote(instr, outstr, flags);
		io_flags(instr, io_flags(instr, 0) & ~FROM_BQ);
		c = getac(instr);
	}
#ifdef _DEBUG2
/*
	if ((flags & EV_TOKEN) && (instr->flags & EV_USE_STRING)) {
		_debug2(stderr, "input is '%.*s'\n", instr->mu.str.count - instr->mu.str.pos, instr->mu.str.val +  instr->mu.str.pos);
	}
*/
#endif

	Lasttok = tok = ET_EOF;
	for (done = FALSE; c; c = getac(instr)) {
		register char	*p;
		char	*strchr();

/*		while (!(p = strchr(spchars, c))) {
abs */
		while(!(p = special_char(c, instr)))  {
			Lasttok = ET_WORD;
			putac(c, outstr);
			if (!(c = getac(instr))) {
				done = TRUE;
				break;
			}
		}
		if (done)
			break;
		/* single | and & are correct here */
		if ((instr->flags | flags) & nflags[tok = p - spchars])
			tok = !!c;
		else {
			tok += ET_DQUOTE;
#ifdef _DEBUG2
			_debug2(stderr, "eval: got special char 0x%x\n", tok);
#endif
		}
		switch (tok) {
		case ET_EOF:
			done = TRUE;
			break;
		case ET_WORD:
			Lasttok = tok;
			putac(c, outstr);
			break;
		case ET_DQUOTE:
			flags ^= IN_DQ;
			if (!(flags & EV_TOKEN))
				putac(c, outstr);
			break;
		case ET_SQUOTE:
			flags ^= IN_SQ;
			if (!(flags & EV_TOKEN))
				putac(c, outstr);
			break;
		case ET_BSLASH:
			c = getac(instr);
			/*
			 * if (not tokenizing or if we're in quotes and the
			 *	next character is not special, leave backslash
			 *	there
			 * else
			 *	remove it (don't copy to output)
			 */
			if (!(flags & EV_TOKEN) || (flags & (IN_SQ | IN_DQ)) && (!(p = strchr(spchars, c)) || (instr->flags | flags) & nflags[p - spchars]))
				putac('\\', outstr);
			putac(c, outstr);
			break;
		case ET_BQUOTE:
			if (flags & EV_TOKEN) {
				if (flags & IN_BQ) {
					if (Lasttok == ET_EOF) {
						putac(c, outstr);
						c = getac(instr);
						Lasttok = tok;
					}
					done = TRUE;
				}
				else
					eval_backquote(instr, outstr, flags);
			}
			else {
				flags ^= IN_BQ;
				putac(c, outstr);
			}
			EV_backquotes = TRUE;
			break;
		case ET_DOLLAR:
			if (flags & EV_TOKEN)
				eval_dollar(instr, outstr, flags);
			else
				putac(c, outstr);
			break;
		case ET_NEWLINE:
		case ET_SPACE:
		case ET_TAB:
			Lasttok = ET_WORD;
			if ((flags & EV_GROUP) && (flags & IN_BQ))
				putac(c, outstr);
			else
				done = TRUE;
			break;
		case ET_OSQUIG:
		case ET_CSQUIG:
			putac(c, outstr);
			if (flags & EV_GROUP)
				flags ^= IN_SQUIG;
			else if (flags & EV_TOKEN) {
				if (Lasttok == ET_EOF) {
					c = getac(instr);
					Lasttok = tok;
				}
				done = TRUE;
			}
			break;
		case ET_PIPE:
		case ET_AMPERSAND:
		case ET_SEMI:
		case ET_LTHAN:
		case ET_GTHAN:
			if (flags & IN_BQ) {
				if (Lasttok == ET_EOF) {
					register int	oldc;

					putac(c, outstr);
					oldc = c;
					if ((c = getac(instr)) == oldc) {
						putac(c, outstr);
						c = getac(instr);
						tok += DOUBLE;
					}
					Lasttok = tok;
				}
				done = TRUE;
			}
			else
				putac(c, outstr);
			break;
		case ET_TWO:
			if (flags & IN_BQ) {
			    if (Lasttok == ET_EOF) {
				putac(c, outstr);
				c = getac(instr); /* gets > known to follow 2 */
				putac(c, outstr);
				if ((c = getac(instr)) == '>') {
				    putac(c, outstr);
				    c = getac(instr);
				    tok += DOUBLE;
				}
				Lasttok = tok;
			    }
			    done = TRUE;
			}
			else
			    putac(c, outstr);
			break;
		}
		if (done)
			break;
		Lasttok = ET_WORD;
	}
	if (c)
		ungetac(c, instr);
#ifdef _DEBUG2
	if (flags & EV_TOKEN) {
		_debug2(stderr, "eval -> '%s'\n", io_ret_string(outstr));
		_debug2(stderr, "eval returning 0x%x\n", Lasttok);
	}
#endif

	return Lasttok;
}

/*
 * NOTE:
 *
 * In pre-4.0 releases of FMLI, the contents of environment variables
 * (after expansion) were put back into the input string for further
 * evaluation (lets call it "double evaluation").
 *
 * To remain backwards compatable, the global variable "Doublevars"
 * will be set to TRUE if double evaluation should be performed on
 * ALL environment variables. 
 *
 * If Doublevars == FALSE, then only evaluate the "contents" of the
 * variable if a "!" follows "$" (i.e., the "new" convention for double
 * evaluation is "$!VARNAME").
 *
 */
/*ARGSUSED*/
static
eval_dollar(instr, outstr, flags)
IOSTRUCT	*instr;
IOSTRUCT	*outstr;
int	flags;
{
	register char	*p;
	register int	c;
	register IOSTRUCT	*iop;
	char	*expand();
	int	evalagain;
	extern  bool Doublevars;

	iop = io_open(EV_USE_STRING, NULL);
	putac('$', iop);
	if (Doublevars == TRUE)
		evalagain = TRUE;
	else {
		if ((c = getac(instr)) == '!') {
			evalagain = TRUE;
		}
		else {
			evalagain = FALSE;
			ungetac(c, instr);
		}
	}
	if ((c = getac(instr)) == '{') {
		while (c != '}') {
			putac(c, iop);
			c = getac(instr);
		}
		putac(c, iop);
	}
	else {
		while (isalpha(c) || isdigit(c) || c == '_') {
			putac(c, iop);
			c = getac(instr);
		}
		if (c)
			ungetac(c, instr);
	}
	if (p = expand(io_ret_string(iop))) {
		io_clear(iop);
		if (evalagain) {
			/*
			 * if the "contents" of the variable should
			 * be evaluated before passing it to outstr ...
			 */
			putastr(p, iop);
			free(p);
			p = (char *)NULL;
			io_seek(iop, 0);
			io_push(instr, iop);	/* push it back in instr */
		}
		else {
			/*
			 * simply put the variable's contents into outstr 
			 */
			putastr(p, outstr);
			free(p);
			p = (char *)NULL;
			io_close(iop);
		}
		return SUCCESS;
	}
	return FAIL;
}

static char *
eval_token(instr, flags)
IOSTRUCT	*instr;
int	flags;
{
	register char	*p;
	static IOSTRUCT	*tmp;

	if (instr == NULL) {
		io_close(tmp);
		return NULL;
	}
	if (tmp == NULL)
		tmp = io_open(EV_USE_STRING, NULL);
	(void) eval(instr, tmp, flags);
	p = io_string(tmp);
	io_seek(tmp, 0);
	return p;
}

static
eval_backquote(instr, outstr, flags)
IOSTRUCT	*instr;
IOSTRUCT	*outstr;
int	flags;
{
    int	argc;
    char	*argv[MAXARGS];
    bool	doit;
    int	conditional = 0;
    int	if_elif = 0;
    bool	skip;
    bool	piped;
    bool	special;
    IOSTRUCT	*mystdin;
    IOSTRUCT	*mystdout;
    IOSTRUCT	*altstdout;
    IOSTRUCT	*altstderr;

#ifdef _DEBUG2
    _debug2(stderr, "eval_backquote\n");
#endif
    mystdin = io_open(EV_USE_STRING, NULL);
    mystdout = io_open(EV_USE_STRING, NULL);
    altstdout = NULL;
    altstderr = NULL;
    doit = skip = piped = special = FALSE;

    for (argc = 0; ; ) {
	conditional = 0;
	argv[argc++] = eval_token(instr, EV_TOKEN | IN_BQ);

	if (argc == 1) {
	    /*
	     * determine whether we've found an 
	     * if/then/else/elif statement
	     */ 
	    if_elif = 0;

#ifdef _DEBUG2
	    _debug2(stderr, "argv[0]=\"%s\"\n\r", argv[0]);
#endif

	    switch(argv[0][0]) {
	    case 'e':	/* else, elif */
		if (!strcmp(argv[0], "else")) 
		    conditional = 1;
		else if (!strcmp(argv[0], "elif")) {
		    conditional = 1;
		    if_elif = 1;
		}
		break;
	    case 'i':	/* if */
		if (argv[0][1] == 'f' && argv[0][2] == '\0') {
		    conditional = 1;
		    if_elif = 1;
		}
		break;
	    case 't':	/* then */
		if (!strcmp(argv[0], "then")) 
		    conditional = 1;
		break;
	    }

	    if (conditional) {
		int	a, nonwhite, start_look;
		char	*cp;
		char	ch;

		/*
		 * Force call to if/then/else/elif built-in
		 * (no arguments) ... Don't modify the input
		 * string here, just put a semi-colon in the
		 * "argv" array and set Lasttok (last token
		 * received) to ET_SEMI (semi-colon).
		 */
		argv[argc++] = strsave(";");
		Lasttok = ET_SEMI;


		/*
		 * Though the implementation of if/then/else
		 * is done via built-ins ... don't allow
		 * semi-colons after if/then/else/elif !!
		 */
		start_look = instr->mu.str.pos;
		cp = instr->mu.str.val + start_look; 
		nonwhite = 1;
		for (a = 0; a < instr->mu.str.count - start_look + 1; a++) {
		    ch = *(cp + a);

		    if (ch == '\n' )  
			break;
		    else if (ch == ';') {
			/*
			 * If all you've seen is
			 * white-space then produce
			 * an error message
			 */ 
			if (nonwhite) {
			    char errbuf[100];

			    sprintf(errbuf, "Syntax error - \";\" found after \"%s\"", argv[0]);
			    mess_temp(errbuf);
			    mess_lock();
			    Lasttok = ET_EOF;
			    in_an_if = 0;
			}
			break;
		    }
		    else if (ch != '\t' && ch != ' ') {
			/*
			 * not a space, tab, new-line
			 * or semi-colon ......
			 */
			nonwhite = 0;
		    }
		}
	    }
	}

	switch (Lasttok) {
	case ET_EOF:
	case ET_BQUOTE:
	    special = doit = TRUE;
	    break;
	case ET_PIPE:
	{
	    register FILE	*fp;
	    FILE	*tempfile();

	    if (altstdout) {
#ifdef _DEBUG2
		_debug2(stderr, "PIPE and > in same eval command\n");
#endif
		io_close(altstdout);
		altstdout = NULL;
	    }
	    if (fp = tempfile(NULL, "w+"))
		altstdout = io_open(EV_USE_FP, fp);
	    special = doit = piped = TRUE;
	}
	    break;
	case ET_AMPERSAND:
	    break;
	case ET_SEMI:
	    special = doit = TRUE;
	    break;
	case ET_LTHAN:
	{
	    register FILE	*fp;
	    register char	*p;

	    special = TRUE;
	    p = eval_token(instr, EV_TOKEN | IN_BQ);
	    if (fp = fopen(p, "r")) {
		io_close(mystdin);
		mystdin = io_open(EV_USE_FP, fp);
	    }
	    else
		warn(NOPEN, p);
	    free(p);
	    p = (char *)NULL;
	}
	    break;
	case ET_GTHAN:
	case ET_GTHAN + DOUBLE:	/* append symbol */
	{
	    register FILE	*fp;
	    register char	*p;
	    int savetok=Lasttok;
	    special = TRUE;
	    if (altstdout) {
#ifdef _DEBUG2
		_debug2(stderr, "2 >'s in eval command\n");
#endif
		io_close(altstdout);
	    }
	    p = eval_token(instr, EV_TOKEN | IN_BQ);
	    if (fp = fopen(p, (savetok & DOUBLE)?"a":"w"))
		altstdout = io_open(EV_USE_FP, fp);
	    else
		warn(NOPEN, p);
	    free(p);
	    p = (char *)NULL;
	}
	    break;
	case ET_TWO:
	case ET_TWO + DOUBLE:	/* append stderr symbol (2>>)*/
	{
	    register FILE	*fp;
	    register char	*p;
	    int savetok=Lasttok;

	    special = TRUE;
	    if (altstderr) {
#ifdef _DEBUG2
		_debug2(stderr, "2 >'s in eval command\n");
#endif
		io_close(altstderr);
		altstderr = NULL;
	    }
	    p = eval_token(instr, EV_TOKEN | IN_BQ);
	    if (fp = fopen(p, (savetok & DOUBLE) ?"a":"w"))
		altstderr = io_open(EV_USE_FP, fp);
	    else
		warn(NOPEN, p);
	    free(p);
	    p = (char *)NULL;
	}
	    break;
	    /* OR symbol */
	case ET_PIPE + DOUBLE:
	    special = doit = TRUE;
	    break;
	    /* AND symbol */
	case ET_AMPERSAND + DOUBLE:
	    special = doit = TRUE;
	    break;
	    /* semicolon (twice in a row) */
	case ET_SEMI + DOUBLE:
	    special = doit = TRUE;
	    break;
	    /* here document */
	case ET_LTHAN + DOUBLE:
	    break;

	}
	if (special) {
	    free(argv[--argc]);
	    argv[argc] = (char *)NULL;
	    special = FALSE;
	}
	if (doit || argc >= MAXARGS - 1) {
	    register int	n;

	    doit = FALSE;
	    if (!skip && !Cur_intr.skip_eval && argc > 0) /* abs */
	    {
		argv[argc] = NULL;
		EV_retcode = evalargv(argc, argv, mystdin,
				      altstdout ? altstdout : mystdout,
				      altstderr);
		/*
		 * if there is a syntax error in the
		 * conditional statement then terminate
		 * evaluation
		 */ 
		if (conditional && (EV_retcode == FAIL))
		    Lasttok = ET_EOF;
	    }
	    skip = (EV_retcode && Lasttok == ET_AMPERSAND + DOUBLE)
		|| (!EV_retcode && Lasttok == ET_PIPE + DOUBLE);
	    for (n = 0; n < argc; n++)
		if (argv[n]) {	/* ehr3 */
		    free(argv[n]);
		    argv[n] = (char *)NULL;
		}
	    argc = 0;
	    io_close(mystdin);
	    if (piped) {
		mystdin = altstdout;
		io_seek(mystdin, 0);
		piped = FALSE;
	    }
	    else {
		mystdin = io_open(EV_USE_STRING, NULL);
		if (altstdout)
		    io_close(altstdout);
	    }
	    if (altstderr)
	    {
		io_close(altstderr);
		altstderr = NULL;
	    }
	    altstdout = NULL;
	    if (Lasttok == ET_EOF || Lasttok == ET_BQUOTE)
		break;
	}
    }
    if ((argc = unputac(mystdout)) && argc != '\n')
	putac(argc, mystdout);
    if (flags & EV_GROUP)
	putac('\n', mystdout);
    io_seek(mystdout, 0);
    io_flags(mystdout, io_flags(mystdout, 0) | FROM_BQ);
    io_push(instr, mystdout);
}







