/*
 *	Copyright 1989 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/*
 * This is the builtin "test" command.  It is implemented by a shunting
 * yard algorithm.  Pretty standard stuff, but hard to follow.
 */

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include "io.h"
#include "shconfig.h"

int test_debug = 0;

extern int testeval();
extern int tellme();

#ifdef	HAVE_LSTAT
extern int lstat();
#else
extern int stat();
#endif

#define dprintf		if (test_debug) printf

STATIC char *testprogname;

STATIC int testgv[20];		/* shunting-yard stack (operand values) */
STATIC int testgc = -1;		/* top of said stack */

STATIC short prec[CHARSETSIZE];

/*
 * Shunting yard galore.
 */

STATIC int
testparse(avp)
	char **avp[];
{
	int	c;
	char	**av;

	dprintf("testparse:");
	for (av = *avp; *av != NULL; av++)
		dprintf(" %s", *av);
	dprintf("\n");

	if (**avp == NULL)
		return 0;
	switch (c = ***avp) {
	case '(':
		++*avp, testparse(avp);
		if (**avp != NULL && ***avp == ')') {
			++*avp;
		} else
			goto err;
		break;
	case ')':
		break;
	case '!':
		++*avp;
		if (**avp == NULL || prec[*((unsigned char *)**avp)]) goto err;
		testparse(avp);
		if (testgc < 0) goto err;
		testgv[testgc] = !testgv[testgc];
		dprintf("[%d] = ![%d] = %d\n", testgc, testgc, testgv[testgc]);
		testparse(avp);
		break;
	case '-':
		switch (*(**avp+1)) {
		case 'a':
			++*avp;
			if (**avp == NULL || prec[*((unsigned char *)**avp)]) goto err;
			testparse(avp);
			if (**avp != NULL &&
			    (testgc < 1 || !prec[*((unsigned char *)**avp)]))
				goto err;

			while (**avp != NULL &&
			       prec[*((unsigned char *)**avp)] > prec[c])
				testparse(avp);
			testgv[testgc-1] &= testgv[testgc];
			testgc--;
			dprintf("[%d] &= [%d] = %d\n",
				      testgc, testgc+1, testgv[testgc]);
			break;
		case 'o':
			++*avp;
			if (**avp == NULL ||
			    prec[*((unsigned char *)**avp)]) goto err;
			testparse(avp);
			if (**avp != NULL && (testgc < 1 || !prec[*((unsigned char *)**avp)]))
				goto err;
			while (**avp != NULL &&
			       prec[*((unsigned char *)**avp)] > prec[c])
				testparse(avp);
			testgv[testgc-1] |= testgv[testgc];
			testgc--;
			dprintf("[%d] |= [%d] = %d\n",
				      testgc, testgc+1, testgv[testgc]);
			break;
		default: /* push value onto stack */
			testgv[++testgc] = testeval(avp);
			if (testgv[testgc] < 0)
				goto err;
			dprintf("[%d] = %d\n", testgc, testgv[testgc]);
			break;
		}
		break;
	default: /* push value onto stack */
		testgv[++testgc] = testeval(avp);
		if (testgv[testgc] < 0)
			goto err;
		dprintf("[%d] = %d\n", testgc, testgv[testgc]);
		break;
	}
	return 0;
err:
	fprintf(stderr, "%s: %s:", testprogname, TEST_SYNTAX_ERROR);
	while (**avp) {
		fprintf(stderr, " %s", **avp);
		++*avp;
	}
	fprintf(stderr, "\n");
	testgv[testgc] = 0;
	return 1;
}

STATIC int
fildes(avp)
	char **avp[];
{
	int fd;

	if (**avp != NULL && isdigit(***avp))
		fd = atoi(**avp), ++*avp;
	else
		fd = 1;
	return isatty(fd);
}

STATIC int
string(avp, i)
	char **avp[];
	int i;
{
	int len;
	
	if (**avp == NULL) abort();
	len = strlen(**avp);
	++*avp;
	return (i ? (!len) : len);
}

