| /* 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)); |
| } |