Skip to content

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).
// Guards with the TS lib's patterns verbatim (std::regex, ECMAScript grammar),
// then runs the Go twin's recursive-descent grammar: comparison < > → + - →
// * / % → ^ (right-assoc) → unary - → postfix ! → primary (number, parens,
// pi/e, 1-arg calls). Unsafe or invalid input yields nullptr, never throws.
#include <cctype>
#include <cmath>
#include <cstdio>
#include <map>
#include <regex>
#include <string>
using std::string;

// COPIED VERBATIM from src/lib/math-evaluator.ts — the code-injection guard.
const std::regex SAFE{R"(^[0-9+\-*/().,\s a-zA-Z%^!<>]+$)"}, FORBIDDEN{
    R"(\b(import|require|eval|function|while|for|process|global|this|window|document|constructor)\b)"};
const size_t MAX_LEN = 200;

const std::map<string, double (*)(double)> FN{
    {"sqrt", sqrt}, {"sin", sin}, {"cos", cos}, {"tan", tan},
    {"log", log},   {"abs", fabs}, {"exp", exp}};

struct Parser {
    string s;
    size_t i = 0;
    bool err = false, is_bool = false;

    int peek() { while (s[i] == ' ' || s[i] == '\t') i++; return s[i]; }
    bool eat(char c) { if (peek() == c && c) { i++; return true; } return false; }
    double factorial(double n) {
        if (n < 0 || n != floor(n)) { err = true; return 0; }
        double r = 1;
        for (int k = 2; k <= n; k++) r *= k;
        return r;
    }
    double primary() {
        int c = peek();
        if (c == '(') { i++; double v = cmp(); err |= !eat(')'); return v; }
        if (isdigit(c) || c == '.') {
            size_t n = 0;
            double v = 0;
            try { v = std::stod(s.substr(i), &n); } catch (...) { err = true; return 0; }
            i += n;
            return v;
        }
        if (isalpha(c)) {
            string name;
            while (isalpha((unsigned char)s[i])) name += tolower(s[i++]);
            auto fn = FN.find(name);
            if (fn != FN.end()) {  // one-arg call, e.g. sqrt(2)
                if (!eat('(')) { err = true; return 0; }
                double v = cmp();
                err |= !eat(')');
                return fn->second(v);
            }
            if (name == "pi") return M_PI;
            if (name == "e") return M_E;
        }
        err = true;  // bare function reference or unknown symbol → rejected
        return 0;
    }
    double post() { double v = primary(); while (eat('!')) v = factorial(v); return v; }
    double unary() { if (eat('-')) return -unary(); if (eat('+')) return unary(); return post(); }
    double powr() { double l = unary(); if (eat('^')) return std::pow(l, powr()); return l; }
    double mul() {
        double v = powr();
        while (eat('*') || eat('/') || eat('%')) {
            char op = s[i - 1];
            double r = powr();
            v = op == '*' ? v * r : op == '/' ? v / r : std::fmod(v, r);
        }
        return v;
    }
    double add() {
        double v = mul();
        while (eat('+') || eat('-')) v = s[i - 1] == '+' ? v + mul() : v - mul();
        return v;
    }
    double cmp() {  // lowest precedence; parens recurse back here
        double v = add();
        while (eat('<') || eat('>')) {
            bool lt = s[i - 1] == '<';
            double r = add();
            v = lt ? v < r : v > r;
            is_bool = true;
        }
        return v;
    }
};

// Mirrors evaluateExpression in src/lib/math-evaluator.ts: formatted result,
// or nullptr for empty/over-long/unsafe input or any parse failure.
const char *evaluate(const char *expr) {
    static char out[32];
    string t = expr;
    size_t b = t.find_first_not_of(" \t\r\n");
    if (b == string::npos) return nullptr;
    t = t.substr(b, t.find_last_not_of(" \t\r\n") - b + 1);
    if (t.empty() || t.size() > MAX_LEN || !std::regex_match(t, SAFE) ||
        std::regex_search(t, FORBIDDEN))
        return nullptr;
    Parser p{t};
    double v = p.cmp();
    p.peek();
    if (p.err || p.i < t.size()) return nullptr;  // parse error or leftover tokens
    if (p.is_bool) return v ? "true" : "false";
    snprintf(out, sizeof out, "%g", v);
    return out;
}

int main() {
    for (const char *c : {"1 + 2 * 3", "2^10", "5! + sqrt(16)", "sin(pi)", "3 > 2", "sqrt"}) {
        const char *r = evaluate(c);
        printf("%-14s = %s\n", c, 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 →