Files
HundM_awl/lib/sclopt.py
T

494 lines
15 KiB
Python

#!/usr/bin/env python3
"""SCL-Zwischenstruktur (IR) + Lesbarkeits-Optimierung fuer awl2scl.py.
Der Translator in awl2scl.py erzeugt IR-Knoten (statt direkt Textzeilen);
render_ir() rendert sie. Ohne aktive Optimierung ist die Ausgabe
byte-identisch zur frueheren stringbasierten Emission (Regressionsabsicherung).
Mit --improve-gotos laufen verlustfreie Peephole-Durchlaeufe:
- merge_adjacent_ifdo : gleiche Bedingung zusammenfassen
- guard_skip_to_if : "IF c THEN GOTO L; body; L:" -> "IF NOT c THEN body"
- synth_case : Verteilung auf eine Variable -> CASE
- drop_unused_labels : nicht angesprungene Marken entfernen
- normalize_bool : Klammern/NOT vereinfachen
"""
import re
# ---------------------------------------------------------------------------
# IR-Knoten
# ---------------------------------------------------------------------------
class Node:
comment = ""
def render(self):
raise NotImplementedError
class Raw(Node):
"""Beliebige vorgefertigte Zeile (Fallback, z.B. Kommentare, ENO-Zeilen)."""
def __init__(self, text):
self.text = text
def render(self):
return [self.text]
class Comment(Node):
def __init__(self, text):
self.text = text
def render(self):
return [f"// {self.text}"]
class Label(Node):
def __init__(self, name):
self.name = name
def render(self):
return [f"{self.name}: ;"]
class Goto(Node):
def __init__(self, target):
self.target = target
def render(self):
return [f"GOTO {self.target};"]
class Return(Node):
def render(self):
return ["RETURN;"]
class Assign(Node):
def __init__(self, lhs, rhs, comment=""):
self.lhs = lhs
self.rhs = rhs
self.comment = comment
def render(self):
line = f"{self.lhs} := {self.rhs};"
if self.comment:
line += f" // {self.comment}"
return [line]
class Call(Node):
def __init__(self, text, comment=""):
self.text = text
self.comment = comment
def render(self):
line = self.text
if self.comment:
line += f" // {self.comment}"
return [line]
class IfDo(Node):
"""Bedingte Ausfuehrung. body ist eine Liste von Node.
single_line=True rendert die urspruengliche einzeilige Form
'IF c THEN <stmt> END_IF;' (fuer Byte-Identitaet der mechanischen Ausgabe)."""
def __init__(self, cond, body, single_line=True):
self.cond = cond
self.body = body
self.single_line = single_line
def render(self):
if self.single_line and len(self.body) == 1:
inner = self.body[0].render()
if len(inner) == 1:
return [f"IF {self.cond} THEN {inner[0]} END_IF;"]
out = [f"IF {self.cond} THEN"]
for n in self.body:
for ln in n.render():
out.append(" " + ln)
out.append("END_IF;")
return out
class Case(Node):
"""CASE selector OF <konst[,konst]>: <body> ... END_CASE;"""
def __init__(self, selector, branches):
self.selector = selector
self.branches = branches # list of (labels:list[str], body:list[Node], comment:str)
def render(self):
out = [f"CASE {self.selector} OF"]
for labels, body, comment in self.branches:
head = " " + ", ".join(labels) + ":"
if comment:
head += f" // {comment}"
out.append(head)
for n in body:
for ln in n.render():
out.append(" " + ln)
out.append("END_CASE;")
return out
# ---------------------------------------------------------------------------
# Rendern
# ---------------------------------------------------------------------------
def render_ir(nodes):
lines = []
for n in nodes:
lines.extend(n.render())
return lines
# ---------------------------------------------------------------------------
# Hilfen: Bedingungen/Ausdruecke
# ---------------------------------------------------------------------------
def _find_matching(expr, open_idx):
"""Index der zu expr[open_idx]=='(' passenden ')'."""
depth = 0
dq = sq = False
for i in range(open_idx, len(expr)):
ch = expr[i]
if ch == '"' and not sq:
dq = not dq
elif ch == "'" and not dq:
sq = not sq
elif ch == "(" and not dq and not sq:
depth += 1
elif ch == ")" and not dq and not sq:
depth -= 1
if depth == 0:
return i
return -1
def strip_outer_parens(expr):
"""Entfernt genau umschliessende aeussere Klammern (mehrfach), verlustfrei."""
expr = expr.strip()
while expr.startswith("(") and _find_matching(expr, 0) == len(expr) - 1:
expr = expr[1:-1].strip()
return expr
def collapse_double_parens(expr):
"""'((x))' -> '(x)' wo die inneren Klammern direkt die aeusseren fuellen."""
changed = True
while changed:
changed = False
i = 0
while i < len(expr) - 1:
if expr[i] == "(" and expr[i + 1] == "(":
close = _find_matching(expr, i)
inner_close = _find_matching(expr, i + 1)
if close != -1 and inner_close == close - 1:
expr = expr[:i] + expr[i + 1:close] + expr[close + 1:]
changed = True
continue
i += 1
return expr
def normalize_cond(cond):
"""Verlustfreie Ausdrucks-Kosmetik: doppelte/aeussere Klammern weg,
NOT (a = b) -> a <> b, NOT (a <> b) -> a = b, NOT (NOT x) -> x."""
prev = None
cond = cond.strip()
while cond != prev:
prev = cond
cond = collapse_double_parens(cond)
# NOT (NOT x) -> x
m = re.match(r"^NOT \((NOT \(.*\))\)$", cond)
if m and _find_matching(cond, 4) == len(cond) - 1:
cond = strip_outer_parens(cond[4:]) # innere NOT(...) behalten
continue
# NOT (a <op> b) mit einzelnem Vergleich -> negierter Vergleich
m = re.match(r"^NOT \((.*)\)$", cond)
if m and _find_matching(cond, 4) == len(cond) - 1:
inner = m.group(1).strip()
flip = _flip_comparison(inner)
if flip is not None:
cond = flip
continue
cond2 = strip_outer_parens(cond)
if cond2 != cond:
cond = cond2
return cond
_CMP_FLIP = {"=": "<>", "<>": "=", ">": "<=", "<": ">=", ">=": "<", "<=": ">"}
_CMP_RE = re.compile(r"^(.*?)\s(<>|>=|<=|=|>|<)\s(.*)$")
def _flip_comparison(inner):
"""Falls inner ein einzelner Top-Level-Vergleich ist: negierten Vergleich zurueck."""
inner = strip_outer_parens(inner)
# Nur genau EIN Top-Level-Vergleich, keine AND/OR-Verknuepfung auf Top-Ebene.
if _has_top_level_boolop(inner):
return None
m = _CMP_RE.match(inner)
if not m:
return None
lhs, op, rhs = m.group(1).strip(), m.group(2), m.group(3).strip()
if _has_top_level_boolop(lhs) or _has_top_level_boolop(rhs):
return None
return f"{lhs} {_CMP_FLIP[op]} {rhs}"
def _has_top_level_boolop(expr):
depth = 0
dq = sq = False
tokens = re.finditer(r'"|\'|\(|\)|\bAND\b|\bOR\b|\bXOR\b', expr)
for m in tokens:
t = m.group(0)
if t == '"' and not sq:
dq = not dq
elif t == "'" and not dq:
sq = not sq
elif t == "(" and not dq and not sq:
depth += 1
elif t == ")" and not dq and not sq:
depth -= 1
elif t in ("AND", "OR", "XOR") and depth == 0 and not dq and not sq:
return True
return False
def negate_cond(cond):
"""Logische Negation einer Bedingung (fuer Guard-Umkehr)."""
cond = cond.strip()
inner = strip_outer_parens(cond)
flip = _flip_comparison(inner)
if flip is not None:
return flip
if inner.startswith("NOT (") and _find_matching(inner, 4) == len(inner) - 1:
return strip_outer_parens(inner[4:])
return f"NOT ({inner})"
# ---------------------------------------------------------------------------
# Improve-Passes (verlustfrei)
# ---------------------------------------------------------------------------
def _referenced_labels(nodes):
refs = {}
for n in nodes:
if isinstance(n, Goto):
refs[n.target] = refs.get(n.target, 0) + 1
elif isinstance(n, IfDo):
for b in n.body:
if isinstance(b, Goto):
refs[b.target] = refs.get(b.target, 0) + 1
return refs
def merge_adjacent_ifdo(nodes):
out = []
for n in nodes:
if (isinstance(n, IfDo) and out and isinstance(out[-1], IfDo)
and out[-1].cond == n.cond):
out[-1] = IfDo(out[-1].cond, out[-1].body + n.body, single_line=False)
else:
out.append(n)
return out
def guard_skip_to_if(nodes):
"""IfDo(c,[Goto L]); <B ohne L/Sprung nach L>; Label(L) -> IfDo(not c, B)
Nur wenn L genau einmal referenziert wird und direkt hinter B steht."""
refs = _referenced_labels(nodes)
out = []
i = 0
n = len(nodes)
while i < n:
node = nodes[i]
if (isinstance(node, IfDo) and len(node.body) == 1
and isinstance(node.body[0], Goto)
and refs.get(node.body[0].target, 0) == 1):
target = node.body[0].target
# Block B bis zur passenden Label(target) sammeln
j = i + 1
body = []
found = False
while j < n:
if isinstance(nodes[j], Label) and nodes[j].name == target:
found = True
break
if isinstance(nodes[j], Label):
break # andere Marke dazwischen -> kein sauberes Guard-Muster
body.append(nodes[j])
j += 1
if found and body:
inv = normalize_cond(negate_cond(node.cond))
out.append(IfDo(inv, guard_skip_to_if(body), single_line=(len(body) == 1)))
i = j + 1 # Label(target) ueberspringen
continue
out.append(node)
i += 1
return out
_EQ_RE = re.compile(r"^(.*?)\s=\s(.+)$")
def _eq_dispatch(cond):
"""Wenn cond ein einzelner '<sel> = <konst>' ist: (sel, konst), sonst None."""
cond = strip_outer_parens(cond)
if _has_top_level_boolop(cond):
return None
m = _EQ_RE.match(cond)
if not m:
return None
sel, const = m.group(1).strip(), m.group(2).strip()
if not re.match(r'^(#?\w+|"[^"]+")$', sel):
return None
if not re.match(r"^(16#[0-9A-Fa-f]+|w#16#[0-9A-Fa-f]+|\d+|W#\d+)$", const, re.IGNORECASE):
return None
return sel, const
def _body_key(body):
return "\n".join(render_ir(body))
def synth_case(nodes):
"""Folge von IfDo(sel = const, body) auf denselben sel -> Case.
Bodies, die auf Return enden, werden im CASE ohne das Return uebernommen."""
out = []
i = 0
n = len(nodes)
while i < n:
node = nodes[i]
disp = None
if isinstance(node, IfDo):
disp = _eq_dispatch(node.cond)
if disp is None:
out.append(node)
i += 1
continue
sel = disp[0]
# Alle direkt folgenden Dispatch-IfDo auf denselben sel sammeln
group = []
j = i
consts_seen = set()
while j < n and isinstance(nodes[j], IfDo):
d = _eq_dispatch(nodes[j].cond)
if d is None or d[0] != sel or d[1] in consts_seen:
break
consts_seen.add(d[1])
group.append((d[1], nodes[j].body))
j += 1
if len(group) < 2:
out.append(node)
i += 1
continue
# Bodies bereinigen (trailing Return entfernen) und gleiche zusammenfassen
branches = []
for const, body in group:
b = list(body)
if b and isinstance(b[-1], Return):
b = b[:-1]
key = _body_key(b)
for lab in branches:
if lab[2] == key:
lab[0].append(const)
break
else:
branches.append([[const], b, key])
out.append(Case(sel, [(labs, body, "") for labs, body, _ in branches]))
i = j
return out
def drop_unused_labels(nodes):
refs = _referenced_labels(nodes)
return [n for n in nodes if not (isinstance(n, Label) and refs.get(n.name, 0) == 0)]
def normalize_bool_pass(nodes):
for n in nodes:
if isinstance(n, IfDo):
n.cond = normalize_cond(n.cond)
normalize_bool_pass(n.body)
elif isinstance(n, Assign):
n.rhs = normalize_cond(n.rhs) if _looks_boolean(n.rhs) else n.rhs
elif isinstance(n, Case):
for _, body, _ in n.branches:
normalize_bool_pass(body)
return nodes
def _looks_boolean(rhs):
return bool(re.search(r"\b(AND|OR|XOR|NOT)\b", rhs)) or rhs.strip().startswith("(")
def improve_gotos(nodes):
"""Alle Passes in sinnvoller Reihenfolge; verlustfrei (je Netzwerk)."""
nodes = merge_adjacent_ifdo(nodes)
nodes = guard_skip_to_if(nodes)
nodes = synth_case(nodes)
nodes = drop_unused_labels(nodes)
nodes = normalize_bool_pass(nodes)
return nodes
def _region_single_dispatch(nodes):
"""Wenn eine REGION (nach improve_gotos) genau aus einem IfDo(sel=konst, body)
besteht (optional gefolgt von reinem Kommentar): (sel, konst, body), sonst None."""
real = [n for n in nodes if not isinstance(n, Comment)]
if len(real) != 1 or not isinstance(real[0], IfDo):
return None
disp = _eq_dispatch(real[0].cond)
if disp is None:
return None
return disp[0], disp[1], real[0].body
def synth_case_across_networks(networks):
"""Fasst aufeinanderfolgende REGIONs, die je eine Einzelverteilung auf DENSELBEN
Selektor sind, zu einer CASE-REGION zusammen. networks: list[(title, nodes)].
Liefert neue Netzwerkliste. REGION-Titel werden zu Branch-Kommentaren."""
out = []
i = 0
n = len(networks)
while i < n:
title, nodes = networks[i]
disp = _region_single_dispatch(nodes)
if disp is None:
out.append((title, nodes))
i += 1
continue
sel = disp[0]
group = [] # (title, const, body)
j = i
consts_seen = set()
while j < n:
d = _region_single_dispatch(networks[j][1])
if d is None or d[0] != sel or d[1] in consts_seen:
break
consts_seen.add(d[1])
group.append((networks[j][0], d[1], d[2]))
j += 1
if len(group) < 2:
out.append((title, nodes))
i += 1
continue
branches = []
for gtitle, const, body in group:
b = list(body)
if b and isinstance(b[-1], Return):
b = b[:-1]
key = _body_key(b)
for lab in branches:
if lab[2] == key and lab[3] == gtitle:
lab[0].append(const)
break
else:
branches.append([[const], b, key, gtitle])
case_branches = [(labs, body, gtitle) for labs, body, _, gtitle in branches]
out.append((f"CASE {sel}", [Case(sel, case_branches)]))
i = j
return out