TurtleC Interpreter
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

574 lines
16 KiB

#!/usr/bin/env python3
# TurtleC - C-like with turtles (i think, not sure)
#
# This programming langauge has been designed and implemented for the
# Turtle-themed esojam hosted by Truttle1 (a very cool YouTuber who
# makes educational videos about esolangs and retrolangs).
#
# Obfuscate will be proud of me :>
#
import sys, re, time, random
blocks = ["Mu", "mU", "MU", "mu"]
int_funcs = ["turtle", "noTurtle"]
pchars = "(){},;="
_parser_re = re.compile(r"(#.*(\n|$)|[\(\),{}=;]|[^\(\),{}=;\s]+)")
### AN USEFUL FUNCTION ###
def argv2turtle(arr):
arr = arr.copy()
newarr = []
for item in arr:
a = TurtleArray([TurtleInteger(ord(x)) for x in item])
newarr.append(a)
return TurtleArray(newarr)
### BUILT-INS ###
# The best way to find out what it does is to run the function
# (see programs/truttle1.trt)
def _builtin_Truttle1(env, *args):
_xor_endec=lambda s,p:''.join((chr(ord(c)^ord(p[x%len(p)]))for x,c in enumerate(s)))
quotes = ['\x1c\x17\x0cT\x13\x19\x00BSI\x04H\x02\x1bP',
'\x00\x1a\x1c\x07T\n\x10_C\x1d\x1aO\rO\x06\x1ft\x17\x05\x1d' + \
'\x17L\x07TC\x08\x06S\x06AAB',
'=\x06U\x10\x1b\t\x16\x1f\x0eG]',
'5\x10\x06\x1b\x18\x19\x11TL\x10]\x0eMAABz',
'\x1a<:;;8-yi\'4g$((+\x135233+"v\x01HR\x01BN']
pswd = "Truttle1 is cool"
for quote in quotes:
print(_xor_endec(quote, pswd))
time.sleep(2)
return TurtleInteger(random.randint(0, 100))
def _builtin_set_value(env, k, v):
k = k.name
assert is_turtle(k), "That variable name is not turtle enough. FIX IT."
env[k] = v(env)
return env[k]
# from pprint import pprint
def _builtin_Woa(env, a, b, *args):
return a(env).__add__(b(env))
def _builtin_WOa(env, a, b, *args):
return a(env).__sub__(b(env))
def _builtin_WoA(env, a, b, *args):
return a(env).__mul__(b(env))
def _builtin_WOA(env, a, b, *args):
return a(env).__floordiv__(b(env))
def _builtin_Wow(env, a, b, *args):
return a(env).__eq__(b(env))
def _builtin_WoW(env, a, b, *args):
return a(env).__lt__(b(env))
def _builtin_wOa(env, *args):
for x in args:
x = x(env).value
print(chr(x), end='')
def _builtin_wOA(env, *args):
for x in args:
x = x(env).value
print(x, end=' ')
print()
def _builtin_woA(env, *args):
return TurtleInteger(ord(input()[0]))
def _builtin_TurtleArray(env, size, *args):
assert type(size(env)) == TurtleInteger, "size must be an integer."
arr = [TurtleInteger(0)] * size(env).value
return TurtleArray(arr)
def _builtin_TurtleSet(env, arr, k, v, *args):
assert type(arr(env)) == TurtleArray, "must be an array."
assert type(k(env)) == TurtleInteger, "the index must be an integer."
k = k(env).value
arr = arr(env)
assert k < len(arr.value) and k >= 0, "list index out of range."
arr.value[k] = v(env)
return arr.value[k]
def _builtin_TurtleGet(env, arr, k, *args):
assert type(arr(env)) == TurtleArray, "must be an array."
assert type(k(env)) == TurtleInteger, "the index must be an integer."
k = k(env).value
arr = arr(env)
assert k < len(arr.value) and k >= 0, "list index out of range."
return arr.value[k]
def _builtin_TurtleRead(env, fn):
assert type(fn(env)) == TurtleArray, \
"filename must be an array of integers."
fn = fn(env).value
afn = ""
for c in fn:
c = c(env)
assert type(c) == TurtleInteger, \
"filename muust be an array of integers."
afn += chr(c.value)
try:
with open(afn, "r") as f:
return TurtleArray([TurtleInteger(ord(x)) for x in f.read()])
except IOError:
raise AssertionError("could not read the file.")
def _builtin_TurtleLength(env, arr):
assert type(arr(env)) == TurtleArray, "must be an array."
arr = arr(env)
return TurtleInteger(len(arr.value))
default_env = {
"_set_value": _builtin_set_value,
# Arithmetics
"Woa": _builtin_Woa,
"WOa": _builtin_WOa,
"WoA": _builtin_WoA,
"WOA": _builtin_WOA,
# Logic
"Wow": _builtin_Wow,
"WoW": _builtin_WoW,
# I/O
"wOa": _builtin_wOa,
"wOA": _builtin_wOA,
"woA": _builtin_woA,
# Array stuff
"TurtleArray": _builtin_TurtleArray,
"TurtleASet": _builtin_TurtleSet,
"TurtleAGet": _builtin_TurtleGet,
"TurtleARead": _builtin_TurtleRead,
"TurtleALength": _builtin_TurtleLength,
# ?????????????
"Truttle1": _builtin_Truttle1
}
### ERROR ###
class TurtleException(Exception):
def __init__(self, linenum, err):
self.linenum = linenum
self.err = err
def __str__(self):
return self.err
### CLASSES ###
class TurtleInteger:
def __init__(self, value, linenum = 0):
self.value = value
self.linenum = linenum
def __str__(self):
return str(self.value)
def __call__(self, env):
return self
def __add__(self, v):
return TurtleInteger(self.value + v.value)
def __sub__(self, v):
return TurtleInteger(self.value - v.value)
def __mul__(self, v):
return TurtleInteger(self.value * v.value)
def __floordiv__(self, v):
return TurtleInteger(self.value // v.value)
def __eq__(self, v):
return TurtleInteger(int(self.value == v.value))
def __lt__(self, v):
return TurtleInteger(int(self.value < v.value))
class TurtleArray:
def __init__(self, value, linenum = 0):
self.value = value
self.linenum = linenum
def __str__(self):
return self(self.value)
def __call__(self, env):
return self
def __add__(self, v):
return TurtleArray(self.value + v.value)
def __oof__(self, v):
raise AssertionError("Operation not possible on an array.")
__sub__ = __oof__
__mul__ = __oof__
__floordiv__ = __oof__
__eq__ = __oof__
__lt__ = __oof__
class TurtleVariable:
def __init__(self, name, linenum = 0):
self.name = name
self.linenum = linenum
def __str__(self):
return f"[[ variable {self.name} ]]"
def __call__(self, env):
if self.name not in env:
raise TurtleException(self.linenum,
f"{self.name}: Variable does not exist.")
elif type(env[self.name]) != TurtleInteger and \
type(env[self.name]) != TurtleArray:
raise TurtleException(self.linenum,
f"{self.name} is not a variable.")
return env[self.name]
class TurtleFunction:
def __init__(self, args, code):
self.args = args
self.code = code
def __call__(self, env, *args):
assert len(args) == len(self.args), "Invalid arguments"
args = [x(env) for x in args]
args = dict(zip(self.args, args))
env = {**env.copy(), **args}
if type(self.code) == list:
for instruction in self.code:
ret = instruction(env)
else:
ret = code(env)
return ret
def __str__(self):
return f"Function {args}"
class TurtleFunctionCall:
def __init__(self, name, args, linenum = 0, pre = None):
self.name = name
self.args = args
self.linenum = linenum
self.pre = pre
def __str__(self):
return f"mu {self.name} {[str(x) for x in self.args]}"
def error(self, msg):
raise TurtleException(self.linenum, f"{self.name}: {msg}")
def _assert(self, cond, msg):
if not cond:
self.error(msg)
def __call__(self, env):
self._assert(self.name in env, "Function does not exist.")
if self.pre:
self.pre(self.name, self.args, self.linenum)
# args = [x(env) for x in self.args]
args = self.args
try:
ret = env[self.name](env, *args)
except AssertionError as e:
self.error(e)
return ret if ret else TurtleInteger(0)
class TurtleBlock:
# name expr
# {
# code
# }
def __init__(self, name, expr, code, linenum = 0):
assert name in blocks, "Unknown Block."
self.name = name
self.expr = expr
self.code = code
self.linenum = linenum
def __str__(self):
return "[[ block ]]"
def __call__(self, env):
name = self.name
if name == "Mu":
if self.expr(env).value > 0:
self.run_block(env)
elif name == "mU":
if self.expr(env).value == 0:
self.run_block(env)
elif name == "MU":
while self.expr(env).value > 0:
self.run_block(env)
elif name == "mu":
if type(self.expr) != TurtleFunctionCall:
raise TurtleException(self.linenum,
"Syntax Error.")
elif not is_turtle(self.expr.name):
raise TurtleException(self.linenum,
"Not a valid turtle name smh")
args = []
for x in self.expr.args:
if type(x) != TurtleVariable:
raise TurtleException(self.linenum,
"Syntax Error.")
elif not is_turtle(x.name):
raise TurtleException(self.linenum,
"Not a valid turtle name smh")
args.append(x.name)
env[self.expr.name] = TurtleFunction(args, self.code)
else:
raise AssertionError("How?????")
def run_block(self, env):
if type(self.code) == list:
for instruction in self.code:
instruction(env)
else:
self.code(env)
class TurtleToken:
def __init__(self, value, linenum = 0, tp = None):
self.value = value
self.linenum = linenum
if tp:
self.type = tp
else:
self.type = find_token_type(self.value)
def __str__(self):
return f"TurtleToken('{self.value}', {self.linenum}, '{self.type}')"
def t(self):
return (self.value, self.type)
### FUNCTIONS ###
def find_token_type(t):
if t in pchars:
return "delim"
elif t in blocks:
return "block"
elif t in int_funcs:
return "integer"
else:
return "symbol"
def is_turtle(s):
return re.match(r"(?i)^t+urtle$", s) != None
def tokenize(s):
output = []
for linenum, line in enumerate(s.split("\n")):
line = line.strip()
if line == '':
continue
matches = [(x[0], linenum+1) for x in _parser_re.findall(line)
if not x[0].startswith("#")]
output += matches
return [TurtleToken(x[0], x[1]) for x in output]
# TODO: AST Builder
def find_parens(oline, tokens, syms = "()"):
b = 1
output = []
sym, isym = syms
while tokens != []:
token = tokens.pop(0)
if token.type == 'delim':
if token.value == sym:
b += 1
elif token.value == isym:
b -= 1
if b == 0:
return output
else:
output.append(token)
raise TurtleException(oline, "Expected parenthese, got EOF.")
def find_until(oline, tokens, sym = '{', parens = '()', fr = False):
expr = []
b = 0
paren, iparen = parens
while tokens != []:
token = tokens.pop(0)
if token.t() == (paren, 'delim'):
b += 1
elif token.t() == (iparen, 'delim'):
b -= 1
if b < 0:
raise TurtleException(token.linenum, "Syntax Error.")
elif token.t() == (sym, 'delim') and b == 0:
return expr
else:
expr.append(token)
if fr:
return expr
else:
raise TurtleException(oline, "Syntax Error.")
def build_ast(tokens, noblock = False, ppre = None):
ast = []
while tokens != []:
token = tokens.pop(0)
if token.type == 'block' and not noblock:
btype = token.value
bline = token.linenum
bexpr = find_until(bline, tokens)
bexpr = build_ast(bexpr, True, ppre = ppre)[0]
bcode = build_ast(find_parens(bline, tokens, "{}"),
ppre = ppre)
ast.append(TurtleBlock(btype, bexpr, bcode, bline))
elif token.type == 'delim':
raise TurtleException(token.linenum, "Syntax Error.")
elif token.type == 'integer':
line = find_until(token.linenum, tokens, ';', fr=True)
if token.value == 'noTurtle':
if line[0].t() == ('(', 'delim') and \
line[1].t() == (')', 'delim'):
ast.append(TurtleInteger(0, token.linenum))
else:
raise TurtleException(token.linenum, "Syntax Error.")
elif token.value == 'turtle':
acc = 1
tline = token.linenum
while line != []:
token = line.pop(0)
if token.t() != ('(', 'delim'):
raise TurtleException(token.linenum,
"Invalid turtle notation.")
else:
token = line.pop(0)
if token.t() == (')', 'delim'):
break
elif token.t() == ('turtle', 'integer'):
acc += 1
else:
raise TurtleException(token.linenum,
"Invalid turtle notation.")
if token.t() != (')', 'delim'):
print(token)
raise TurtleException(token.linenum, "Syntax Error.")
else:
ast.append(TurtleInteger(acc, token.linenum))
else:
# Unlikely to happen so, Imma put a random message.
raise TurtleException(1, ":worry:")
elif token.type == "symbol":
line = find_until(token.linenum, tokens, ';', fr=True)
if line == []:
ast.append(TurtleVariable(token.value, token.linenum))
elif line[0].t() == ('=', 'delim') and len(line) != 1:
vname = token.value
vexpr = build_ast(line[1:], True)[0]
ast.append(TurtleFunctionCall("_set_value",
[TurtleVariable(vname), vexpr],
token.linenum, ppre))
elif line[0].t() == ('(', 'delim') and \
line[-1].t() == (')', 'delim'):
vname = token.value
line = line[1:-1]
mu = []
while line != []:
mu.append(build_ast(find_until(
token.linenum, line, ',', fr=True))[0])
ast.append(TurtleFunctionCall(vname, mu, token.linenum, ppre))
else:
raise TurtleException(token.linenum, "Syntax Error.")
else:
raise TurtleException(1, "???????")
return ast
# TODO: Write tests
# TODO: Add a lil turtle to the interpreter :3
from pprint import pprint
def debug_interpreter(prgm, args):
lines = prgm.split('\n')
env = default_env.copy()
env["TurtleArgv"] = argv2turtle(args)
try:
ast = build_ast(tokenize(prgm))
for line in ast:
line(env)
except TurtleException as e:
print("ERROR!")
print(' |',
'\n | '.join(lines[max(e.linenum-3, 0):e.linenum+2]))
print(f" At line {e.linenum}")
print(f" -> {e.err}")
sys.exit(1)
except RecursionError:
print("Implementing TCO :despair:")
sys.exit(1)
except KeyboardInterrupt:
pass
def main(filename, args):
with open(filename) as f:
debug_interpreter(f.read(), args)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: mtl FILENAME ...")
sys.exit(1)
try:
main(sys.argv[1], sys.argv[2:])
except IOError:
print("Couldn't load the file :pensive:")