Finish refactoring

This commit is contained in:
Jan-Niclas Loosen
2025-11-20 19:19:52 +01:00
parent 346c3c8ffd
commit 7ac8889a1d
3 changed files with 92 additions and 101 deletions

BIN
Project-02/Source.gv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -6,52 +6,47 @@ from pathlib import Path
from graphviz import Source from graphviz import Source
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.image as mpimg import matplotlib.image as mpimg
import os
import matplotlib
matplotlib.use("TkAgg")
# ------------------------------------------------------------ def render_ast_from_string(dot_string: str):
# Utility: rendering DOT → PNG → matplotlib # Set DPI for PNG
# ------------------------------------------------------------ os.environ["GV_FILE_DPI"] = "300"
def render_ast(dotfile: str, outfile: str = "ast.png"): src = Source(dot_string, format="png", engine="dot")
with open(dotfile) as f: png_path = src.render(cleanup=True)
dot_data = f.read()
src = Source(dot_data) img = mpimg.imread(png_path)
src.render(outfile.replace(".png", ""), format="png", cleanup=True)
fig = plt.figure(figsize=(12, 12))
fig.canvas.manager.window.wm_title("TRIPLA AST Viewer")
# Show via matplotlib only
img = mpimg.imread(outfile)
plt.imshow(img) plt.imshow(img)
plt.axis("off") plt.axis("off")
plt.show() plt.show()
# ------------------------------------------------------------
# Pretty print AST
# ------------------------------------------------------------
def pretty_print(node, indent=0): def pretty_print(node, indent=0):
prefix = " " * indent prefix = " " * indent
print(f"{prefix}{type(node).__name__}") print(f"{prefix}{type(node).__name__}:")
for key, value in node.__dict__.items(): for key, value in node.__dict__.items():
if key == "pp": if isinstance(value, syntax.EXPRESSION):
continue
if isinstance(value, list):
print(f"{prefix} {key}: [")
for v in value:
if isinstance(v, syntax.EXPRESSION):
pretty_print(v, indent + 4)
else:
print(" " * (indent + 4) + str(v))
print(f"{prefix} ]")
elif isinstance(value, syntax.EXPRESSION):
print(f"{prefix} {key}:")
pretty_print(value, indent + 4) pretty_print(value, indent + 4)
elif isinstance(value, list):
print(f"{prefix} {key}: [")
for element in value:
if isinstance(element, syntax.EXPRESSION):
pretty_print(element, indent + 4)
else:
print(" " * (indent + 4) + str(element))
print(f"{prefix} ]")
else: else:
print(f"{prefix} {key}: {value}") print(f"{prefix} {key}: {value}")
# Ask for a choice input
def prompt_choice(prompt: str, options: list) -> int: def prompt_choice(prompt: str, options: list) -> int:
print(prompt) print(prompt)
for i, opt in enumerate(options, 1): for i, opt in enumerate(options, 1):
@@ -65,21 +60,14 @@ def prompt_choice(prompt: str, options: list) -> int:
return idx return idx
except Exception: except Exception:
pass pass
print(f"Invalid. Enter a number between 1 and {len(options)}.") print(f"Invalid. Enter a number between 1-{len(options)}.")
# Ask for a yes/no input
def prompt_yesno(question: str, default="y"): def prompt_yesno(question: str, default="y"):
s = input(f"{question} (y/n) [{default}]: ").strip().lower() s = input(f"{question} (y/n) [{default}]: ").strip().lower()
if not s: if not s:
s = default s = default
return s.startswith("y") return s.startswith("y")
# Ask for a value input
def prompt_value(prompt: str, default: str):
s = input(f"{prompt} [{default}]: ").strip()
return s if s else default
# Search for known .tripla files
def search_programs(): def search_programs():
base = Path(__file__).parent / "triplaprograms" base = Path(__file__).parent / "triplaprograms"
if not base.exists(): if not base.exists():
@@ -90,38 +78,30 @@ if __name__ == "__main__":
print("\nTRIPLA Parser Tool") print("\nTRIPLA Parser Tool")
while True: while True:
choice = prompt_choice("\nSelect action:",["Parse TRIPLA program", "Exit"]) choice = prompt_choice("\nSelect action:", ["Parse .tripla", "Exit"])
if choice == 1: if choice == 1:
print("\nBye.") print("\nBye Bye.")
break break
else: programs = search_programs()
programs = search_programs() if not programs:
if not programs: print("\nNo .tripla files found.")
print("\nNo .tripla files found.") continue
continue
idx = prompt_choice("\nSelect program to parse:", [p.name for p in programs]) idx = prompt_choice("\nSelect program to parse:", [p.name for p in programs])
path = programs[idx] path = programs[idx]
source = path.read_text() source = path.read_text()
ast = yacc.parser.parse(source) ast = yacc.parser.parse(source)
# Pretty print # Pretty print
if prompt_yesno("\nPretty-print AST?"): if prompt_yesno("\nPretty-print AST?"):
print("") print("")
pretty_print(ast) pretty_print(ast)
# Export DOT # Show AST graph
dot_name = prompt_value("\nSave DOT as", "ast.dot") if prompt_yesno("Display AST diagram?"):
if not dot_name.endswith(".dot"): dot_str = ast.to_dot()
dot_name += ".dot" render_ast_from_string(dot_str)
ast.to_dot(ast, dot_name) print("Rendered AST diagram.")
# Show graph
if prompt_yesno("Display AST diagram?"):
render_ast(dot_name, "ast.png")
print("Rendered AST to ast.png")
print("\nDone.")

