233 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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
 |