CS152 Assignment: Lisp

Due Wednesday, October 6

LISP

The executable version of the LISP interpreter is ~cs152/bin/lisp. For this assignment, you will not need to change or compile the interpreter. Some of the LISP code from Chapter 2 is in ~cs152/kamin/code.lsp.

As an alternative, you can try the interpeter ~cs152/bin/mllisp, which has the following features:

But be warned that You might also find it useful to try ~cs152/bin/qlisp; this is an interpreter that does not prompt, so it is more convenient for piping things into.

We recommend that you use mllisp, qlisp, or mllispx for development but that you test your work on the C lisp before turning it in. Remember not to include quit in your solutions!

There are two regular problems and one extra-credit problem for this assignment:

  1. Assocation lists. It is possible to use an association list to represent a directed graph. Write a LISP function to topologically sort such a graph. The function (tsort alist) should return a list that gives the keys in association list alist in topological order. The pairs of symbols in alist specify precedence constraints, e.g., '(a b) indicates that a must precede b in the output list. As an example, (tsort '((a b) (a c) (c b) (d b))) can return either '(a d c b) or '(a c d b). For details on topological sorting, see Sedgewick, chapter 32 or Knuth vol 1, section 2.2.3.

    Note that you will do best if you implement several small ``helper'' functions. Your documentation/explanation for this assignment should be sure to explain those functions.

    For extra credit, have tsort print out a cycle if the graph has a cycle.

    My functional solution to this problem, with comments, is 104 lines of code. My imperative solution is 88 lines.

  2. The metacircular evaluator. Do exercise 6 on page 61 of Kamin, which extends the LISP metacircular evaluator by adding begin, set, print, and local variables. We have extracted the lisp code you need to get started (for eval, r-e-p-loop, and related functions) from ~cs152/kamin/code.lsp.

    The crucial part of this assignment is handling mutation, especially setting of local variables. When you did this in C, you could simply change bindings in place. In pure LISP, you can't mutate the environment; instead, you have to create a new environment with new bindings. You must be sure to return that new environment from eval, so that the new environment is used in the future. But eval also has to return a value! Obviously you have to figure out a way to return both, so you can evaluate expressions both for their results and for their side effects. The hard part is figuring out what to do with the environment after you return it. You can't just throw it on the floor; you have to make sure it gets seen in subsequent evaluations.

    Here's a high-stress test case to help you with this problem.

    (define rough () (x) 
       (begin 
         (begin (set x 1))
         x))
    
    A call to (rough) should return 1.

  3. Good functional style. The function
    (define f-imperative (y)
      (begin 
        (set x e)
        (while (p x y) 
           (set x (g x y)))
        (h x y)))
    
    is in a typical imperative style, with assignment and looping. Write an equivalent function f-functional that doesn't use the imperative features begin (sequencing), while (goto), and set (assignment). You may use as many ``helper functions'' as you like.

    Hint #1: If you have trouble getting started, rewrite while to use if and goto. Now, what is like a goto?

    Hint #2: (set x e) creates a binding of e to the name x. What other ways do you know of creating a binding of e to the name x?

    Don't be confused about the purpose of this exercise. The exercise is a ``thought experiment.'' We don't want you to write and run code for some particular choice of g, h, p, e, x, and y. Instead, we want you write a function that works the same as f-imperative given any choice of g, h, p, e, x, and y. So for example, if f-imperative would loop forever on some inputs, your f-functional should also loop forever on exactly the same inputs.

    Once you get your mind twisted in the right way, this exercise should be easy. The point of the exercise is not only to show that you can program without imperative features, but help you develop a technique for eliminating such features. You'll use this technique again later on.

  4. Extra credit---programs as data. Consider the class of well-formed arithmetic computations using the numeral 5. These are expressions formed by taking the integer literal 5, the four arithmetic operators +, -, *, and /, and properly placed parentheses. Such expressions correspond to binary trees in which the internal nodes are operators and every leaf is a 5. Write a LISP program to answer one or more of the following questions: And, without implementing anything, This is a toy example of the kind of symbolic problem for which LISP is famous.

    Hints:

    When we get to higher-order functions in Scheme, we'll see that this problem becomes much easier to solve...

What to submit

Please submit README, solution1, solution2 and solution3, e.g., using
  ~cs152/bin/submit README solution[123]
In the README file you should describe your solutions for problems 1, 2 and 3 (even if you have put comments on your source file). In the files solution1, solution2 and solution3 you should write the solutions for problems 1, 2 and 3 respectively. Please use the names of the functions as specified here. If you wish, you may also submit a file named transcript that contains test cases for your solutions.

Files that do not compile will not be graded; your first two solutions should pass the test

  qlisp < solutionx
without any diagnostics.

If you do the extra credit, please submit solution4 and include an explanation in your README file.