# -*- coding: utf-8 -*- """ Logik-Ausdruck-Evaluator fuer Ternary-Ausdruecke. Wertet Ausdruecke der Form (condition ? result_true : result_false) aus. Unterstuetzt Vergleichsoperatoren, Typ-Pruefung, Else-If-Ketten (::), und Boolean-Keywords. Portiert von PrrintText_cpp/format.cpp (logic Namespace). """ from __future__ import annotations import re from typing import Any # Boolean-Keywords _TRUE_KEYWORDS = {"true", "on", "yes", "ok", "success", "1", "wahr", "ja", "an"} _FALSE_KEYWORDS = {"false", "off", "no", "error", "failed", "0", "falsch", "nein", "aus"} # Vergleichsoperatoren (laengste zuerst, damit >= vor > matcht) _OPERATORS = ["===", "!==", ">=", "<=", "!=", "==", ">", "<"] def _try_parse_number(s: str) -> float | None: """Versucht einen String als Zahl zu parsen.""" s = s.strip() if not s: return None try: return float(s) except ValueError: # Lokalisierte Formate: 1.234,56 → 1234.56 if "," in s: try: return float(s.replace(".", "").replace(",", ".")) except ValueError: pass return None def _try_parse_version(s: str) -> tuple[int, ...] | None: """Versucht einen String als Version (x.y.z.w) zu parsen.""" s = s.strip() parts = s.split(".") if len(parts) < 2 or len(parts) > 4: return None try: nums = tuple(int(p) for p in parts) return nums except ValueError: return None def _is_truthy(value: str) -> bool: """Prueft ob ein Wert als 'wahr' gilt.""" v = value.strip().lower() if v in _TRUE_KEYWORDS: return True if v in _FALSE_KEYWORDS: return False num = _try_parse_number(v) if num is not None: return num > 0 # Nicht-leerer String → True return bool(v) def _compare(lhs: str, op: str, rhs: str) -> bool: """Vergleicht zwei Werte mit dem gegebenen Operator.""" lhs = lhs.strip() rhs = rhs.strip() # Typ-Pruefung mit === if op == "===": return _type_check(lhs, rhs) if op == "!==": return not _type_check(lhs, rhs) # Zahlen vergleichen lnum = _try_parse_number(lhs) rnum = _try_parse_number(rhs) if lnum is not None and rnum is not None: return _compare_numbers(lnum, op, rnum) # Versionen vergleichen lver = _try_parse_version(lhs) rver = _try_parse_version(rhs) if lver is not None and rver is not None: return _compare_versions(lver, op, rver) # Boolean-Keywords lb = lhs.lower() in _TRUE_KEYWORDS rb = rhs.lower() in _TRUE_KEYWORDS if lhs.lower() in (_TRUE_KEYWORDS | _FALSE_KEYWORDS) and \ rhs.lower() in (_TRUE_KEYWORDS | _FALSE_KEYWORDS): return _compare_numbers(float(lb), op, float(rb)) # String-Vergleich (case-insensitive) llow = lhs.lower() rlow = rhs.lower() if op == "==": return llow == rlow if op == "!=": return llow != rlow if op == "<": return llow < rlow if op == ">": return llow > rlow if op == "<=": return llow <= rlow if op == ">=": return llow >= rlow return False def _compare_numbers(a: float, op: str, b: float) -> bool: if op == "==": return a == b if op == "!=": return a != b if op == "<": return a < b if op == ">": return a > b if op == "<=": return a <= b if op == ">=": return a >= b return False def _compare_versions(a: tuple[int, ...], op: str, b: tuple[int, ...]) -> bool: # Auf gleiche Laenge auffuellen max_len = max(len(a), len(b)) a_padded = a + (0,) * (max_len - len(a)) b_padded = b + (0,) * (max_len - len(b)) if op == "==": return a_padded == b_padded if op == "!=": return a_padded != b_padded if op == "<": return a_padded < b_padded if op == ">": return a_padded > b_padded if op == "<=": return a_padded <= b_padded if op == ">=": return a_padded >= b_padded return False def _type_check(value: str, type_name: str) -> bool: """Typ-Pruefung: value === int/float/bool/byte.""" t = type_name.lower().strip() v = value.strip() if t == "int": try: int(v) return "." not in v except ValueError: return False if t in ("float", "decimal", "number", "numeric"): return _try_parse_number(v) is not None if t in ("bool", "boolean"): return v.lower() in (_TRUE_KEYWORDS | _FALSE_KEYWORDS) if t == "byte": try: n = int(v) return 0 <= n <= 255 except ValueError: return False if t in ("string", "str"): return True if t == "empty": return v == "" return False def _find_at_depth_0(text: str, needle: str, start: int = 0) -> int: """Findet needle bei Klammer-Tiefe 0.""" depth = 0 i = start nlen = len(needle) while i < len(text): if text[i] == "(": depth += 1 elif text[i] == ")": depth -= 1 elif depth == 0 and text[i:i + nlen] == needle: return i i += 1 return -1 def _split_branches(text: str) -> list[str]: """Teilt den Ausdruck an :: bei Tiefe 0.""" branches: list[str] = [] start = 0 while True: pos = _find_at_depth_0(text, "::", start) if pos == -1: branches.append(text[start:]) break branches.append(text[start:pos]) start = pos + 2 return branches def _parse_condition(text: str) -> tuple[str, str, str] | None: """Parst 'lhs op rhs' aus einem Bedingungsstring.""" text = text.strip() # Operator finden (laengste zuerst) for op in _OPERATORS: pos = text.find(op) if pos != -1: lhs = text[:pos].strip() rhs = text[pos + len(op):].strip() return lhs, op, rhs return None def eval_logic(expr: str) -> str | None: """ Wertet einen logischen Ternary-Ausdruck aus. Format: condition ? result_true : result_false Else-If: c1 ? r1 :: c2 ? r2 :: ... : default Args: expr: Der Ausdruck (ohne die aeusseren Klammern). Returns: Das Ergebnis als String oder None bei Parse-Fehler. """ expr = expr.strip() if not expr: return None # Branches aufteilen (an :: bei Tiefe 0) branches = _split_branches(expr) for i, branch in enumerate(branches): branch = branch.strip() is_last = (i == len(branches) - 1) # ? finden bei Tiefe 0 q_pos = _find_at_depth_0(branch, "?") if q_pos == -1: if is_last: # Letzter Branch ohne ? → Default-Wert return branch continue condition_str = branch[:q_pos].strip() rest = branch[q_pos + 1:] # Bei letztem Branch: true_result : false_result # Bei nicht-letztem Branch: nur true_result if is_last: colon_pos = _find_at_depth_0(rest, ":") if colon_pos == -1: true_result = rest.strip() false_result = "" else: true_result = rest[:colon_pos].strip() false_result = rest[colon_pos + 1:].strip() else: true_result = rest.strip() false_result = None # Kein False-Zweig bei Nicht-letztem Branch # Bedingung auswerten cond_result = _eval_condition(condition_str) if cond_result: return true_result elif is_last and false_result is not None: return false_result return None def _eval_condition(condition: str) -> bool: """Wertet eine einzelne Bedingung aus.""" condition = condition.strip() # Versuche Operator zu finden parsed = _parse_condition(condition) if parsed: lhs, op, rhs = parsed return _compare(lhs, op, rhs) # Kein Operator → Truthiness-Pruefung return _is_truthy(condition)