Finish MaMaGUI and create MaMaMa
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
							
								
								
									
										42
									
								
								Uebung-01/MaMaMa.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Uebung-01/MaMaMa.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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()) | ||||
|         } | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user