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, 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 null, never throws.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
class MathEvaluator
{
// COPIED VERBATIM from src/lib/math-evaluator.ts — the code-injection guard.
static readonly Regex SafeChars = new("^[0-9+\\-*/().,\\s a-zA-Z%^!<>]+$");
static readonly Regex Forbidden = new("\\b(import|require|eval|function|while|for|process|global|this|window|document|constructor)\\b");
const int MaxLen = 200;
static readonly Dictionary<string, Func<double, double>> Fn = new() {
["sqrt"] = Math.Sqrt, ["sin"] = Math.Sin, ["cos"] = Math.Cos, ["tan"] = Math.Tan,
["log"] = Math.Log, ["abs"] = Math.Abs, ["exp"] = Math.Exp,
};
string s;
int i;
bool err, isBool;
MathEvaluator(string s) { this.s = s; }
// '\0' stands in for end-of-input (C# strings carry no NUL terminator).
char Peek() { while (i < s.Length && (s[i] == ' ' || s[i] == '\t')) i++; return i < s.Length ? s[i] : '\0'; }
bool Eat(char c) { if (Peek() == c && c != '\0') { i++; return true; } return false; }
double Factorial(double n) {
if (n < 0 || n != Math.Floor(n)) { err = true; return 0; }
double r = 1;
for (int k = 2; k <= n; k++) r *= k;
return r;
}
double Primary() {
char c = Peek();
if (c == '(') { i++; double v = Cmp(); err |= !Eat(')'); return v; }
if (char.IsDigit(c) || c == '.') {
int start = i;
while (i < s.Length && (char.IsDigit(s[i]) || s[i] == '.')) i++;
if (!double.TryParse(s[start..i], out double v)) { err = true; return 0; }
return v;
}
if (char.IsLetter(c)) {
string name = "";
while (i < s.Length && char.IsLetter(s[i])) name += char.ToLower(s[i++]);
if (Fn.TryGetValue(name, out var fn)) { // one-arg call, e.g. sqrt(2)
if (!Eat('(')) { err = true; return 0; }
double v = Cmp();
err |= !Eat(')');
return fn(v);
}
if (name == "pi") return Math.PI;
if (name == "e") return Math.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 Math.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 : 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;
isBool = true;
}
return v;
}
// Mirrors evaluateExpression in src/lib/math-evaluator.ts: formatted
// result, or null for empty/over-long/unsafe input or any parse failure.
public static string Evaluate(string expr) {
string t = expr.Trim();
if (t.Length == 0 || t.Length > MaxLen || !SafeChars.IsMatch(t) || Forbidden.IsMatch(t))
return null;
var p = new MathEvaluator(t);
double v = p.Cmp();
p.Peek();
if (p.err || p.i < t.Length) return null; // parse error or leftover tokens
return p.isBool ? (v != 0 ? "true" : "false") : v.ToString("G15");
}
static void Main() {
foreach (string c in new[] { "1 + 2 * 3", "2^10", "5! + sqrt(16)", "sin(pi)", "3 > 2", "sqrt" })
Console.WriteLine($"{c,-14} = {Evaluate(c) ?? "(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 →