The purpose of this code is to parse any expression as could appear in C that involves doubles or functions that involve only doubles.
I have written parsers before:
This is an upgraded version that uses more descriptive variable names and structures instead of parallel arrays. This version can also handle functions of varying arity. I was even planning on doing short string optimization here but had to skip that because reasons.
#include <math.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
typedef struct Symbol {
unsigned arity, len;
const char *name;
union {
double
func0,
(*func1)(double),
(*func2)(double, double),
(*func3)(double, double, double);
};
} Symbol;
static int compar(const void *const restrict strp, const void *const restrict p) {
const unsigned len = ((const Symbol *)p)->len;
const char
*const str = *(const char *const *)strp,
*const name = ((const Symbol *)p)->name;
const int cmp = memcmp(str, name, len);
return cmp
? cmp
: isalnum((unsigned char)str[len]) || str[len] == '_'
? str[len]-name[len]
: (*(const char **)strp += len, 0);
}
#define SYMBOL(a, n)\
{\
.arity = a,\
.len = sizeof(#n)-1,\
.name = #n,\
.func##a = n\
}
static const Symbol symbols[] = {
SYMBOL(0, M_1_PI),
SYMBOL(0, M_2_PI),
SYMBOL(0, M_2_SQRTPI),
SYMBOL(0, M_E),
SYMBOL(0, M_LN10),
SYMBOL(0, M_LN2),
SYMBOL(0, M_LOG10E),
SYMBOL(0, M_LOG2E),
SYMBOL(0, M_PI),
SYMBOL(0, M_PI_2),
SYMBOL(0, M_PI_4),
SYMBOL(0, M_SQRT1_2),
SYMBOL(0, M_SQRT2),
SYMBOL(1, acos),
SYMBOL(1, acosh),
SYMBOL(1, asin),
SYMBOL(1, asinh),
SYMBOL(1, atan),
SYMBOL(2, atan2),
SYMBOL(1, atanh),
SYMBOL(1, cbrt),
SYMBOL(1, ceil),
SYMBOL(1, cos),
SYMBOL(1, cosh),
SYMBOL(2, copysign),
SYMBOL(1, erf),
SYMBOL(1, erfc),
SYMBOL(1, exp),
SYMBOL(1, exp2),
SYMBOL(1, expm1),
SYMBOL(1, fabs),
SYMBOL(2, fdim),
SYMBOL(1, floor),
SYMBOL(3, fma),
SYMBOL(2, fmax),
SYMBOL(2, fmin),
SYMBOL(2, fmod),
SYMBOL(2, hypot),
SYMBOL(1, lgamma),
SYMBOL(1, log),
SYMBOL(1, log10),
SYMBOL(1, log1p),
SYMBOL(1, log2),
SYMBOL(1, logb),
SYMBOL(2, nextafter),
SYMBOL(2, pow),
SYMBOL(2, remainder),
SYMBOL(1, round),
SYMBOL(1, sin),
SYMBOL(1, sinh),
SYMBOL(1, sqrt),
SYMBOL(1, tan),
SYMBOL(1, tanh),
SYMBOL(1, tgamma),
SYMBOL(1, trunc)
};
static double term(const char **);
static double expr(const char **const str, const unsigned level) {
*str += level != 0;
for (double val = term(str);;) {
while (isspace((unsigned char)**str))
++*str;
switch (**str) {
case '*':
if (level > 2)
break;
val *= expr(str, 2);
continue;
case '/':
if (level > 2)
break;
val /= expr(str, 2);
continue;
case '+':
if (level > 1)
break;
val += expr(str, 1);
continue;
case '-':
if (level > 1)
break;
val -= expr(str, 1);
continue;
}
return val;
}
}
static double term(const char **const str) {
while (isspace((unsigned char)**str))
++*str;
{
char *end;
const double val = strtod(*str, &end);
if (*str != end) {
*str = end;
return val;
}
}
if (isalpha((unsigned char)**str)) {
const Symbol *const symbol = bsearch(str, symbols, sizeof(symbols)/sizeof(Symbol), sizeof(Symbol), compar);
if (symbol) {
if (!symbol->arity)
return symbol->func0;
while (isspace((unsigned char)**str))
++*str;
if (*(*str)++ == '(') {
double vals[symbol->arity];
for (unsigned i = 0;;) {
vals[i] = expr(str, 0);
if (++i >= symbol->arity) {
if (*(*str)++ == ')') switch (symbol->arity) {
case 1:
return symbol->func1(vals[0]);
case 2:
return symbol->func2(vals[0], vals[1]);
case 3:
return symbol->func3(vals[0], vals[1], vals[2]);
}
} else if (*(*str)++ == ',')
continue;
break;
}
}
}
} else switch (*(*str)++) {
case '+':
return term(str);
case '-':
return -term(str);
case '(':
{
const double val = expr(str, 0);
if (*(*str)++ == ')')
return val;
}
}
return NAN;
}
#include <stdio.h>
int main(int argc, char **argv) {
for (int arg = 1; arg < argc; ++arg)
if (printf("%g\n", expr(&(const char *){argv[arg]}, 0)) < 0)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}