From 4ed01f84394c696c711b072abef53a6b847b3f15 Mon Sep 17 00:00:00 2001 From: etc404 Date: Mon, 27 Apr 2026 00:43:43 -0600 Subject: [PATCH] Approximately fully functional! --- .gitignore | 1 + Makefile | 2 + lisp.py | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++-- reader.py | 36 ++++++++++--- repl.py | 13 ++++- 5 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index 36b13f1..5f6ed46 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,4 @@ cython_debug/ # PyPI configuration file .pypirc +output.txt \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5190423 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +run: + python repl.py diff --git a/lisp.py b/lisp.py index 893c721..cbdc585 100644 --- a/lisp.py +++ b/lisp.py @@ -1,6 +1,148 @@ -import atom -import reader +import math +import string +from functools import reduce + +def prettyprint(expression) -> str: + if type(expression) is list: + retvalue = "(" + for x in expression: + retvalue += prettyprint(x) + " " + return retvalue.rstrip() + ")" + elif type(expression) is bool: + return "T" if expression else "NIL" + elif str(): + return expression.upper() + else: + return str(expression) + + +class Function: + def __init__(self, parameters, expression): + self.parameters = parameters + self.expression = expression class Lisp: + def __init__(self, env=None): + if env is None: + self.env = {} + else: + self.env = env + def evaluate(self, expression): - print(expression) \ No newline at end of file + if type(expression) is not list: + if expression in self.env.keys(): + return self.env[expression] + return expression + operation = expression[0] + if type(operation) is list: + operation = self.evaluate(operation) + if type(operation) is list: + raise TypeError("This value is not a valid operation: " + str(operation)) + if operation == "QUOTE": + return expression[1:][0] + if operation != "DEFUN": + arguments = [self.evaluate(x) for x in expression[1:]] + else: + arguments = expression[1:] + match operation: + case "+": + return sum(arguments) + case "-": + return arguments[0] - sum(arguments[1:]) + case "*": + return reduce(lambda x,y: x*y, arguments) + case "/": + return reduce(lambda x,y: x/y, arguments) + case "CAR": + if len(arguments) != 1: + raise ValueError("Expected 1 argument") + return arguments[0][0] + case "CDR": + if len(arguments) != 1: + raise ValueError("Expected 1 argument") + return arguments[0][1:] + case "CONS": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + if type(arguments[0]) == list: + return [arguments[0]]+arguments[1] + else: + return [arguments[0]]+arguments[1] + case "SQRT": + if len(arguments) != 1: + raise ValueError("Expected 1 argument") + return math.sqrt(arguments[0]) + case "POW": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + return pow(arguments[0], arguments[1]) + case "IF": + if len(arguments) != 3: + raise ValueError("Expected 3 arguments") + if arguments[0]: + return arguments[1] + else: + return arguments[2] + case ">": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + return arguments[0] > arguments[1] + case "<": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + return arguments[0] < arguments[1] + case "=": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + return arguments[0] == arguments[1] + case "!=": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + return arguments[0] != arguments[1] + case "AND": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + return arguments[0] and arguments[1] + case "OR": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + return arguments[0] or arguments[1] + case "NOT": + if len(arguments) != 1: + raise ValueError("Expected 1 argument") + return not arguments[0] + case "QUIT": + print(">> bye") + raise SystemExit + case "DEFINE": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + self.env[arguments[0]] = arguments[1] + return arguments[0] + case "DEFUN": + if len(arguments) != 3: + raise ValueError("Expected 3 arguments") + self.env[arguments[0]] = Function(arguments[1], arguments[2]) + return arguments[0] + case "SET!": + if len(arguments) != 2: + raise ValueError("Expected 2 arguments") + if arguments[0] in self.env: + self.env[arguments[0]] = arguments[1] + else: + raise ValueError("Undefined variable: " + str(arguments[0])) + return arguments[0] + case _: + if operation in self.env.keys(): + if type(self.env[operation]) == Function: + func = self.env[operation] + functionrunner = Lisp(self.env) + if len(arguments) != len(func.parameters): + raise ValueError("Expected " + str(len(func.parameters)) + " arguments") + for i in range(0, len(arguments)): + functionrunner.env[func.parameters[i]] = arguments[i] + return functionrunner.evaluate(self.env[operation].expression) + else: + raise TypeError("This value is not a valid function: " + operation) + else: + raise ValueError("Undefined operation: " + operation) \ No newline at end of file diff --git a/reader.py b/reader.py index 4a6f35c..9c7da0f 100644 --- a/reader.py +++ b/reader.py @@ -1,24 +1,42 @@ import re import lisp +from lisp import prettyprint + class Reader: tokens: list[str] - def __init__(self): + def __init__(self, outputfile: str): self.tokens = [] self.interpreter = lisp.Lisp() + self.outputfile = outputfile def run(self): - while self.peek() != "": - self.interpreter.evaluate(self.read_expression()) - self.consume() + if len(self.tokens) == 0: + return + try: + with open(self.outputfile, "a") as file: + while self.peek() != "": + result = prettyprint(self.interpreter.evaluate(self.read_expression())) + print(">> " + result) + file.write(result+"\n") + except Exception as e: + print("ERROR: " + str(e)) + self.tokens = [] + return + self.flush() def tokenize(self, expression: str): - self.tokens += re.findall(r"""[\s,]*[;.*]*([()']|"(?:\\.|[^\\"])*"?|[^\s()'",;]*)""", expression) + self.tokens += re.findall(r"""(?:;.*|[\s,]*)([()']|"(?:\\.|[^\\"])*"?|[^\s()'",;]*)""", expression) def peek(self): return self.tokens[0] + def flush(self): + while len(self.tokens) > 0: + if self.peek() == "": + self.consume() + def consume(self): token = self.tokens[0] self.tokens = self.tokens[1:] @@ -34,8 +52,8 @@ class Reader: while token != ")": atomlist.append(self.read_expression()) while self.peek() == "": - self.consume() - expression: str = input("... ") + self.flush() + expression: str = input("... ").upper() self.tokenize(expression) token = self.peek() self.consume() @@ -43,7 +61,9 @@ class Reader: elif self.peek() == "'": # Expand the quote macro! self.consume() - return ["quote", self.read_expression()] + return ["QUOTE", self.read_expression()] + elif self.peek() == ")": + raise SyntaxError("Unexpected ')'") else: return self.read_atom() diff --git a/repl.py b/repl.py index 92599b8..fe6948e 100644 --- a/repl.py +++ b/repl.py @@ -1,11 +1,20 @@ import reader +import sys def main(): print("Welcome message.") - interpreter = reader.Reader() + outputfile = "output.txt" if len(sys.argv) == 1 else sys.argv[1] + interpreter = reader.Reader(outputfile) # REPL Loop while True: - expression: str = input(">>> ") + try: + expression: str = input("> ").upper() + except KeyboardInterrupt: + print("\n>> bye") + return + except EOFError: + print("\n>> bye") + return interpreter.tokenize(expression) interpreter.run()