From eab6386b8373fa58207eec6bafeacc8026607083 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sat, 18 Oct 2025 10:59:29 +0200 Subject: [PATCH] Finish MaMaGUI and create MaMaMa --- .idea/dictionaries/project.xml | 1 + Uebung-01/MaMa.py | 155 +++++++++------------------------ Uebung-01/MaMaGUI.py | 144 +++++++++++++++++------------- Uebung-01/MaMaMa.py | 42 +++++++++ Uebung-01/ggt.py | 10 ++- Uebung-01/test.py | 6 +- 6 files changed, 178 insertions(+), 180 deletions(-) create mode 100644 Uebung-01/MaMaMa.py diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index d335fa7..94f5af2 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -1,6 +1,7 @@ + btns ldsp diff --git a/Uebung-01/MaMa.py b/Uebung-01/MaMa.py index 55edcc4..e5b068e 100644 --- a/Uebung-01/MaMa.py +++ b/Uebung-01/MaMa.py @@ -1,133 +1,37 @@ -from typing import Dict, Optional, Tuple, List +from typing import Dict, Optional, Tuple, List, Any class MaMa: - def __init__(self, prog: Dict[int, str] | List[str], stack: Dict[int, int] | List[int] | None = None) -> None: - # Execution state + def __init__(self, prog: Dict[int, str] | List[str], + stack: Dict[int, int] | List[int] | None = None) -> None: self.steps = 0 - - # Allow either dict or list for program input - if isinstance(prog, list): - self.prog = {i: instr for i, instr in enumerate(prog)} - else: - self.prog = prog - - # Program counter + self.prog = {i: instr for i, instr in enumerate(prog)} if isinstance(prog, list) else prog self.p_prog = 0 - - # Stack memory and pointer self.stack: Dict[int, int] = {} self.p_stack = -1 - self.initial_stack: Dict[int, int] = {} + self.halted = False if stack is not None: - self.reload(stack) - - # Machine halted flag - self.halted = False - - # Macro system - self.macros: Dict[str, Dict[str, object]] = {} - self.call_stack: List[Tuple[Dict[int, str], int, Optional[str]]] = [] # (prog, return_pc, macro_name) - self.in_macro_stack: List[str] = [] # first = outermost, last = innermost - - # Add new macro with parameters - def add_macro(self, name: str, prog: List[str] | Dict[int, str], args: List[str]) -> None: - if isinstance(prog, list): - prog = {i: instr for i, instr in enumerate(prog)} - self.macros[name] = {"prog": prog, "args": args} - - # Restore initial machine state - def reload(self, stack: Dict[int, int] | List[int] | None = None) -> None: - if stack is None: - self.stack = dict(self.initial_stack) - else: if isinstance(stack, list): - self.stack.update({i: v for i, v in enumerate(stack)}) + self.stack = {i: v for i, v in enumerate(stack)} else: - self.stack.update(stack) - self.p_prog = 0 - self.p_stack = max(self.stack.keys(), default=-1) - self.halted = False - self.steps = 0 - self.call_stack = [] - self.in_macro_stack = [] + self.stack = dict(stack) + self.p_stack = max(self.stack.keys(), default=-1) + self.initial_stack = dict(self.stack) + + # ------------------------------------------------------------- def run(self, max_steps: int = 1000) -> List[Dict[str, int | str | dict | list]]: steps = 0 + # always insert init configuration as step 0 journal = [self.config("init")] while not self.halted and steps < max_steps: - journal.append(self.step()) + journal.append(self.__step()) steps += 1 return journal - def is_halted(self) -> bool: - return self.halted + # ------------------------------------------------------------- - def step(self) -> Dict[str, int | str | dict | list]: - if self.halted or self.p_prog not in self.prog: - # if macro ended, return to caller - if self.call_stack: - self.prog, self.p_prog, macro_name = self.call_stack.pop() - if self.in_macro_stack: - self.in_macro_stack.pop() - return self.step() # skip macro return entry - else: - self.halted = True - return self.config("halted") - - call = self.prog[self.p_prog] - name, args = self.decode(call) - - # Handle macro invocation with parameter substitution - if name in self.macros: - macro = self.macros[name] - formal_args = macro["args"] - actual_args = args if isinstance(args, list) else [] - mapping = dict(zip(formal_args, actual_args)) - - expanded_prog = {} - for i, instr in macro["prog"].items(): - # split into opcode and argument part for safe substitution - if "(" in instr: - instr_name, rest = instr.split("(", 1) - arg_text = rest[:-1] - if arg_text.strip() != "": - parts = [a.strip() for a in arg_text.split(",")] - resolved = [] - for a in parts: - if a in mapping: - resolved.append(str(mapping[a])) - else: - resolved.append(a) - instr = f"{instr_name}({','.join(resolved)})" - else: - instr = f"{instr_name}()" - expanded_prog[i] = instr - - # push current program context - self.call_stack.append((self.prog, self.p_prog + 1, name)) - self.in_macro_stack.append(name) - self.prog = expanded_prog - self.p_prog = 0 - return self.step() # directly continue without adding step - - method = getattr(self, f"_{name}", None) - if method is None: - raise ValueError(f"Unknown instruction: {call}") - - method(args) - self.steps += 1 - return self.config(call) - - # Return current configuration including macro nesting - def config(self, call: str) -> Dict[str, int | str | dict | list]: - return { - "step": self.steps, - "call": call, - "p_prog": self.p_prog, - "p_stack": self.p_stack, - "stack": dict(self.stack), - "in_macro": list(self.in_macro_stack), # first = outermost, last = innermost - } + def structure(self) -> Dict[int, Dict[str, Any]]: + return {i: {"call": call, "macros": []} for i, call in sorted(self.prog.items())} @staticmethod def decode(call: str) -> Tuple[str, Optional[List[int]]]: @@ -140,6 +44,33 @@ class MaMa: return name, args return call, None + # ------------------------------------------------------------- + + def config(self, call: str) -> Dict[str, int | str | dict | list]: + return { + "step": self.steps, + "call": call, + "p_prog": self.p_prog, + "p_stack": self.p_stack, + "stack": dict(self.stack), + } + + # ------------------------------------------------------------- + + def __step(self) -> Dict[str, int | str | dict | list]: + if self.halted or self.p_prog not in self.prog: + self.halted = True + return self.config("halted") + + call = self.prog[self.p_prog] + name, args = MaMa.decode(call) + method = getattr(self, f"_{name}", None) + if method is None: + raise ValueError(f"Unknown instruction: {call}") + method(args) + self.steps += 1 + return self.config(call) + # Stop execution def _stop(self, _: Optional[int]) -> None: self.halted = True diff --git a/Uebung-01/MaMaGUI.py b/Uebung-01/MaMaGUI.py index 0f787e8..6fc6713 100644 --- a/Uebung-01/MaMaGUI.py +++ b/Uebung-01/MaMaGUI.py @@ -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() diff --git a/Uebung-01/MaMaMa.py b/Uebung-01/MaMaMa.py new file mode 100644 index 0000000..1219c13 --- /dev/null +++ b/Uebung-01/MaMaMa.py @@ -0,0 +1,42 @@ +from typing import Dict, Optional, List, Any, Tuple +from MaMa import MaMa + +class MaMaMa(MaMa): + def __init__(self, prog, stack=None) -> None: + self.macros: Dict[str, Dict[str, Any]] = {} + self.initial_macros: Dict[str, Dict[str, Any]] = {} + self._macro_trace: Dict[int, List[str]] = {} + super().__init__(prog, stack) + + def add_macro(self, name: str, prog: List[str] | Dict[int, str], args: List[str] = None) -> None: + if isinstance(prog, list): + prog = {i: instr for i, instr in enumerate(prog)} + self.macros[name] = {"prog": prog, "args": args} + self.initial_macros[name] = dict(self.macros[name]) + + def run(self, max_steps: int = 1000): + # automatically flatten macros before execution + self._expand_macros_into_prog() + return super().run(max_steps) + + # --- flatten macros recursively before execution --- + def _expand_macros_into_prog(self) -> None: + def expand(prog: Dict[int, str], stack: List[str]) -> List[Tuple[str, List[str]]]: + out: List[Tuple[str, List[str]]] = [] + for _, call in sorted(prog.items()): + name, args = self.decode(call) + if name in self.macros: + out.extend(expand(self.macros[name]["prog"], stack + [name])) + else: + out.append((call, list(stack))) + return out + + expanded = expand(self.prog, []) + self.prog = {i: call for i, (call, _) in enumerate(expanded)} + self._macro_trace = {i: macros for i, (_, macros) in enumerate(expanded)} + + def structure(self) -> Dict[int, Dict[str, Any]]: + return { + i: {"call": call, "macros": self._macro_trace.get(i, []), "p_prog": self.p_prog} + for i, call in sorted(self.prog.items()) + } diff --git a/Uebung-01/ggt.py b/Uebung-01/ggt.py index 99e0b1d..14c94e3 100644 --- a/Uebung-01/ggt.py +++ b/Uebung-01/ggt.py @@ -1,5 +1,5 @@ from MaMaGUI import MaMaGUI -from MaMa import MaMa +from MaMaMa import MaMaMa if __name__ == "__main__": prog = { @@ -14,7 +14,7 @@ if __name__ == "__main__": 8: 'leq(14)', 9: 'ldo(-1)', 10: 'ldo(-1)', - 11: 'sub', + 11: 'subs', 12: 'sto(-2)', 13: 'ujp(6)', 14: 'ldo(-1)', @@ -27,7 +27,11 @@ if __name__ == "__main__": } # Create and execute MaMa instance - machine = MaMa(prog, [4, 6]) + machine = MaMaMa(prog, [4, 6]) + + machine.add_macro('subs', { + 0: "sub", + }) # Visualize finished execution using journal gui = MaMaGUI(machine) diff --git a/Uebung-01/test.py b/Uebung-01/test.py index dba0365..a4145ed 100644 --- a/Uebung-01/test.py +++ b/Uebung-01/test.py @@ -1,5 +1,5 @@ from MaMaGUI import MaMaGUI -from MaMa import MaMa +from MaMaMa import MaMaMa # ← use macro-capable class if __name__ == "__main__": prog = { @@ -7,7 +7,8 @@ if __name__ == "__main__": 1: 'stop' } - machine = MaMa(prog, [4, 6]) + # use MaMaMa instead of MaMa + machine = MaMaMa(prog, [4, 6]) inner_prog = [ 'push(1)', @@ -26,6 +27,5 @@ if __name__ == "__main__": machine.add_macro("inner", inner_prog, []) machine.add_macro("outer", outer_prog, []) - # Run machine once for GUI visualization gui = MaMaGUI(machine) gui.display()