Skip to content

Instantly share code, notes, and snippets.

@PurpleMyst
Created March 7, 2018 22:40
Show Gist options
  • Save PurpleMyst/ade7635edb4d7ec84c32cdc666cb96e0 to your computer and use it in GitHub Desktop.
Save PurpleMyst/ade7635edb4d7ec84c32cdc666cb96e0 to your computer and use it in GitHub Desktop.
An experiment in shitty LISP interpreters.
#!/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