// SPDX-FileCopyrightText: 2012-2021 pancake // 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 #include #include #include /* 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; }