diff options
| -rw-r--r-- | scripts/test/expr.test | 10 | ||||
| -rw-r--r-- | toys/pending/expr.c | 272 | 
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)); +}  | 
