221 lines
8.1 KiB
Python
221 lines
8.1 KiB
Python
# (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()
|