154 lines
4.4 KiB
Python
Executable File
154 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
"""Horrlang Interpreter."""
|
|
|
|
import sys
|
|
import io
|
|
from collections import defaultdict
|
|
|
|
|
|
class Machine:
|
|
def __init__(self, code, read=sys.stdin.read, write=sys.stdout.write, max_cycles=None):
|
|
self.read = read
|
|
self.write = write
|
|
self.mem = defaultdict(int)
|
|
self.mem.update({i: ord(char) for i, char in enumerate(code)})
|
|
self.exc_ptr = 0
|
|
self.mem_ptr = 0
|
|
self.max_cycles = max_cycles
|
|
self.cycles = 0
|
|
|
|
def cycle(self):
|
|
self.cycles += 1
|
|
if self.max_cycles is not None:
|
|
if self.cycles >= self.max_cycles:
|
|
return False
|
|
return True
|
|
|
|
def get_integer(self):
|
|
parameter = 0
|
|
while self.mem[self.exc_ptr + 1] >= 0x30 and self.mem[self.exc_ptr + 1] <= 0x39:
|
|
self.exc_ptr += 1
|
|
if not self.cycle():
|
|
return parameter
|
|
parameter = parameter * 10 + (self.mem[self.exc_ptr] - ord("0"))
|
|
if parameter == 0:
|
|
parameter = 1
|
|
return parameter
|
|
|
|
def L(self):
|
|
"""Move memory pointer to the left."""
|
|
self.mem_ptr -= self.get_integer()
|
|
if self.mem_ptr < 0:
|
|
self.mem_ptr = 0
|
|
|
|
def R(self):
|
|
"""Move memory pointer to the right."""
|
|
self.mem_ptr += self.get_integer()
|
|
|
|
def O(self):
|
|
"""Output."""
|
|
char = self.mem[self.mem_ptr]
|
|
if 0 < char < 256:
|
|
self.write(chr(char))
|
|
|
|
def I(self):
|
|
"""Input."""
|
|
self.mem[self.mem_ptr] = ord(self.read(1))
|
|
|
|
def N(self):
|
|
"""Forward jump."""
|
|
self.exc_ptr += 1
|
|
search = self.mem[self.exc_ptr]
|
|
while self.mem[self.exc_ptr] != search:
|
|
self.exc_ptr += 1
|
|
if not self.cycle():
|
|
return
|
|
|
|
def P(self):
|
|
"""Backward jump."""
|
|
search = self.mem[self.exc_ptr + 1]
|
|
self.exc_ptr -= 1
|
|
while self.mem[self.exc_ptr] != search:
|
|
self.exc_ptr -= 1
|
|
if not self.cycle():
|
|
return
|
|
|
|
def J(self):
|
|
"""Conditional jump."""
|
|
if self.mem[self.mem_ptr] != ord("0"):
|
|
search = self.mem[self.exc_ptr + 1]
|
|
self.exc_ptr -= 1
|
|
while self.mem[self.exc_ptr] != search:
|
|
self.exc_ptr -= 1
|
|
if not self.cycle():
|
|
return
|
|
|
|
def H(self):
|
|
"""Increment."""
|
|
self.mem[self.mem_ptr] = self.mem[self.mem_ptr] + 1
|
|
|
|
def K(self):
|
|
"""Decrement."""
|
|
self.mem[self.mem_ptr] = self.mem[self.mem_ptr] - 1
|
|
|
|
def _debug(self):
|
|
print(f"# Cycle {self.cycles}\n")
|
|
for i, value in sorted(self.mem.items()):
|
|
marker = ""
|
|
if i == self.exc_ptr:
|
|
marker += "← exc ptr"
|
|
control = chr(self.mem[self.exc_ptr])
|
|
if hasattr(self, control):
|
|
marker += " (" + getattr(self, control).__doc__ + ")"
|
|
if i == self.mem_ptr:
|
|
marker += "← mem ptr"
|
|
printable = ""
|
|
if chr(value).isprintable():
|
|
printable = f"{chr(value)}"
|
|
print(f"{i:3d} | {value:5d} | {printable:5s}", marker)
|
|
print()
|
|
|
|
def run(self, debug=False):
|
|
while True:
|
|
if self.mem[self.exc_ptr] == 0:
|
|
return
|
|
char = self.mem[self.exc_ptr]
|
|
if 0 < char < 256:
|
|
control = chr(char)
|
|
if debug:
|
|
self._debug()
|
|
if hasattr(self, control):
|
|
getattr(self, control)()
|
|
self.exc_ptr += 1
|
|
if not self.cycle():
|
|
return
|
|
|
|
|
|
def eval(instructions, input="", max_cycles=None):
|
|
"""Convenience, non-interactive wrapper around HorrlangMachine."""
|
|
stdin = io.StringIO(input)
|
|
stdout = io.StringIO()
|
|
machine = Machine(instructions, stdin.read, stdout.write, max_cycles=max_cycles)
|
|
machine.run()
|
|
return stdout.getvalue()
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("code", type=str)
|
|
parser.add_argument("--debug", action="store_true")
|
|
parser.add_argument("--max-cycles", type=int, default=None)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
Machine(args.code, max_cycles=args.max_cycles,).run(debug=args.debug)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|