View File

@@ -9,17 +9,6 @@ class EXPRESSION:
def copy(): def copy():
return EXPRESSION() return EXPRESSION()
def allNodes(self):
ret = [self]
for node in (self.__getattribute__(a) for a in self.__dict__.keys()):
if isinstance(node, EXPRESSION):
ret += node.allNodes()
if isinstance(node, list):
for n in node:
if isinstance(n, EXPRESSION):
ret += n.allNodes()
return ret
def children(self): def children(self):
"""Return a list of (name, childNode).""" """Return a list of (name, childNode)."""
out = [] out = []
@@ -31,33 +20,55 @@ class EXPRESSION:
elif isinstance(value, list): elif isinstance(value, list):
for i, elem in enumerate(value): for i, elem in enumerate(value):
if isinstance(elem, EXPRESSION): if isinstance(elem, EXPRESSION):
out.append((f"{key}[{i}]", elem)) out.append((f"{key}{i}", elem))
return out return out
def to_dot(self, out): def to_dot(self, visited=None, root=True):
# node label is class name or class name + value if visited is None:
visited = set()
# Prevent infinite recursion
if id(self) in visited:
return ""
visited.add(id(self))
parts = []
# Add a header at the root node
if root:
parts.append("digraph AST {\n")
# Append to label
label = type(self).__name__ label = type(self).__name__
if hasattr(self, "operator"): # AOP/EQOP/COMP/LOP if hasattr(self, "operator"):
label += f"({self.operator})" label += f"({self.operator})"
if hasattr(self, "name"): # ID if hasattr(self, "name"):
label += f"({self.name})" label += f"({self.name})"
if hasattr(self, "value"): # CONST if hasattr(self, "value"):
label += f"({self.value})" label += f"({self.value})"
out.write(f' node{self.pp} [label="{label}"];\n') parts.append(f' node{self.pp} [label="{label}"];\n')
# Draw edges
for edge_name, child in self.children():
parts.append(f' node{self.pp} -> node{child.pp} [label="{edge_name}"];\n')
parts.append(child.to_dot(visited, root=False))
# Add footer at the root node
if root:
parts.append("}\n")
return "".join(parts)
for (edge_name, child) in self.children():
out.write(f' node{self.pp} -> node{child.pp} [label="{edge_name}"];\n')
child.to_dot(out)
class LET(EXPRESSION): class LET(EXPRESSION):
def __init__(self, declarations, body): def __init__(self, decls, body):
super().__init__() super().__init__()
self.declarations = declarations self.decl = decls
self.body = body self.body = body
def __str__(self): def __str__(self):
return "let " + ", ".join(str(d) for d in self.declarations) + " in " + str(self.body) return "let " + ", ".join(str(d) for d in self.decl) + " in " + str(self.body)
class DECL(EXPRESSION): class DECL(EXPRESSION):
def __init__(self, f_name, params, body): def __init__(self, f_name, params, body):
@@ -70,13 +81,13 @@ class DECL(EXPRESSION):
return f"{self.f_name}({self.params}) {{ {self.body} }}" return f"{self.f_name}({self.params}) {{ {self.body} }}"
class CALL(EXPRESSION): class CALL(EXPRESSION):
def __init__(self, f_name, arguments): def __init__(self, f_name, args):
super().__init__() super().__init__()
self.f_name = f_name self.f_name = f_name
self.arguments = arguments self.arg = args
def __str__(self): def __str__(self):
return self.f_name + "(" + ",".join(str(a) for a in self.arguments) + ")" return self.f_name + "(" + ",".join(str(a) for a in self.arg) + ")"
class ID(EXPRESSION): class ID(EXPRESSION):
def __init__(self, name): def __init__(self, name):
@@ -135,13 +146,13 @@ class LOP(EXPRESSION):
return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")" return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"
class ASSIGN(EXPRESSION): class ASSIGN(EXPRESSION):
def __init__(self, variable, expression): def __init__(self, var, expr):
super().__init__() super().__init__()
self.variable = variable self.var = var
self.expression = expression self.expr = expr
def __str__(self): def __str__(self):
return self.variable.name + " = " + str(self.expression) return self.var.name + " = " + str(self.expr)
class SEQ(EXPRESSION): class SEQ(EXPRESSION):
def __init__(self, exp1, exp2): def __init__(self, exp1, exp2):
@@ -153,20 +164,20 @@ class SEQ(EXPRESSION):
return str(self.exp1) + "; " + str(self.exp2) return str(self.exp1) + "; " + str(self.exp2)
class IF(EXPRESSION): class IF(EXPRESSION):
def __init__(self, condition, exp1, exp2): def __init__(self, cond, exp1, exp2):
super().__init__() super().__init__()
self.condition = condition self.cond = cond
self.exp1 = exp1 self.exp1 = exp1
self.exp2 = exp2 self.exp2 = exp2
def __str__(self): def __str__(self):
return "if (" + str(self.condition) + ") then { " + str(self.exp1) + " } else { " + str(self.exp2) + " }" return "if (" + str(self.cond) + ") then { " + str(self.exp1) + " } else { " + str(self.exp2) + " }"
class WHILE(EXPRESSION): class WHILE(EXPRESSION):
def __init__(self, condition, body): def __init__(self, cond, body):
super().__init__() super().__init__()
self.condition = condition self.cond = cond
self.body = body self.body = body
def __str__(self): def __str__(self):
return "while (" + str(self.condition) + ") do { " + str(self.body) + " }" return "while (" + str(self.cond) + ") do { " + str(self.body) + " }"