# ملف: safe_calculator.py
# وصف: آلة حاسبة تقيّم تعابير رياضية بأمان (بدون eval المباشر).
# كيف تستعمل: شغّل الملف: python safe_calculator.py
# ثم اكتب تعابير مثل:
# 2+3*4
# (1+2)**3
# sqrt(16) + sin(pi/2)
# اكتب "exit" للخروج، أو "help" للمساعدة.
import ast
import math
import operator
# دوال وعوامل مسموح بها
_ALLOWED_NAMES = {
# ثوابت
'pi': math.pi,
'e': math.e,
# دوال شائعة
'sin': math.sin,
'cos': math.cos,
'tan': math.tan,
'sqrt': math.sqrt,
'log': math.log, # natural log
'log10': math.log10,
'abs': abs,
'round': round,
'floor': math.floor,
'ceil': math.ceil,
'factorial': math.factorial,
}
_ALLOWED_BINOPS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.Mod: operator.mod,
ast.FloorDiv: operator.floordiv,
}
_ALLOWED_UNARYOPS = {
ast.UAdd: operator.pos,
ast.USub: operator.neg,
}
class SafeEvalError(Exception):
pass
def safe_eval(node):
"""قيّم شجرة AST node بطريقة آمنة."""
if isinstance(node, ast.Expression):
return safe_eval(node.body)
if isinstance(node, ast.Constant): # Python 3.8+
if isinstance(node.value, (int, float)):
return node.value
raise SafeEvalError(f"غير مسموح بالقيمة: {node.value!r}")
if isinstance(node, ast.Num): # for older AST nodes
return node.n
if isinstance(node, ast.BinOp):
op_type = type(node.op)
if op_type in _ALLOWED_BINOPS:
left = safe_eval(node.left)
right = safe_eval(node.right)
try:
return _ALLOWED_BINOPS[op_type](left, right)
except Exception as e:
raise SafeEvalError(f"خطأ أثناء الحساب: {e}")
raise SafeEvalError(f"عامل ثنائي غير مسموح: {op_type}")
if isinstance(node, ast.UnaryOp):
op_type = type(node.op)
if op_type in _ALLOWED_UNARYOPS:
operand = safe_eval(node.operand)
return _ALLOWED_UNARYOPS[op_type](operand)
raise SafeEvalError("عامل أحادي غير مسموح")
if isinstance(node, ast.Call):
# دوال فقط من _ALLOWED_NAMES، ولا نقبل استدعاءات مع سمات أو غير ذلك
if isinstance(node.func, ast.Name):
func_name = node.func.id
if func_name not in _ALLOWED_NAMES:
raise SafeEvalError(f"دالة غير مسموح بها: {func_name}")
func = _ALLOWED_NAMES[func_name]
args = [safe_eval(arg) for arg in node.args]
try:
return func(*args)
except Exception as e:
raise SafeEvalError(f"خطأ في استدعاء الدالة {func_name}: {e}")
raise SafeEvalError("مكالمات غير مسموح بها (فقط اسم دالة بسيط مسموح)")
if isinstance(node, ast.Name):
if node.id in _ALLOWED_NAMES:
val = _ALLOWED_NAMES[node.id]
if isinstance(val, (int, float)):
return val
raise SafeEvalError(f"اسم غير مسموح: {node.id}")
if isinstance(node, ast.Tuple):
return tuple(safe_eval(elt) for elt in node.elts)
raise SafeEvalError(f"تركيب غير مدعوم: {type(node).__name__}")
def evaluate(expr: str):
"""تقيّم التعبير النصي وترجع الناتج أو ترفع SafeEvalError."""
try:
parsed = ast.parse(expr, mode='eval')
except SyntaxError as e:
raise SafeEvalError(f"خطأ في بناء الجملة: {e}")
# تحقق من أن الشجرة لا تحتوي على عناصر غير متوقعة (هذه خطوة إضافية بسيطة)
return safe_eval(parsed)
def print_help():
print("آلة حاسبة آمنة — أمثلة:")
print(" 2 + 3 * 4")
print(" (1+2)**3")
print(" sqrt(16) + sin(pi/2)")
print("دوال مسموحة:", ", ".join(sorted([k for k in _ALLOWED_NAMES.keys()])))
print('أدخل "exit" للخروج.')
def main():
print(">>> آلة حاسبة بايثون آمنة. اكتب 'help' للمساعدة، 'exit' للخروج.")
while True:
try:
s = input("expr> ").strip()
except (EOFError, KeyboardInterrupt):
print("\nخروج.")
break
if not s:
continue
if s.lower() in ('exit', 'quit'):
print("خروج.")
break
if s.lower() in ('help', '?'):
print_help()
continue
try:
result = evaluate(s)
print(result)
except SafeEvalError as e:
print("خطأ:", e)
except Exception as e:
print("خطأ غير متوقع:", e)
if __name__ == "__main__":
main()