This code is an arithmetic parser as is the code in a previous question of mine. However this parser handles floating point arguments and mathematical functions, and handles them without needing to define a new function for every precedence level. The component to parse function names is the topic of a different previous question of mine.
The code uses a recursive approach to parse strings of mathematical expressions. u
is short for unary, which handles individual terms, such as individual numbers, functions, or parenthesized expressions. b
is short for binary, which handles the binary operators. Associativity and precedence is respected. The first term is evaluated. Then, any operators operating on it are checked. If they are found, the right operand of the operator is recursively evaluated by passing the precedence operator to the function. The function only evaluates until an operator of precedence lower or equal (for right-to-left associativity) or lower (for left-to-right associativity) precedence is encountered. As such, the recursion ends, and the previous recursion level handles the rest. The workings are complicated but I am trying my best to explain them.
As always, if my code is needlessly non-portable, or could use general improvement advice, corrections or comments would be appreciated.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> static size_t l; static int c(const void *const restrict a, const void *const restrict b) { const unsigned char *const sa = *(const unsigned char *const *)a, *const sb = *(const unsigned char *const *)b; const int cmp = memcmp(sa, sb, l = strlen((const char *)sb)); return cmp ? cmp : isalpha(sa[l]) ? sa[l]-sb[l] : 0; } static double (*func(const char **const str))(double) { static const char *const s[] = {"abs", "acos", "acosh", "asin", "asinh", "atan", "atanh", "cbrt", "ceil", "cos", "cosh", "erf", "erfc", "exp", "expb", "floor", "gamma", "lgamma", "lb", "ln", "log", "round", "sin", "sinh", "sqrt", "tan", "tanh", "trunc"}; static double (*const f[])(double) = {fabs, acos, acosh, asin, asinh, atan, atanh, cbrt, ceil, cos, cosh, erf, erfc, exp, exp2, floor, tgamma, lgamma, log2, log, log10, round, sin, sinh, sqrt, tan, tanh, trunc}; const char *const *const r = bsearch(str, s, sizeof(s)/sizeof(*s), sizeof(*s), c); if (r) { *str += l; return f[r-s]; } return NULL; } static double u(const char **); static double b(const char **const str, const unsigned l) { *str += l != 0; // `l` will only be nonzero if recursively called by `b`, in which case there is an operator character to skip over for (double r = u(str);;) { while (isspace(**str)) ++*str; switch (**str) { case '^': if (l <= 3) // Using `<=` instead of `<` gives `^` right-to-left associativity r = pow(r, b(str, 3)); else break; continue; case '*': if (l < 2) r *= b(str, 2); else break; continue; case '/': if (l < 2) r /= b(str, 2); else break; continue; case '%': if (l < 2) r = fmod(r, b(str, 2)); else break; continue; case '+': if (l < 1) r += b(str, 1); else break; continue; case '-': if (l < 1) r -= b(str, 1); else break; continue; } return r; } } static double u(const char **const str) { while (isspace(**str)) ++*str; if (isdigit(**str) || **str == '.') return strtod(*str, (char **)str); if (isalpha(**str)) { double (*f)(double) = func(str); if (f) return f(u(str)); } else switch (*(*str)++) { case '+': return +u(str); case '-': return -u(str); case '(': { register const double r = b(str, 0); if (*(*str)++ == ')') return r; } } return NAN; } #include <errno.h> int main(const int argc, const char *const *argv) { while (*++argv) if (printf("%g\n", b(&(const char *){*argv}, 0)) < 0) return EXIT_FAILURE; return EXIT_SUCCESS; }
unary
and be done with it. In a related vein, sometimesl
denoteslength
and sometimes a precedencelevel
. Add two or so characters to at least one of those identifiers already. Consider stack allocating it rather than using static, so it goes out of scope. Don't compilers mostly ignore "register" advice these days? It's too bad we don't have a bunch of pairs that look like("abs", fabs), ...
\$\endgroup\$int main(const int argc, const char *const *argv) { while (*++argv)
====> I suggest looking at your last review.\$\endgroup\$.
is parsed as0
.c()
is used as the comparison function forbsearch()
.\$\endgroup\$argv
was valid before dereferencing it. It could easily be renderedNULL
. And what do you suppose theregister
keyword does?\$\endgroup\$