From 32526f25a7e62b1fe82d1ea30dc4a8506d0ee0d4 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Wed, 5 Jun 2013 00:59:01 -0500 Subject: Start of expr, by Daniel Verkamp. --- scripts/test/expr.test | 10 ++ toys/pending/expr.c | 272 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 scripts/test/expr.test create mode 100644 toys/pending/expr.c 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 + * + * 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)); +} -- cgit v1.2.3