Spaces:
Running
Running
File size: 1,889 Bytes
16ab8a2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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)}")
|