CS 360
Winter 2015
Programming Language Concepts
Tuesdays, Thursdays 14:00-15:20
University Crossings 151

Geoffrey Mainland
University Crossings 106
Teaching Assistant:
Brian Lee
Warning! This material is for an old version of the course.

CS 360 Homework 3: Working with the Metacircular Interpreter

In this assignment, you will modify the metacircular interpreter we saw in class. Successfully completing this assignment requires reading and understanding a medium-sized program written by someone else. If you do not have a good understanding of how the interpreter works, please review the material covered in lecture and in the book. Diving straight in to the homework without this understanding is going to make your task much more difficult.

Your solution must be based on the metacircular interpreter from SICP, available here: ch4-mceval.scm.

This assignment is worth 125 points. There are 136 possible points.

Please submit all files for this assignment by checking them in to your cs360 git repository at ~/cs360/git/hw3. Be sure to commit your work to the repository.

A complete submission will include two files:

  1. problem1.txt
  2. mceval.scm

Your code must run on tux under mit-scheme.

Code that is not valid Scheme will not be graded and will receive a score of zero.

Note: Problem 1 is due in week 1, separately from problems 2–7. However, it is recommended that you complete at least problems 1–3 the first week, and preferably problems 1–4.

Working with the interpreter

Start by saving a copy of the SICP metacircular interpreter as mceval.scm in your hw3 folder. Commit your work periodically as you complete the problems. I recommend commiting the mceval.scm from SICP as soon as you download it so that you can use git diff to easily see the changes you have made.

Begin by uncommenting the definition of the-global-environment at the end of the file. You can start mit-scheme with your metacircular interpreter loaded by executing the following command at the prompt (the prompt below is assumed to be $):

$ mit-scheme --load mceval.scm

