| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- # -*- 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)
|