blob: dd27d58f0e1c562b79b402d738beab2531a559a9 [file] [log] [blame]
/* 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 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 = strtoll(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 long num)
{
static char num_buf[21];
snprintf(num_buf, sizeof(num_buf), "%lld", 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("%lld\n", ret.i);
exit(is_zero(&ret));
}