from typing import Dict, Optional, Tuple, List class MaMa: def __init__(self, prog: Dict[int, str] | List[str], stack: Dict[int, int] | List[int] | None = None) -> None: # Execution state 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.p_prog = 0 # Stack memory and pointer self.stack: Dict[int, int] = {} self.p_stack = -1 self.initial_stack: Dict[int, int] = {} 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)}) 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 | list]]: steps = 0 journal = [self.config("init")] while not self.halted and steps < max_steps: 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 } @staticmethod def decode(call: str) -> Tuple[str, Optional[List[int]]]: if "(" in call: 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 def _stop(self, _: Optional[int]) -> None: self.halted = True # Remove top element from stack def _pop(self, _: Optional[int]) -> None: self.p_stack -= 1 self.p_prog += 1 # Load stack pointer value onto stack def _ldsp(self, _: Optional[int]) -> None: self.p_stack += 1 self.stack[self.p_stack] = self.p_stack self.p_prog += 1 # Push constant n onto stack 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] = val self.p_prog += 1 # Add top two stack elements def _add(self, _: Optional[int]) -> None: self.stack[self.p_stack - 1] += self.stack[self.p_stack] self.p_stack -= 1 self.p_prog += 1 # Subtract top element from second-top element def _sub(self, _: Optional[int]) -> None: self.stack[self.p_stack - 1] -= self.stack[self.p_stack] self.p_stack -= 1 self.p_prog += 1 # Multiply top two stack elements def _mult(self, _: Optional[int]) -> None: self.stack[self.p_stack - 1] *= self.stack[self.p_stack] self.p_stack -= 1 self.p_prog += 1 # Integer division of second-top by top stack element def _div(self, _: Optional[int]) -> None: self.stack[self.p_stack - 1] //= self.stack[self.p_stack] self.p_stack -= 1 self.p_prog += 1 # Load value from relative address n in stack 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 + offset] self.p_prog += 1 # Store top value to relative address n in 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[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[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 = addr else: self.p_stack -= 2 self.p_prog += 1 # Conditional jump if second-top ≤ top element 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 = addr else: self.p_stack -= 2 self.p_prog += 1