There are then two ways you can test the evaluator:

  1. Execute (driver-loop) from the mit-scheme REPL. The driver loop will read in Scheme expressions and pass them to your interpreter for evaluation.

  2. Call the eval function with an expression and a global environment from the mit-scheme REPL, like this:

 (eval '(+ 2 3) the-global-environment)

I used the second approach. I also made judicious use of display and newline to debug my implementation.

Hints for solving the problems

There are three ways to extend the interpreter:

  1. Add a primitive.
  2. Add a definition to the global environment.
  3. Add a special form.

You should only add a special form when it is absolutely necessary. Most of the time, the standard Scheme evaluation rules are exactly what you want. Solving a problem by adding a definition rather than a new special form is also much easier and avoids cluttering up your eval function.

Use display and newline to print out intermediate expressions! This is extremely helpful when debugging.

Problem 1: Code Reading Questions (25 points total)

Submit the solutions to this problem only in a file named problem1.txt in the hw3 subdirectory of your git repository.

Problem 1.1: Environment representation (5 points)

In the previous homework, you implemented environments as a list of frames, where each frame was a list of bindings, and a binding was a list of two elements, a symbol and its value.

The metacircular interpreter uses a different representation. What is it? Please be specific—an English description such as that given above for the Homework 2 representation will suffice. However, your answer will be stronger if you also provide examples.

Problem 1.2: Defining the primitives (5 points)

What top-level define contains the list of primitives supported by the metacircular interpreter? Please name the variable.

Problem 1.3: Understanding primitives (5 points)

This is Exercise 4.14 from SICP.

Eva Lu Ator and Louis Reasoner are each experimenting with the metacircular evaluator. Eva types in the definition of map, and runs some test programs that use it. They work fine. Louis, in contrast, has installed the system version of map as a primitive for the metacircular evaluator. When he tries it, things go terribly wrong. Explain why Louis’s map fails even though Eva’s works.

Problem 1.4: Understanding eval (5 points)

This is Exercise 4.2a from SICP.

Louis Reasoner plans to reorder the cond clauses in eval so that the clause for procedure applications appears before the clause for assignments. He argues that this will make the interpreter more efficient: Since programs usually contain more applications than assignments, definitions, and so on, his modified eval will usually check fewer clauses than the original eval before identifying the type of an expression.

What is wrong with Louis’s plan? (Hint: What will Louis’s evaluator do with the expression (define x 3)?)

Problem 1.5: Extending the environment (5 points)

The function setup-environment is used to create the initial global environment used by the metacircular interpreter. For later problems, it will be convenient to add your own definitions to the initial global environment. The most convenient way to do this is to call eval-definition with the appropriate arguments from within the function setup-environment. If you were to add a definition in this manner, what arguments would you pass to eval-definition to add the following top-level define to the initial global environment? You may give your answer in the form of a Scheme expression.

(define (not x) (if x false true))

Problem 2: Adding Primitives (10 points total)

Add the following primitives: +, *, -, /, <, <=, =, >=, >, and error. 1 point each.

The error primitive should take no arguments and abort the interpreter with the message “Metacircular Interpreter Aborted” (without the quotes).

Problem 3: Implementing and and or (20 points total)

Add support for and and or to your interpreter (10 points each). Be sure your implementation adheres to the Scheme language standard (see here) in terms of how the arguments to and and or are evaluated and in terms of what value is returned.

You will probably want to use the last-exp?, first-exp, and rest-exps helper functions.

Remember that the metacircular interpreter cannot interpret #t and #f; use true and false instead.

Problem 4: Implementing let (20 points total)

This is Exercise 4.6 from SICP.

Let expressions are derived expressions, because

(let ((<var1> <exp1>) ... (<varn> <expn>))

is equivalent to

((lambda (<var1> ... <varn>)

Implement a syntactic transformation let->combination that reduces evaluating let expressions to evaluating combinations of the type shown above, and add the appropriate clause to eval to handle let expressions.

Hint: Use display to print out the result of let->combination to make sure you get it right! I did not get it right the first time…

Problem 5: Implementing force and delay (40 points total)

Add support for force and delay to your interpreter, where delay‘ed expressions are only evaluated once when force‘d.

It is recommended that you implement the call-by-name version first, since this is much easier. You will receive 20 points for a fully correct call-by-name implementation.

For full credit, you must support call-by-need evaluation, which only evaluates delay‘ed expressions once.

There are two ways to solve this problem, both of which we covered in class.

Strategy 1: Use memoization to implement call-by-need force and delay

You can implement delay in such a way that it will memoize the function that wraps the delayed expression.

Strategy 2: Use thunks to implement call-by-need force and delay

This approach adopts some of the techniques from the lazy evaluator we saw in class for use in your evaluator. You may use code from the lazy evaluator, which you can find here. Note that you cannot simply copy the lazy interpreter to implement the call-by-need version of delay and force—that would make the entire language call-by-need! Your interpreter must implement applicative order evaluation.

You will want to make force a special form, and you will want to introduce the thunk and evaluated-thunk tags and some of the associated machinery that are used in the lazy evaluator. Since thunks, whether or not they are evaluated, can only legally appear as arguments to force, you should only have to modify a small part of the interpreter, in contrast to the more significant surgery needed for general call-by-need.

Be sure you use set-car! and set-cdr! as needed to turn a thunk into an evaluated-thunk once it has been evaluated by force.

Problem 6: Implementing streams (20 points total)

Add support for the following stream functions to your interpreter:

  1. cons-stream (10 points)
  2. the-empty-stream (2 points)
  3. stream-null? (2 points)
  4. stream-car (2 points)
  5. stream-cdr (4 points)

You should be able to complete this problem with either the call-by-name or call-by-need implementation of force and delay. That is, you can receive full credit for this problem even if you did not receive full credit for Problem 5.

Hint: the hints given for Problem 5 apply to this problem!

Problem 7: Homework Statistics (1 point total)

How long did it take you to complete problems 2–6? Please tell us in a comment in mceval.scm. You must tell us how long each problem took you to receive the point.