Documentation and report.

This commit is contained in:
etc404
2026-04-27 01:44:38 -06:00
parent b04f9945ea
commit c65b29485b
6 changed files with 102 additions and 39 deletions
+53
View File
@@ -1,2 +1,55 @@
# lisp-interpreter
This project is a simple Lisp interpreter written in Python, to the spec of New Mexico Tech's 2026 CSE3024 Project 2 assignment.
## Running the Interpreter
You may run the interpreter with the commands `make`, `make run`, or `python repl.py`. To supply an alternative location for the output file, specify it as a command-line argument. By default, the output file is `output.txt`.
## Completeness
The interpreter's support and completeness is outlined below:
| Lisp Construct | Status |
|------------------------|--------|
| Variable reference | Done |
| Constant literal | Done |
| Quotation | Done |
| Conditional | Done |
| Variable definition | Done |
| Function call | Done |
| Assignment | Done |
| Function definition | Done |
| Arithmetic operators | Done |
| Integer type | Done |
| `car` and `cdr` | Done |
| `cons` support | Done |
| sqrt, pow | Done |
| Comparison expressions | Done |
| Logical operations | Done |
| `mapcar` function | Done |
| Lambda support | Not |
## Implementation Notes
### Data Types
Data types were straightforward to implement, as I opted for a Python-native approach to data representation, with the actual back-end values being simple heterogeneous lists of lists, `int`s, `str`s, `float`s, and `bool`s.
### Mathematical Operators
All the built-in functions follow a similar principle, sifting through a match case of built-ins before checking for user defined functions. The mathematical operations, as well as the logical expressions, all simply check for correct parameters, then return the result of the native Python operation on the arguments.
### Environment Management
Variables and function definitions make use of the `Lisp` class's `env` property, a simple hashmap relating a symbol name to a value, for variables, or for a simple `Function` struct, for functions. When the function is executed, the `Lisp` instance recursively creates a new interpreter instance so as to offer the passed parameters to the expression within the function.
### List Manipulation
List manipulation was simple in this implementation, largely due to Python's loose typing and highly dynamic lists. `car`, `cdr`, and `cons` only needed simple list appending and concatenation.
## References
> [Make-a-Lisp](https://github.com/kanaka/mal/blob/master/process/guide.md) by `kanaka` on GitHub
This resource proved to be less applicable than I had originally hoped, and in practice I only benefitted from the birds-eye understanding given by the REPL data-flow diagrams, and a starting point for the tokenization regex. The rest of the project was much more thorough than was useful to me, and the rest was through trial-and-error.
-21
View File
@@ -1,21 +0,0 @@
class Atom:
car: Atom | int | str | bool | None
cdr: Atom | None
def __init__(self, car: Atom|int|str|bool|None, cdr: Atom|None):
self.car = car
self.cdr = cdr
def setval(self, atom: Atom | int | str | bool | None):
self.car = atom
def append(self, atom: Atom):
self.cdr = atom
def __str__(self):
if type(self.car) is Atom:
return "(" + self.car.__str__() + ("" if self.cdr is None else (" " + self.cdr.__str__())) + ")"
elif self.car is None:
return "()"
else:
return self.car + ("" if self.cdr is None else self.cdr.__str__())
+18 -17
View File
@@ -1,20 +1,11 @@
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)
### LISP.PY
### Autumn Wolf
### 04/26/26
### CSE3024
# This file (and contained class) handles lisp interpreter details, environment management, and expression evaluation.
class Function:
def __init__(self, parameters, expression):
@@ -28,11 +19,15 @@ class Lisp:
else:
self.env = env
# Recursively evaluate an expression
def evaluate(self, expression):
# Handle literals
if type(expression) is not list:
if expression in self.env.keys():
return self.env[expression]
return expression
# Handling of some special operations
operation = expression[0]
if type(operation) is list:
operation = self.evaluate(operation)
@@ -41,7 +36,7 @@ class Lisp:
if operation == "QUOTE":
return expression[1:][0]
if operation != "DEFUN":
arguments = [self.evaluate(x) for x in expression[1:]]
arguments = [self.evaluate(x) for x in expression[1:]] # Don't evaluate for defun or quote!
else:
arguments = expression[1:]
match operation:
@@ -53,6 +48,7 @@ class Lisp:
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")
@@ -68,6 +64,7 @@ class Lisp:
return [arguments[0]]+arguments[1]
else:
return [arguments[0]]+arguments[1]
case "SQRT":
if len(arguments) != 1:
raise ValueError("Expected 1 argument")
@@ -76,6 +73,7 @@ class Lisp:
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")
@@ -111,9 +109,11 @@ class Lisp:
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")
@@ -132,6 +132,7 @@ class Lisp:
else:
raise ValueError("Undefined variable: " + str(arguments[0]))
return arguments[0]
case "MAPCAR":
if len(arguments) != 3:
raise ValueError("Expected 3 arguments")
@@ -140,14 +141,14 @@ class Lisp:
result.append(self.evaluate([arguments[0], arguments[1][i], arguments[2][i]]))
return result
case _:
if operation in self.env.keys():
if operation in self.env.keys(): # Check for user-defined functions
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]
functionrunner.env[func.parameters[i]] = arguments[i] # Populate sub-interpreter environment
return functionrunner.evaluate(self.env[operation].expression)
else:
raise TypeError("This value is not a valid function: " + operation)
+24 -1
View File
@@ -1,7 +1,26 @@
import re
import lisp
from lisp import prettyprint
### READER.PY
### Autumn Wolf
### 04/26/26
### CSE3024
# This file contains the main Reader class, which is responsible for tokenizing, parsing, and handling user input,
# passing it to the lisp interpreter for evaluation, then returning the results to the user.
# Recursively print data for human readability
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 Reader:
tokens: list[str]
@@ -11,6 +30,7 @@ class Reader:
self.interpreter = lisp.Lisp()
self.outputfile = outputfile
# Run through tokens, passing them to the interpreter
def run(self):
if len(self.tokens) == 0:
return
@@ -26,6 +46,7 @@ class Reader:
return
self.flush()
# Capture or discard tokens based on Lisp syntax
def tokenize(self, expression: str):
self.tokens += re.findall(r"""(?:;.*|[\s,]*)([()']|"(?:\\.|[^\\"])*"?|[^\s()'",;]*)""", expression)
@@ -42,6 +63,7 @@ class Reader:
self.tokens = self.tokens[1:]
return token
# Parse an expression into a nested list of values for interpreter consumption
def read_expression(self):
if len(self.tokens) == 0:
return None
@@ -67,6 +89,7 @@ class Reader:
else:
return self.read_atom()
# Smart value conversions for some Lisp-specific syntax, such as T and NIL.
def read_atom(self):
token = self.consume()
if token.upper() == "T":
+7
View File
@@ -1,6 +1,13 @@
import reader
import sys
### REPL.PY
### Autumn Wolf
### 04/26/26
### CSE3024
# This file is the main driver code for the interpreter, acting as an entrypoint for the program and handling
# terminal-side interaction and argument handling.
def main():
print("Welcome to the lisp interpreter! REPL expression results will be stored in an output file, output.txt by default. This can be overridden by passing an argument to the program.\n"
"Enter (quit) to exit the program. Supported functionality includes mathematical operations, conditional statements, variables, user-defined functions, logical operations, and list manipulation.")
BIN
View File
Binary file not shown.