Finish MaMaGUI and create MaMaMa

This commit is contained in:
Jan-Niclas Loosen
2025-10-18 10:59:29 +02:00
parent 5c6aca64da
commit eab6386b83
6 changed files with 178 additions and 180 deletions

View File

@@ -5,88 +5,106 @@ from MaMa import MaMa
class MaMaGUI:
def __init__(self, mama: MaMa, delay: int = 400) -> None:
self.machine = mama
# Autoplay speed
self.delay = delay
self.index = 0
self.journal: List[Dict[str, object]] = []
# Tracks automatic running state
self.running = False
# Rather autoplay is activated
self.do_autoplay = False
# Run machine once and include initial configuration
self.journal = self.machine.run()
# Run MaMa and create journal
self.machine = mama
self.journal: List[Dict[str, object]] = self.machine.run()
if not self.journal:
raise ValueError("Journal is empty. Make sure MaMa executed successfully.")
print(self.journal)
raise ValueError("Journal is empty.")
self.journal_index = 0
# Bootstrap UI
self.root = tk.Tk()
self.root.title("MaMa GUI")
# Prepare rows and columns
container = ttk.Frame(self.root, padding=10)
container.grid(sticky="nsew")
container.columnconfigure(0, weight=1)
container.columnconfigure(1, weight=1)
container.columnconfigure((0, 1), weight=1)
container.rowconfigure(0, weight=1)
# Program display
prog_frame = ttk.LabelFrame(container, text="Program", padding=5)
prog_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
self.prog_list = tk.Listbox(prog_frame, width=30, height=22, font=("Consolas", 11))
self.prog_list = tk.Listbox(prog_frame, width=70, height=24, font=("Consolas", 11))
self.prog_list.pack(fill="both", expand=True)
# Stack and configuration display
# Stack display
stack_frame = ttk.LabelFrame(container, text="Stack", padding=5)
stack_frame.grid(row=0, column=1, sticky="nsew")
stack_frame.rowconfigure(0, weight=1)
stack_frame.columnconfigure(0, weight=1)
self.stack_list = tk.Listbox(stack_frame, width=32, height=20, font=("Consolas", 11))
self.stack_list = tk.Listbox(stack_frame, width=36, height=22, font=("Consolas", 11))
self.stack_list.grid(row=0, column=0, sticky="nsew")
# Configuration label
# Configuration display
self.conf_label = ttk.Label(stack_frame, text="(s, p, S) = (-, -, [])", font=("Consolas", 11), anchor="e")
self.conf_label.grid(row=1, column=0, sticky="ew", pady=(4, 0))
# Init and bind controls
# Create and bind controls
control = ttk.Frame(self.root, padding=5)
control.grid(row=1, column=0, columnspan=2, sticky="ew")
btns = [
("▶ Start", self.__start),
("⏸ Stop", self.__stop),
("⏭ Next", self.__next_step),
("↩ Prev", self.__prev_step),
("⟲ Reset", self.__reset)
]
ttk.Button(control, text="▶ Start", command=self.start).pack(side="left", padx=5)
ttk.Button(control, text="⏸ Stop", command=self.stop).pack(side="left", padx=5)
ttk.Button(control, text="⏭ Next", command=self.next_step).pack(side="left", padx=5)
ttk.Button(control, text="↩ Prev", command=self.prev_step).pack(side="left", padx=5)
ttk.Button(control, text="⟲ Reset", command=self.reset).pack(side="left", padx=5)
for text, cmd in btns:
ttk.Button(control, text=text, command=cmd).pack(side="left", padx=5)
ttk.Label(control, text="Speed:").pack(side="left", padx=5)
self.speed_scale = ttk.Scale(control, from_=2000, to=100, orient="horizontal", command=self.__update_speed)
self.speed_scale.set(self.delay)
self.speed_scale.pack(side="left", padx=5)
# Status bar
# Global status bar
self.status = ttk.Label(self.root, text="Ready", padding=5, anchor="w")
self.status.grid(row=2, column=0, columnspan=2, sticky="ew")
# Initial update
self.__update()
def __update(self) -> None:
state = self.journal[self.index]
prog = self.machine.prog
p_prog = state["p_prog"]
p_stack = state["p_stack"]
stack = state["stack"]
step = state["step"]
call = state["call"]
# Load current journal entry
state = self.journal[self.journal_index]
stack = dict(state["stack"])
p_prog, p_stack = int(state["p_prog"]), int(state["p_stack"])
step, call = int(state["step"]), str(state["call"])
# Program display
# Load code structure
structure = self.machine.structure()
self.prog_list.delete(0, tk.END)
for addr, instr in prog.items():
opened_macros: List[str] = []
macro_offset = 4
# Display code lines and unpack macros
for addr, info in structure.items():
macros = info.get("macros", [])
depth = len(macros)
# Align macro headers at parent's instruction column
if len(macros) > len(opened_macros):
parent_depth = len(macros) - 1
header_indent = " " * parent_depth + " " * macro_offset
self.prog_list.insert(tk.END, f"{header_indent}[{macros[-1]}]")
self.prog_list.itemconfig(tk.END, {"bg": "#e6e6e6"})
instr_indent = " " * depth
prefix = ">> " if addr == p_prog else " "
self.prog_list.insert(tk.END, f"{prefix}{addr:>3}: {instr}")
line = f"{prefix}{instr_indent}{addr:>3}: {info['call']}"
self.prog_list.insert(tk.END, line)
opened_macros = macros
# Highlight current program line
if addr == p_prog:
self.prog_list.itemconfig(tk.END, {"bg": "#ffd966"})
# Stack display
# Visualize current stack
self.stack_list.delete(0, tk.END)
if stack:
for i in range(p_stack, -1, -1):
@@ -96,50 +114,52 @@ class MaMaGUI:
if i == p_stack:
self.stack_list.itemconfig(tk.END, {"bg": "#cfe2f3"})
else:
self.stack_list.insert(tk.END, "(empty)")
self.stack_list.insert(tk.END, "Empty")
# Configuration line
conf_str = f"({p_stack}, {p_prog}, {list(stack.values())})"
self.conf_label.config(text=conf_str)
# Status update
status = f"Step {step}/{len(self.journal)-1} | PC={p_prog} | SP={p_stack} | Last call: {call}"
self.status.config(text=status)
# Update configuration and global stack
self.conf_label.config(text=f"({p_stack}, {p_prog}, {list(stack.values())})")
self.status.config(text=f"Step {step}/{len(self.journal)-1} | PC={p_prog} | SP={p_stack} | Call: {call}")
# Configure auto step speed
def __update_speed(self, value: str) -> None:
self.delay = int(float(value))
def start(self) -> None:
if not self.running:
self.running = True
# Start autoplay
def __start(self) -> None:
if not self.do_autoplay:
self.do_autoplay = True
self.__auto_step()
def stop(self) -> None:
self.running = False
# Stop autoplay
def __stop(self) -> None:
self.do_autoplay = False
# Play an autoplay step
def __auto_step(self) -> None:
if self.running and self.index < len(self.journal) - 1:
self.index += 1
if self.do_autoplay and self.journal_index < len(self.journal) - 1:
self.journal_index += 1
self.__update()
self.root.after(self.delay, self.__auto_step)
else:
self.running = False
self.do_autoplay = False
def next_step(self) -> None:
if self.index < len(self.journal) - 1:
self.index += 1
# Play next step
def __next_step(self) -> None:
if self.journal_index < len(self.journal) - 1:
self.journal_index += 1
self.__update()
def prev_step(self) -> None:
if self.index > 0:
self.index -= 1
# Play previous step
def __prev_step(self) -> None:
if self.journal_index > 0:
self.journal_index -= 1
self.__update()
def reset(self) -> None:
self.machine.reload()
self.journal = self.machine.run()
self.index = 0
# Reset machine
def __reset(self) -> None:
self.journal_index = 0
self.__update()
# Run main loop
def display(self) -> None:
self.root.mainloop()