From 82d93b6b6879ee7a965ff17f6150b9079a1ba776 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Wed, 8 Jul 2026 16:24:14 +0200 Subject: [PATCH] Schalter --show-missing dazu gebaut --- doc/awl2scl.md | 19 +++++++++++++++++++ lib/awl2scl.py | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/doc/awl2scl.md b/doc/awl2scl.md index f49a176..c538a85 100644 --- a/doc/awl2scl.md +++ b/doc/awl2scl.md @@ -92,6 +92,7 @@ bin\awl2scl_deep.bat --dir . --category A | `--force` | vorhandene **generierte** `.scl` überschreiben | | `--tochange ` | toChange-Liste (Default `\data\globals2params_tochange.json`) | | `--log ` | Log-Datei | +| `--show-missing` | am Ende alle nicht übersetzten Bausteine (manuelle Prüfung + Fehler) mit Grund auf der Konsole auflisten | `--improve-gotos` und `--globals2params` implizieren `--force` (Regenerierung). Der Zielpfad je Baustein ist `\\.scl`. @@ -315,3 +316,21 @@ Jeder Lauf schreibt `log\awl2scl_.log` mit: - **Fehler**. Bei `--globals2params` zusätzlich der Pfad der toChange-Liste. + +Mit `--show-missing` wird am Ende zusätzlich auf der **Konsole** (nicht nur im +Log) eine kompakte Liste aller nicht übersetzten Bausteine ausgegeben — sowohl +`manuelle Prüfung nötig` als auch echte `Fehler`, je mit Grund: + +```text +====================================================================== +Nicht uebersetzt (2) +====================================================================== +MANUELL [B] =A01+UH06-KF00\Programmbausteine\Anlage\AMR_Bereich.awl + Grund: Timer-Mnemonik SE nicht unterstuetzt +MANUELL [C] =A01+UH06-KF00\Programmbausteine\Module\Set_Input.awl + Grund: registerindirekter/zeigerbasierter Operand: = E[ #AR1Zeiger] +``` + +Bausteine, die nur wegen falscher Kategorie oder als reiner Datenbaustein +übersprungen wurden, zählen nicht als „nicht übersetzt" (die stehen bereits +in den eigenen Log-Abschnitten). diff --git a/lib/awl2scl.py b/lib/awl2scl.py index 305c650..462d581 100644 --- a/lib/awl2scl.py +++ b/lib/awl2scl.py @@ -62,7 +62,11 @@ class ParsedBlock: class NotABlockError(Exception): - """Datei ist keine Baustein-AWL (z.B. leere/fremde Datei).""" + """Datei ist keine uebersetzbare Baustein-AWL (z.B. leere/fremde Datei).""" + + +class DataBlockError(NotABlockError): + """Reiner Datenbaustein (DB) ohne Programmlogik -- nichts zu uebersetzen.""" # --------------------------------------------------------------------------- @@ -138,6 +142,10 @@ def detect_format(text): # --------------------------------------------------------------------------- HEADER_RE = re.compile(r"^===\s*(\w+)\s+(\S*):\s*(.+?)\s*\[STL\]\s*===\s*$") +# Kopfzeile mit beliebigem Sprach-/Typ-Tag (z.B. "[DB]") -- fuer die Erkennung +# reiner Datenbausteine, die kein STL enthalten. +ANY_HEADER_RE = re.compile(r"^===\s*(\w+)\s+(\S*):\s*(.+?)\s*\[(\w+)\]\s*===\s*$") +DATA_BLOCK_TYPES = {"GlobalDB", "InstanceDB", "DB", "ArrayDB"} NW_RE = re.compile(r"^---\s*NW\s*\d+:\s*(.*?)\s*---\s*$") STMT_RE = re.compile(r"^(?:(\S+:)\s+)?(\S+)(?:\s+(.*))?$") MEMBER_RE = re.compile(r'^(\s*)(\S+|"[^"]+")\s*:\s*(\S+)(?:\s*:=\s*(\S+))?\s*(?:$|(?<=\S)\s{2,}//\s*(.*)$)') @@ -148,6 +156,10 @@ def parse_rendered(text): i = 0 m = HEADER_RE.match(lines[0].strip()) if not m: + any_m = ANY_HEADER_RE.match(lines[0].strip()) + if any_m and (any_m.group(4) == "DB" or any_m.group(1) in DATA_BLOCK_TYPES): + raise DataBlockError( + f"Datenbaustein ({any_m.group(1)} {any_m.group(2)}: {any_m.group(3)}) - keine Logik") raise NotABlockError("Kopfzeile nicht erkannt (gerendertes Format)") btype, num, name = m.group(1), m.group(2), m.group(3) i = 1 @@ -274,6 +286,9 @@ def parse_native(text): i = 0 mh = NATIVE_HEADER_RE.match(lines[0].strip()) if not mh: + mdb = re.match(r'^DATA_BLOCK\s+"([^"]+)"', lines[0].strip()) + if mdb: + raise DataBlockError(f"Datenbaustein (DATA_BLOCK {mdb.group(1)}) - keine Logik") raise NotABlockError("Kopfzeile nicht erkannt (natives Format)") btype = {"FUNCTION_BLOCK": "FB", "FUNCTION": "FC", "ORGANIZATION_BLOCK": "OB"}[mh.group(1)] name = mh.group(2) @@ -1007,6 +1022,9 @@ def _is_generated(scl_path): def process_file(awl_path, requested_categories, target_dir, console, opts): try: parsed = parse_awl_file(awl_path) + except DataBlockError as exc: + console(f"UEBERSPRUNGEN [Datenbaustein, keine Logik] {awl_path}: {exc}") + return Result(awl_path, Outcome.SKIPPED_CATEGORY, reason=f"Datenbaustein (no logic): {exc}") except NotABlockError as exc: console(f"UEBERSPRUNGEN [kein Baustein] {awl_path}: {exc}") return Result(awl_path, Outcome.SKIPPED_CATEGORY, reason=str(exc)) @@ -1203,6 +1221,9 @@ def main(argv=None): parser.add_argument("--tochange", type=Path, default=None, help="Pfad der toChange-Liste (Default: /data/globals2params_tochange.json)") parser.add_argument("--log", type=Path, help="Pfad der Log-Datei") + parser.add_argument("--show-missing", action="store_true", + help="Am Ende alle nicht uebersetzten Bausteine (manuelle Pruefung + Fehler) " + "mit Grund auf der Konsole auflisten") args = parser.parse_args(argv) targets = list(args.files) @@ -1252,8 +1273,29 @@ def main(argv=None): if args.globals2params and store is not None: print(f"toChange-Liste: {store.path}") print(f"Log: {log_path}") + + if args.show_missing: + print_missing(results) + return 1 if n_err else 0 +def print_missing(results): + """Listet alle nicht uebersetzten Bausteine (manuelle Pruefung + Fehler) mit Grund auf.""" + missing = [r for r in results if r.outcome in (Outcome.MANUAL_REVIEW, Outcome.ERROR)] + print() + print("=" * 70) + print(f"Nicht uebersetzt ({len(missing)})") + print("=" * 70) + if not missing: + print("(keine)") + return + for r in sorted(missing, key=lambda r: str(r.path)): + tag = "MANUELL" if r.outcome == Outcome.MANUAL_REVIEW else "FEHLER " + cat = f"[{r.category}] " if r.category else "" + print(f"{tag} {cat}{r.path}") + print(f" Grund: {r.reason}") + + if __name__ == "__main__": sys.exit(main())