rizin/librz/util/calc.c
2022-03-24 08:27:18 +08:00

517 lines
11 KiB
C

// SPDX-FileCopyrightText: 2012-2021 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
/* ported to C by pancake for r2 in 2012-2017 */
// TODO: integrate floating point support
// TODO: do not use global variables
/*
Reference Chapter 6:
"The C++ Programming Language", Special Edition.
Bjarne Stroustrup,Addison-Wesley Pub Co; 3 edition (February 15, 2000)
ISBN: 0201700735
*/
#include <rz_types.h>
#include <rz_util.h>
#include <ctype.h>
#include <stdio.h>
/* accessors */
static inline RzNumCalcValue Nset(ut64 v) {
RzNumCalcValue n;
n.d = (double)v;
n.n = v;
return n;
}
static inline RzNumCalcValue Nsetf(double v) {
RzNumCalcValue n;
n.d = v;
n.n = (ut64)v;
return n;
}
// UNUSED static inline RzNumCalcValue Naddf(RzNumCalcValue n, double v) { n.d += v; n.n += (ut64)v; return n; }
static inline RzNumCalcValue Naddi(RzNumCalcValue n, ut64 v) {
n.d += (double)v;
n.n += v;
return n;
}
static inline RzNumCalcValue Nsubi(RzNumCalcValue n, ut64 v) {
n.d -= (double)v;
n.n -= v;
return n;
}
static inline RzNumCalcValue Nneg(RzNumCalcValue n) {
n.n = ~n.n;
return n;
}
static inline RzNumCalcValue Norr(RzNumCalcValue n, RzNumCalcValue v) {
n.d = v.d;
n.n |= v.n;
return n;
}
static inline RzNumCalcValue Nxor(RzNumCalcValue n, RzNumCalcValue v) {
n.d = v.d;
n.n ^= v.n;
return n;
}
static inline RzNumCalcValue Nlt(RzNumCalcValue n, RzNumCalcValue v) {
n.d = v.d;
n.n = n.n < v.n;
return n;
}
static inline RzNumCalcValue Ngt(RzNumCalcValue n, RzNumCalcValue v) {
n.d = v.d;
n.n = n.n > v.n;
return n;
}
static inline RzNumCalcValue Nand(RzNumCalcValue n, RzNumCalcValue v) {
n.d = v.d;
n.n &= v.n;
return n;
}
static inline RzNumCalcValue Nadd(RzNumCalcValue n, RzNumCalcValue v) {
n.d += v.d;
n.n += v.n;
return n;
}
static inline RzNumCalcValue Nsub(RzNumCalcValue n, RzNumCalcValue v) {
n.d -= v.d;
n.n -= v.n;
return n;
}
static inline RzNumCalcValue Nmul(RzNumCalcValue n, RzNumCalcValue v) {
n.d *= v.d;
n.n *= v.n;
return n;
}
static inline RzNumCalcValue Nshl(RzNumCalcValue n, RzNumCalcValue v) {
n.d += v.d;
n.n <<= v.n;
return n;
}
static inline RzNumCalcValue Nshr(RzNumCalcValue n, RzNumCalcValue v) {
n.d += v.d;
n.n >>= v.n;
return n;
}
static inline RzNumCalcValue Nrol(RzNumCalcValue n, RzNumCalcValue v) {
n.d += v.d;
n.n = (n.n << v.n) | (n.n >> (sizeof(n.n) * 8 - v.n));
return n;
}
static inline RzNumCalcValue Nror(RzNumCalcValue n, RzNumCalcValue v) {
n.d += v.d;
n.n = (n.n >> v.n) | (n.n << (sizeof(n.n) * 8 - v.n));
return n;
}
static inline RzNumCalcValue Nmod(RzNumCalcValue n, RzNumCalcValue v) {
if (v.d) {
n.d = (n.d - (n.d / v.d));
} else {
n.d = 0;
}
if (v.n) {
n.n %= v.n;
} else {
n.n = 0;
}
return n;
}
static inline RzNumCalcValue Ndiv(RzNumCalcValue n, RzNumCalcValue v) {
if (v.d) {
n.d /= v.d;
} else {
n.d = 0;
}
if (v.n) {
n.n /= v.n;
} else {
n.n = 0;
}
return n;
}
static inline RzNumCalcValue Nexp(RzNumCalcValue n, RzNumCalcValue v) {
RzNumCalcValue exp_n = n;
if (v.d - (st64)v.n) {
RZ_LOG_WARN("floating point powers not yet supported\n");
}
if ((st64)v.n > 0) {
for (st64 i = 1; i < (st64)v.n; i++) {
n = Nmul(exp_n, n);
}
} else if ((st64)v.n < 0) {
for (st64 i = 1; i > (st64)v.n; i--) {
n = Ndiv(n, exp_n);
}
} else {
n = Ndiv(n, exp_n);
}
return n;
}
static RzNumCalcValue expr(RzNum *, RzNumCalc *, int);
static RzNumCalcValue term(RzNum *, RzNumCalc *, int);
static void error(RzNum *, RzNumCalc *, const char *);
static RzNumCalcValue prim(RzNum *, RzNumCalc *, int);
static RzNumCalcToken get_token(RzNum *, RzNumCalc *);
static void error(RzNum *num, RzNumCalc *nc, const char *s) {
nc->errors++;
nc->calc_err = s;
// fprintf (stderr, "error: %s\n", s);
}
static RzNumCalcValue expr(RzNum *num, RzNumCalc *nc, int get) {
RzNumCalcValue left = term(num, nc, get);
for (;;) {
switch (nc->curr_tok) {
case RNCSHL: left = Nshl(left, term(num, nc, 1)); break;
case RNCSHR: left = Nshr(left, term(num, nc, 1)); break;
case RNCROL: left = Nrol(left, term(num, nc, 1)); break;
case RNCROR: left = Nror(left, term(num, nc, 1)); break;
case RNCPLUS: left = Nadd(left, term(num, nc, 1)); break;
case RNCMINUS: left = Nsub(left, term(num, nc, 1)); break;
case RNCXOR: left = Nxor(left, term(num, nc, 1)); break;
case RNCORR: left = Norr(left, term(num, nc, 1)); break;
case RNCAND: left = Nand(left, term(num, nc, 1)); break;
case RNCLT: left = Nlt(left, term(num, nc, 1)); break;
case RNCGT: left = Ngt(left, term(num, nc, 1)); break;
default:
return left;
}
}
return left;
}
static RzNumCalcValue term(RzNum *num, RzNumCalc *nc, int get) {
RzNumCalcValue left = prim(num, nc, get);
for (;;) {
if (nc->curr_tok == RNCMUL) {
left = Nmul(left, prim(num, nc, 1));
} else if (nc->curr_tok == RNCMOD) {
RzNumCalcValue d = prim(num, nc, 1);
if (!d.d) {
// error (num, nc, "divide by 0");
return d;
}
left = Nmod(left, d);
} else if (nc->curr_tok == RNCDIV) {
RzNumCalcValue d = prim(num, nc, 1);
if (num != NULL && (!d.d || !d.n)) {
num->dbz = 1;
return d;
}
left = Ndiv(left, d);
} else if (nc->curr_tok == RNCEXP) {
left = Nexp(left, prim(num, nc, 1));
} else {
return left;
}
}
}
static RzNumCalcValue prim(RzNum *num, RzNumCalc *nc, int get) {
RzNumCalcValue v = { 0 };
if (get) {
get_token(num, nc);
}
switch (nc->curr_tok) {
case RNCNUMBER:
v = nc->number_value;
get_token(num, nc);
return v;
case RNCNAME:
// fprintf (stderr, "error: unknown keyword (%s)\n", nc->string_value);
// double& v = table[nc->string_value];
rz_str_trim(nc->string_value);
v = Nset(rz_num_get(num, nc->string_value));
get_token(num, nc);
if (nc->curr_tok == RNCASSIGN) {
v = expr(num, nc, 1);
}
if (nc->curr_tok == RNCINC) {
Naddi(v, 1);
}
if (nc->curr_tok == RNCDEC) {
Nsubi(v, 1);
}
return v;
case RNCNEG:
get_token(num, nc);
return Nneg(nc->number_value); // prim (num, nc, 1), 1);
case RNCINC:
return Naddi(prim(num, nc, 1), 1);
case RNCDEC:
return Naddi(prim(num, nc, 1), -1);
case RNCORR:
return Norr(v, prim(num, nc, 1));
case RNCMINUS:
return Nsub(v, prim(num, nc, 1));
case RNCLEFTP:
v = expr(num, nc, 1);
if (nc->curr_tok == RNCRIGHTP) {
get_token(num, nc);
} else {
error(num, nc, " ')' expected");
}
return v;
case RNCLT:
case RNCGT:
case RNCEND:
case RNCXOR:
case RNCAND:
case RNCPLUS:
case RNCMOD:
case RNCMUL:
case RNCDIV:
case RNCEXP:
case RNCPRINT:
case RNCASSIGN:
case RNCRIGHTP:
case RNCSHL:
case RNCSHR:
case RNCROL:
case RNCROR:
return v;
// default: error (num, nc, "primary expected");
}
return v;
}
static void cin_putback(RzNum *num, RzNumCalc *nc, char c) {
nc->oc = c;
}
RZ_API const char *rz_num_calc_index(RzNum *num, const char *p) {
if (!num) {
return NULL;
}
if (p) {
num->nc.calc_buf = p;
num->nc.calc_len = strlen(p);
num->nc.calc_i = 0;
}
return num->nc.calc_buf + num->nc.calc_i;
}
static int cin_get(RzNum *num, RzNumCalc *nc, char *c) {
if (nc->oc) {
*c = nc->oc;
nc->oc = 0;
} else {
if (!nc->calc_buf || !*nc->calc_buf) {
return 0;
}
*c = nc->calc_buf[nc->calc_i];
if (*c) {
nc->calc_i++;
} else {
return 0;
}
}
return 1;
}
static int cin_get_num(RzNum *num, RzNumCalc *nc, RzNumCalcValue *n) {
double d;
char str[RZ_NUMCALC_STRSZ + 1]; // TODO: move into the heap?
int i = 0;
char c;
str[0] = 0;
while (cin_get(num, nc, &c)) {
if (c != '_' && c != ':' && c != '.' && !isalnum((ut8)c)) {
cin_putback(num, nc, c);
break;
}
if (i < RZ_NUMCALC_STRSZ) {
str[i++] = c;
}
}
str[i] = 0;
*n = Nset(rz_num_get(num, str));
if (IS_DIGIT(*str) && strchr(str, '.')) {
if (sscanf(str, "%lf", &d) < 1) {
return 0;
}
if (n->n < d) {
*n = Nsetf(d);
}
n->d = d;
}
return 1;
}
static RzNumCalcToken get_token(RzNum *num, RzNumCalc *nc) {
char ch = 0, c = 0;
do {
if (!cin_get(num, nc, &ch)) {
return nc->curr_tok = RNCEND;
}
} while (ch != '\n' && isspace((ut8)ch));
switch (ch) {
case 0:
case ';':
case '\n':
return nc->curr_tok = RNCEND;
case '+': // added for ++name and name++
if (cin_get(num, nc, &c) && c == '+') {
return nc->curr_tok = RNCINC;
}
cin_putback(num, nc, c);
return nc->curr_tok = (RzNumCalcToken)ch;
// negate hack
case '~':
if (cin_get(num, nc, &c) && c == '-') {
return nc->curr_tok = RNCNEG;
}
cin_putback(num, nc, c);
return nc->curr_tok = (RzNumCalcToken)ch;
// negative number
case '-':
if (cin_get(num, nc, &c) && c == '-') {
return nc->curr_tok = RNCDEC;
}
cin_putback(num, nc, c);
return nc->curr_tok = (RzNumCalcToken)ch;
case '<':
if (cin_get(num, nc, &c) && c == '<') { // "<<" = shift left
if (cin_get(num, nc, &c) && c == '<') { // "<<<" = rotate left
return nc->curr_tok = RNCROL;
}
cin_putback(num, nc, c);
return nc->curr_tok = RNCSHL;
}
cin_putback(num, nc, c);
return nc->curr_tok = RNCLT;
case '>':
if (cin_get(num, nc, &c) && c == '>') {
if (cin_get(num, nc, &c) && c == '>') {
return nc->curr_tok = RNCROR;
}
cin_putback(num, nc, c);
return nc->curr_tok = RNCSHR;
}
cin_putback(num, nc, c);
return nc->curr_tok = RNCGT;
case '^':
case '&':
case '|':
case '%':
case '/':
case '(':
case ')':
case '=':
return nc->curr_tok = (RzNumCalcToken)ch;
case '*':
if (cin_get(num, nc, &c) && c == '*') {
return nc->curr_tok = RNCEXP;
}
cin_putback(num, nc, c);
return nc->curr_tok = RNCMUL;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
cin_putback(num, nc, ch);
if (!cin_get_num(num, nc, &nc->number_value)) {
error(num, nc, "invalid number conversion");
return 1;
}
return nc->curr_tok = RNCNUMBER;
#define isvalidchar(x) \
(isalnum(x) || (x) == ':' || (x) == '$' || (x) == '.' || (x) == '_' || (x) == '?' || (x) == '\\' || (x) == ' ' || (x) == '[' || (x) == ']' || (x) == '}' || (x) == '{' || ((x) >= '0' && (x) <= '9'))
default: {
int i = 0;
#define stringValueAppend(x) \
{ \
const size_t max = sizeof(nc->string_value) - 1; \
if (i < max) \
nc->string_value[i++] = x; \
else \
nc->string_value[max] = 0; \
}
stringValueAppend(ch);
if (ch == '[') {
while (cin_get(num, nc, &ch) && ch != ']') {
if (i > RZ_NUMCALC_STRSZ - 1) {
error(num, nc, "string too long");
return 0;
}
stringValueAppend(ch);
}
stringValueAppend(ch);
} else {
while (cin_get(num, nc, &ch) && isvalidchar((unsigned char)ch)) {
if (i >= RZ_NUMCALC_STRSZ) {
error(num, nc, "string too long");
return 0;
}
stringValueAppend(ch);
}
}
stringValueAppend(0);
if (ch != '\'') {
cin_putback(num, nc, ch);
}
return nc->curr_tok = RNCNAME;
}
}
}
static void load_token(RzNum *num, RzNumCalc *nc, const char *s) {
nc->calc_i = 0;
nc->calc_len = strlen(s);
nc->calc_buf = s;
nc->calc_err = NULL;
}
RZ_API ut64 rz_num_calc(RzNum *num, const char *str, const char **err) {
RzNumCalcValue n;
RzNumCalc *nc, nc_local;
if (!str || !*str) {
return 0LL;
}
if (num) {
nc = &num->nc;
num->dbz = 0;
} else {
nc = &nc_local;
}
/* init */
nc->curr_tok = RNCPRINT;
nc->number_value.d = 0.0;
nc->number_value.n = 0LL;
nc->errors = 0;
nc->oc = 0;
nc->calc_err = NULL;
nc->calc_i = 0;
nc->calc_len = 0;
nc->calc_buf = NULL;
nc->under_calc = true;
load_token(num, nc, str);
get_token(num, nc);
n = expr(num, nc, 0);
if (err) {
*err = nc->calc_err;
}
if (num) {
num->fvalue = n.d;
}
nc->under_calc = false;
return n.n;
}