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
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:")
|
|
|