"""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)