STATIC struct flags {
	char	flag;
	int	(*func)();
	int	i1,i2,i3;
} flarr[] = {
#ifdef	USG
{ 'f',	stat,	0,		S_IFMT,		S_IFREG			},
#else	/* BSD */
{ 'f',	stat,	0,		S_IFMT,		S_IFMT&~S_IFDIR		},
#endif	/* USG */
{ 'd',	stat,	0,		S_IFMT,		S_IFDIR			},
{ 's',	stat,	1,		~0,		~0			},
#ifdef	S_IRUSR
{ 'r',	stat,	0,		~S_IFMT,	S_IRUSR|S_IRGRP|S_IROTH	},
{ 'w',	stat,	0,		~S_IFMT,	S_IWUSR|S_IWGRP|S_IWOTH	},
{ 'x',	stat,	0,		~S_IFMT,	S_IXUSR|S_IXGRP|S_IXOTH	},
#else	/* !S_IRUSR */
{ 'r',	stat,	0,		~S_IFMT,	0x444			},
{ 'w',	stat,	0,		~S_IFMT,	0x222			},
{ 'x',	stat,	0,		~S_IFMT,	0x111			},
#endif	/* S_IRUSR */
#ifdef	HAVE_LSTAT
{ 'h',	lstat,	0,		S_IFMT,		S_IFLNK			},
#endif	/* HAVE_LSTAT */
{ 'b',	stat,	0,		S_IFMT,		S_IFBLK			},
{ 'c',	stat,	0,		S_IFMT,		S_IFCHR			},
#ifdef	S_IFIFO
{ 'p',	stat,	0,		S_IFMT,		S_IFIFO			},
#endif	/* S_IFIFO */
{ 'u',	stat,	0,		~S_IFMT,	S_ISUID			},
{ 'g',	stat,	0,		~S_IFMT,	S_ISGID			},
{ 'k',	stat,	0,		~S_IFMT,	S_ISVTX			},
{ 't',	fildes,	0,		0,		0			},
{ 'l',	string,	0,		~0,		~0			},
{ 'n',	string,	0,		~0,		~0			},
{ 'z',	string,	1,		~0,		~0			},
};

#define	N1	(value < 0 ? atoi(cp) : value)
#define N2	(strcmp(*(*avp+1), "-l") == 0 && *(*avp+2) != NULL ? \
		       ++*avp, strlen(*(*avp+1)) : atoi(*(*avp+1)))

/*
 * Complicated operand evaluation.
 */

int
testeval(avp)
	char **avp[];
{
	register char *cp;
	int	i, value = -1;

	cp = **avp;
	dprintf("testeval: %s (%d)\n", cp, value);
	++*avp;
	if (*cp == '-' && *(cp+1) != '\0') {
		++cp;
		for (i = 0; i < sizeof flarr / sizeof flarr[0]; ++i) {
			if (*cp == flarr[i].flag) {
				value = tellme(avp, i);
				if (*cp != 'l')
					goto gotvalue;
				break;
			}
		}
	}
	dprintf("    : %s (%d)\n", cp, value);
	if (value < 0 && **avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "=") == 0) {
		value = !strcmp(cp, *(*avp+1)), *avp += 2;
	} else if (value < 0 && **avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "!=") == 0) {
		value = strcmp(cp, *(*avp+1)), *avp += 2;
	} else if (**avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "-eq") == 0) {
		value = (N1 == N2), *avp += 2;
	} else if (**avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "-ne") == 0) {
		value = (N1 != N2), *avp += 2;
	} else if (**avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "-gt") == 0) {
		value = (N1 > N2), *avp += 2;
	} else if (**avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "-ge") == 0) {
		value = (N1 >= N2), *avp += 2;
	} else if (**avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "-lt") == 0) {
		value = (N1 < N2), *avp += 2;
	} else if (**avp != NULL && *(*avp+1) != NULL
		   && strcmp(**avp, "-le") == 0) {
		value = (N1 <= N2), *avp += 2;
	} else if (**avp == NULL) {
		fprintf(stderr, "%s: argument expected\n", testprogname);
		return -1;
	} else if (*(*avp+1) == NULL) {
		fprintf(stderr, "%s: unknown operand '%s'\n", testprogname, cp);
		return -1;
	} else
		value = (*cp != '\0');
gotvalue:
#if 0
	if (**avp != NULL && ***avp == ']' && *(**avp+1) == '\0')
		++*avp;
#endif
	return value != 0;
}

int
tellme(avp, i)
	char **avp[];
	int i;
{
	struct stat stbuf;

	if (flarr[i].func == stat
#ifdef	HAVE_LSTAT
	    || flarr[i].func == lstat
#endif	/* HAVE_LSTAT */
	    ) {
		if ((flarr[i].func)(**avp, &stbuf) < 0) {
			++*avp;
			return 0;
		}
		++*avp;
		if (flarr[i].i1)
			return stbuf.st_size > 0;
		return stbuf.st_mode & flarr[i].i2 & flarr[i].i3;
	}
	return (flarr[i].func)(avp, flarr[i].i1, flarr[i].i2, flarr[i].i3);
}

/*
 * test(1).
 */

int
sh_test(argc, argv)
	int argc;
	char *argv[];
{
	if (argc == 1)
		return 1;
	testprogname = argv[0];
	testgc = -1;
	++argv, --argc;
	prec[')'] = 1;	/* do everything before absorbing this */
	prec['|'] = 5;	/* OR has lower precedence than... */
	prec['&'] = 10;	/* AND, obviously. */
	if (strcmp(testprogname, "[") == 0) {
		if (strcmp(argv[argc-1], "]") != 0) {
			fprintf(stderr, "test: missing ]\n");
			return -1;
		}
		argv[--argc] = NULL;
		if (argc == 0)
			return 1;
	}
	if (argc == 1)
		return argv[0][0] == '\0';
	while (*argv)
		if (testparse(&argv))
			break;
	dprintf("ac = %d, av[%d] = %d\n", testgc, testgc, testgv[testgc]);
	return !testgv[testgc];
}

