# -*- coding: utf-8 -*- """ Mathematischer Ausdruck-Evaluator (Recursive Descent Parser). Wertet Ausdruecke der Form (:expression) aus. Unterstuetzt: +, -, *, /, %, ^ (Potenz), &, |, xor, ~, not, and, or, nand, nor. Klammern fuer Gruppierung. Hex-Literale (0xFF). Float/Int. Praezedenz (niedrig → hoch): nor → nand → | / or → xor → & / and → + - → * / % → ^ → unaer (- ~ not) Portiert von PrrintText_cpp/format.cpp (matheval Namespace). """ from __future__ import annotations import math from enum import Enum, auto from typing import Any class _TokenType(Enum): NUMBER = auto() PLUS = auto() MINUS = auto() STAR = auto() SLASH = auto() PERCENT = auto() CARET = auto() # ^ AMP = auto() # & PIPE = auto() # | TILDE = auto() # ~ LPAREN = auto() RPAREN = auto() KW_AND = auto() KW_OR = auto() KW_XOR = auto() KW_NOT = auto() KW_NAND = auto() KW_NOR = auto() END = auto() ERROR = auto() class _Token: __slots__ = ("type", "value") def __init__(self, tt: _TokenType, value: float = 0.0) -> None: self.type = tt self.value = value def _tokenize(expr: str) -> list[_Token]: """Tokenisiert einen mathematischen Ausdruck.""" tokens: list[_Token] = [] i = 0 n = len(expr) while i < n: ch = expr[i] # Whitespace ueberspringen if ch in " \t\r\n": i += 1 continue # Zahlen (int, float, hex, scientific) if ch.isdigit() or (ch == "." and i + 1 < n and expr[i + 1].isdigit()): start = i # Hex: 0x... if ch == "0" and i + 1 < n and expr[i + 1] in "xX": i += 2 while i < n and (expr[i].isdigit() or expr[i] in "abcdefABCDEF"): i += 1 tokens.append(_Token(_TokenType.NUMBER, float(int(expr[start:i], 16)))) continue # Dezimal / Float while i < n and expr[i].isdigit(): i += 1 if i < n and expr[i] == ".": i += 1 while i < n and expr[i].isdigit(): i += 1 # Scientific Notation: 1e6, 2.5e-3 if i < n and expr[i] in "eE": i += 1 if i < n and expr[i] in "+-": i += 1 while i < n and expr[i].isdigit(): i += 1 tokens.append(_Token(_TokenType.NUMBER, float(expr[start:i]))) continue # Operatoren if ch == "+": tokens.append(_Token(_TokenType.PLUS)) i += 1 elif ch == "-": tokens.append(_Token(_TokenType.MINUS)) i += 1 elif ch == "*": tokens.append(_Token(_TokenType.STAR)) i += 1 elif ch == "/": tokens.append(_Token(_TokenType.SLASH)) i += 1 elif ch == "%": tokens.append(_Token(_TokenType.PERCENT)) i += 1 elif ch == "^": tokens.append(_Token(_TokenType.CARET)) i += 1 elif ch == "&": tokens.append(_Token(_TokenType.AMP)) i += 1 elif ch == "|": tokens.append(_Token(_TokenType.PIPE)) i += 1 elif ch == "~": tokens.append(_Token(_TokenType.TILDE)) i += 1 elif ch == "(": tokens.append(_Token(_TokenType.LPAREN)) i += 1 elif ch == ")": tokens.append(_Token(_TokenType.RPAREN)) i += 1 elif ch.isalpha(): # Keywords: and, or, xor, not, nand, nor start = i while i < n and expr[i].isalpha(): i += 1 word = expr[start:i].lower() kw_map = { "and": _TokenType.KW_AND, "or": _TokenType.KW_OR, "xor": _TokenType.KW_XOR, "not": _TokenType.KW_NOT, "nand": _TokenType.KW_NAND, "nor": _TokenType.KW_NOR, } if word in kw_map: tokens.append(_Token(kw_map[word])) else: # Unbekanntes Wort — Fehler tokens.append(_Token(_TokenType.ERROR)) else: tokens.append(_Token(_TokenType.ERROR)) i += 1 tokens.append(_Token(_TokenType.END)) return tokens def _to_int(d: float) -> int: """Konvertiert float zu int fuer bitweise Operationen.""" return round(d) class _Parser: """Recursive-Descent-Parser fuer mathematische Ausdruecke.""" def __init__(self, tokens: list[_Token]) -> None: self._tokens = tokens self._pos = 0 self.error = False def _peek(self) -> _Token: return self._tokens[self._pos] def _advance(self) -> _Token: tok = self._tokens[self._pos] self._pos += 1 return tok def _expect(self, tt: _TokenType) -> bool: if self._peek().type == tt: self._advance() return True self.error = True return False def parse(self) -> float: """Parst den kompletten Ausdruck.""" result = self._parse_nor() if self._peek().type != _TokenType.END: self.error = True return result # Praezedenz 1: NOR def _parse_nor(self) -> float: left = self._parse_nand() while self._peek().type == _TokenType.KW_NOR: self._advance() right = self._parse_nand() left = float(~(_to_int(left) | _to_int(right))) return left # Praezedenz 2: NAND def _parse_nand(self) -> float: left = self._parse_or() while self._peek().type == _TokenType.KW_NAND: self._advance() right = self._parse_or() left = float(~(_to_int(left) & _to_int(right))) return left # Praezedenz 3: OR / | def _parse_or(self) -> float: left = self._parse_xor() while self._peek().type in (_TokenType.PIPE, _TokenType.KW_OR): self._advance() right = self._parse_xor() left = float(_to_int(left) | _to_int(right)) return left # Praezedenz 4: XOR def _parse_xor(self) -> float: left = self._parse_and() while self._peek().type == _TokenType.KW_XOR: self._advance() right = self._parse_and() left = float(_to_int(left) ^ _to_int(right)) return left # Praezedenz 5: AND / & def _parse_and(self) -> float: left = self._parse_add() while self._peek().type in (_TokenType.AMP, _TokenType.KW_AND): self._advance() right = self._parse_add() left = float(_to_int(left) & _to_int(right)) return left # Praezedenz 6: + - def _parse_add(self) -> float: left = self._parse_mul() while self._peek().type in (_TokenType.PLUS, _TokenType.MINUS): op = self._advance().type right = self._parse_mul() if op == _TokenType.PLUS: left += right else: left -= right return left # Praezedenz 7: * / % def _parse_mul(self) -> float: left = self._parse_pow() while self._peek().type in (_TokenType.STAR, _TokenType.SLASH, _TokenType.PERCENT): op = self._advance().type right = self._parse_pow() if op == _TokenType.STAR: left *= right elif op == _TokenType.SLASH: if right == 0: self.error = True return 0.0 left /= right else: if right == 0: self.error = True return 0.0 left = float(_to_int(left) % _to_int(right)) return left # Praezedenz 8: ^ (rechtsassoziativ) def _parse_pow(self) -> float: base = self._parse_unary() if self._peek().type == _TokenType.CARET: self._advance() exp = self._parse_pow() # Rechtsassoziativ: Rekursion return math.pow(base, exp) return base # Praezedenz 9: unaer (- ~ not) def _parse_unary(self) -> float: if self._peek().type == _TokenType.MINUS: self._advance() return -self._parse_unary() if self._peek().type in (_TokenType.TILDE, _TokenType.KW_NOT): self._advance() return float(~_to_int(self._parse_unary())) return self._parse_primary() # Primaer: Zahl oder geklammert def _parse_primary(self) -> float: tok = self._peek() if tok.type == _TokenType.NUMBER: self._advance() return tok.value if tok.type == _TokenType.LPAREN: self._advance() val = self._parse_nor() self._expect(_TokenType.RPAREN) return val self.error = True self._advance() return 0.0 def _format_result(value: float) -> str: """Formatiert das Ergebnis: Ganzzahlen ohne Dezimalpunkt.""" if value == int(value) and abs(value) < 1e15: return str(int(value)) # Bis zu 10 signifikante Stellen, trailing Zeros entfernen s = f"{value:.10g}" return s def eval_math(expr: str) -> str | None: """ Wertet einen mathematischen Ausdruck aus. Args: expr: Der Ausdruck (ohne die (:...) Klammern). Returns: Das Ergebnis als String oder None bei Fehler. """ tokens = _tokenize(expr.strip()) parser = _Parser(tokens) result = parser.parse() if parser.error: return None return _format_result(result)