/*
 *	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"

#include "listutils.h"
#include "libsh.h"

int test_debug = 0;

static int testeval __((const char **avp[], const int ignoreferrs));
static int tellme   __((const char **avp[], int));

#define dprintf		if (test_debug) printf

STATIC const 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 __((const char **avp[], int ignoreferrs));
STATIC int
testparse(avp,ignoreferrs)
	const char **avp[];
	int ignoreferrs;
{
	int	c;
	const char **av;

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

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

			while (**avp != NULL &&
			       prec[(**avp)[0] & 0xFF] > prec[c]) {
				ignoreferrs |= !testgv[testgc];
				testparse(avp,ignoreferrs);
			}
			testgv[testgc - 1] &= testgv[testgc];
			testgc--;
			dprintf("[%d] &= [%d] = %d\n",
				      testgc, testgc + 1, testgv[testgc]);
			break;
		case 'o':
			++*avp;
			dprintf("-o: **avp = %p (**avp)[0] = '%c'\n",
				**avp, **avp ? ((**avp)[0] & 0xFF) : '\277');
			if (**avp == NULL || prec[(**avp)[0] & 0xFF])
			  goto err;
			ignoreferrs |= testgv[testgc];
			testparse(avp,ignoreferrs);
			dprintf("-o2: **avp = %p (**avp)[0] = '%c'\n",
				**avp, **avp ? ((**avp)[0] & 0xFF) : '\277');
			if (**avp != NULL &&
			    (testgc < 1 || !prec[(**avp)[0] & 0xFF]))
				goto err;
			while (**avp != NULL &&
			       prec[(**avp)[0] & 0xFF] > prec[c]) {
				ignoreferrs |= testgv[testgc];
				testparse(avp,ignoreferrs);
			}
			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, ignoreferrs);
			if (testgv[testgc] < 0)
				goto err;
			dprintf("[%d] = %d\n", testgc, testgv[testgc]);
			break;
		}
		break;
	default: /* push value onto stack */
		testgv[++testgc] = testeval(avp, ignoreferrs);
		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 __((char **avp[]));
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 strng __((char **avp[], int));
STATIC int
strng(avp, i)
	char **avp[];
	int i;
{
	int len;
	
	if (**avp == NULL) abort(); /* calling convention error! */
	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',	strng,	0,		~0,		~0			},
{ 'n',	strng,	0,		~0,		~0			},
{ 'z',	strng,	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.
 */

static int
testeval(avp, ignoreferrs)
	const char **avp[];
	const int ignoreferrs;
{
	register const char *cp;
	int	i, value = -1;
	const char *av1, *av2;

	cp = (*avp)[0];
	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;
			}
		}
	}
	av1 = (*avp)[0];
	av2 = (*avp)[1];
	dprintf("    : %s (%d) av1=%p av2=%p\n", cp, value, av1, av2);
	if (value < 0 && av1 != NULL && av2 != NULL
		   && strcmp(av1, "=") == 0) {
		value = !strcmp(cp, av2);
		*avp += 2;
	} else if (value < 0 && av1 != NULL && av2 != NULL
		   && strcmp(av1, "!=") == 0) {
		value = strcmp(cp, av2);
		*avp += 2;
	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-eq") == 0) {
		value = (N1 == N2);
		*avp += 2;
	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-ne") == 0) {
		value = (N1 != N2);
		*avp += 2;
	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-gt") == 0) {
		value = (N1 > N2);
		*avp += 2;
	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-ge") == 0) {
		value = (N1 >= N2);
		*avp += 2;
	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-lt") == 0) {
		value = (N1 < N2);
		*avp += 2;
	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-le") == 0) {
		value = (N1 <= N2), *avp += 2;

	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-nt") == 0) {

	  struct stat stb1, stb2;

	  if (stat(av2, &stb2) != 0) {
	    if (!ignoreferrs) {
	      fprintf(stderr,"%s:  '%s' stat failed on right of '-nt'\n",
		      testprogname, av2);
	      return -1;
	    } else
	      /* So what if it failed ! */
	      stb2.st_mtime = 0;
	  }
	  if (stat(cp,  &stb1) != 0) {
	    if (!ignoreferrs) {
	      fprintf(stderr,"%s:  '%s' stat failed on left of '-nt'\n",
		      testprogname, cp);
	      return -1;
	    } else
	      /* IgnoredFErrs means this is always true.. */
	      stb1.st_mtime = stb2.st_mtime + 1;
	  }
	  value = (stb1.st_mtime > stb2.st_mtime);
	  *avp += 2;

	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-ot") == 0) {

	  struct stat stb1, stb2;
	  if (stat(cp,  &stb1) != 0) {
	    if (!ignoreferrs) {
	      fprintf(stderr,"%s: '%s' stat failed on left of '-ot'\n",
		      testprogname, cp);
	      return -1;
	    }
	    stb1.st_mtime = 0;
	  }
	  if (stat(av2,  &stb2) != 0) {
	    if (!ignoreferrs) {
	      fprintf(stderr,"%s: '%s' stat failed on right of '-ot'\n",
		      testprogname, av2);
	      return -1;
	    }
	    stb2.st_mtime = stb1.st_mtime + 1;
	  }
	  value = (stb1.st_mtime < stb2.st_mtime);
	  *avp += 2;

	} else if (av1 != NULL && av2 != NULL
		   && strcmp(av1, "-ef") == 0) {

	  struct stat stb1, stb2;
	  if (stat(cp,  &stb1) != 0 ||
	      stat(av2, &stb2) != 0) {
	    if (!ignoreferrs) {
	      fprintf(stderr,"%s: either '%s' or '%s' stat failed around '-ef'\n",
		      testprogname, cp, av2);
	      return -1;
	    }
	    value = 0;
	  } else
	    value = ((stb1.st_ino == stb2.st_ino) &&
		     (stb1.st_dev == stb2.st_dev));
	  *avp += 2;


	} else if (av1 == NULL) {
		fprintf(stderr, "%s: argument expected\n", testprogname);
		return -1;
	} else if (av2 == NULL) {
		fprintf(stderr, "%s: unknown operand '%s'\n", testprogname, cp);
		return -1;
	} else
		value = (*cp != '\0');
gotvalue:
#if 0
	if (av1 != NULL && av1[0] == ']' && av1[1] == '\0')
		++*avp;
#endif
	return value != 0;
}

static int
tellme(avp, i)
	const 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 ((int)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;
	const 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,0))
			break;
	dprintf("ac = %d, av[%d] = %d\n", testgc, testgc, testgv[testgc]);
	return !testgv[testgc];
}

