Created
March 7, 2018 22:40
-
-
Save PurpleMyst/ade7635edb4d7ec84c32cdc666cb96e0 to your computer and use it in GitHub Desktop.
An experiment in shitty LISP interpreters.
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
#!/usr/bin/env python3 | |
from operator import sub, mul, floordiv | |
from functools import reduce | |
SHORT_LIST_THRESHOLD = 3 | |
def add_repr(cls): | |
import inspect | |
argspec = inspect.getargspec(cls.__init__) | |
def _repr(self): | |
args = ("%s=%r" % (name, getattr(self, name)) | |
for name in argspec.args[1:]) | |
return "%s(%s)" % (self.__class__.__name__, | |
", ".join(args)) | |
cls.__repr__ = _repr | |
return cls | |
@add_repr | |
class LispList: | |
def __init__(self, contents): | |
self.contents = list(contents) | |
@add_repr | |
class LispString: | |
def __init__(self, value): | |
self.value = value | |
@add_repr | |
class LispSymbol: | |
def __init__(self, value): | |
self.value = value | |
@add_repr | |
class LispNumber: | |
def __init__(self, value): | |
self.value = value | |
def tokenize(expr): | |
return expr.translate({ord('('): '( ', ord(')'): ' )'}).split() | |
def parse(tokens, depth=0): | |
# XXX: Do we need `depth`? | |
tokens = iter(tokens) | |
result = [] | |
for token in tokens: | |
if token == "(": | |
result.append(parse(tokens, depth + 1)) | |
elif token == "'(": | |
parsed = parse(tokens, depth + 1) | |
result.append(LispList([LispSymbol("quote"), parsed])) | |
elif token == ")": | |
break | |
elif token.isdigit(): | |
# TODO: Add floats. | |
result.append(LispNumber(int(token))) | |
else: | |
result.append(LispSymbol(token)) | |
if len(result) == 1: | |
return result[0] | |
else: | |
return LispList(result) | |
def pretty_print(lisp_obj, depth=0): | |
if isinstance(lisp_obj, LispList): | |
contents = lisp_obj.contents | |
if len(contents) <= SHORT_LIST_THRESHOLD: | |
return "(%s)" % " ".join(pretty_print(item, depth + 1) | |
for item in contents) | |
indent = " " * (depth + 1) | |
pretty_contents = (indent + pretty_print(item, depth + 1) | |
for item in contents) | |
return "(\n%s\n%s)" % ("\n".join(pretty_contents), " " * depth) | |
elif isinstance(lisp_obj, LispSymbol): | |
return lisp_obj.value | |
elif isinstance(lisp_obj, LispString): | |
return lisp_obj.value | |
elif isinstance(lisp_obj, LispNumber): | |
return str(lisp_obj.value) | |
else: | |
return repr(lisp_obj) | |
class LispContext: | |
def __init__(self, parent=None): | |
self.vars = {} | |
self.parent = parent | |
def evaluate(self, lisp_obj): | |
if isinstance(lisp_obj, LispList): | |
return self._eval_list(lisp_obj) | |
elif isinstance(lisp_obj, LispSymbol): | |
return self.vars[lisp_obj.value] | |
elif isinstance(lisp_obj, LispNumber): | |
return lisp_obj.value | |
else: | |
raise TypeError(type(lisp_obj)) | |
def _eval_symbol(self, symbol): | |
if not isinstance(symbol, LispSymbol): | |
raise TypeError(type(symbol)) | |
if symbol.value in self.vars: | |
return self.vars[symbol.value] | |
elif self.parent is None: | |
raise NameError("name %r is not defined" % symbol.value) | |
else: | |
return self.parent._eval_symbol(symbol) | |
def _eval_list(self, lisp_list): | |
if not isinstance(lisp_list, LispList): | |
raise TypeError(type(lisp_list)) | |
symbol, *args = lisp_list.contents | |
return self._eval_symbol(symbol)(self, *args) | |
@classmethod | |
def master_context(cls): | |
context = cls() | |
def define_builtin(name): | |
def inner(func): | |
context.vars[name] = func | |
return inner | |
@define_builtin("print") | |
def _print(context, *args): | |
args = map(context.evaluate, args) | |
print(pretty_print(LispList(args))) | |
@define_builtin("+") | |
def _add(context, *args): | |
return LispNumber(sum(map(context.evaluate, args))) | |
@define_builtin("-") | |
def _sub(context, *args): | |
return LispNumber(reduce(sub, map(context.evaluate, args))) | |
@define_builtin("*") | |
def _mul(context, *args): | |
return LispNumber(reduce(mul, map(context.evaluate, args))) | |
@define_builtin("/") | |
def _div(context, *args): | |
return LispNumber(reduce(floordiv, map(context.evaluate, args))) | |
@define_builtin("quote") | |
def _quote(context, arg): | |
if not isinstance(arg, LispList): | |
raise TypeError(type(arg)) | |
return arg | |
@define_builtin("eval") | |
def _eval(context, arg): | |
if not isinstance(arg, LispList): | |
raise TypeError(type(arg)) | |
return context.evaluate(arg) | |
@define_builtin("list") | |
def _list(context, *args): | |
return LispList(map(context.evaluate, args)) | |
@define_builtin("head") | |
def _head(context, arg): | |
return arg.contents[0] | |
@define_builtin("tail") | |
def _tail(context, arg): | |
return arg.contents[1:] | |
@define_builtin("cons") | |
def _cons(context, arg, lisp_list): | |
return LispList([arg] + lisp_list.contents) | |
@define_builtin("if") | |
def _if(context, value, truthy, falsey): | |
if context.evaluate(value): | |
return context.evaluate(truthy) | |
else: | |
return context.evaluate(falsey) | |
@define_builtin("words") | |
def _words(context, *args): | |
words = [] | |
for arg in args: | |
if isinstance(arg, LispSymbol): | |
words.append(arg.value) | |
else: | |
raise TypeError(type(arg)) | |
return LispString(" ".join(words)) | |
# TODO: Add some builtins. | |
return context | |
def main(): | |
context = LispContext(parent=LispContext.master_context()) | |
while True: | |
try: | |
expr = input("> ") | |
except (EOFError, KeyboardInterrupt): | |
break | |
tokens = tokenize(expr) | |
tree = parse(tokens) | |
try: | |
value = context.evaluate(tree) | |
except Exception as e: | |
message = getattr(e, "message", " ".join(map(str, e.args))) | |
print(e.__class__.__name__, message, sep=": ") | |
else: | |
if value is not None: | |
print(pretty_print(value)) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment