CS 251: Interpreter, Last portion

This assignment is the final phase of your interpreter. Under college policy, no extensions are allowed past the end of the last final exam.

This last stage consists of a list of additions to make to your interpreter. This assignment is perhaps longer than many of the others, for two reasons; all portions within it are independent, and it gives you more flexibility at this busy point in the term to work on your project. That said, don't underestimate the amount of work here; this assignment has more individual pieces to do than any of the other interpreter project parts.

Required additions to your interpreter are as follows:

  1. Some missing special forms: let*, letrec, cond, and, or, set!, and begin.

    Here are some more detailed specifications. As in some previous assignments, I have included links for more detail to "The Scheme Programming Language" by R. Kent Dybvig. Though we're technically working with the Racket dialect of Scheme, all of the features below that you are implementing are the same between both languages (at least, they are for our purposes), and I find Dybvig's writing to be clearer than the Racket language reference.

  2. More primitive functions. Feel free to add others to get your old Scheme code working. The following functions are required:

Here are some optional additions that I encourage, in addition to other optional ideas I have suggested throughout the project. If you have implemented anything optional, explain what you have done in a readme.txt file.

  1. Any missing primitive functions or special forms that you'd like to add. (Suggestions: >=, <=, list, eq?, equal?)
  2. Garbage collection. (A simple mark-and-sweep collector is relatively easy to implement and should improve your memory usage.)
  3. A simple interface. The classic core of an interpreter is the read-eval-print loop, a.k.a. REPL. You can add this functionality to your code so that you can more easily use your interpreter interactively. For example:

    $ ./interpreter
    > (define factorial
    .   (lambda (n)
    .     (if (= n 0)
    .         1
    .         (* n (factorial (- n 1))))))
    > (factorial 5)
    120
    >
    

    One issue you will run into with this is you get unnecessary printouts of prompts when you are using a file for input instead of typing the code in manually. You can figure out whether the interpreter is being called interactively using the isatty and fileno functions (man isatty). You should only print the prompts if stdin is not interactive.

    Note that you can end an interactive session by typing Ctrl-D (sending an "end-of-file" character), so there is no need for a "quit" command.

  4. Use of the readline library to provide things like history and editing for your REPL.
  5. (load file.rkt). This is a primitive function that reads in a file and executes the code in the file as if it were typed in.

Memory errors

Your code should have no memory leaks or memory errors when run on any input (correct or incorrect) using valgrind. We will be checking this during grading.

What to submit

Submit via git as usual, and tag your submission as complete. If you need to resubmit, create another tag with a dot extension, such as complete.1, complete.2, and so on. Make sure that you both push your tags and your code itself, as in:

      git tag complete
      git commit -am "We are done!"
      git push
      git push --tags
    

Add at least four more tests to your test files that test the functionality of the primitives you have added. Indicate in a readme.txt file which tests are designed to be testing this assignment in particular.

Each pair of files should be named interpreter-test.input.XX and interpreter-test.output.XX, where XX is a test number. It should be the case that if I run the following command then I get no output:

./interpreter < interpreter-test.input.XX | diff interpreter-test.output.XX -

When we test your code for grading purposes, we will use our own set of test files which we will run using exactly the same command as described above. Test carefully.

Your code should have no memory leaks or memory errors when run on any input (correct or incorrect) using valgrind. We will be checking this as well during grading.

Remember, you can test your code in valgrind this way:

./valgrind.sh < interpreter-test.input.XX

Have fun interpreting!


This assignment was originally created by David Liben-Nowell and has since been updated by Dave Musicant and Laura Effinger-Dean.