forked from steam-bell-92/python-mini-project
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevaluator.py
More file actions
119 lines (99 loc) · 4.1 KB
/
Copy pathevaluator.py
File metadata and controls
119 lines (99 loc) · 4.1 KB
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""
Evaluator for AST nodes generated by Parser.
Evaluates math expressions with variable and function support.
"""
import math
from typing import Dict, Any, Union
from expression_parser.parser import ASTNode, NumberNode, VariableNode, UnaryOpNode, BinaryOpNode, FunctionCallNode
class EvaluationError(Exception):
pass
class Evaluator:
BUILTIN_FUNCTIONS = {
"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"asin": math.asin,
"acos": math.acos,
"atan": math.atan,
"sqrt": math.sqrt,
"abs": abs,
"log": math.log,
"exp": math.exp,
"floor": math.floor,
"ceil": math.ceil,
"min": min,
"max": max,
}
BUILTIN_CONSTANTS = {
"pi": math.pi,
"e": math.e,
"tau": math.tau,
}
def __init__(self, variables: Dict[str, Union[int, float]] = None):
self.variables = self.BUILTIN_CONSTANTS.copy()
if variables:
self.variables.update(variables)
def evaluate(self, node: ASTNode) -> Union[int, float]:
if isinstance(node, NumberNode):
return node.value
if isinstance(node, VariableNode):
if node.name in self.variables:
return self.variables[node.name]
raise EvaluationError(f"Undefined variable or constant: '{node.name}'")
if isinstance(node, UnaryOpNode):
val = self.evaluate(node.operand)
if node.operator == '+':
return +val
elif node.operator == '-':
return -val
raise EvaluationError(f"Unsupported unary operator: '{node.operator}'")
if isinstance(node, BinaryOpNode):
left_val = self.evaluate(node.left)
right_val = self.evaluate(node.right)
if node.operator == '+':
return left_val + right_val
elif node.operator == '-':
return left_val - right_val
elif node.operator == '*':
return left_val * right_val
elif node.operator == '/':
if right_val == 0:
raise EvaluationError("Division by zero")
return left_val / right_val
elif node.operator == '%':
if right_val == 0:
raise EvaluationError("Modulo by zero")
return left_val % right_val
elif node.operator in ('^', '**'):
try:
res = left_val ** right_val
if isinstance(res, complex):
raise EvaluationError("Complex numbers result not supported in real domain")
return res
except OverflowError:
raise EvaluationError("Numerical overflow in exponentiation")
raise EvaluationError(f"Unsupported binary operator: '{node.operator}'")
if isinstance(node, FunctionCallNode):
fn_name = node.name.lower()
if fn_name not in self.BUILTIN_FUNCTIONS:
raise EvaluationError(f"Unknown or unsupported function: '{node.name}'")
args = [self.evaluate(arg) for arg in node.args]
func = self.BUILTIN_FUNCTIONS[fn_name]
try:
result = func(*args)
return result
except TypeError as e:
raise EvaluationError(f"Invalid arguments for function '{node.name}': {e}")
except ValueError as e:
raise EvaluationError(f"Mathematical domain error in function '{node.name}': {e}")
raise EvaluationError(f"Unknown AST node type: {type(node).__name__}")
def evaluate_expression(expression: str, variables: Dict[str, Union[int, float]] = None) -> Union[int, float]:
"""Helper function to tokenize, parse, and evaluate a math expression string."""
from expression_parser.tokenizer import Tokenizer
from expression_parser.parser import Parser
tokenizer = Tokenizer(expression)
tokens = tokenizer.tokenize()
parser = Parser(tokens)
ast = parser.parse()
evaluator = Evaluator(variables)
return evaluator.evaluate(ast)