93 lines
2.6 KiB
Python
93 lines
2.6 KiB
Python
"""Mathematical expression evaluator with precedence (Punkt vor Strich).
|
||
Supports +, -, *, /, (, ), and decimal numbers. Safe – no eval()."""
|
||
|
||
import re
|
||
from decimal import Decimal, InvalidOperation
|
||
|
||
TOKEN_NUM = 'NUM'
|
||
TOKEN_PLUS = '+'
|
||
TOKEN_MINUS = '-'
|
||
TOKEN_MUL = '*'
|
||
TOKEN_DIV = '/'
|
||
TOKEN_LPAR = '('
|
||
TOKEN_RPAR = ')'
|
||
|
||
def tokenize(expr):
|
||
tokens = []
|
||
i = 0
|
||
while i < len(expr):
|
||
c = expr[i]
|
||
if c in ' \t':
|
||
i += 1
|
||
continue
|
||
if c in '+-*/()':
|
||
tokens.append(c)
|
||
i += 1
|
||
elif c.isdigit() or c == '.':
|
||
start = i
|
||
had_dot = False
|
||
while i < len(expr) and (expr[i].isdigit() or (expr[i] == '.' and not had_dot)):
|
||
if expr[i] == '.':
|
||
had_dot = True
|
||
i += 1
|
||
tokens.append(('NUM', expr[start:i]))
|
||
else:
|
||
raise ValueError(f"Ungültiges Zeichen: '{c}'")
|
||
return tokens
|
||
|
||
def parse_primary(tokens, pos):
|
||
if pos >= len(tokens):
|
||
raise ValueError("Erwarte Zahl oder (")
|
||
tok = tokens[pos]
|
||
if isinstance(tok, tuple) and tok[0] == 'NUM':
|
||
return Decimal(tok[1]), pos + 1
|
||
if tok == '(':
|
||
val, pos = parse_expr(tokens, pos + 1)
|
||
if pos >= len(tokens) or tokens[pos] != ')':
|
||
raise ValueError("Fehlende schließende Klammer")
|
||
return val, pos + 1
|
||
raise ValueError(f"Unerwartetes Token: {tok}")
|
||
|
||
def parse_mul(tokens, pos):
|
||
val, pos = parse_primary(tokens, pos)
|
||
while pos < len(tokens):
|
||
tok = tokens[pos]
|
||
if tok in ('*', '/'):
|
||
right, pos = parse_primary(tokens, pos + 1)
|
||
if tok == '*':
|
||
val *= right
|
||
else:
|
||
if right == 0:
|
||
raise ValueError("Division durch Null")
|
||
val /= right
|
||
else:
|
||
break
|
||
return val, pos
|
||
|
||
def parse_expr(tokens, pos):
|
||
val, pos = parse_mul(tokens, pos)
|
||
while pos < len(tokens):
|
||
tok = tokens[pos]
|
||
if tok in ('+', '-'):
|
||
right, pos = parse_mul(tokens, pos + 1)
|
||
if tok == '+':
|
||
val += right
|
||
else:
|
||
val -= right
|
||
else:
|
||
break
|
||
return val, pos
|
||
|
||
def berechne_formel(expr_str):
|
||
if not expr_str or not expr_str.strip():
|
||
return 0
|
||
# Deutsche Dezimaltrenner ersetzen
|
||
expr_str = expr_str.replace(',', '.')
|
||
tokens = tokenize(expr_str.strip())
|
||
if not tokens:
|
||
return 0
|
||
val, pos = parse_expr(tokens, 0)
|
||
if pos != len(tokens):
|
||
raise ValueError("Überflüssige Zeichen am Ende")
|
||
return float(val)
|