494 lines
15 KiB
Python
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
|