From 43a1ac4f3514406da9ce2a8eb41cc178c7531eca Mon Sep 17 00:00:00 2001
From: Laurent Bercot
Date: Tue, 20 Dec 2022 09:46:56 +0000
Subject: Prepare for 2.9.1.0; add eltest
Signed-off-by: Laurent Bercot
---
NEWS | 7 +-
doc/eltest.html | 172 +++++++++++++++
doc/index.html | 3 +-
doc/upgrade.html | 3 +-
package/deps.mak | 3 +
package/info | 2 +-
package/modes | 1 +
package/targets.mak | 1 +
src/execline/deps-exe/eltest | 1 +
src/execline/eltest.c | 505 +++++++++++++++++++++++++++++++++++++++++++
10 files changed, 692 insertions(+), 6 deletions(-)
create mode 100644 doc/eltest.html
create mode 100644 src/execline/deps-exe/eltest
create mode 100644 src/execline/eltest.c
diff --git a/NEWS b/NEWS
index 475ed87..559fc17 100644
--- a/NEWS
+++ b/NEWS
@@ -1,9 +1,10 @@
Changelog for execline.
-In 2.9.0.2
+In 2.9.1.0
----------
- Adaptation to skalibs-2.9.0.2.
+ - New program: eltest.
In 2.9.0.1
@@ -16,8 +17,8 @@ In 2.9.0.0
----------
- Bugfixes.
- - New -a/-o options to wait (-o waits for one process only)
- - wait now exits 99 on timeout
+ - New -a/-o options to wait (-o waits for one process only).
+ - wait now exits 99 on timeout.
In 2.8.3.0
diff --git a/doc/eltest.html b/doc/eltest.html
new file mode 100644
index 0000000..068cd4c
--- /dev/null
+++ b/doc/eltest.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+ execline: the eltest program
+
+
+
+
+
+
+
+execline
+Software
+skarnet.org
+
+
+ The eltest program
+
+
+ eltest evaluates an expression and indicates the result via its
+exit status.
+
+
+ Interface
+
+
+ eltest expression...
+
+
+
+ eltest acts as the generic POSIX
+test utility,
+but it diverges from the specification on how it parses ambiguous arguments, see below.
+
+
+
+ eltest supports all the standard
+test
+operands, plus all the extensions from
+GNU test, plus a few
+extensions from the test builtin from
+bash.
+The extensions to POSIX test are listed below.
+
+
+
+ eltest accepts an arbitrary number of arguments and, if the expression is
+valid, always returns the result of the expression no matter how complex it
+is.
+
+
+ Exit codes
+
+
+ - 0: the test is true
+ - 1: the test is false
+ - 100: wrong usage
+ - 101: internal error (should never happen, warrants a bug-report)
+ - 111: system call failure
+
+
+ Posixness
+
+
+ eltest is not suitable as a Single Unix
+test
+program, due to the way it disambiguates between arguments and operators, see below.
+However, if you never use arguments that start with a backslash, or that have the
+same name as an existing operator, then
+eltest exhibits the same behaviour as test.
+
+
+ Extensions to POSIX
+
+
+ - expr1 -a expr2 :
+tests whether expr1 and expr2 are true.
+If expr1 is false, then expr2 is not evaluated.
+ - expr1 -o expr2 :
+tests whether expr1 or expr2 is true.
+If expr1 is true, then expr2 is not evaluated.
+ - -k file : tests whether file
+has the sticky bit.
+ - -O file : tests whether file
+is owned by the effective uid of the current process.
+ - -U file : same.
+ - -G file : tests whether file's gid
+is the effective gid of the current process.
+ - -N file : tests whether file exists
+and has been modified since it was last read.
+ - file1 -nt file2 :
+tests whether file1 has a (strictly) newer modification date than file2.
+ - file1 -ot file2 :
+tests whether file1 has a (strictly) older modification date than file2.
+ - file1 -ef file2 :
+tests whether file1 and file2 are physically the same
+file (same device and inode numbers).
+ - -v var : tests whether the
+var variable is defined in the current environment.
+ - string =~ pattern :
+tries to match string against extended regular expression
+pattern. True if any part of string matches pattern;
+in order to match whole strings, you must anchor pattern with
+^ and $ markers.
+
+
+ Argument disambiguation
+
+
+ Unlike test,
+which has different fixed syntax trees depending on the number of arguments it receives and
+has undefined behaviour when called with more than 5 arguments, eltest accepts any
+number of arguments and builds its syntax trees on the fly. This means that expressions such
+as -n = -n cannot be automatically disambiguated: eltest does not know that
+there are 3 arguments, so when it reads the first -n it assumes that it is an unary
+operator, then when it reads = it assumes it is the argument to -n, then
+when it reads the second -n it exits with a syntax error.
+
+
+
+ Doing otherwise would result in a combinatory explosion of possible syntax trees, making
+it easy for users to trigger unbounded RAM consumption, and turning a simple utility into
+a programming nightmare. This is why POSIX
+test
+is so restricted. But we don't want the same restrictions.
+
+
+
+ So, instead, eltest provides the user with a mechanism to make sure that
+operands are never mistaken for operators:
+
+
+
+ - An word that looks like an operator will always be interpreted like an operator.
+So, expressions like -n = -n will result in a syntax error, because the
+first -n will never be understood as data for the = operator.
+ - A word that starts with a \ (backslash) will always be interpreted
+like data, never like an operator, and the backslash will be removed. This
+means: \-n = \-n is a valid expression testing the equality between
+the strings -n and -n.
+
+ - Be aware that execline as well as the shell use one backlash for their own
+unquoting mechanism, so when using backslashes in an execline or shell script, they
+must be doubled. You would probably need to type something like \\-n = \\-n.
+
+ - So, if your script tests equality between $a and $b, and there's
+a possiblity that the contents of these variables look like eltest operators,
+the proper syntax would be: eltest \\${a} = \\${b}.
+
+
+
+ Note that these details are irrelevant to a huge majority of eltest use
+cases, because most of the time users only need a simple test
+such as eltest -r ${file} to check that $file is readable, and
+there's no possible ambiguity. So don't panic over this.
+
+
+ Notes
+
+
+ - eltest is a replacement for the ill-named, and now deprecated,
+s6-test
+program, part of the (just as ill-named)
+s6-portable-utils
+package. It is too valuable a utility to be part of a marginal package, and
+has nothing to do with s6.
+
+
+
+
diff --git a/doc/index.html b/doc/index.html
index f9c985a..5d2f402 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -77,7 +77,7 @@ want nsswitch-like functionality:
Download
- - The current released version of execline is 2.9.0.2.
+ - The current released version of execline is 2.9.1.0.
- Alternatively, you can checkout a copy of the
execline
git repository:
@@ -194,6 +194,7 @@ the previous versions of execline and the current one.
(Miscellaneous)
diff --git a/doc/upgrade.html b/doc/upgrade.html
index 26e119d..493c6e1 100644
--- a/doc/upgrade.html
+++ b/doc/upgrade.html
@@ -18,13 +18,14 @@
What has changed in execline
- in 2.9.0.2
+ in 2.9.1.0
- skalibs
dependency bumped to 2.13.0.0.
- nsss
optional dependency bumped to 0.2.0.2.
+ - New program: eltest.
in 2.9.0.1
diff --git a/package/deps.mak b/package/deps.mak
index 17d654e..d9ab00a 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -10,6 +10,7 @@ src/execline/dollarat.o src/execline/dollarat.lo: src/execline/dollarat.c
src/execline/elgetopt.o src/execline/elgetopt.lo: src/execline/elgetopt.c src/include/execline/execline.h
src/execline/elgetpositionals.o src/execline/elgetpositionals.lo: src/execline/elgetpositionals.c src/include-local/exlsn.h
src/execline/elglob.o src/execline/elglob.lo: src/execline/elglob.c src/include-local/exlsn.h
+src/execline/eltest.o src/execline/eltest.lo: src/execline/eltest.c
src/execline/emptyenv.o src/execline/emptyenv.lo: src/execline/emptyenv.c src/include/execline/execline.h
src/execline/envfile.o src/execline/envfile.lo: src/execline/envfile.c
src/execline/exec.o src/execline/exec.lo: src/execline/exec.c
@@ -93,6 +94,8 @@ elgetpositionals: EXTRA_LIBS := -lskarnet
elgetpositionals: src/execline/elgetpositionals.o ${LIBEXECLINE}
elglob: EXTRA_LIBS := -lskarnet
elglob: src/execline/elglob.o ${LIBEXECLINE}
+eltest: EXTRA_LIBS := -lskarnet
+eltest: src/execline/eltest.o
emptyenv: EXTRA_LIBS := -lskarnet
emptyenv: src/execline/emptyenv.o ${LIBEXECLINE}
envfile: EXTRA_LIBS := -lskarnet
diff --git a/package/info b/package/info
index 2ff5910..5a2157b 100644
--- a/package/info
+++ b/package/info
@@ -1,4 +1,4 @@
package=execline
-version=2.9.0.2
+version=2.9.1.0
category=admin
package_macro_name=EXECLINE
diff --git a/package/modes b/package/modes
index 17c52ce..78671a7 100644
--- a/package/modes
+++ b/package/modes
@@ -6,6 +6,7 @@ dollarat 0755
elgetopt 0755
elgetpositionals 0755
elglob 0755
+eltest 0755
execline-cd 0755
execline-umask 0755
emptyenv 0755
diff --git a/package/targets.mak b/package/targets.mak
index a0e5970..0b41b79 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -7,6 +7,7 @@ dollarat \
elgetopt \
elgetpositionals \
elglob \
+eltest \
emptyenv \
envfile \
exec \
diff --git a/src/execline/deps-exe/eltest b/src/execline/deps-exe/eltest
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/eltest
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/eltest.c b/src/execline/eltest.c
new file mode 100644
index 0000000..b14318b
--- /dev/null
+++ b/src/execline/eltest.c
@@ -0,0 +1,505 @@
+/* ISC license. */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+enum opnum
+{
+ T_NOT,
+ T_AND,
+ T_OR,
+ T_LEFTP,
+ T_RIGHTP,
+ T_BLOCK,
+ T_CHAR,
+ T_DIR,
+ T_EXIST,
+ T_REGULAR,
+ T_SGID,
+ T_SYMLINK,
+ T_STICKY,
+ T_NONZERO,
+ T_FIFO,
+ T_READABLE,
+ T_NONZEROFILE,
+ T_TERM,
+ T_SUID,
+ T_WRITABLE,
+ T_EXECUTABLE,
+ T_ZERO,
+ T_EUID,
+ T_EGID,
+ T_SOCKET,
+ T_MODIFIED,
+ T_NEWER,
+ T_OLDER,
+ T_DEVINO,
+ T_STREQUAL,
+ T_STRNEQUAL,
+ T_STRLESSER,
+ T_STRLESSERE,
+ T_STRGREATER,
+ T_STRGREATERE,
+ T_NUMEQUAL,
+ T_NUMNEQUAL,
+ T_NUMGREATER,
+ T_NUMGREATERE,
+ T_NUMLESSER,
+ T_NUMLESSERE,
+ T_ENV,
+ T_MATCH
+} ;
+
+struct token
+{
+ char const *string ;
+ enum opnum op ;
+ unsigned int type ;
+} ;
+
+struct node
+{
+ enum opnum op ;
+ unsigned int type ;
+ unsigned int arg1 ;
+ unsigned int arg2 ;
+ char const *data ;
+} ;
+
+static unsigned int lex (struct node *tree, char const *const *argv)
+{
+ static struct token const tokens[46] =
+ {
+ { "-n", T_NONZERO, 2 },
+ { "-z", T_ZERO, 2 },
+ { "=", T_STREQUAL, 3 },
+ { "!=", T_STRNEQUAL, 3 },
+ { "-eq", T_NUMEQUAL, 3 },
+ { "-ne", T_NUMNEQUAL, 3 },
+ { "-gt", T_NUMGREATER, 3 },
+ { "-ge", T_NUMGREATERE, 3 },
+ { "-lt", T_NUMLESSER, 3 },
+ { "-le", T_NUMLESSERE, 3 },
+ { "-f", T_REGULAR, 2 },
+ { "-h", T_SYMLINK, 2 },
+ { "-L", T_SYMLINK, 2 },
+ { "-e", T_EXIST, 2 },
+ { "-k", T_STICKY, 2 },
+ { "-a", T_AND, 7 },
+ { "-o", T_OR, 8 },
+ { "!", T_NOT, 6 },
+ { "(", T_LEFTP, 4 },
+ { ")", T_RIGHTP, 5 },
+ { "-b", T_BLOCK, 2 },
+ { "-c", T_CHAR, 2 },
+ { "-d", T_DIR, 2 },
+ { "-g", T_SGID, 2 },
+ { "-p", T_FIFO, 2 },
+ { "-r", T_READABLE, 2 },
+ { "-s", T_NONZEROFILE, 2 },
+ { "-t", T_TERM, 2 },
+ { "-u", T_SUID, 2 },
+ { "-w", T_WRITABLE, 2 },
+ { "-x", T_EXECUTABLE, 2 },
+ { "-O", T_EUID, 2 },
+ { "-U", T_EUID, 2 },
+ { "-G", T_EGID, 2 },
+ { "-S", T_SOCKET, 2 },
+ { "-N", T_MODIFIED, 2 },
+ { "-nt", T_NEWER, 3 },
+ { "-ot", T_OLDER, 3 },
+ { "-ef", T_DEVINO, 3 },
+ { "<", T_STRLESSER, 3 },
+ { "<=", T_STRLESSERE, 3 },
+ { ">", T_STRGREATER, 3 },
+ { ">=", T_STRGREATERE, 3 },
+ { "-v", T_ENV, 2 },
+ { "=~", T_MATCH, 3 },
+ { 0, 0, 0 }
+ } ;
+ unsigned int pos = 0 ;
+
+ for (; argv[pos] ; pos++)
+ {
+ unsigned int i = 0 ;
+ tree[pos].data = argv[pos] ;
+ for (i = 0 ; tokens[i].string ; i++)
+ if (!strcmp(argv[pos], tokens[i].string))
+ {
+ tree[pos].op = tokens[i].op ;
+ tree[pos].type = tokens[i].type ;
+ break ;
+ }
+ if (!tokens[i].string)
+ {
+ tree[pos].op = T_NONZERO ;
+ tree[pos].type = 0 ;
+ tree[pos].arg1 = pos ;
+ if (*(argv[pos]) == '\\') tree[pos].data++ ;
+ }
+ }
+ return pos ;
+}
+
+static unsigned int parse (struct node *tree, unsigned int n)
+{
+ static char const table[9][13] =
+ {
+ "xssssxsssxxxx",
+ "xxxxxaxxxxxxx",
+ "xsxxsxsssxxxx",
+ "sxxxxxxxxxxxx",
+ "xsxxsxsssxxxx",
+ "nxxxxNxxxAOEs",
+ "xsxxsxsssxxxx",
+ "nxxxxNxxxAsxx",
+ "nxxxxNxxxAOsx"
+ } ;
+
+ unsigned int stack[n+2] ;
+ unsigned int sp = 0, pos = 0 ;
+ int cont = 1 ;
+
+ stack[0] = n+1 ;
+ tree[n].type = 5 ; /* add ) for the final reduce */
+ tree[n+1].type = 1 ; /* add EOF */
+
+ while (cont)
+ {
+ switch (table[tree[pos].type][tree[stack[sp]].type])
+ {
+ case 'x' : /* error */
+ {
+ char fmt[UINT_FMT] ;
+ fmt[uint_fmt(fmt, pos)] = 0 ;
+ strerr_dief2x(100, "parse error at argument ", fmt) ;
+ break ;
+ }
+ case 'a' : /* accept */
+ {
+ cont = 0 ;
+ break ;
+ }
+ case 's' : /* shift */
+ {
+ stack[++sp] = pos++ ;
+ break ;
+ }
+ case 'n' : /* reduce -> expr without nots, from atom */
+ {
+ switch (tree[stack[sp-1]].type)
+ {
+ case 2 :
+ {
+ tree[stack[sp-1]].arg1 = stack[sp] ;
+ sp-- ;
+ break ;
+ }
+ case 3 :
+ {
+ tree[stack[sp-1]].arg1 = stack[sp-2] ;
+ tree[stack[sp-1]].arg2 = stack[sp] ;
+ stack[sp-2] = stack[sp-1] ;
+ sp -= 2 ;
+ break ;
+ }
+ /* default : assert: its a zero */
+ }
+ tree[stack[sp]].type = 9 ;
+ while (tree[stack[sp-1]].type == 6)
+ {
+ tree[stack[sp-1]].type = 9 ;
+ tree[stack[sp-1]].arg1 = stack[sp] ;
+ sp-- ;
+ }
+ break ;
+ }
+ case 'N' : /* reduce -> expr without nots, from expr */
+ {
+ if (tree[stack[sp-2]].type != 4)
+ {
+ char fmt[UINT_FMT] ;
+ fmt[uint_fmt(fmt, pos)] = 0 ;
+ strerr_dief2x(100, "parse error: bad right parenthesis at argument ", fmt) ;
+ }
+ stack[sp-2] = stack[sp-1] ;
+ sp -= 2 ;
+ tree[stack[sp]].type = 9 ;
+ while (tree[stack[sp-1]].type == 6)
+ {
+ tree[stack[sp-1]].type = 9 ;
+ tree[stack[sp-1]].arg1 = stack[sp] ;
+ sp-- ;
+ }
+ break ;
+ }
+ case 'A' : /* reduce -> exprs without ands */
+ {
+ if (tree[stack[sp-1]].type == 7)
+ {
+ tree[stack[sp-1]].arg1 = stack[sp-2] ;
+ tree[stack[sp-1]].arg2 = stack[sp] ;
+ stack[sp-2] = stack[sp-1] ;
+ sp -= 2 ;
+ }
+ tree[stack[sp]].type = 10 ;
+ break ;
+ }
+ case 'O' : /* reduce -> expr without ors */
+ {
+ if (tree[stack[sp-1]].type == 8)
+ {
+ tree[stack[sp-1]].arg1 = stack[sp-2] ;
+ tree[stack[sp-1]].arg2 = stack[sp] ;
+ stack[sp-2] = stack[sp-1] ;
+ sp -= 2 ;
+ }
+ tree[stack[sp]].type = 11 ;
+ break ;
+ }
+ case 'E' : /* reduce -> expr */
+ {
+ tree[stack[sp]].type = 12 ;
+ break ;
+ }
+ default : /* can't happen */
+ strerr_dief1x(101, "internal error, please submit a bug-report.") ;
+ }
+ }
+ if (sp != 2) strerr_dief1x(100, "parse error: too many left parentheses") ;
+ return stack[1] ;
+}
+
+static int run (struct node const *tree, unsigned int root)
+{
+ switch (tree[root].op)
+ {
+ case T_NOT :
+ return !run(tree, tree[root].arg1) ;
+ case T_AND :
+ return run(tree, tree[root].arg1) && run(tree, tree[root].arg2) ;
+ case T_OR :
+ return run(tree, tree[root].arg1) || run(tree, tree[root].arg2) ;
+ case T_EXIST :
+ return access(tree[tree[root].arg1].data, F_OK) == 0 ;
+ case T_BLOCK :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return S_ISBLK(st.st_mode) ;
+ }
+ case T_CHAR :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return S_ISCHR(st.st_mode) ;
+ }
+ case T_DIR :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return S_ISDIR(st.st_mode) ;
+ }
+ case T_REGULAR :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return S_ISREG(st.st_mode) ;
+ }
+ case T_FIFO :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return S_ISFIFO(st.st_mode) ;
+ }
+ case T_SOCKET :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return S_ISSOCK(st.st_mode) ;
+ }
+ case T_SYMLINK :
+ {
+ struct stat st ;
+ if (lstat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return S_ISLNK(st.st_mode) ;
+ }
+ case T_SGID :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return (st.st_mode & S_ISGID) ;
+ }
+ case T_SUID :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return (st.st_mode & S_ISUID) ;
+ }
+ case T_STICKY :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return (st.st_mode & S_ISVTX) ;
+ }
+ case T_NONZEROFILE :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return (st.st_size > 0) ;
+ }
+ case T_MODIFIED :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return (st.st_mtime > st.st_atime) ;
+ }
+ case T_EUID :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return st.st_uid == geteuid() ;
+ }
+ case T_EGID :
+ {
+ struct stat st ;
+ if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+ return st.st_gid == getegid() ;
+ }
+ case T_READABLE :
+ return access(tree[tree[root].arg1].data, R_OK) == 0 ;
+ case T_WRITABLE :
+ return access(tree[tree[root].arg1].data, W_OK) == 0 ;
+ case T_EXECUTABLE :
+ return access(tree[tree[root].arg1].data, X_OK) == 0 ;
+ case T_NEWER :
+ {
+ struct stat st1, st2 ;
+ if (stat(tree[tree[root].arg1].data, &st1) == -1) return 0 ;
+ if (stat(tree[tree[root].arg2].data, &st2) == -1) return 1 ;
+ return st1.st_mtime > st2.st_mtime ;
+ }
+ case T_OLDER :
+ {
+ struct stat st1, st2 ;
+ if (stat(tree[tree[root].arg1].data, &st1) == -1) return 1 ;
+ if (stat(tree[tree[root].arg2].data, &st2) == -1) return 0 ;
+ return st1.st_mtime < st2.st_mtime ;
+ }
+ case T_DEVINO :
+ {
+ struct stat st1, st2 ;
+ if (stat(tree[tree[root].arg1].data, &st1) == -1) return 0 ;
+ if (stat(tree[tree[root].arg2].data, &st2) == -1) return 1 ;
+ return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino) ;
+ }
+ case T_TERM :
+ {
+ unsigned int fd ;
+ if (!uint0_scan(tree[tree[root].arg1].data, &fd))
+ strerr_dief2x(100, tree[root].data, " requires an integer argument") ;
+ return isatty(fd) ;
+ }
+ case T_NONZERO :
+ return tree[tree[root].arg1].data[0] ;
+ case T_ZERO :
+ return !tree[tree[root].arg1].data[0] ;
+ case T_STREQUAL :
+ return !strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) ;
+ case T_STRNEQUAL :
+ return !!strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) ;
+ case T_STRLESSER :
+ return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) < 0 ;
+ case T_STRLESSERE :
+ return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) <= 0 ;
+ case T_STRGREATER :
+ return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) > 0 ;
+ case T_STRGREATERE :
+ return strcmp(tree[tree[root].arg1].data, tree[tree[root].arg2].data) >= 0 ;
+ case T_NUMEQUAL :
+ {
+ int n1, n2 ;
+ if (!int_scan(tree[tree[root].arg1].data, &n1)
+ || !int_scan(tree[tree[root].arg2].data, &n2))
+ goto errorint ;
+ return n1 == n2 ;
+ }
+ case T_NUMNEQUAL :
+ {
+ int n1, n2 ;
+ if (!int_scan(tree[tree[root].arg1].data, &n1)
+ || !int_scan(tree[tree[root].arg2].data, &n2))
+ goto errorint ;
+ return n1 != n2 ;
+ }
+ case T_NUMGREATER :
+ {
+ int n1, n2 ;
+ if (!int_scan(tree[tree[root].arg1].data, &n1)
+ || !int_scan(tree[tree[root].arg2].data, &n2))
+ goto errorint ;
+ return n1 > n2 ;
+ }
+ case T_NUMGREATERE :
+ {
+ int n1, n2 ;
+ if (!int_scan(tree[tree[root].arg1].data, &n1)
+ || !int_scan(tree[tree[root].arg2].data, &n2))
+ goto errorint ;
+ return n1 >= n2 ;
+ }
+ case T_NUMLESSER :
+ {
+ int n1, n2 ;
+ if (!int_scan(tree[tree[root].arg1].data, &n1)
+ || !int_scan(tree[tree[root].arg2].data, &n2))
+ goto errorint ;
+ return n1 < n2 ;
+ }
+ case T_NUMLESSERE :
+ {
+ int n1, n2 ;
+ if (!int_scan(tree[tree[root].arg1].data, &n1)
+ || !int_scan(tree[tree[root].arg2].data, &n2))
+ goto errorint ;
+ return n1 <= n2 ;
+ }
+ case T_ENV :
+ return !!getenv(tree[tree[root].arg1].data) ;
+ case T_MATCH :
+ {
+ regex_t re ;
+ int r = skalibs_regcomp(&re, tree[tree[root].arg2].data, REG_EXTENDED | REG_NOSUB) ;
+ if (r)
+ {
+ char buf[256] ;
+ regerror(r, &re, buf, 256) ;
+ strerr_diefu4x(r == REG_ESPACE ? 111 : 100, "compile ", tree[tree[root].arg2].data, " into a regular expression: ", buf) ;
+ }
+ r = regexec(&re, tree[tree[root].arg1].data, 0, 0, 0) ;
+ regfree(&re) ;
+ return !r ;
+ }
+ default:
+ strerr_dief1x(101, "operation not implemented") ;
+ }
+
+errorint:
+ strerr_dief2x(100, tree[root].data, " requires integer arguments") ;
+}
+
+int main (int argc, char const *const *argv)
+{
+ struct node tree[argc+2] ;
+ if (argc <= 1) return 1 ;
+ PROG = "eltest" ;
+ return !run(tree, parse(tree, lex(tree, argv+1))) ;
+}
--
cgit v1.2.3