174 lines
5.7 KiB
Python
174 lines
5.7 KiB
Python
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:
|
|
self.step_counter = 0
|
|
self.halted = False
|
|
|
|
# Init prog from list or dict
|
|
self.p_prog = 0
|
|
if isinstance(prog, list):
|
|
self.prog = {i: micro for i, micro in enumerate(prog)}
|
|
else:
|
|
self.prog = prog
|
|
|
|
# Default stack
|
|
self.p_stack = -1
|
|
self.stack: Dict[int, int] = {}
|
|
|
|
# Init custom stack from list or dict
|
|
if stack is not None:
|
|
if isinstance(stack, list):
|
|
self.stack = {i: v for i, v in enumerate(stack)}
|
|
else:
|
|
self.stack = dict(stack)
|
|
self.p_stack = max(self.stack.keys(), default=-1)
|
|
self.initial_stack = dict(self.stack)
|
|
|
|
# Runs the machine and returns execution journal
|
|
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())
|
|
steps += 1
|
|
return journal
|
|
|
|
# Decodes string MaMa instructions to function callables
|
|
@staticmethod
|
|
def decode(micro: str) -> Tuple[str, Optional[List[Any]]]:
|
|
if "(" in micro:
|
|
name, rest = micro.split("(", 1)
|
|
args_str = rest[:-1].strip()
|
|
if args_str == "":
|
|
return name, []
|
|
args = []
|
|
for a in args_str.split(","):
|
|
a = a.strip()
|
|
try:
|
|
a = int(a)
|
|
except ValueError:
|
|
pass # keep symbolic argument (e.g., 'n')
|
|
args.append(a)
|
|
return name, args
|
|
return micro, None
|
|
|
|
# Generates journal entry for a step
|
|
def config(self, micro: str) -> Dict[str, int | str | dict | list]:
|
|
return {
|
|
"step": self.step_counter,
|
|
"micro": micro,
|
|
"p_prog": self.p_prog,
|
|
"p_stack": self.p_stack,
|
|
"stack": dict(self.stack),
|
|
}
|
|
|
|
# Executes one step of the machine
|
|
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")
|
|
|
|
micro = self.prog[self.p_prog]
|
|
name, args = MaMa.decode(micro)
|
|
method = getattr(self, f"_{name}", None)
|
|
if method is None:
|
|
raise ValueError(f"Unknown instruction: {micro}")
|
|
method(args)
|
|
self.step_counter += 1
|
|
return self.config(micro)
|
|
|
|
# DEFINE MaMa Micros
|
|
|
|
# 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
|