diff --git a/Project-02/Source.gv.png b/Project-02/Source.gv.png new file mode 100644 index 0000000..a3eb0b2 Binary files /dev/null and b/Project-02/Source.gv.png differ diff --git a/Project-02/main.py b/Project-02/main.py index dbd4633..66c30d2 100644 --- a/Project-02/main.py +++ b/Project-02/main.py @@ -6,52 +6,47 @@ from pathlib import Path from graphviz import Source import matplotlib.pyplot as plt import matplotlib.image as mpimg +import os +import matplotlib +matplotlib.use("TkAgg") -# ------------------------------------------------------------ -# Utility: rendering DOT → PNG → matplotlib -# ------------------------------------------------------------ +def render_ast_from_string(dot_string: str): + # Set DPI for PNG + os.environ["GV_FILE_DPI"] = "300" -def render_ast(dotfile: str, outfile: str = "ast.png"): - with open(dotfile) as f: - dot_data = f.read() + src = Source(dot_string, format="png", engine="dot") + png_path = src.render(cleanup=True) - src = Source(dot_data) - src.render(outfile.replace(".png", ""), format="png", cleanup=True) + img = mpimg.imread(png_path) + + 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.axis("off") plt.show() - -# ------------------------------------------------------------ -# Pretty print AST -# ------------------------------------------------------------ - def pretty_print(node, indent=0): prefix = " " * indent - print(f"{prefix}{type(node).__name__}") + print(f"{prefix}{type(node).__name__}:") for key, value in node.__dict__.items(): - if key == "pp": - 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}:") + if isinstance(value, syntax.EXPRESSION): 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: print(f"{prefix} {key}: {value}") -# Ask for a choice input def prompt_choice(prompt: str, options: list) -> int: print(prompt) for i, opt in enumerate(options, 1): @@ -65,21 +60,14 @@ def prompt_choice(prompt: str, options: list) -> int: return idx except Exception: 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"): s = input(f"{question} (y/n) [{default}]: ").strip().lower() if not s: s = default 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(): base = Path(__file__).parent / "triplaprograms" if not base.exists(): @@ -90,38 +78,30 @@ if __name__ == "__main__": print("\nTRIPLA Parser Tool") while True: - choice = prompt_choice("\nSelect action:",["Parse TRIPLA program", "Exit"]) + choice = prompt_choice("\nSelect action:", ["Parse .tripla", "Exit"]) if choice == 1: - print("\nBye.") + print("\nBye Bye.") break - else: - programs = search_programs() - if not programs: - print("\nNo .tripla files found.") - continue + programs = search_programs() + if not programs: + print("\nNo .tripla files found.") + continue - idx = prompt_choice("\nSelect program to parse:", [p.name for p in programs]) - path = programs[idx] + idx = prompt_choice("\nSelect program to parse:", [p.name for p in programs]) + path = programs[idx] - source = path.read_text() - ast = yacc.parser.parse(source) + source = path.read_text() + ast = yacc.parser.parse(source) - # Pretty print - if prompt_yesno("\nPretty-print AST?"): - print("") - pretty_print(ast) + # Pretty print + if prompt_yesno("\nPretty-print AST?"): + print("") + pretty_print(ast) - # Export DOT - dot_name = prompt_value("\nSave DOT as", "ast.dot") - if not dot_name.endswith(".dot"): - dot_name += ".dot" - ast.to_dot(ast, dot_name) - - # Show graph - if prompt_yesno("Display AST diagram?"): - render_ast(dot_name, "ast.png") - print("Rendered AST to ast.png") - - print("\nDone.") + # Show AST graph + if prompt_yesno("Display AST diagram?"): + dot_str = ast.to_dot() + render_ast_from_string(dot_str) + print("Rendered AST diagram.") \ No newline at end of file diff --git a/Project-02/syntax.py b/Project-02/syntax.py index 652e026..9ca58e6 100644 --- a/Project-02/syntax.py +++ b/Project-02/syntax.py @@ -9,17 +9,6 @@ class EXPRESSION: def copy(): 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): """Return a list of (name, childNode).""" out = [] @@ -31,33 +20,55 @@ class EXPRESSION: elif isinstance(value, list): for i, elem in enumerate(value): if isinstance(elem, EXPRESSION): - out.append((f"{key}[{i}]", elem)) + out.append((f"{key}{i}", elem)) return out - def to_dot(self, out): - # node label is class name or class name + value + def to_dot(self, visited=None, root=True): + 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__ - if hasattr(self, "operator"): # AOP/EQOP/COMP/LOP + if hasattr(self, "operator"): label += f"({self.operator})" - if hasattr(self, "name"): # ID + if hasattr(self, "name"): label += f"({self.name})" - if hasattr(self, "value"): # CONST + if hasattr(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): - def __init__(self, declarations, body): + def __init__(self, decls, body): super().__init__() - self.declarations = declarations + self.decl = decls self.body = body 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): def __init__(self, f_name, params, body): @@ -70,13 +81,13 @@ class DECL(EXPRESSION): return f"{self.f_name}({self.params}) {{ {self.body} }}" class CALL(EXPRESSION): - def __init__(self, f_name, arguments): + def __init__(self, f_name, args): super().__init__() self.f_name = f_name - self.arguments = arguments + self.arg = args 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): def __init__(self, name): @@ -135,13 +146,13 @@ class LOP(EXPRESSION): return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")" class ASSIGN(EXPRESSION): - def __init__(self, variable, expression): + def __init__(self, var, expr): super().__init__() - self.variable = variable - self.expression = expression + self.var = var + self.expr = expr def __str__(self): - return self.variable.name + " = " + str(self.expression) + return self.var.name + " = " + str(self.expr) class SEQ(EXPRESSION): def __init__(self, exp1, exp2): @@ -153,20 +164,20 @@ class SEQ(EXPRESSION): return str(self.exp1) + "; " + str(self.exp2) class IF(EXPRESSION): - def __init__(self, condition, exp1, exp2): + def __init__(self, cond, exp1, exp2): super().__init__() - self.condition = condition + self.cond = cond self.exp1 = exp1 self.exp2 = exp2 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): - def __init__(self, condition, body): + def __init__(self, cond, body): super().__init__() - self.condition = condition + self.cond = cond self.body = body def __str__(self): - return "while (" + str(self.condition) + ") do { " + str(self.body) + " }" \ No newline at end of file + return "while (" + str(self.cond) + ") do { " + str(self.body) + " }" \ No newline at end of file