diff --git a/Project-02/main.py b/Project-02/main.py index 0729f00..dbd4633 100644 --- a/Project-02/main.py +++ b/Project-02/main.py @@ -1,52 +1,127 @@ -# (c) Stephan Diehl / Updated for AST display by ChatGPT - import triplayacc as yacc import triplalex as lex import syntax + +from pathlib import Path from graphviz import Source import matplotlib.pyplot as plt import matplotlib.image as mpimg -def display_ast_graph(dotfile="ast.dot"): - """Render DOT → PNG and display via matplotlib (always).""" +# ------------------------------------------------------------ +# Utility: rendering DOT → PNG → matplotlib +# ------------------------------------------------------------ - # Read DOT +def render_ast(dotfile: str, outfile: str = "ast.png"): with open(dotfile) as f: dot_data = f.read() - # Render using Graphviz src = Source(dot_data) - src.render("ast", format="png", cleanup=True) + src.render(outfile.replace(".png", ""), format="png", cleanup=True) - # Load rendered PNG - img = mpimg.imread("ast.png") - - # Show in matplotlib window + # Show via matplotlib only + img = mpimg.imread(outfile) plt.imshow(img) - plt.axis('off') + plt.axis("off") plt.show() -def test_parser(filepath): - # Load program - with open(filepath) as f: - source = f.read() +# ------------------------------------------------------------ +# Pretty print AST +# ------------------------------------------------------------ - # Parse input - ast = yacc.parser.parse(source) +def pretty_print(node, indent=0): + prefix = " " * indent + print(f"{prefix}{type(node).__name__}") - # Print plain-text version of the AST (optional) - print("AST object:") - print(ast) + 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}:") + pretty_print(value, indent + 4) + else: + print(f"{prefix} {key}: {value}") - # Export DOT file - syntax.export_dot(ast, "ast.dot") +# Ask for a choice input +def prompt_choice(prompt: str, options: list) -> int: + print(prompt) + for i, opt in enumerate(options, 1): + print(f" {i}. {opt}") - # Display AST diagram - print("Rendering AST diagram with matplotlib...") - display_ast_graph("ast.dot") + while True: + try: + s = input(f"Enter choice (1-{len(options)}): ").strip() + idx = int(s) - 1 + if 0 <= idx < len(options): + return idx + except Exception: + pass + print(f"Invalid. Enter a number between 1 and {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") -if __name__ == '__main__': - test_parser('triplaprograms/or.tripla') +# 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(): + return [] + return sorted([f for f in base.glob("*.tripla")]) + +if __name__ == "__main__": + print("\nTRIPLA Parser Tool") + + while True: + choice = prompt_choice("\nSelect action:",["Parse TRIPLA program", "Exit"]) + + if choice == 1: + print("\nBye.") + break + + else: + 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] + + source = path.read_text() + ast = yacc.parser.parse(source) + + # 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.") diff --git a/Project-02/syntax.py b/Project-02/syntax.py index 6103b5b..652e026 100644 --- a/Project-02/syntax.py +++ b/Project-02/syntax.py @@ -1,5 +1,3 @@ -# (c) Stephan Diehl, University of Trier, Germany, 2025 - class EXPRESSION: pp_count = 0 @@ -69,7 +67,7 @@ class DECL(EXPRESSION): self.body = body def __str__(self): - return f"{self.f_name}(" + ",".join(str(p) for p in self.params) + ") { " + str(self.body) + " }" + return f"{self.f_name}({self.params}) {{ {self.body} }}" class CALL(EXPRESSION): def __init__(self, f_name, arguments): @@ -171,20 +169,4 @@ class WHILE(EXPRESSION): self.body = body def __str__(self): - return "while (" + str(self.condition) + ") do { " + str(self.body) + " }" - -def pretty_print(clas, indent=0): - print(' ' * indent + type(clas).__name__ + ':') - indent += 4 - for k, v in clas.__dict__.items(): - if isinstance(v, EXPRESSION): - pretty_print(v, indent) - else: - print(' ' * indent + f"{k}: {v}") - -def export_dot(ast, filename="ast.dot"): - with open(filename, "w") as f: - f.write("digraph AST {\n") - f.write(" node [shape=box];\n") - ast.to_dot(f) - f.write("}\n") + return "while (" + str(self.condition) + ") do { " + str(self.body) + " }" \ No newline at end of file diff --git a/Project-02/test.py b/Project-02/test.py new file mode 100644 index 0000000..d9727ae --- /dev/null +++ b/Project-02/test.py @@ -0,0 +1,18 @@ +# This is a sample Python script for testing your TRIPLA parser. + +# In PyCharm press Umschalt+F10 to execute it. + +import triplayacc as yacc +import triplalex as lex + +def test_parser(name): + source = "\n".join(open(name).readlines()) + ast = yacc.parser.parse(source) # ,debug=True) + print("AST:") + print(ast) + +# Press the green button in the gutter to run the script. +if __name__ == '__main__': + test_parser('triplaprograms/complex.tripla') + +# See PyCharm help at https://www.jetbrains.com/help/pycharm/ \ No newline at end of file diff --git a/Project-02/triplayacc.py b/Project-02/triplayacc.py index afbd06e..650a2ff 100644 --- a/Project-02/triplayacc.py +++ b/Project-02/triplayacc.py @@ -79,11 +79,11 @@ def p_A_multiple(p): def p_D_single(p): 'D : ID LPAREN V RPAREN LBRACE E RBRACE' - p[0] = ast.DECL(p[1], p[3], p[6]) + p[0] = [ast.DECL(p[1], p[3], p[6])] def p_D_concat(p): 'D : D D' - p[0] = p[1] + [p[2]] if isinstance(p[1], list) else [p[1], p[2]] + p[0] = p[1] + p[2] # ------------------------------------------------------------ # Rules for V