Skip to content

Instantly share code, notes, and snippets.

@Reconcyl
Last active November 2, 2017 04:18
Show Gist options
  • Save Reconcyl/70e3dfbe091c5f3e46d47a99a8e4e7e6 to your computer and use it in GitHub Desktop.
Save Reconcyl/70e3dfbe091c5f3e46d47a99a8e4e7e6 to your computer and use it in GitHub Desktop.
Intepreter for the MMP esolang: https://esolangs.org/wiki/MMP
# 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