Programming Language Concepts
CS 360-001 Tuesday/Thursday 15:30-16:50 (Rush 209)
CS 360-002 Tuesday/Thursday 14:00-15:20 (Rush 209)
CS 360-003 Tuesday 18:30-21:20 (UCross 151)
Office: University Crossings 106
Office hours: Mondays 4pm–7pm; Thursdays 5pm–6pm.
CLC office hours: Tuesday 12pm–2pm; Thursday 6pm–8pm Allen Yang
CLC office hours: Wednesday 6pm–8pm
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.
You should complete all problems by modifying the
lazy-mceval.rkt files in your repository. Please do not create additional copies of
lazy-mceval.rkt for the different parts of the assignment.
Only Problems 7 and 8 requires modifications to the lazy evaluator,
lazy-mceval.rkt. For Problems 2–6, you should modify the applicative-order interpreter in
For Part II only, you may work with other students in class. You must record the members of your group in your
This assignment is worth 100 points. There are 115 possible points.
Note: No late days may be used for Part I. We will cover the answers to Part I in class on Tuesday 1/30.
There are then two ways you can test your evaluator evaluator:
Type make and run the resulting binary, named
mceval program will repeatedly read in a Scheme expression and pass it to
your interpreter for evaluation.
From DrRacket, call the
top-mc-eval function with an expression, like this:
(top-mc-eval '(+ 2 3))
I used the second approach. I also made judicious use of
to debug my implementation.
There are three ways to extend the interpreter:
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
newline to print out intermediate expressions! This is
extremely helpful when debugging.
Due Monday, January 29, 11:59:59PM EST. No late days may be used for Part I.
Submit the solutions to this problem in
What representation does the metacircular evaluator use for environments?
Please be specific. An English description will suffice; however, your answer will be stronger if you also provide examples.
define contains the list of primitives supported by the
metacircular interpreter? Please name the variable.
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
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
This is Exercise 4.2a from SICP.
Louis Reasoner plans to reorder the
cond clauses in
mc-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
mc-eval will usually check fewer clauses than the original
mc-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)?)
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
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))
Due Monday, January 29, 11:59:59PM EST. You may work in a group only on this part of the homework.
Add the following primitives:
mceval.rkt. 1 point each.
error primitive should take no arguments and abort the interpreter with
the message “Metacircular Interpreter Aborted” (without the quotes).
Hint: You should use Racket’s
error function to raise an exception.
or(10 points total)
Add support for
or to your interpreter (5 points each) by modifying
mceval.rkt. Be sure your implementation adheres to the Scheme language standard (see here for the relevant standard). Pay careful attention to how the arguments to
or are evaluated and the value of the
You will probably want to use the
rest-exps helper functions.
Remember that the metacircular interpreter cannot interpret
Due Monday, January 29, 11:59:59PM EST.
let(10 points total)
let expressions. Your implementation must create a new environment and evaluate the body of the let in this new environment. You will receive no credit for a solution that does not create a new environment, i.e., you may not implement
let using the rewrite-as-a-lambda-application technique described in lecture.
Pay careful attention to the following “features” of
letis a sequence of expressions.
If you are unclear on the semantics of
let, you can read the language standard.
Section 1.3.2 of SICP also contains an explanation of
delay(20 points total)
Add support for
delay to your interpreter, where
expressions are only evaluated once when
For full credit, you must support call-by-need evaluation, which only evaluates
delay‘ed expressions once.
If your implementation is call-by-name but otherwise correct, you will receive 10 points. You do not need to submit both a call-by-name and a call-by-need implementation for full credit; just the call-by-need version will do.
It is highly recommended that you implement the call-by-name version first. If you get stuck on the call-by-need version without having implemented the call-by-name version, stop what you’re doing and get the call-by-name version working first.
We recommend you use memoization to implement call-by-need
The implementations of both the call-by-name and call-by-need versions of force
and delay were discussed in lecture. The easiest path to success will follow
that implementation. If you have successfully implemented
delay using the technique from lecture, then your evaluator should evaluate
((delay 5)) to
Your solution must demonstrate that you understand the underlying mechanisms for
implementing lazy evaluation. Therefore, you may not use Racket’s
delay or equivalent syntax macros in your solution to Problem 5. The
homework template is set up to prevent you from accidentally using Racket’s
Due Monday, February 5, 11:59:59PM EST.
Add support for the following stream functions to your interpreter:
You should be able to complete this problem with either the call-by-name or
call-by-need implementation of
delay. That is, you can receive
full credit for this problem even if you did not receive full credit for Problem
Your streams should be strict in the head of the stream and lazy in the tail. Note that Racket streams are lazy in both the head and the tail.
Your implementation must use your
delay from Problem 5. You may
not use Racket’s
delay or equivalent syntax macros in your
solution to Problem 6.
For this problem, you will modify the lazy evaluator in
Add your primitives (Problem 1) and
or (Problem 2) to the lazy evaluator. You should almost be able to copy the code over as-is. Be careful with
For this problem, you will modify the lazy evaluator in
The original metacircular evaluator in
mceval.rkt implements applicative-order evaluation—arguments are evaluated before a function is called. The lazy evaluator in
lazy-mceval.rkt implements normal-order evaluation—arguments are evaluated when they are needed. For this problem, you will modify the lazy evaluator to implement a hybrid approach: the definition of each function will specify which arguments are delayed and which are evaluated immediately. To illustrate, consider the following function we saw in lecture:
(define (try a b) (if (= a 0) 1 b))
(try 0 (/ 1 0)) will produce an error in the applicative-order interpreter. The problem is that the applicative-order interpreter evaluates all function arguments, even when they are not needed. Imagine we could write this instead (this is not standard Scheme!)
(define (try a (delayed b)) (if (= a 0) 1 b))
(delayed b) is an annotation to change the way compound procedures are applied as follows:
delayed, delay its evaluation.
In the case of
try, this would mean that
a is evaluated and
b is delayed.
Modifying the interpreter to support this extension takes very little code, but it requires that you understand the metacircular interpreter and lazy evaluation.
How long did spend on problems 1–8? Please tell us in your
README.md. You may enter time using any format described here.