aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/test/expr.test10
-rw-r--r--toys/pending/expr.c272
2 files changed, 282 insertions, 0 deletions
diff --git a/scripts/test/expr.test b/scripts/test/expr.test
new file mode 100644
index 00000000..cce7d9d0
--- /dev/null
+++ b/scripts/test/expr.test
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+testing "expr integer" "expr 5" "5\n" "" ""
+testing "expr integer negative" "expr -5" "-5\n" "" ""
+testing "expr string" "expr astring" "astring\n" "" ""
+testing "expr 1 + 3" "expr 1 + 3" "4\n" "" ""
+testing "expr 5 + 6 * 3" "expr 5 + 6 \* 3" "23\n" "" ""
+testing "expr ( 5 + 6 ) * 3" "expr \( 5 + 6 \) \* 3" "33\n" "" ""
diff --git a/toys/pending/expr.c b/toys/pending/expr.c
new file mode 100644
index 00000000..949bb664
--- /dev/null
+++ b/toys/pending/expr.c
@@ -0,0 +1,272 @@
+/* expr.c - evaluate expression
+ *
+ * Copyright 2013 Daniel Verkamp <daniel@drv.nu>
+ *
+ * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html
+
+USE_EXPR(NEWTOY(expr, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+
+config EXPR
+ bool "expr"
+ default n
+ help
+ usage: expr args
+
+ Evaluate expression and print result.
+
+ The supported operators, in order of increasing precedence, are:
+
+ | & = > >= < <= != + - * / %
+
+ In addition, parentheses () are supported for grouping.
+*/
+
+// TODO: int overflow checking
+
+#define FOR_expr
+#include "toys.h"
+
+
+GLOBALS(
+ int argidx;
+)
+
+// Scalar value.
+// If s is NULL, the value is an integer (i).
+// If s is not NULL, the value is a string (s).
+struct value {
+ char *s;
+ long i;
+};
+
+static void parse_expr(struct value *ret, struct value *v);
+
+static void get_value(struct value *v)
+{
+ char *endp, *arg;
+
+ if (TT.argidx == toys.optc) {
+ v->i = 0;
+ v->s = ""; // signal end of expression
+ return;
+ }
+
+ if (TT.argidx >= toys.optc) {
+ error_exit("syntax error");
+ }
+
+ arg = toys.optargs[TT.argidx++];
+
+ v->i = strtol(arg, &endp, 10);
+ v->s = *endp ? arg : NULL;
+}
+
+
+// check if v matches a token, and consume it if so
+static int match(struct value *v, const char *tok)
+{
+ if (v->s && !strcmp(v->s, tok)) {
+ get_value(v);
+ return 1;
+ }
+
+ return 0;
+}
+
+// check if v is the integer 0 or the empty string
+static int is_zero(const struct value *v)
+{
+ return ((v->s && *v->s == '\0') || v->i == 0);
+}
+
+static char *num_to_str(long num)
+{
+ static char num_buf[21];
+ snprintf(num_buf, sizeof(num_buf), "%ld", num);
+ return num_buf;
+}
+
+static int cmp(const struct value *lhs, const struct value *rhs)
+{
+ if (lhs->s || rhs->s) {
+ // at least one operand is a string
+ char *ls = lhs->s ? lhs->s : num_to_str(lhs->i);
+ char *rs = rhs->s ? rhs->s : num_to_str(rhs->i);
+ return strcmp(ls, rs);
+ } else {
+ return lhs->i - rhs->i;
+ }
+}
+
+
+// operators
+
+struct op {
+ const char *tok;
+
+ // calculate "lhs op rhs" (e.g. lhs + rhs) and store result in lhs
+ void (*calc)(struct value *lhs, const struct value *rhs);
+};
+
+
+static void re(struct value *lhs, const struct value *rhs)
+{
+ error_exit("regular expression match not implemented");
+}
+
+static void mod(struct value *lhs, const struct value *rhs)
+{
+ if (lhs->s || rhs->s) error_exit("non-integer argument");
+ if (is_zero(rhs)) error_exit("division by zero");
+ lhs->i %= rhs->i;
+}
+
+static void divi(struct value *lhs, const struct value *rhs)
+{
+ if (lhs->s || rhs->s) error_exit("non-integer argument");
+ if (is_zero(rhs)) error_exit("division by zero");
+ lhs->i /= rhs->i;
+}
+
+static void mul(struct value *lhs, const struct value *rhs)
+{
+ if (lhs->s || rhs->s) error_exit("non-integer argument");
+ lhs->i *= rhs->i;
+}
+
+static void sub(struct value *lhs, const struct value *rhs)
+{
+ if (lhs->s || rhs->s) error_exit("non-integer argument");
+ lhs->i -= rhs->i;
+}
+
+static void add(struct value *lhs, const struct value *rhs)
+{
+ if (lhs->s || rhs->s) error_exit("non-integer argument");
+ lhs->i += rhs->i;
+}
+
+static void ne(struct value *lhs, const struct value *rhs)
+{
+ lhs->i = cmp(lhs, rhs) != 0;
+ lhs->s = NULL;
+}
+
+static void lte(struct value *lhs, const struct value *rhs)
+{
+ lhs->i = cmp(lhs, rhs) <= 0;
+ lhs->s = NULL;
+}
+
+static void lt(struct value *lhs, const struct value *rhs)
+{
+ lhs->i = cmp(lhs, rhs) < 0;
+ lhs->s = NULL;
+}
+
+static void gte(struct value *lhs, const struct value *rhs)
+{
+ lhs->i = cmp(lhs, rhs) >= 0;
+ lhs->s = NULL;
+}
+
+static void gt(struct value *lhs, const struct value *rhs)
+{
+ lhs->i = cmp(lhs, rhs) > 0;
+ lhs->s = NULL;
+}
+
+static void eq(struct value *lhs, const struct value *rhs)
+{
+ lhs->i = cmp(lhs, rhs) == 0;
+ lhs->s = NULL;
+}
+
+static void and(struct value *lhs, const struct value *rhs)
+{
+ if (is_zero(lhs) || is_zero(rhs)) {
+ lhs->i = 0;
+ lhs->s = NULL;
+ }
+}
+
+static void or(struct value *lhs, const struct value *rhs)
+{
+ if (is_zero(lhs)) {
+ *lhs = *rhs;
+ }
+}
+
+
+// operators in order of increasing precedence
+static const struct op ops[] = {
+ {"|", or },
+ {"&", and },
+ {"=", eq },
+ {">", gt },
+ {">=", gte },
+ {"<", lt },
+ {"<=", lte },
+ {"!=", ne },
+ {"+", add },
+ {"-", sub },
+ {"*", mul },
+ {"/", divi},
+ {"%", mod },
+ {":", re },
+ {"(", NULL}, // special case - must be last
+};
+
+
+static void parse_parens(struct value *ret, struct value *v)
+{
+ if (match(v, "(")) {
+ parse_expr(ret, v);
+ if (!match(v, ")")) error_exit("syntax error"); // missing closing paren
+ } else {
+ // v is a string or integer - return it and get the next token
+ *ret = *v;
+ get_value(v);
+ }
+}
+
+static void parse_op(struct value *lhs, struct value *tok, const struct op *op)
+{
+ // special case parsing for parentheses
+ if (*op->tok == '(') {
+ parse_parens(lhs, tok);
+ return;
+ }
+
+ parse_op(lhs, tok, op + 1);
+ while (match(tok, op->tok)) {
+ struct value rhs;
+ parse_op(&rhs, tok, op + 1);
+ if (rhs.s && !*rhs.s) error_exit("syntax error"); // premature end of expression
+ op->calc(lhs, &rhs);
+ }
+}
+
+static void parse_expr(struct value *ret, struct value *v)
+{
+ parse_op(ret, v, ops); // start at the top of the ops table
+}
+
+void expr_main(void)
+{
+ struct value tok, ret = {0};
+
+ toys.exitval = 2; // if exiting early, indicate invalid expression
+
+ TT.argidx = 0;
+
+ get_value(&tok); // warm up the parser with the initial value
+ parse_expr(&ret, &tok);
+
+ if (!tok.s || *tok.s) error_exit("syntax error"); // final token should be end of expression
+
+ if (ret.s) printf("%s\n", ret.s);
+ else printf("%ld\n", ret.i);
+
+ exit(is_zero(&ret));
+}