From 5c6aca64dab6d10bee7903007984cabfcd90c201 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Fri, 17 Oct 2025 21:27:05 +0200 Subject: [PATCH] MaMa support macros --- Uebung-01/MaMa.py | 123 +++++++++++++++++++++++++++++++++++----------- Uebung-01/test.py | 31 ++++++++++++ 2 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 Uebung-01/test.py diff --git a/Uebung-01/MaMa.py b/Uebung-01/MaMa.py index c2ea282..55edcc4 100644 --- a/Uebung-01/MaMa.py +++ b/Uebung-01/MaMa.py @@ -24,6 +24,16 @@ class MaMa: # 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: @@ -34,15 +44,16 @@ class MaMa: self.stack.update({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 = [] - def run(self, max_steps: int = 1000) -> List[Dict[str, int|str|dict]]: + def run(self, max_steps: int = 1000) -> List[Dict[str, int | str | dict | list]]: steps = 0 - journal = [self.config('init')] + journal = [self.config("init")] while not self.halted and steps < max_steps: journal.append(self.step()) steps += 1 @@ -51,35 +62,82 @@ class MaMa: def is_halted(self) -> bool: return self.halted - def step(self) -> Dict[str, int|str|dict]: + 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") + # 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, arg = self.decode(call) + 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(arg) + method(args) self.steps += 1 return self.config(call) - def config(self, call: str) -> Dict[str, int|str|dict]: + # 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 } @staticmethod - def decode(call: str) -> Tuple[str, Optional[int]]: + def decode(call: str) -> Tuple[str, Optional[List[int]]]: if "(" in call: - name, arg = call.split("(") - return name, int(arg[:-1]) + name, rest = call.split("(", 1) + args_str = rest[:-1] + if args_str.strip() == "": + return name, [] + args = [int(a.strip()) for a in args_str.split(",")] + return name, args return call, None # Stop execution @@ -98,10 +156,11 @@ class MaMa: self.p_prog += 1 # Push constant n onto stack - def _push(self, n: Optional[int]) -> None: - assert n is not None + def _push(self, n: Optional[List[int]]) -> None: + assert n is not None and len(n) == 1 + val = n[0] self.p_stack += 1 - self.stack[self.p_stack] = n + self.stack[self.p_stack] = val self.p_prog += 1 # Add top two stack elements @@ -129,41 +188,45 @@ class MaMa: self.p_prog += 1 # Load value from relative address n in stack - def _ldo(self, n: Optional[int]) -> None: - assert n is not None + def _ldo(self, n: Optional[List[int]]) -> None: + assert n is not None and len(n) == 1 + offset = n[0] old_sp = self.p_stack self.p_stack += 1 - self.stack[self.p_stack] = self.stack[old_sp + n] + self.stack[self.p_stack] = self.stack[old_sp + offset] self.p_prog += 1 # Store top value to relative address n in stack - def _sto(self, n: Optional[int]) -> None: - assert n is not None - self.stack[self.p_stack + n] = self.stack[self.p_stack] + def _sto(self, n: Optional[List[int]]) -> None: + assert n is not None and len(n) == 1 + offset = n[0] + self.stack[self.p_stack + offset] = self.stack[self.p_stack] self.p_stack -= 1 self.p_prog += 1 # Unconditional jump to address a - def _ujp(self, a: Optional[int]) -> None: - assert a is not None - self.p_prog = a + def _ujp(self, a: Optional[List[int]]) -> None: + assert a is not None and len(a) == 1 + self.p_prog = a[0] # Conditional jump if top two elements are equal - def _equal(self, a: Optional[int]) -> None: - assert a is not None + def _equal(self, a: Optional[List[int]]) -> None: + assert a is not None and len(a) == 1 + addr = a[0] if self.stack[self.p_stack - 1] == self.stack[self.p_stack]: self.p_stack -= 2 - self.p_prog = a + self.p_prog = addr else: self.p_stack -= 2 self.p_prog += 1 # Conditional jump if second-top ≤ top element - def _leq(self, a: Optional[int]) -> None: - assert a is not None + def _leq(self, a: Optional[List[int]]) -> None: + assert a is not None and len(a) == 1 + addr = a[0] if self.stack[self.p_stack - 1] <= self.stack[self.p_stack]: self.p_stack -= 2 - self.p_prog = a + self.p_prog = addr else: self.p_stack -= 2 self.p_prog += 1 diff --git a/Uebung-01/test.py b/Uebung-01/test.py new file mode 100644 index 0000000..dba0365 --- /dev/null +++ b/Uebung-01/test.py @@ -0,0 +1,31 @@ +from MaMaGUI import MaMaGUI +from MaMa import MaMa + +if __name__ == "__main__": + prog = { + 0: 'outer', + 1: 'stop' + } + + machine = MaMa(prog, [4, 6]) + + inner_prog = [ + 'push(1)', + 'push(2)', + 'add', + 'stop' + ] + + outer_prog = [ + 'push(10)', + 'inner', + 'mult', + 'stop' + ] + + machine.add_macro("inner", inner_prog, []) + machine.add_macro("outer", outer_prog, []) + + # Run machine once for GUI visualization + gui = MaMaGUI(machine) + gui.display()