SlytherLisp Project¶
SlytherLisp is a lexically-scoped Scheme-like programming language. You will be implementing an interpreter for this language.
Partners Required
This project is very nontrivial. You are required to complete this project in a group of two or three. Please use Piazza as a resource in finding your group.
If you really want to work alone, you can email me and ask for permission.
You should find a group and get started on this project right away, as Deliverable 1 is due on February 21.
After you have found a group, the first thing you need to do is download the starter code via one of the methods below.
TAR File: | slytherlisp-starter-code.tar.gz |
---|---|
GitLab: | sumner/slytherlisp-starter-code |
Next, before you do anything else, open and read README.rst
. This will
instruct you on how to setup and get going with the project.
Deliverable 1: Basic Data Structures¶
Due Date: | Thursday, February 21 at 23:59 |
---|
For this deliverable, you will be implementing a few simple data structures
under slyther/types.py
. Replace all of the lines that say raise
NotImplementedError('Deliverable 1')
. Note that there’s some stuff for
Deliverable 3 at the bottom of the file: you don’t have to do this (yet!).
Note
It is advised that you finish Deliverable 1 early so that you can get a head start on the next deliverable.
Deliverable 2: Parsing¶
Due Date: | Tuesday, March 19 at 23:59 |
---|
For this deliverable, you will implement the lexer and parser of the
language. You’ll be working under slyther/parser.py
.
Deliverable 3: Evaluator¶
Due Date: | Tuesday, April 2 at 23:59 PM |
---|
For this deliverable, you’ll be implementing the evaluator, builtins, REPL, and functions. You’ll be working under four different files:
slyther/evaluator.py
slyther/types.py
(at the bottom)slyther/builtins.py
slyther/repl.py
This one is a big deliverable, so please plan ahead and get started early.
By the end of this deliverable, you should have a working REPL, which you can
use to compute the results of basic mathematical operations, such as
(+ 1 (* 2 3))
. Please be sure to test your REPL, as there are no unit tests
for this. You will receive a low grade on this deliverable if you do not have a
working REPL.
You are not required to have tail-call optimization working for this deliverable.
Note
It is advised that you finish Deliverable 3 early so that you can get a head start on the next deliverable.
Deliverable 4: Language Structures¶
Due Date: | Tuesday, April 9 at 23:59 |
---|
Deliverable 4 is an extension of Deliverable 3, you will complete the remainder
of the builtin operations for the SlytherLisp language, as defined in the
docstrings of slyther/builtins.py
.
Deliverable 5: Tail-Call Optimization¶
Due Date: | Tuesday, April 23 at 23:59 |
---|
For this deliverable, you’ll be extending SlytherLisp by extending your work from Deliverable 3 to use tail-call optimization.
Consider the following (simple) example:
(define (print-n-times f args n)
(if (<= n 0)
NIL
(let ()
(print (eval (cons f args)))
(print-n-times f args (- n 1)))))
Since the call to print-n-times
is the last call of the function (what
returns from print-n-times
is returned by the function), we can reuse our
current stack frame for that function call, rather than allocating a new stack
frame and returning the result of the computation.
My advice (for a starting point) is to move your lisp_eval
function body
into a loop, and if, at the bottom of the loop, it can reassign expr
and
stg
and repeat rather than calling lisp_eval
recursively, do so.
You’ll probably need to make modifications to other parts of the interpreter as well.
Test your optimization works using the fib-iter
and carmichael
examples. At this point, all of the provided code examples should run. You
will receive a low grade on this deliverable if all of the code examples do not
produce a correct result. Be sure to check your results for correctness. For
example, if the primes example is producing numbers that are not prime, then it
is not correct.
You are free to implement tail call optimization however you wish, but you implementation must be properly tail-call optimized. We will use the same definition as the Scheme specification to determine if your implementation is properly tail-call optimized:
A Scheme implementation is properly tail-recursive if it supports an unbounded number of active tail calls. A call is active if the called procedure may still return.
Deliverable 6: Extending the Language¶
Due Date: | Thursday, May 2 at 23:59 |
---|
Come up with a significant extension to the language. Suggestions include:
- A
match
macro that behaves like Racket’s pattern matching - Adding a new syntax to the language (such as quasiquotations)
- Optimizing the abstract syntax tree
- Adding a library and/or namespace mechanism
- Adding a way to load and use Python libraries
- Allowing for user-defined macros. Since macros are first class, you could even add anonymous macros to the language.
- Making the
cons
form allow for a tail recursive CDR argument by using the tail call modulo cons technique. - Adding functions with default parameters and keyword arguments
- A
use
macro, such as proposed in one of the LGA-06 videos - Continuation
- More and more and more inspiration
- Any other significant extension of your liking!
Implement your extension, and document how it works in the README.rst
file. Include at least one example program that makes use of your
extension.
Deliverable 7: Extra Credit¶
Due Date: | Thursday, May 9 at 23:59 |
---|
Note
This deliverable is optional. If you do not want the extra credit, you may simply ignore it.
Extend SlytherLisp in as many ways as you choose, and document your extensions for extra credit. Here are a few ideas which you could do for extra credit:
- Make the REPL nicer by using Readline or Prompt Toolkit.
- Do a performance comparison of SlytherLisp on PyPy versus on CPython: and maybe even compare the same algorithms implemented on other languages.
- Rewrite SlytherLisp in a totally different programming language.
- Add the ability to import Python modules into SlytherLisp.
- Add a way to partially apply (curry) functions in Racket.
You are not required to maintain passing unit tests for this extra credit deliverable. Have fun!
This deliverable is worth up to 50 points extra credit, but the allocation
is determined based on the significance and effort of your work, so simple
extensions will only receive a little extra credit. Please include an estimate
for the number of hours you spent on this deliverable in your README.rst
.
Note that I am not able to offer extensions on this deliverable (nor can slip days be spent) as I will need time to grade before grades are due.
Grading Rubric¶
This project is worth 250 points of your overall grade in the course. The grade is divided up as follows:
Points | Description |
---|---|
37 | Deliverable 1 |
37 | Deliverable 2 |
37 | Deliverable 3 |
37 | Deliverable 4 |
50 | Deliverable 5 |
50 | Deliverable 6 |
50 XC | Deliverable 7: Up to 50 points of extra credit, which is 5% of your overall course grade. |
An astute reader may notice that the amount of points allocated to Deliverables 1-6 does not add up to 250. That is because everyone gets 2 free points.
Grader Script¶
You can download the script the grader will be using here (SHA256 Checksum:
e9c1dcb16de8282005d6fc0d9a7b9300d2197860f4b42d3d79b22fb271cbe501
). I have
only tested this script on Arch Linux (the distro in the ALAMODE machines).
Dependencies:
bc
python-virtualenv
To use the script make it executable (chmod +x
slytherlisp-grader-script.sh
), then just type:
./slytherlisp-grader-script.sh <your_submission_tar> <deliverable_number>
Replacing <your_submission_tar>
with the filename of the
submission.tar.bz2
file and <deliverable_number>
with the deliverable
you would like to grade.
For example:
./slytherlisp-grader-script.sh submission.tar.bz2 1
Will run all of the D1 tests on the code in submission.tar.bz2
.
Note that this script does not perform all of the tests (the grader will perform additional manual tests), but it will give you an idea of what your grade will be.