Math Evaluator — Java 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 Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// math-evaluator — Java 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.
import java.util.Map;
import java.util.function.DoubleUnaryOperator;
import java.util.regex.Pattern;
class MathEvaluator {
// COPIED VERBATIM from src/lib/math-evaluator.ts — the code-injection guard.
static final Pattern SAFE = Pattern.compile("^[0-9+\\-*/().,\\s a-zA-Z%^!<>]+$");
static final Pattern FORBIDDEN = Pattern.compile("\\b(import|require|eval|function|while|for|process|global|this|window|document|constructor)\\b");
static final int MAX_LEN = 200;
static final Map<String, DoubleUnaryOperator> FN = Map.of(
"sqrt", Math::sqrt, "sin", Math::sin, "cos", Math::cos, "tan", Math::tan,
"log", Math::log, "abs", Math::abs, "exp", Math::exp);
static final class Parser {
final String s;
int i;
boolean err, isBool;
Parser(String s) { this.s = s; }
// '\0' stands in for end-of-input.
char peek() { while (i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t')) i++; return i < s.length() ? s.charAt(i) : '\0'; }
boolean 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 (Character.isDigit(c) || c == '.') {
int start = i;
while (i < s.length() && (Character.isDigit(s.charAt(i)) || s.charAt(i) == '.')) i++;
try { return Double.parseDouble(s.substring(start, i)); }
catch (NumberFormatException e) { err = true; return 0; }
}
if (Character.isLetter(c)) {
String name = "";
while (i < s.length() && Character.isLetter(s.charAt(i)))
name += Character.toLowerCase(s.charAt(i++));
DoubleUnaryOperator fn = FN.get(name);
if (fn != null) { // one-arg call, e.g. sqrt(2)
if (!eat('(')) { err = true; return 0; }
double v = cmp();
err |= !eat(')');
return fn.applyAsDouble(v);
}
if (name.equals("pi")) return Math.PI;
if (name.equals("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.charAt(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.charAt(i - 1) == '+' ? v + mul() : v - mul();
return v;
}
double cmp() { // lowest precedence; parens recurse back here
double v = add();
while (eat('<') || eat('>')) {
boolean lt = s.charAt(i - 1) == '<';
double r = add();
v = lt ? (v < r ? 1 : 0) : (v > r ? 1 : 0);
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.
// Integer-valued doubles print without a trailing ".0", matching JS.
static String evaluate(String expr) {
String t = expr.trim();
if (t.isEmpty() || t.length() > MAX_LEN || !SAFE.matcher(t).matches() || FORBIDDEN.matcher(t).find())
return null;
Parser p = new Parser(t);
double v = p.cmp();
p.peek();
if (p.err || p.i < t.length()) return null; // parse error or leftover tokens
if (p.isBool) return v != 0 ? "true" : "false";
return v == Math.floor(v) && !Double.isInfinite(v) && Math.abs(v) < 1e15
? String.valueOf((long) v) : String.valueOf(v);
}
public static void main(String[] args) {
for (String c : new String[] { "1 + 2 * 3", "2^10", "5! + sqrt(16)", "sin(pi)", "3 > 2", "sqrt" }) {
String r = evaluate(c);
System.out.printf("%-14s = %s%n", c, r != null ? 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 →