Spaces:
Running
Running
| from __future__ import annotations | |
| import ast | |
| import math | |
| import operator | |
| import re | |
| from typing import Any | |
| _BINARY = { | |
| ast.Add: operator.add, | |
| ast.Sub: operator.sub, | |
| ast.Mult: operator.mul, | |
| ast.Div: operator.truediv, | |
| ast.FloorDiv: operator.floordiv, | |
| ast.Mod: operator.mod, | |
| ast.Pow: operator.pow, | |
| } | |
| _UNARY = {ast.UAdd: operator.pos, ast.USub: operator.neg} | |
| _FUNCTIONS = {"abs": abs, "round": round, "sqrt": math.sqrt, "ceil": math.ceil, "floor": math.floor} | |
| def calculate_expression(expression: str) -> dict[str, Any]: | |
| cleaned = _extract_expression(expression) | |
| value = _evaluate(ast.parse(cleaned, mode="eval").body) | |
| return { | |
| "ok": True, | |
| "source": "safe_calculator", | |
| "content": str(value), | |
| "metadata": {"expression": cleaned}, | |
| } | |
| def _extract_expression(text: str) -> str: | |
| text = text.strip().replace("×", "*").replace("÷", "/").replace("^", "**") | |
| match = re.search(r"(?:expression\s*[:=]\s*)?([0-9eE\.\s+\-*/%(),]+)$", text) | |
| candidate = (match.group(1) if match else text).strip() | |
| if not candidate: | |
| raise ValueError("No arithmetic expression found.") | |
| return candidate | |
| def _evaluate(node: ast.AST) -> int | float: | |
| if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): | |
| return node.value | |
| if isinstance(node, ast.BinOp) and type(node.op) in _BINARY: | |
| return _BINARY[type(node.op)](_evaluate(node.left), _evaluate(node.right)) | |
| if isinstance(node, ast.UnaryOp) and type(node.op) in _UNARY: | |
| return _UNARY[type(node.op)](_evaluate(node.operand)) | |
| if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in _FUNCTIONS: | |
| return _FUNCTIONS[node.func.id](*[_evaluate(arg) for arg in node.args]) | |
| raise ValueError(f"Unsupported expression element: {ast.dump(node, include_attributes=False)}") | |