Last active
November 2, 2017 04:18
-
-
Save Reconcyl/70e3dfbe091c5f3e46d47a99a8e4e7e6 to your computer and use it in GitHub Desktop.
Intepreter for the MMP esolang: https://esolangs.org/wiki/MMP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# An interpreter for the MMP (MultiMath Processor) esolang. | |
# Has no dependencies other than the standard library. | |
# A few things to note: | |
# - Cells contain unbounded integers. | |
# - Arithmetic is signed, and negative tape indices are allowed. | |
# - Tape and registers are initialized to 0. | |
# - Invalid commands are ignored. | |
# - IP out-of-bounds ends execution. | |
# - Instructions are indexed from zero. | |
# - GOTO executes the instruction it lands on | |
# (so the program 'g' is an infinite loop). | |
# - Brackets are matched at runtime | |
# (so '+++++(t>r+++++t<r)g(' halts normally). | |
# - Also includes a debug instruction '`' that prints out program state. | |
import functools | |
import collections | |
def input_number(): | |
# Read lines until a valid integer literal is found | |
while True: | |
try: | |
line = input() | |
except EOFError: | |
raise MmpError("Out of input") | |
try: | |
return int(line) | |
except ValueError: | |
pass | |
def output_number(n): | |
print(n) | |
class MmpError(Exception): | |
pass | |
class State: | |
def __init__(self, code, input_func=None, output_func=None): | |
self.code = code | |
self.code_len = len(code) | |
self.register = 0 | |
self.tape_pos = 1 | |
self.ip = 0 | |
self.tape = collections.defaultdict(lambda: 0) | |
if input_func is None: | |
self.input_func = input_number | |
if output_func is None: | |
self.output_func = output_number | |
@functools.lru_cache() | |
def bracket_end(self, start): | |
pos = start | |
while True: | |
pos += 1 | |
if pos >= self.code_len: | |
raise MmpError("Bracket at {} is unmatched" | |
.format(start)) | |
char = self.code[pos] | |
if char == "(": | |
pos = bracket_end(pos) | |
if char == ")": | |
return pos | |
@functools.lru_cache() | |
def bracket_start(self, end): | |
pos = end | |
while True: | |
pos -= 1 | |
if pos < 0: | |
raise MmpError("Bracket at {} is unmatched" | |
.format(end)) | |
char = self.code[pos] | |
if char == "(": | |
return pos | |
elif char == ")": | |
pos = bracket_start(pos) | |
def run(self): | |
while 0 <= self.ip < self.code_len: | |
char = self.code[self.ip] | |
if char == "s": | |
self.tape_pos = self.register | |
elif char == "r": | |
self.register = self.tape[self.tape_pos] | |
elif char == "t": | |
self.tape[self.tape_pos] = self.register | |
elif char == "o": | |
self.output_func(self.register) | |
elif char == "i": | |
self.register = self.input_func() | |
elif char == "(": | |
if self.register == 0: | |
self.ip = self.bracket_end(self.ip) | |
elif char == ")": | |
if self.register != 0: | |
self.ip = self.bracket_start(self.ip) | |
elif char == "f": | |
if self.register > 0: | |
# Go to the location behind the target so it will | |
# advance to the target on the next tick. | |
self.ip = self.tape[self.tape_pos] - 1 | |
elif char == "g": | |
# See comment above. | |
self.ip = self.register - 1 | |
elif char == "e": | |
return | |
elif char == "+": | |
self.register += 1 | |
elif char == "-": | |
self.register -= 1 | |
elif char == ">": | |
self.tape_pos += 1 | |
elif char == "<": | |
self.tape_pos -= 1 | |
elif char == "`": | |
print("=== DEBUG ===") | |
print(self.code) | |
print(" " * self.ip + "^") | |
print("R =", self.register) | |
print(dict(self.tape), self.tape_pos) | |
print() | |
self.ip += 1 | |
if __name__ == "__main__": | |
import sys | |
def error(*lines): | |
print(*lines, sep="\n", file=sys.stderr) | |
sys.exit() | |
def run_string(string): | |
try: | |
State(string).run() | |
except MmpError as e: | |
error("Error: {}".format(e)) | |
def run_file(filename): | |
try: | |
with open(filename) as f: | |
string = f.read() | |
except OSError: | |
error("Error: could not read file {}".format(filename)) | |
run_string(string) | |
def usage(): | |
name = sys.argv[0] | |
error("Usage:", | |
"{} <file> - interprets file as MMP code".format(name), | |
"{} -c <code> - interprets code from command-line argument".format(name)) | |
if len(sys.argv) == 2: | |
run_file(sys.argv[1]) | |
if len(sys.argv) == 3 and sys.argv[1] == "-c": | |
run_string(sys.argv[2]) | |
else: | |
usage() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment