Math Evaluator — Kotlin 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 Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// math-evaluator — Kotlin 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 kotlin.math.E
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.exp
import kotlin.math.floor
import kotlin.math.ln
import kotlin.math.pow
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.tan
// COPIED VERBATIM from src/lib/math-evaluator.ts — the code-injection guard.
private val SAFE = Regex("^[0-9+\\-*/().,\\s a-zA-Z%^!<>]+$")
private val FORBIDDEN = Regex("\\b(import|require|eval|function|while|for|process|global|this|window|document|constructor)\\b")
private const val MAX_LEN = 200
private val FN: Map<String, (Double) -> Double> = mapOf(
"sqrt" to ::sqrt, "sin" to ::sin, "cos" to ::cos, "tan" to ::tan,
"log" to ::ln, "abs" to ::abs, "exp" to ::exp)
private class Parser(val s: String) {
var i = 0
var err = false
var isBool = false
// '\u0000' stands in for end-of-input.
fun peek(): Char {
while (i < s.length && (s[i] == ' ' || s[i] == '\t')) i++
return if (i < s.length) s[i] else '\u0000'
}
fun eat(c: Char): Boolean {
if (peek() == c && c != '\u0000') { i++; return true }
return false
}
fun factorial(n: Double): Double {
if (n < 0 || n != floor(n)) { err = true; return 0.0 }
var r = 1.0
for (k in 2..n.toInt()) r *= k
return r
}
fun primary(): Double {
val c = peek()
if (c == '(') { i++; val v = cmp(); err = err || !eat(')'); return v }
if (c.isDigit() || c == '.') {
val start = i
while (i < s.length && (s[i].isDigit() || s[i] == '.')) i++
return s.substring(start, i).toDoubleOrNull() ?: run { err = true; 0.0 }
}
if (c.isLetter()) {
val name = buildString { while (i < s.length && s[i].isLetter()) append(s[i++].lowercaseChar()) }
val fn = FN[name]
if (fn != null) { // one-arg call, e.g. sqrt(2)
if (!eat('(')) { err = true; return 0.0 }
val v = cmp()
err = err || !eat(')')
return fn(v)
}
if (name == "pi") return PI
if (name == "e") return E
}
err = true // bare function reference or unknown symbol → rejected
return 0.0
}
fun post(): Double { var v = primary(); while (eat('!')) v = factorial(v); return v }
fun unary(): Double = if (eat('-')) -unary() else if (eat('+')) unary() else post()
fun powr(): Double { val l = unary(); return if (eat('^')) l.pow(powr()) else l }
fun mul(): Double {
var v = powr()
while (eat('*') || eat('/') || eat('%')) {
val op = s[i - 1]
val r = powr()
v = when (op) { '*' -> v * r; '/' -> v / r; else -> v % r }
}
return v
}
fun add(): Double {
var v = mul()
while (eat('+') || eat('-')) v = if (s[i - 1] == '+') v + mul() else v - mul()
return v
}
fun cmp(): Double { // lowest precedence; parens recurse back here
var v = add()
while (eat('<') || eat('>')) {
val lt = s[i - 1] == '<'
val r = add()
v = if (lt) { if (v < r) 1.0 else 0.0 } else { if (v > r) 1.0 else 0.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 String(number).
fun evaluate(expr: String): String? {
val t = expr.trim()
if (t.isEmpty() || t.length > MAX_LEN || !SAFE.matches(t) || FORBIDDEN.containsMatchIn(t)) return null
val p = Parser(t)
val v = p.cmp()
p.peek()
if (p.err || p.i < t.length) return null // parse error or leftover tokens
if (p.isBool) return if (v != 0.0) "true" else "false"
return if (v == floor(v) && !v.isInfinite() && abs(v) < 1e15) v.toLong().toString() else v.toString()
}
fun main() {
for (c in listOf("1 + 2 * 3", "2^10", "5! + sqrt(16)", "sin(pi)", "3 > 2", "sqrt")) {
val r = evaluate(c)
println("%-14s = %s".format(c, 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 →