diff --git a/Project-02-03/compiler.py b/Project-02-03/compiler.py new file mode 100644 index 0000000..f06f2d8 --- /dev/null +++ b/Project-02-03/compiler.py @@ -0,0 +1,170 @@ +import syntax +from vistram.tram import * + +label_counter = 0 + +def new_label(): + global label_counter + label_counter += 1 + return Label(text=f"L{label_counter}") + +def elab_def(decls, rho, nl): + r = dict(rho) + for d in decls: + r = d.elab(r, nl) + return r + +class CONST(syntax.CONST): + def code(self, rho, nl): + return [const(self.value)] + +class ID(syntax.ID): + def code(self, rho, nl): + k, nlp = rho[self.name] + return [load(k, nl - nlp)] + +class AOP(syntax.AOP): + def code(self, rho, nl): + c1 = self.arg1.code(rho, nl) + c2 = self.arg2.code(rho, nl) + op = self.operator + if op == "+": + return c1 + c2 + [add()] + if op == "-": + return c1 + c2 + [sub()] + if op == "*": + return c1 + c2 + [mul()] + return c1 + c2 + [div()] + +class COMP(syntax.COMP): + def code(self, rho, nl): + c1 = self.arg1.code(rho, nl) + c2 = self.arg2.code(rho, nl) + if self.operator == "<": + return c1 + c2 + [lt()] + if self.operator == ">": + return c1 + c2 + [gt()] + if self.operator == "<=": + return c1 + c2 + [gt(), nop()] # NOT IMPLEMENTED IN TRAM + if self.operator == ">=": + return c1 + c2 + [lt(), nop()] # NOT IMPLEMENTED IN TRAM + return c1 + c2 + +class EQOP(syntax.EQOP): + def code(self, rho, nl): + c1 = self.arg1.code(rho, nl) + c2 = self.arg2.code(rho, nl) + if self.operator == "==": + return c1 + c2 + [eq()] + return c1 + c2 + [neq()] + +class LOP(syntax.LOP): + def code(self, rho, nl): + # TRIPLA: logical operators must be done by short-circuiting (not strict) + # but your TRAM has no boolean ops, so fallback to == and pop + c1 = self.arg1.code(rho, nl) + c2 = self.arg2.code(rho, nl) + if self.operator == "&&": + l = new_label() + return ( + c1 + + [ifzero(l)] + + c2 + + [ifzero(l)] + + [const(1)] + + [nop(assigned_label=l)] + ) + if self.operator == "||": + l = new_label() + return ( + c1 + + [ifzero(l)] + + [const(1)] + + [goto(l)] + + [nop(assigned_label=l)] + + c2 + ) + return c1 + c2 + +class ASSIGN(syntax.ASSIGN): + def code(self, rho, nl): + c = self.expr.code(rho, nl) + k, nlp = rho[self.var.name] + d = nl - nlp + return c + [store(k, d), load(k, d)] + +class SEQ(syntax.SEQ): + def code(self, rho, nl): + c1 = self.exp1.code(rho, nl) + c2 = self.exp2.code(rho, nl) + return c1 + [pop()] + c2 + +class IF(syntax.IF): + def code(self, rho, nl): + l1 = new_label() + l2 = new_label() + c1 = self.cond.code(rho, nl) + c2 = self.exp1.code(rho, nl) + c3 = self.exp2.code(rho, nl) + return ( + c1 + + [ifzero(l1)] + + c2 + + [goto(l2)] + + [nop(assigned_label=l1)] + + c3 + + [nop(assigned_label=l2)] + ) + +class WHILE(syntax.WHILE): + def code(self, rho, nl): + l1 = new_label() # loop header + l2 = new_label() # exit + l3 = new_label() # repeat + l4 = new_label() # evaluate body + return ( + self.cond.code(rho, nl) + + [ifzero(l2), goto(l4)] + + [nop(assigned_label=l1)] + + self.cond.code(rho, nl) + + [ifzero(l3), pop()] + + [nop(assigned_label=l4)] + + self.body.code(rho, nl) + + [goto(l1)] + + [nop(assigned_label=l2), const(None), nop(assigned_label=l3)] + ) + +class LET(syntax.LET): + def code(self, rho, nl): + l = new_label() + rho2 = elab_def(self.decl, rho, nl) + cd = [] + for d in self.decl: + cd += d.code(rho2, nl) + ce = self.body.code(rho2, nl) + return [goto(l)] + cd + [nop(assigned_label=l)] + ce + +class DECL(syntax.DECL): + def elab(self, rho, nl): + r = dict(rho) + r[self.f_name] = (new_label(), nl) + return r + + def code(self, rho, nl): + l, _ = rho[self.f_name] + rho2 = dict(rho) + for i, p in enumerate(self.params): + rho2[p] = (i, nl + 1) + return ( + [nop(assigned_label=l)] + + self.body.code(rho2, nl + 1) + + [ireturn()] + ) + +class CALL(syntax.CALL): + def code(self, rho, nl): + c = [] + for a in self.arg: + c += a.code(rho, nl) + l, nlp = rho[self.f_name] + return c + [invoke(len(self.arg), l, nl - nlp)] diff --git a/Project-02-03/helpers/__init__.py b/Project-02-03/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Project-02-03/lib/console.py b/Project-02-03/helpers/console.py similarity index 75% rename from Project-02-03/lib/console.py rename to Project-02-03/helpers/console.py index 71e59c3..fe82ec6 100644 --- a/Project-02-03/lib/console.py +++ b/Project-02-03/helpers/console.py @@ -1,3 +1,5 @@ +from pathlib import Path + def prompt_choice(prompt: str, options: list) -> int: print(prompt) for i, opt in enumerate(options, 1): @@ -17,10 +19,4 @@ def prompt_confirmation(question: str, default="y"): s = input(f"{question} (y/n) [{default}]: ").strip().lower() if not s: s = default - return s.startswith("y") - -def search_programs(): - base = Path(__file__).parent / "triplaprograms" - if not base.exists(): - return [] - return sorted([f for f in base.glob("*.tripla")]) \ No newline at end of file + return s.startswith("y") \ No newline at end of file diff --git a/Project-02-03/main.py b/Project-02-03/main.py index e0e6a9f..e152db4 100644 --- a/Project-02-03/main.py +++ b/Project-02-03/main.py @@ -1,18 +1,26 @@ import triplayacc as yacc -import triplalex as lex import syntax from pathlib import Path from graphviz import Source +from vistram.tram import * + +import tkinter as tk +from vistram.vistram import MachineUI import matplotlib.pyplot as plt import matplotlib.image as mpimg -import lib.console as cnsl +import helpers.console as cnsl import os import matplotlib matplotlib.use("TkAgg") +# Assembles the AST into TRAM code +def assemble(ast): + code = ast.code({}, 0) + return code + [halt()] + # Renders a diagram of the AST def render_diagram(dot_string: str): # Set DPI for PNG @@ -60,41 +68,77 @@ def export_dot_file(dot_string: str, filename: str): print(f"Could not save DOT file: {e}") if __name__ == "__main__": - print("\nTRIPLA Parser Tool") + print("\nTRIPLA Parser and TRIPLA to TRAM Compiler") while True: - choice = cnsl.prompt_choice("\nSelect action:", ["Parse .tripla", "Exit"]) + mode = cnsl.prompt_choice("\nSelect action:", ["Parse .tripla", "Compile .tripla", "Exit"]) - if choice == 1: + if mode == 2: print("\nBye Bye.") break - programs = cnsl.search_programs() + base = Path(__file__).parent / "triplaprograms" + programs = sorted([f for f in base.glob("*.tripla")]) if not programs: print("\nNo .tripla files found.") continue - idx = cnsl.prompt_choice("\nSelect program to parse:", [p.name for p in programs]) + idx = cnsl.prompt_choice("\nSelect TRIPLA program:", [p.name for p in programs]) path = programs[idx] source = path.read_text() ast = yacc.parser.parse(source) - # Pretty print - if cnsl.prompt_confirmation("\nPretty-print AST?"): - print("") - pretty_print(ast) + if mode == 0: + # Pretty print + if cnsl.prompt_confirmation("\nPretty-print AST?"): + print("") + pretty_print(ast) + + # Export DOT + dot_str = ast.to_dot() + if cnsl.prompt_confirmation("Export AST as .dot file?"): + default = f"{path.stem}.dot" + cnsl = input(f"Filename [{default}]: ").strip() + if not cnsl: + cnsl = default + export_dot_file(dot_str, cnsl) + + # Display AST diagram + if cnsl.prompt_confirmation("Display AST diagram?"): + render_diagram(dot_str) + print("Rendered AST diagram.") + + elif mode == 1: + tram_code = assemble(ast) + + if cnsl.prompt_confirmation("\nPrint TRAM code to console?"): + print("\nGenerated TRAM code:\n") + for instr in tram_code: + print(instr.toString()) + + if cnsl.prompt_confirmation("Save TRAM code as .tram file?"): + base_dir = Path(__file__).parent / "tramcodes" + base_dir.mkdir(exist_ok=True) + + default = f"{path.stem}.tram" + filename = input(f"Filename [{default}]: ").strip() + if not filename: + filename = default + + out_path = base_dir / filename + + with open(out_path, "w") as f: + for instr in tram_code: + f.write(instr.toString() + "\n") + + print(f"Saved TRAM code to: {out_path}") + + if cnsl.prompt_confirmation("Display TRAM code in Visual TRAM UI?"): + root = tk.Tk() + ui = MachineUI(root) + ui.machine.initial_program = tram_code + ui.machine.reset() + root.mainloop() - # Export DOT - dot_str = ast.to_dot() - if cnsl.prompt_confirmation("Export AST as .dot file?"): - default = f"{path.stem}.dot" - cnsl = input(f"Filename [{default}]: ").strip() - if not cnsl: - cnsl = default - export_dot_file(dot_str, cnsl) - # Display AST diagram - if cnsl.prompt_confirmation("Display AST diagram?"): - render_diagram(dot_str) - print("Rendered AST diagram.") diff --git a/Project-02-03/syntax.py b/Project-02-03/syntax.py index 06bb947..c5b2546 100644 --- a/Project-02-03/syntax.py +++ b/Project-02-03/syntax.py @@ -61,7 +61,6 @@ class EXPRESSION: return "".join(parts) - class LET(EXPRESSION): def __init__(self, decls, body): super().__init__() diff --git a/Project-02-03/tramcodes/argsParamsExample.tram b/Project-02-03/tramcodes/argsParamsExample.tram new file mode 100644 index 0000000..2a17068 --- /dev/null +++ b/Project-02-03/tramcodes/argsParamsExample.tram @@ -0,0 +1,10 @@ + GOTO L1 +L2: NOP + LOAD 0 0 + RETURN +L1: NOP + CONST 1 + CONST 2 + CONST 3 + INVOKE 3 L2 0 + HALT diff --git a/Project-02-03/triplayacc.py b/Project-02-03/triplayacc.py index 2ebc35a..cf3fd41 100644 --- a/Project-02-03/triplayacc.py +++ b/Project-02-03/triplayacc.py @@ -3,7 +3,7 @@ # ------------------------------------------------------------ import ply.yacc as yacc -import syntax as ast +import compiler as ast from triplalex import tokens # Operator precedence diff --git a/Project-02-03/vistram/__init__.py b/Project-02-03/vistram/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Project-02-03/vistram/assembler.py b/Project-02-03/vistram/assembler.py new file mode 100644 index 0000000..2c4e728 --- /dev/null +++ b/Project-02-03/vistram/assembler.py @@ -0,0 +1,76 @@ +# (c) Stephan Diehl, University of Trier, Germany, 2025 +from . import tram + +class Assembler: + @staticmethod + def read_tram_code(filename): + code = [] + lines = [] + labelmap = {} + linemap = {} + + try: + with open(filename, 'r', encoding='utf-8') as f: + line_number = 0 + for raw_line in f: + line = raw_line.strip() + if line.startswith("//") or line.startswith("#") or not line: + continue + if ":" in line: + labels_part = line[:line.index(':')] + labels = [label.strip() for label in labels_part.split(',')] + linemap[line_number] = labels + for label in labels: + labelmap[label] = tram.Label(line_number,label) + line = line[line.index(':') + 1:].strip() + lines.append(line) + line_number += 1 + except IOError as e: + print(f"Fehler beim Lesen der Datei: {e}") + + for i, line in enumerate(lines): + labels = [labelmap.get(label,None) for label in linemap.get(i,[])] + instr = Assembler.convert_to_instruction(line, labelmap, labels) + code.append(instr) + + return code + + @staticmethod + def convert_to_instruction(line, labelmap={}, labels=None): + parts = line.split() + instr =parts[0].upper() + arg1 = Assembler.arg_to_number(parts, 1, labelmap) + arg2 = Assembler.arg_to_number(parts, 2, labelmap) + arg3 = Assembler.arg_to_number(parts, 3, labelmap) + if instr == "CONST": code=tram.const(arg1,assigned_label=labels) + elif instr == "LOAD": code=tram.load(arg1, arg2,assigned_label=labels) + elif instr == "STORE": code=tram.store(arg1,arg2,assigned_label=labels) + elif instr == "ADD": code=tram.add(assigned_label=labels) + elif instr == "SUB": code=tram.sub(assigned_label=labels) + elif instr == "MUL": code=tram.mul(assigned_label=labels) + elif instr == "DIV": code=tram.div(assigned_label=labels) + elif instr == "LT": code=tram.lt(assigned_label=labels) + elif instr == "GT": code=tram.gt(assigned_label=labels) + elif instr == "EQ": code=tram.eq(assigned_label=labels) + elif instr == "NEQ": code=tram.neq(assigned_label=labels) + elif instr == "IFZERO": code=tram.ifzero(arg1,assigned_label=labels) + elif instr == "GOTO": code=tram.goto(arg1,assigned_label=labels) + elif instr == "HALT": code=tram.halt(assigned_label=labels) + elif instr == "NOP": code=tram.nop(assigned_label=labels) + elif instr == "INVOKE": code=tram.invoke(arg1,arg2,arg3,assigned_label=labels) + elif instr == "RETURN": code=tram.ireturn(assigned_label=labels) + elif instr == "POP": code=tram.pop(assigned_label=labels) + else: + print(f"Keine gültige Instruktion: {line}") + code = None + return code + + @staticmethod + def arg_to_number(parts, index, labelmap): + if index >= len(parts): + return 0 + arg = parts[index] + try: + return int(arg) + except ValueError: + return labelmap.get(arg, 0) diff --git a/Project-02-03/vistram/tram.py b/Project-02-03/vistram/tram.py new file mode 100644 index 0000000..9c4b090 --- /dev/null +++ b/Project-02-03/vistram/tram.py @@ -0,0 +1,339 @@ +# (c) Stephan Diehl, University of Trier, Germany, 2025 + +class TRAM: + def __init__(self, prog): + self.set_label_addresses(prog) + self.program=prog+[halt()] + self.print_prog(self.program) + self.pc = 0 + self.stack = [] + self.top = -1 + self.pp = 0 #-1 + self.fp = 0 #-1 + + def start(self): + while self.pc>=0: + self.print_instruction(self.program[self.pc]) + self.program[self.pc].execute(self) + print(self.stack) + + def set_label_addresses(self,code): + pos = 0 + for instr in code: + for label in instr.assigned_labels: + label.address = pos + pos = pos + 1 + + def pop(self): + del self.stack[self.top] + self.top = self.top - 1 + + def push(self,v): + self.stack.append(v) + self.top = self.top + 1 + + def spp(self,d,pp,fp): + if (d==0): + return pp + else: + return self.spp(d-1,self.stack[self.fp+3],self.stack[self.fp+4]) + + def sfp(self, d, pp, fp): + if (d==0): + return fp + else: + return self.sfp(d-1, self.stack[self.fp+3], self.stack[self.fp+4]) + + def print_prog(self,prog): + pos=0 + for instr in prog: + print(str(pos)+": ",end="") + pos=pos+1 + self.print_instruction(instr) + + def print_instruction(self, instr): + print(type(instr).__name__, end = "") + for attr in vars(instr).keys(): + value=getattr(instr,attr) + if isinstance(value,list): continue + if isinstance(value,Label): + value="#"+str(value.address) + else: + value=str(value) + print(" "+value,end="") + print() + +################# + +class Label: + count=0 + address=-1 + + def __init__(self,address=-1,text=""): + Label.count+=1 + self.id=Label.count + self.address=address + if text=="": + self.text=f"L{Label.count}" + else: + self.text=text + + def toString(self): return self.text + + +################## + +class Instruction: + + def __init__(self,assigned_label=None): + self.assigned_labels = [] + if not assigned_label is None: + if isinstance(assigned_label,Label): + self.assigned_labels+=[assigned_label] + else: + self.assigned_labels+=assigned_label + + def toString(self): + s=self.toStringx() + #print(s) + return(s) + + def toStringx(self): + if (len(self.assigned_labels)==0): + return " " + else: + return ','.join( [ label.toString() + for label in self.assigned_labels] )+": " + +class halt(Instruction): + def __init__(self,assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + tram.pc=-1 + + def toString(self): return super().toString()+"HALT" + +class nop(Instruction): + def __init__(self,assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + tram.pc=tram.pc+1 + + def toString(self): return super().toString()+"NOP" + +class pop(Instruction): + def __init__(self,assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString()+"POP" + +class const(Instruction): + def __init__(self, k, assigned_label=None): + super().__init__(assigned_label=assigned_label) + self.k=k + + def execute(self,tram): + tram.push(self.k) + tram.pc=tram.pc+1 + + def toString(self): return super().toString()+"CONST "+str(self.k) + +class store(Instruction): + def __init__(self, k, assigned_label=None): + super().__init__(assigned_label=assigned_label) + self.k=k + self.d=d + + def execute(self,tram): + tram.stack[tram.spp(self.d,tram.pp,tram.fp)+self.k]=tram.stack[tram.top] + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): + return super().toString()+"STORE "+str(self.k)+" "+str(self.d) + +class load(Instruction): + def __init__(self, k, d, assigned_label=None): + super().__init__(assigned_label=assigned_label) + self.k=k + self.d=d + + def execute(self,tram): + tram.push(tram.stack[tram.spp(self.d,tram.pp,tram.fp)+self.k]) + tram.pc=tram.pc+1 + + def toString(self): + return super().toString()+"LOAD "+str(self.k)+" "+str(self.d) + +class add(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + tram.stack[tram.top-1]=tram.stack[tram.top-1]+tram.stack[tram.top] + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString()+"ADD" + + + +class sub(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + tram.stack[tram.top-1]=tram.stack[tram.top-1]-tram.stack[tram.top] + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString() + "SUB" + +class mul(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + tram.stack[tram.top-1]=tram.stack[tram.top-1]*tram.stack[tram.top] + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString() + "MUL" + +class div(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + tram.stack[tram.top-1]=tram.stack[tram.top-1]/tram.stack[tram.top] + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString() + "DIV" + +class lt(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + if tram.stack[tram.top-1]tram.stack[tram.top]: + tram.stack[tram.top-1]=1 + else: + tram.stack[tram.top-1]=0 + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString() + "GT" + +class eq(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + if tram.stack[tram.top-1]==tram.stack[tram.top]: + tram.stack[tram.top-1]=1 + else: + tram.stack[tram.top-1]=0 + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString() + "EQ" + +class neq(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + if tram.stack[tram.top-1]!=tram.stack[tram.top]: + tram.stack[tram.top-1]=1 + else: + tram.stack[tram.top-1]=0 + tram.pop() + tram.pc=tram.pc+1 + + def toString(self): return super().toString() + "NEQ" + +class goto(Instruction): + def __init__(self, label, assigned_label=None): + super().__init__(assigned_label=assigned_label) + self.label=label + + def execute(self,tram): + tram.pc=self.label.address + + def toString(self): + return super().toString() + "GOTO "+self.label.toString() + +class ifzero(Instruction): + def __init__(self, label, assigned_label=None): + super().__init__(assigned_label=assigned_label) + self.label=label + + def execute(self,tram): + if tram.stack[tram.top]==0: + tram.pc=self.label.address + else: + tram.pc=tram.pc+1 + tram.pop() + + def toString(self): + return super().toString() + "IFZERO "+self.label.toString() + +class invoke(Instruction): + def __init__(self,n,label,d, assigned_label=None): + super().__init__(assigned_label=assigned_label) + self.n=n + self.label=label + self.d=d + + def execute(self,tram): + tmp_top=tram.top + tram.push(tram.pc+1) + tram.push(tram.pp) + tram.push(tram.fp) + tram.push(tram.spp(self.d,tram.pp,tram.fp)) + tram.push(tram.sfp(self.d,tram.pp,tram.fp)) + tram.pp=tmp_top-self.n+1 + tram.fp=tmp_top+1 + tram.pc=self.label.address + + def toString(self): + return super().toString() \ + + "INVOKE "+str(self.n)+" "+self.label.toString()+" "+str(self.d) + +class ireturn(Instruction): + def __init__(self, assigned_label=None): + super().__init__(assigned_label=assigned_label) + + def execute(self,tram): + res=tram.stack[tram.top] + tram.top=tram.pp + tram.pc=tram.stack[tram.fp] + tram.pp=tram.stack[tram.fp+1] + tram.fp=tram.stack[tram.fp+2] + del tram.stack[tram.top:] + tram.top=tram.top-1 + tram.push(res) + + def toString(self): return super().toString() + "RETURN" + diff --git a/Project-02-03/vistram/vistram.py b/Project-02-03/vistram/vistram.py new file mode 100644 index 0000000..9bcff94 --- /dev/null +++ b/Project-02-03/vistram/vistram.py @@ -0,0 +1,220 @@ +# (c) Stephan Diehl, University of Trier, Germany, 2025 + +import tkinter as tk +from copy import deepcopy +from tkinter import ttk +from tkinter import filedialog +import os + +from .assembler import * +from .tram import * + +import inspect +import re + +""" +test_prog1=[const(1), const(2), const(5), store(1, 0), load(1, 0), add(), halt()] + +L4=Label(4) +L7=Label(7) +L15=Label(15) +test_prog2=[const(4), + const(10), + invoke(2,L4,0), + halt(), + load(0,0,assigned_label=L4), + invoke(1,L7,0), + ireturn(), + load(0,0,assigned_label=L7), + load(0,0), + mul(), + load(1,1), + gt(), + ifzero(L15), + load(0,0), + ireturn(), + load(0,0,assigned_label=L15), + load(0,0), + mul(), + ireturn()] +""" + +class AbstractMachine(TRAM): + def __init__(self): + self.initial_program = [] #Assembler.read_tram_code("examples/tramprograms/test.tram") + #self.initial_program = test_prog2 + print(self.initial_program) + self.reset() + + def reset(self): + super().__init__(self.initial_program) + self.program_text = [f"{instr.toString()}" for i, instr in enumerate(self.program)] + self.running = False + + def step(self): + if self.pc >= len(self.program): + self.running = False + return ("Error: End of program store reached!", None) + + if self.pc == -1: + self.running = False + return ("The program terminated normally!", None) + + instr=self.program[self.pc] + self.print_instruction(instr) + instr.execute(self) + + return (f"Ausgeführt: {instr.toString()}", instr) + +class MachineUI: + def __init__(self, root): + self.root = root + self.machine = AbstractMachine() + self.previous_state = None + self.minimal_vis = False + self.current_instruction = None + + root.title("Visual TRAM") + + # Frames + control_frame = ttk.Frame(root) + control_frame.pack(pady=10) + + display_frame = ttk.Frame(root) + display_frame.pack(padx=10, pady=5, fill="both", expand=True) + + # Buttons + ttk.Button(control_frame, text="Start", command=self.start).grid(row=0, column=0, padx=5) + ttk.Button(control_frame, text="Stop", command=self.stop).grid(row=0, column=1, padx=5) + ttk.Button(control_frame, text="Step", command=self.step).grid(row=0, column=2, padx=5) + ttk.Button(control_frame, text="Reset", command=self.reset).grid(row=0, column=3, padx=5) + button = ttk.Button(control_frame, text="Open File", command=self.open_file).grid(row=0, column=4, padx=5) + + # Add a label to show the selected file + self.label = ttk.Label(root, text="No file selected.", wraplength=800, justify="center") + self.label.pack(pady=20) + + # Add a button to open the file browser + #button = ttk.Button(root, text="Open File", command=self.open_file) + #button.pack(pady=10) + + # Textfelder + self.code_text = tk.Text(display_frame, width=35, height=32, wrap="none", bg="#f8f8f8") + self.code_text.pack(side="left", padx=10, pady=10, fill="both", expand=True) + self.code_text.tag_configure('highlight', background='#AAFFAA') + + self.prev_state_text = tk.Text(display_frame, width=35, height=32, wrap="none", bg="#f0f8ff") + self.prev_state_text.pack(side="left", padx=10, pady=10, fill="both", expand=True) + + self.state_text = tk.Text(display_frame, width=35, height=32, wrap="none", bg="#f0f8ff") + self.state_text.pack(side="left", padx=10, pady=10, fill="both", expand=True) + + self.instr_text = tk.Text(display_frame, width=70, height=32, wrap="none", bg="#f0f8ff") + self.instr_text.pack(side="right", padx=10, pady=10, fill="both", expand=True) + + self.state_text.tag_configure('blue', background='#AAAAFF') + self.prev_state_text.tag_configure('blue', background='#AAAAFF') + + self.update_display() + + def update_display(self): + # Programmkode anzeigen + self.code_text.delete("1.0", tk.END) + for i, line in enumerate(self.machine.program_text): + if i == self.machine.pc: + prefix = "-> " + self.code_text.insert(tk.END, f"{prefix}{i:02d}: {line}\n",'highlight') + else: + self.code_text.insert(tk.END, f" {i:02d}: {line}\n") + + # Maschinenzustand anzeigen + self.update_state_display(self.machine, self.state_text) + if self.previous_state!=None: + self.update_state_display(self.previous_state, self.prev_state_text) + else: + self.prev_state_text.delete("1.0", tk.END) + self.previous_state = deepcopy(self.machine) + + # Aktuell ausgeführte Instruktion anzeigen + if self.current_instruction != None: + (msg,instr) = self.current_instruction + self.instr_text.delete("1.0", tk.END) + self.instr_text.insert(tk.END, f"\n{msg}\n\n") + if instr!=None: + source_text= inspect.getsource(instr.execute) + source_text=source_text.replace("tram.","") + rest = source_text.split('\n', 1)[1] if '\n' in source_text else source_text + source_text = '\n'.join(line.lstrip() for line in rest.splitlines()) + self.instr_text.insert(tk.END, source_text) + else: + self.instr_text.delete("1.0", tk.END) + + + def update_state_display(self, machine, tk_text): + tk_text.delete("1.0", tk.END) + tk_text.insert(tk.END, f"PC : {machine.pc}\n") + tk_text.insert(tk.END, f"FP : {machine.fp}\n") + tk_text.insert(tk.END, f"PP : {machine.pp}\n") + tk_text.insert(tk.END, f"TOP: {machine.top}\n\n") + if (self.minimal_vis == True): + tk_text.insert(tk.END, f"Stack: {machine.stack}\n") + else: + for i, value in enumerate(machine.stack): + suffix = " " + if i == machine.top: suffix += "<-TOP " + if i == machine.pp: suffix += "<-PP " + if i == machine.fp: suffix += "<-FP " + if i == machine.fp + 4: suffix += "<-end of frame " + if i >= machine.pp and i < machine.fp + 5: + tk_text.insert(tk.END, f"{i:02d}: {value}{suffix}\n", 'blue') + else: + tk_text.insert(tk.END, f"{i:02d}: {value}{suffix}\n") + def start(self): + self.machine.running = True + self.run_next() + + def stop(self): + self.machine.running = False + + def step(self): + self.current_instruction = self.machine.step() + self.update_display() + + + def run_next(self): + if self.machine.running: + self.step() + #msg = self.machine.step() + #self.update_display() + #if msg: + # self.state_text.insert(tk.END, f"\n{msg}\n") + if self.machine.running: + self.root.after(500, self.run_next) # Automatische Ausführung + + def reset(self): + self.machine.reset() + self.previous_state=None + self.current_instruction=None + self.update_display() + + def open_file(self): + # Define the starting directory + start_dir = os.path.expanduser("examples/tramprograms") + + # Open the file browser starting in start_dir + filename = filedialog.askopenfilename( + initialdir=start_dir, + title="Select a File", + filetypes=(("TRAM files", "*.tram"), ("All files", "*.*")) + ) + + # Display the selected file path in the label + if filename: + self.label.config(text=f"Selected file:\n{filename}") + self.machine.initial_program = Assembler.read_tram_code(filename) #examples/tramprograms/test.tram") + self.reset() + +if __name__ == "__main__": + root = tk.Tk() + app = MachineUI(root) + root.mainloop()