From bde60ad530d7774ec610e25c28f527537d547326 Mon Sep 17 00:00:00 2001 From: rajjagirdar-root Date: Wed, 13 May 2026 11:24:44 -0400 Subject: [PATCH] Add safe expression evaluator (calc/engine/evaluator.py + builtins.py) - evaluator.evaluate(expr, extra_names) returns (result_str, is_error) - blocks __builtins__ to prevent arbitrary code execution - handles ZeroDivisionError, ValueError, NameError with friendly messages - builtins.SAFE_NAMES covers trig, hyperbolic, log, exp, rounding, constants --- calc/__init__.py | 0 calc/engine/__init__.py | 0 calc/engine/builtins.py | 26 ++++++++++++++++++++++++++ calc/engine/evaluator.py | 22 ++++++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 calc/__init__.py create mode 100644 calc/engine/__init__.py create mode 100644 calc/engine/builtins.py create mode 100644 calc/engine/evaluator.py diff --git a/calc/__init__.py b/calc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/calc/engine/__init__.py b/calc/engine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/calc/engine/builtins.py b/calc/engine/builtins.py new file mode 100644 index 0000000..bfd1606 --- /dev/null +++ b/calc/engine/builtins.py @@ -0,0 +1,26 @@ +import math + +SAFE_NAMES = { + # trig (radians) + "sin": math.sin, "cos": math.cos, "tan": math.tan, + "asin": math.asin, "acos": math.acos, "atan": math.atan, + "atan2": math.atan2, + # trig (degrees) + "sind": lambda x: math.sin(math.radians(x)), + "cosd": lambda x: math.cos(math.radians(x)), + "tand": lambda x: math.tan(math.radians(x)), + # hyperbolic + "sinh": math.sinh, "cosh": math.cosh, "tanh": math.tanh, + # logs & exp + "log": math.log, "log2": math.log2, "log10": math.log10, + "ln": math.log, "exp": math.exp, + # powers & roots + "sqrt": math.sqrt, "pow": math.pow, + # rounding + "abs": abs, "round": round, "floor": math.floor, "ceil": math.ceil, + # misc + "factorial": math.factorial, "gcd": math.gcd, + "degrees": math.degrees, "radians": math.radians, + # constants + "pi": math.pi, "e": math.e, "tau": math.tau, "inf": math.inf, +} diff --git a/calc/engine/evaluator.py b/calc/engine/evaluator.py new file mode 100644 index 0000000..3a5280a --- /dev/null +++ b/calc/engine/evaluator.py @@ -0,0 +1,22 @@ +from calc.engine.builtins import SAFE_NAMES + + +def evaluate(expr: str, extra_names: dict = None) -> tuple[str, bool]: + """ + Evaluate a math expression safely. + Returns (result_string, is_error). + """ + namespace = dict(SAFE_NAMES) + if extra_names: + namespace.update(extra_names) + try: + result = eval(expr, {"__builtins__": {}}, namespace) + return str(result), False + except ZeroDivisionError: + return "Error: Division by zero", True + except ValueError as err: + return f"Error: {err}", True + except NameError as err: + return f"Error: {err}", True + except Exception as err: + return f"Error: {err}", True