Add test from 4.4BSD-Lite Release 2
authorJason Self <j@jxself.org>
Mon, 10 Jun 2019 01:00:21 +0000 (18:00 -0700)
committerJason Self <j@jxself.org>
Mon, 10 Jun 2019 01:00:21 +0000 (18:00 -0700)
Signed-off-by: Jason Self <j@jxself.org>
usr/src/bin/test/Makefile [new file with mode: 0644]
usr/src/bin/test/TEST.csh [new file with mode: 0644]
usr/src/bin/test/operators.c [new file with mode: 0644]
usr/src/bin/test/operators.h [new file with mode: 0644]
usr/src/bin/test/test.1 [new file with mode: 0644]
usr/src/bin/test/test.c [new file with mode: 0644]

diff --git a/usr/src/bin/test/Makefile b/usr/src/bin/test/Makefile
new file mode 100644 (file)
index 0000000..ccd6089
--- /dev/null
@@ -0,0 +1,8 @@
+#        @(#)Makefile  8.1 (Berkeley) 5/31/93
+
+PROG=  test
+SRCS=   test.c operators.c
+LINKS= ${BINDIR}/test ${BINDIR}/[
+MLINKS=        test.1 '[.1'
+
+.include <bsd.prog.mk>
diff --git a/usr/src/bin/test/TEST.csh b/usr/src/bin/test/TEST.csh
new file mode 100644 (file)
index 0000000..e5b9652
--- /dev/null
@@ -0,0 +1,137 @@
+#      @(#)TEST.csh    5.2 (Berkeley) 4/30/93
+
+#alias t '/usr/src/bin/test/obj/test \!*; echo $status'
+alias t '/bin/test \!*; echo $status'
+
+echo 't -b /dev/ttyp2'
+t -b /dev/ttyp2
+echo 't -b /dev/jb1a'
+t -b /dev/jb1a
+
+echo 't -c test.c'
+t -c test.c
+echo 't -c /dev/tty'
+t -c /dev/tty
+
+echo 't -d test.c'
+t -d test.c
+echo 't -d /etc'
+t -d /etc
+
+echo 't -e noexist'
+t -e noexist
+echo 't -e test.c'
+t -e test.c
+
+echo 't -f noexist'
+t -f noexist
+echo 't -f /dev/tty'
+t -f /dev/tty
+echo 't -f test.c'
+t -f test.c
+
+echo 't -g test.c'
+t -g test.c
+echo 't -g /bin/ps'
+t -g /bin/ps
+
+echo 't -n ""'
+t -n ""
+echo 't -n "hello"'
+t -n "hello"
+
+echo 't -p test.c'
+t -p test.c
+
+echo 't -r noexist'
+t -r noexist
+echo 't -r /etc/master.passwd'
+t -r /etc/master.passwd
+echo 't -r test.c'
+t -r test.c
+
+echo 't -s noexist'
+t -s noexist
+echo 't -s /dev/null'
+t -s /dev/null
+echo 't -s test.c'
+t -s test.c
+
+echo 't -t 20'
+t -t 20
+echo 't -t 0'
+t -t 0
+
+echo 't -u test.c'
+t -u test.c
+echo 't -u /bin/rcp'
+t -u /bin/rcp
+
+echo 't -w noexist'
+t -w noexist
+echo 't -w /etc/master.passwd'
+t -w /etc/master.passwd
+echo 't -w /dev/null'
+t -w /dev/null
+
+echo 't -x noexist'
+t -x noexist
+echo 't -x /bin/ps'
+t -x /bin/ps
+echo 't -x /etc/motd'
+t -x /etc/motd
+
+echo 't -z ""'
+t -z ""
+echo 't -z "foo"'
+t -z "foo"
+
+echo 't "foo"'
+t "foo"
+echo 't ""'
+t ""
+
+echo 't "hello" = "hello"'
+t "hello" = "hello"
+echo 't "hello" = "goodbye"'
+t "hello" = "goodbye"
+
+echo 't "hello" != "hello"'
+t "hello" != "hello"
+echo 't "hello" != "goodbye"'
+t "hello" != "goodbye"
+
+echo 't 200 -eq 200'
+t 200 -eq 200
+echo 't 34 -eq 222'
+t 34 -eq 222
+
+echo 't 200 -ne 200'
+t 200 -ne 200
+echo 't 34 -ne 222'
+t 34 -ne 222
+
+echo 't 200 -gt 200'
+t 200 -gt 200
+echo 't 340 -gt 222'
+t 340 -gt 222
+
+echo 't 200 -ge 200'
+t 200 -ge 200
+echo 't 34 -ge 222'
+t 34 -ge 222
+
+echo 't 200 -lt 200'
+t 200 -lt 200
+echo 't 34 -lt 222'
+t 34 -lt 222
+
+echo 't 200 -le 200'
+t 200 -le 200
+echo 't 340 -le 222'
+t 340 -le 222
+
+echo 't 700 -le 1000 -a -n "1" -a "20" = "20"'
+t 700 -le 1000 -a -n "1" -a "20" = "20"
+echo 't ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)'
+t ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)
diff --git a/usr/src/bin/test/operators.c b/usr/src/bin/test/operators.c
new file mode 100644 (file)
index 0000000..ca22e10
--- /dev/null
@@ -0,0 +1,144 @@
+/*-
+ * Copyright (c) 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)operators.c        8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+
+/*
+ * Operators used in the test command.
+ */
+
+#include <stdio.h>
+
+#include "operators.h"
+
+const char *const unary_op[] = {
+      "!",
+      "-b",
+      "-c",
+      "-d",
+      "-e",
+      "-f",
+      "-g",
+      "-h",
+      "-k",
+      "-n",
+      "-p",
+      "-r",
+      "-s",
+      "-t",
+      "-u",
+      "-w",
+      "-x",
+      "-z",
+      NULL
+};
+
+const char *const binary_op[] = {
+      "-o",
+      "|",
+      "-a",
+      "&",
+      "=",
+      "!=",
+      "-eq",
+      "-ne",
+      "-gt",
+      "-lt",
+      "-le",
+      "-ge",
+      NULL
+};
+
+const char op_priority[] = {
+      3,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      12,
+      1,
+      1,
+      2,
+      2,
+      4,
+      4,
+      4,
+      4,
+      4,
+      4,
+      4,
+      4,
+};
+
+const char op_argflag[] = {
+      0,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_STRING,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_INT,
+      OP_FILE,
+      OP_FILE,
+      OP_FILE,
+      OP_STRING,
+      0,
+      0,
+      0,
+      0,
+      OP_STRING,
+      OP_STRING,
+      OP_INT,
+      OP_INT,
+      OP_INT,
+      OP_INT,
+      OP_INT,
+      OP_INT,
+};
diff --git a/usr/src/bin/test/operators.h b/usr/src/bin/test/operators.h
new file mode 100644 (file)
index 0000000..e4212a9
--- /dev/null
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (c) 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)operators.h 8.3 (Berkeley) 4/2/94
+ */
+
+#define        NOT             0
+#define        ISBLOCK         1
+#define        ISCHAR          2
+#define        ISDIR           3
+#define        ISEXIST         4
+#define        ISFILE          5
+#define        ISSETGID        6
+#define        ISSYMLINK       7
+#define        ISSTICKY        8
+#define        STRLEN          9
+#define        ISFIFO          10
+#define        ISREAD          11
+#define        ISSIZE          12
+#define        ISTTY           13
+#define        ISSETUID        14
+#define        ISWRITE         15
+#define        ISEXEC          16
+#define        NULSTR          17
+
+#define        FIRST_BINARY_OP 18
+#define        OR1             18
+#define        OR2             19
+#define        AND1            20
+#define        AND2            21
+#define        STREQ           22
+#define        STRNE           23
+#define        EQ              24
+#define        NE              25
+#define        GT              26
+#define        LT              27
+#define        LE              28
+#define        GE              29
+
+
+#define        OP_INT          1       /* arguments to operator are integer */
+#define        OP_STRING       2       /* arguments to operator are string */
+#define        OP_FILE         3       /* argument is a file name */
+
+extern const char *const unary_op[];
+extern const char *const binary_op[];
+extern const char op_priority[];
+extern const char op_argflag[];
diff --git a/usr/src/bin/test/test.1 b/usr/src/bin/test/test.1
new file mode 100644 (file)
index 0000000..ef70397
--- /dev/null
@@ -0,0 +1,251 @@
+.\" Copyright (c) 1991, 1993
+.\"    The Regents of the University of California.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"     @(#)test.1     8.1 (Berkeley) 5/31/93
+.\"
+.Dd May 31, 1993
+.Dt TEST 1
+.Os
+.Sh NAME
+.Nm test
+.Nd condition evaluation utility
+.Sh SYNOPSIS
+.Nm test
+.Ar expression
+.Sh DESCRIPTION
+The
+.Nm test
+utility evaluates the expression and, if it evaluates
+to true, returns a zero (true) exit status; otherwise
+it returns 1 (false).
+If there is no expression, test also
+returns 1 (false).
+.Pp
+All operators and flags are separate arguments to the
+.Nm test
+utility.
+.Pp
+The following primaries are used to construct expression:
+.Bl -tag -width Ar
+.It Fl b Ar file
+True if
+.Ar file
+exists and is a block special
+file.
+.It Fl c Ar file
+True if
+.Ar file
+exists and is a character
+special file.
+.It Fl d Ar file
+True if
+.Ar file
+exists and is a directory.
+.It Fl e Ar file
+True if
+.Ar file
+exists (regardless of type).
+.It Fl f Ar file
+True if
+.Ar file
+exists and is a regular file.
+.It Fl g Ar file
+True if
+.Ar file
+exists and its set group ID flag
+is set.
+.It Fl h Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+.It Fl n Ar string
+True if the length of
+.Ar string
+is nonzero.
+.It Fl p Ar file
+True if
+.Ar file
+is a named pipe
+.Po Tn FIFO Pc .
+.It Fl r Ar file
+True if
+.Ar file exists and is readable.
+.It Fl s Ar file
+True if
+.Ar file
+exists and has a size greater
+than zero.
+.It Fl t Ar [file_descriptor]
+True if the file whose file descriptor number
+is
+.Ar file_descriptor
+(default 1) is open and is
+associated with a terminal.
+.It Fl u Ar file
+True if
+.Ar file
+exists and its set user ID flag
+is set.
+.It Fl w Ar file
+True if
+.Ar file
+exists and is writable.
+True
+indicates only that the write flag is on.
+The file is not writable on a read-only file
+system even if this test indicates true.
+.It Fl x Ar file
+True if
+.Ar file
+exists and is executable.
+True
+indicates only that the execute flag is on.
+If
+.Ar file
+is a directory, true indicates that
+.Ar file
+can be searched.
+.It Fl z Ar string
+True if the length of
+.Ar string
+is zero.
+.It Ar string
+True if
+.Ar string
+is not the null
+string.
+.It Ar \&s\&1 Cm \&= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are identical.
+.It Ar \&s\&1 Cm \&!= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are not identical.
+.It Ar \&n\&1 Fl \&eq Ar \&n\&2 
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are algebraically
+equal.
+.It Ar \&n\&1 Fl \&ne Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are not
+algebraically equal.
+.It Ar \&n\&1 Fl \&gt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&ge Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than or equal to the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&lt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&le Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than or equal to the integer
+.Ar \&n\&2 .
+.El
+.Pp
+These primaries can be combined with the following operators:
+.Bl -tag -width Ar
+.It Cm \&! Ar expression
+True if
+.Ar expression
+is false.
+.It Ar expression1 Fl a Ar expression2
+True if both
+.Ar expression1
+and
+.Ar expression2
+are true.
+.It Ar expression1 Fl o Ar expression2
+True if either
+.Ar expression1
+or
+.Ar expression2
+are true.
+.It Cm \&( Ns Ar expression Ns Cm \&)
+True if expression is true.
+.El
+.Pp
+The
+.Fl a
+operator has higher precedence than the
+.Fl o
+operator.
+.Sh GRAMMAR AMBIGUITY
+The 
+.Nm test
+grammar is inherently ambiguous.  In order to assure a degree of consistency,
+the cases described in the 
+.St -p1003.2 , 
+section D11.2/4.62.4, standard
+are evaluated consistently according to the rules specified in the
+standards document.  All other cases are subject to the ambiguity in the
+command semantics.
+.Sh RETURN VALUES
+The
+.Nm test
+utility exits with one of the following values:
+.Bl -tag -width Ds
+.It 0
+expression evaluated to true.
+.It 1
+expression evaluated to false or expression was
+missing.
+.It >1
+An error occurred.
+.El
+.Sh STANDARDS
+The
+.Nm test
+function is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr/src/bin/test/test.c b/usr/src/bin/test/test.c
new file mode 100644 (file)
index 0000000..037b2e6
--- /dev/null
@@ -0,0 +1,556 @@
+/*-
+ * Copyright (c) 1992, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+       The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)test.c     8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "operators.h"
+
+#define        STACKSIZE       12
+#define        NESTINCR        16
+
+/* data types */
+#define        STRING  0
+#define        INTEGER 1
+#define        BOOLEAN 2
+
+#define        IS_BANG(s) (s[0] == '!' && s[1] == '\0')
+
+/*
+ * This structure hold a value.  The type keyword specifies the type of
+ * the value, and the union u holds the value.  The value of a boolean
+ * is stored in u.num (1 = TRUE, 0 = FALSE).
+ */
+struct value {
+       int type;
+       union {
+               char *string;
+               long num;
+       } u;
+};
+
+struct operator {
+       short op;               /* Which operator. */
+       short pri;              /* Priority of operator. */
+};
+
+struct filestat {
+       char *name;             /* Name of file. */
+       int rcode;              /* Return code from stat. */
+       struct stat stat;       /* Status info on file. */
+};
+
+static int     expr_is_false __P((struct value *));
+static void    expr_operator __P((int, struct value *, struct filestat *));
+static void    get_int __P((char *, long *));
+static int     lookup_op __P((char *, const char *const *));
+static void    overflow __P((void));
+static int     posix_binary_op __P((char **));
+static int     posix_unary_op __P((char **));
+static void    syntax __P((void));
+
+int
+main(argc, argv)
+       int argc;
+       char *argv[];
+{
+       struct operator opstack[STACKSIZE];
+       struct operator *opsp;
+       struct value valstack[STACKSIZE + 1];
+       struct value *valsp;
+       struct filestat fs;
+       char  c, **ap, *opname, *p;
+       int binary, nest, op, pri, ret_val, skipping;
+
+       if ((p = argv[0]) == NULL)
+               errx(2, "test: argc is zero");
+
+       if (*p != '\0' && p[strlen(p) - 1] == '[') {
+               if (strcmp(argv[--argc], "]"))
+                       errx(2, "missing ]");
+               argv[argc] = NULL;
+       }
+       ap = argv + 1;
+       fs.name = NULL;
+
+       /*
+        * Test(1) implements an inherently ambiguous grammer.  In order to
+        * assure some degree of consistency, we special case the POSIX 1003.2
+        * requirements to assure correct evaluation for POSIX scripts.  The
+        * following special cases comply with POSIX P1003.2/D11.2 Section
+        * 4.62.4.
+        */
+       switch(argc - 1) {
+       case 0:                         /* % test */
+               return (1);
+               break;
+       case 1:                         /* % test arg */
+               return (argv[1] == NULL || *argv[1] == '\0') ? 1 : 0;
+               break;
+       case 2:                         /* % test op arg */
+               opname = argv[1];
+               if (IS_BANG(opname))
+                       return (*argv[2] == '\0') ? 0 : 1;
+               else {
+                       ret_val = posix_unary_op(&argv[1]);
+                       if (ret_val >= 0)
+                               return (ret_val);
+               }
+               break;
+       case 3:                         /* % test arg1 op arg2 */
+               if (IS_BANG(argv[1])) {
+                       ret_val = posix_unary_op(&argv[1]);
+                       if (ret_val >= 0)
+                               return (!ret_val);
+               } else {
+                       ret_val = posix_binary_op(&argv[1]);
+                       if (ret_val >= 0)
+                               return (ret_val);
+               }
+               break;
+       case 4:                         /* % test ! arg1 op arg2 */
+               if (IS_BANG(argv[1])) {
+                       ret_val = posix_binary_op(&argv[2]);
+                       if (ret_val >= 0)
+                               return (!ret_val);
+               }
+               break;
+       default:
+               break;
+       }
+
+       /*
+        * We use operator precedence parsing, evaluating the expression as
+        * we parse it.  Parentheses are handled by bumping up the priority
+        * of operators using the variable "nest."  We use the variable
+        * "skipping" to turn off evaluation temporarily for the short
+        * circuit boolean operators.  (It is important do the short circuit
+        * evaluation because under NFS a stat operation can take infinitely
+        * long.)
+        */
+       opsp = opstack + STACKSIZE;
+       valsp = valstack;
+       nest = skipping = 0;
+       if (*ap == NULL) {
+               valstack[0].type = BOOLEAN;
+               valstack[0].u.num = 0;
+               goto done;
+       }
+       for (;;) {
+               opname = *ap++;
+               if (opname == NULL)
+                       syntax();
+               if (opname[0] == '(' && opname[1] == '\0') {
+                       nest += NESTINCR;
+                       continue;
+               } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) {
+                       if (opsp == &opstack[0])
+                               overflow();
+                       --opsp;
+                       opsp->op = op;
+                       opsp->pri = op_priority[op] + nest;
+                       continue;
+               } else {
+                       valsp->type = STRING;
+                       valsp->u.string = opname;
+                       valsp++;
+               }
+               for (;;) {
+                       opname = *ap++;
+                       if (opname == NULL) {
+                               if (nest != 0)
+                                       syntax();
+                               pri = 0;
+                               break;
+                       }
+                       if (opname[0] != ')' || opname[1] != '\0') {
+                               if ((op = lookup_op(opname, binary_op)) < 0)
+                                       syntax();
+                               op += FIRST_BINARY_OP;
+                               pri = op_priority[op] + nest;
+                               break;
+                       }
+                       if ((nest -= NESTINCR) < 0)
+                               syntax();
+               }
+               while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) {
+                       binary = opsp->op;
+                       for (;;) {
+                               valsp--;
+                               c = op_argflag[opsp->op];
+                               if (c == OP_INT) {
+                                       if (valsp->type == STRING)
+                                               get_int(valsp->u.string,
+                                                   &valsp->u.num);
+                                       valsp->type = INTEGER;
+                               } else if (c >= OP_STRING) {    
+                                                   /* OP_STRING or OP_FILE */
+                                       if (valsp->type == INTEGER) {
+                                               if ((p = malloc(32)) == NULL)
+                                                       err(2, NULL);
+#ifdef SHELL
+                                               fmtstr(p, 32, "%d", 
+                                                   valsp->u.num);
+#else
+                                               (void)sprintf(p,
+                                                   "%d", valsp->u.num);
+#endif
+                                               valsp->u.string = p;
+                                       } else if (valsp->type == BOOLEAN) {
+                                               if (valsp->u.num)
+                                                       valsp->u.string = 
+                                                           "true";
+                                               else
+                                                       valsp->u.string = "";
+                                       }
+                                       valsp->type = STRING;
+                                       if (c == OP_FILE && (fs.name == NULL ||
+                                           strcmp(fs.name, valsp->u.string))) {
+                                               fs.name = valsp->u.string;
+                                               fs.rcode = 
+                                                   stat(valsp->u.string, 
+                                                    &fs.stat);
+                                       }
+                               }
+                               if (binary < FIRST_BINARY_OP)
+                                       break;
+                               binary = 0;
+                       }
+                       if (!skipping)
+                               expr_operator(opsp->op, valsp, &fs);
+                       else if (opsp->op == AND1 || opsp->op == OR1)
+                               skipping--;
+                       valsp++;                /* push value */
+                       opsp++;                 /* pop operator */
+               }
+               if (opname == NULL)
+                       break;
+               if (opsp == &opstack[0])
+                       overflow();
+               if (op == AND1 || op == AND2) {
+                       op = AND1;
+                       if (skipping || expr_is_false(valsp - 1))
+                               skipping++;
+               }
+               if (op == OR1 || op == OR2) {
+                       op = OR1;
+                       if (skipping || !expr_is_false(valsp - 1))
+                               skipping++;
+               }
+               opsp--;
+               opsp->op = op;
+               opsp->pri = pri;
+       }
+done:  return (expr_is_false(&valstack[0]));
+}
+
+static int
+expr_is_false(val)
+       struct value *val;
+{
+
+       if (val->type == STRING) {
+               if (val->u.string[0] == '\0')
+                       return (1);
+       } else {                /* INTEGER or BOOLEAN */
+               if (val->u.num == 0)
+                       return (1);
+       }
+       return (0);
+}
+
+
+/*
+ * Execute an operator.  Op is the operator.  Sp is the stack pointer;
+ * sp[0] refers to the first operand, sp[1] refers to the second operand
+ * (if any), and the result is placed in sp[0].  The operands are converted
+ * to the type expected by the operator before expr_operator is called.
+ * Fs is a pointer to a structure which holds the value of the last call
+ * to stat, to avoid repeated stat calls on the same file.
+ */
+static void
+expr_operator(op, sp, fs)
+       int op;
+       struct value *sp;
+       struct filestat *fs;
+{
+       int i;
+
+       switch (op) {
+       case NOT:
+               sp->u.num = expr_is_false(sp);
+               sp->type = BOOLEAN;
+               break;
+       case ISEXIST:
+               if (fs == NULL || fs->rcode == -1)
+                       goto false;
+               else
+                       goto true;
+       case ISREAD:
+               i = S_IROTH;
+               goto permission;
+       case ISWRITE:
+               i = S_IWOTH;
+               goto permission;
+       case ISEXEC:
+               i = S_IXOTH;
+permission:    if (fs->stat.st_uid == geteuid())
+                       i <<= 6;
+               else if (fs->stat.st_gid == getegid())
+                       i <<= 3;
+               goto filebit;   /* true if (stat.st_mode & i) != 0 */
+       case ISFILE:
+               i = S_IFREG;
+               goto filetype;
+       case ISDIR:
+               i = S_IFDIR;
+               goto filetype;
+       case ISCHAR:
+               i = S_IFCHR;
+               goto filetype;
+       case ISBLOCK:
+               i = S_IFBLK;
+               goto filetype;
+       case ISSYMLINK:
+               i = S_IFLNK;
+               (void)lstat(sp->u.string, &fs->stat);
+               goto filetype;
+       case ISFIFO:
+               i = S_IFIFO;
+               goto filetype;
+filetype:      if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0)
+true:                  sp->u.num = 1;
+               else
+false:                 sp->u.num = 0;
+               sp->type = BOOLEAN;
+               break;
+       case ISSETUID:
+               i = S_ISUID;
+               goto filebit;
+       case ISSETGID:
+               i = S_ISGID;
+               goto filebit;
+       case ISSTICKY:
+               i = S_ISVTX;
+filebit:       if (fs->stat.st_mode & i && fs->rcode >= 0)
+                       goto true;
+               goto false;
+       case ISSIZE:
+               sp->u.num = fs->rcode >= 0 ? fs->stat.st_size : 0L;
+               sp->type = INTEGER;
+               break;
+       case ISTTY:
+               sp->u.num = isatty(sp->u.num);
+               sp->type = BOOLEAN;
+               break;
+       case NULSTR:
+               if (sp->u.string[0] == '\0')
+                       goto true;
+               goto false;
+       case STRLEN:
+               sp->u.num = strlen(sp->u.string);
+               sp->type = INTEGER;
+               break;
+       case OR1:
+       case AND1:
+               /*
+                * These operators are mostly handled by the parser.  If we
+                * get here it means that both operands were evaluated, so
+                * the value is the value of the second operand.
+                */
+               *sp = *(sp + 1);
+               break;
+       case STREQ:
+       case STRNE:
+               i = 0;
+               if (!strcmp(sp->u.string, (sp + 1)->u.string))
+                       i++;
+               if (op == STRNE)
+                       i = 1 - i;
+               sp->u.num = i;
+               sp->type = BOOLEAN;
+               break;
+       case EQ:
+               if (sp->u.num == (sp + 1)->u.num)
+                       goto true;
+               goto false;
+       case NE:
+               if (sp->u.num != (sp + 1)->u.num)
+                       goto true;
+               goto false;
+       case GT:
+               if (sp->u.num > (sp + 1)->u.num)
+                       goto true;
+               goto false;
+       case LT:
+               if (sp->u.num < (sp + 1)->u.num)
+                       goto true;
+               goto false;
+       case LE:
+               if (sp->u.num <= (sp + 1)->u.num)
+                       goto true;
+               goto false;
+       case GE:
+               if (sp->u.num >= (sp + 1)->u.num)
+                       goto true;
+               goto false;
+
+       }
+}
+
+static int
+lookup_op(name, table)
+       char *name;
+       const char *const * table;
+{
+       const char *const * tp;
+       const char *p;
+       char c;
+
+       c = name[1];
+       for (tp = table; (p = *tp) != NULL; tp++)
+               if (p[1] == c && !strcmp(p, name))
+                       return (tp - table);
+       return (-1);
+}
+
+static int
+posix_unary_op(argv)
+       char **argv;
+{
+       struct filestat fs;
+       struct value valp;
+       int op, c;
+       char *opname;
+
+       opname = *argv;
+       if ((op = lookup_op(opname, unary_op)) < 0)
+               return (-1);
+       c = op_argflag[op];
+       opname = argv[1];
+       valp.u.string = opname;
+       if (c == OP_FILE) {
+               fs.name = opname;
+               fs.rcode = stat(opname, &fs.stat);
+       } else if (c != OP_STRING)
+               return (-1);
+
+       expr_operator(op, &valp, &fs);
+       return (valp.u.num == 0);
+}
+
+static int
+posix_binary_op(argv)
+       char  **argv;
+{
+       struct value v[2];
+       int op, c;
+       char *opname;
+
+       opname = argv[1];
+       if ((op = lookup_op(opname, binary_op)) < 0)
+               return (-1);
+       op += FIRST_BINARY_OP;
+       c = op_argflag[op];
+
+       if (c == OP_INT) {
+               get_int(argv[0], &v[0].u.num);
+               get_int(argv[2], &v[1].u.num);
+       } else {
+               v[0].u.string = argv[0];
+               v[1].u.string = argv[2];
+       }
+       expr_operator(op, v, NULL);
+       return (v[0].u.num == 0);
+}
+
+/*
+ * Integer type checking.
+ */
+static void
+get_int(v, lp)
+       char *v;
+       long *lp;
+{
+       long val;
+       char *ep;
+
+       for (; *v && isspace(*v); ++v);
+       if (isdigit(*v)) {
+               errno = 0;
+               val = strtol(v, &ep, 10);
+               if (*ep != '\0')
+                       errx(2, "%s: trailing non-numeric characters", v);
+               if (errno == ERANGE) {
+                       if (val == LONG_MIN)
+                               errx(2, "%s: underflow", v);
+                       if (val == LONG_MAX)
+                               errx(2, "%s: overflow", v);
+               }
+               *lp = val;
+               return;
+       }
+       errx(2, "%s: expected integer", v);
+}
+
+static void
+syntax()
+{
+
+       err(2, "syntax error");
+}
+
+static void
+overflow()
+{
+
+       err(2, "expression is too complex");
+}