Math Evaluator — C source
Evaluate math expressions - arithmetic, functions (sqrt, sin, log), comparisons, and constants (pi, e) - safely, in real time. Input is constrained to math-safe characters, fully client-side.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* math-evaluator — C port: safe arithmetic expression evaluator (no eval).
* The TS lib leans on mathjs; C has no equivalent, so this ports the Go
* twin's recursive-descent parser. Guard first (allowlist + keyword
* blacklist + MAX_LEN 200), then parse; anything unsafe yields NULL. */
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LEN 200
/* The TS allowlist pattern checked by hand (no regex lib): OK is its
* non-letter half, isalpha covers a-zA-Z. */
static const char OK[] = "0123456789+-*/()., \t\n\r\v\f%^!<>";
static const char *const BAD[] = { "import", "require", "eval", "function", "while", "for",
"process", "global", "this", "window", "document", "constructor" };
static const struct { const char *n; double (*f)(double); } FN[] = {
{ "sqrt", sqrt }, { "sin", sin }, { "cos", cos }, { "tan", tan },
{ "log", log }, { "abs", fabs }, { "exp", exp },
};
static int safe_chars(const char *s) {
for (const char *p = s; *p; p++)
if (!strchr(OK, *p) && !isalpha((unsigned char)*p))
return 0;
return 1;
}
/* The TS \b blacklist by hand: whole-word substring match. */
static int bad_word(const char *s) {
for (size_t i = 0; i < sizeof BAD / sizeof *BAD; i++) {
size_t n = strlen(BAD[i]);
for (const char *p = strstr(s, BAD[i]); p; p = strstr(p + 1, BAD[i]))
if ((p == s || !isalnum((unsigned char)p[-1])) && !isalnum((unsigned char)p[n]))
return 1;
}
return 0;
}
static const char *src;
static int pos, err, is_bool;
static int peekch(void) { while (src[pos] == ' ' || src[pos] == '\t') pos++; return src[pos]; }
static int eat(int c) { if (peekch() == c && c) { pos++; return 1; } return 0; }
static double factorial(double n) {
if (n < 0 || n != floor(n)) { err = 1; return 0; }
double r = 1;
for (double i = 2; i <= n; i++) r *= i;
return r;
}
static double parse_cmp(void);
static double parse_primary(void) {
int c = peekch(), n = 0;
char name[16];
if (c == '(') { pos++; double v = parse_cmp(); if (!eat(')')) err = 1; return v; }
if (isdigit(c) || c == '.') { char *end; double v = strtod(src + pos, &end); pos = end - src; return v; }
if (isalpha(c)) {
while (isalpha((unsigned char)src[pos]) && n < 15)
name[n++] = tolower((unsigned char)src[pos++]);
name[n] = 0;
for (size_t k = 0; k < sizeof FN / sizeof *FN; k++)
if (!strcmp(name, FN[k].n)) { /* one-arg call, e.g. sqrt(2) */
if (!eat('(')) { err = 1; return 0; }
double v = parse_cmp();
if (!eat(')')) err = 1;
return FN[k].f(v);
}
if (!strcmp(name, "pi")) return M_PI;
if (!strcmp(name, "e")) return M_E;
}
err = 1; /* bare function reference or unknown symbol → rejected */
return 0;
}
/* Precedence low → high: cmp < > → + - → * / % → ^ (right-assoc) → unary -
* → postfix ! → primary. Comparisons yield 1/0, remembered in is_bool. */
static double parse_post(void) { double v = parse_primary(); while (eat('!')) v = factorial(v); return v; }
static double parse_unary(void) { if (eat('-')) return -parse_unary(); if (eat('+')) return parse_unary(); return parse_post(); }
static double parse_pow(void) { double l = parse_unary(); if (eat('^')) return pow(l, parse_pow()); return l; }
static double parse_mul(void) {
double v = parse_pow();
while (eat('*') || eat('/') || eat('%')) {
int op = src[pos - 1];
double r = parse_pow();
v = op == '*' ? v * r : op == '/' ? v / r : fmod(v, r);
}
return v;
}
static double parse_add(void) {
double v = parse_mul();
while (eat('+') || eat('-')) v = src[pos - 1] == '+' ? v + parse_mul() : v - parse_mul();
return v;
}
static double parse_cmp(void) {
double v = parse_add();
while (eat('<') || eat('>')) {
int lt = src[pos - 1] == '<';
double r = parse_add();
v = lt ? v < r : v > r;
is_bool = 1;
}
return v;
}
/* Mirrors evaluateExpression in src/lib/math-evaluator.ts: formatted result,
* or NULL for empty/over-long/unsafe input or any parse failure. */
static const char *evaluate(const char *expr) {
static char t[MAX_LEN + 2], out[32];
snprintf(t, sizeof t, "%s", expr);
for (char *e = t + strlen(t) - 1; e >= t && isspace((unsigned char)*e); e--) *e = 0;
char *b = t;
while (isspace((unsigned char)*b)) b++;
if (!*b || strlen(b) > MAX_LEN || !safe_chars(b) || bad_word(b)) return NULL;
src = b;
pos = err = is_bool = 0;
double v = parse_cmp();
peekch();
if (err || src[pos]) return NULL; /* parse error or leftover tokens */
if (is_bool) return v ? "true" : "false";
snprintf(out, sizeof out, "%g", v);
return out;
}
int main(void) {
const char *cases[] = { "1 + 2 * 3", "2^10", "5! + sqrt(16)", "sin(pi)", "3 > 2", "sqrt" };
for (size_t i = 0; i < sizeof cases / sizeof *cases; i++) {
const char *r = evaluate(cases[i]);
printf("%-14s = %s\n", cases[i], r ? r : "(rejected)");
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →