blob: 763ad02d65e986e1c6eeae72a6311719afcf811d [file] [log] [blame]
/* expr.c - evaluate expression
*
* Copyright 2013 Daniel Verkamp <daniel@drv.nu>
*
* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html
*
* The web standard is incomplete (precedence grouping missing), see:
* http://permalink.gmane.org/gmane.comp.standards.posix.austin.general/10141
USE_EXPR(NEWTOY(expr, NULL, TOYFLAG_USR|TOYFLAG_BIN))
config EXPR
bool "expr"
default n
help
usage: expr ARG1 OPERATOR ARG2...
Evaluate expression and print result. For example, "expr 1 + 2".
The supported operators are (grouped from highest to lowest priority):
( ) : * / % + - != <= < >= > = & |
Each constant and operator must be a separate command line argument.
All operators are infix, meaning they expect a constant (or expression
that resolves to a constant) on each side of the operator. Operators of
the same priority (within each group above) are evaluated left to right.
Parentheses may be used (as separate arguments) to elevate the priority
of expressions.
Calling expr from a command shell requires a lot of \( or '*' escaping
to avoid interpreting shell control characters.
The & and | operators are logical (not bitwise) and may operate on
strings (a blank string is "false"). Comparison operators may also
operate on strings (alphabetical sort).
Constants may be strings or integers. Comparison, logical, and regex
operators may operate on strings (a blank string is "false"), other
operators require integers.
*/
// 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;
};
// check if v is the integer 0 or the empty string
static int is_zero(struct value *v)
{
return v->s ? !*v->s : !v->i;
}
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(struct value *lhs, 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;
}
static void re(struct value *lhs, struct value *rhs)
{
regex_t rp;
regmatch_t rm[2];
xregcomp(&rp, rhs->s, 0);
if (!regexec(&rp, lhs->s, 2, rm, 0) && rm[0].rm_so == 0) {
if (rp.re_nsub > 0 && rm[1].rm_so >= 0)
lhs->s = xmprintf("%.*s", rm[1].rm_eo - rm[1].rm_so, lhs->s+rm[1].rm_so);
else {
lhs->i = rm[0].rm_eo;
lhs->s = 0;
}
} else {
if (!rp.re_nsub) {
lhs->i = 0;
lhs->s = 0;
} else lhs->s = "";
}
}
static void mod(struct value *lhs, 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, 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, struct value *rhs)
{
if (lhs->s || rhs->s) error_exit("non-integer argument");
lhs->i *= rhs->i;
}
static void sub(struct value *lhs, struct value *rhs)
{
if (lhs->s || rhs->s) error_exit("non-integer argument");
lhs->i -= rhs->i;
}
static void add(struct value *lhs, struct value *rhs)
{
if (lhs->s || rhs->s) error_exit("non-integer argument");
lhs->i += rhs->i;
}
static void ne(struct value *lhs, struct value *rhs)
{
lhs->i = cmp(lhs, rhs) != 0;
lhs->s = NULL;
}
static void lte(struct value *lhs, struct value *rhs)
{
lhs->i = cmp(lhs, rhs) <= 0;
lhs->s = NULL;
}
static void lt(struct value *lhs, struct value *rhs)
{
lhs->i = cmp(lhs, rhs) < 0;
lhs->s = NULL;
}
static void gte(struct value *lhs, struct value *rhs)
{
lhs->i = cmp(lhs, rhs) >= 0;
lhs->s = NULL;
}
static void gt(struct value *lhs, struct value *rhs)
{
lhs->i = cmp(lhs, rhs) > 0;
lhs->s = NULL;
}
static void eq(struct value *lhs, struct value *rhs)
{
lhs->i = !cmp(lhs, rhs);
lhs->s = NULL;
}
static void and(struct value *lhs, struct value *rhs)
{
if (is_zero(lhs) || is_zero(rhs)) {
lhs->i = 0;
lhs->s = NULL;
}
}
static void or(struct value *lhs, struct value *rhs)
{
if (is_zero(lhs)) *lhs = *rhs;
}
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;
}
// can't happen, the increment is after the == test
// 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, char *tok)
{
if (v->s && !strcmp(v->s, tok)) {
get_value(v);
return 1;
}
return 0;
}
// operators in order of increasing precedence
static struct op {
char *tok;
// calculate "lhs op rhs" (e.g. lhs + rhs) and store result in lhs
void (*calc)(struct value *lhs, struct value *rhs);
} ops[] = {
{"|", or }, {"&", and }, {"=", eq }, {"==", eq }, {">", gt },
{">=", gte }, {"<", lt }, {"<=", lte }, {"!=", ne }, {"+", add },
{"-", sub }, {"*", mul }, {"/", divi}, {"%", mod }, {":", re },
{"(", NULL}, // special case - must be last
};
// "|,&,= ==> >=< <= !=,+-,*/%,:"
static void parse_op(struct value *lhs, struct value *tok, struct op *op)
{
if (!op) op = ops;
// special case parsing for parentheses
if (*op->tok == '(') {
if (match(tok, "(")) {
parse_op(lhs, tok, 0);
if (!match(tok, ")")) error_exit("syntax error"); // missing closing paren
} else {
// tok is a string or integer - return it and get the next token
*lhs = *tok;
get_value(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);
}
}
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_op(&ret, &tok, 0);
// final token should be end of expression
if (!tok.s || *tok.s) error_exit("syntax error");
if (ret.s) printf("%s\n", ret.s);
else printf("%lld\n", ret.i);
exit(is_zero(&ret));
}