When writing an interpreter, it is easiest if the number of syntactic forms is small. Programmers, on the other hand, often like to have a variety of syntactic forms. For example, in Java, the while, do, and for loops all accomplish basically the same thing, but each one makes for the clearest code in some particular situation.
Rather than clutter up the core of the interpreter with redundant syntactic forms, we can run a preprocessor which converts some of them into others. The output of the preprocessor can then be fed to the interpreter. In this assignment, you will write a program which eliminates let expressions in (a subset of) Scheme.
Our grammar is the one at the top of p. 37, with one additional production:
<expression> ::= (let ({(<identifier>
<expression>)}*) <expression>)
Any expression involving lets can be converted into an exactly equivalent expression without lets. For example,
(let ((x one)
(y two))
(+ x y))
is exactly equivalent to:
((lambda (x y) (+ x y)) one two)As a more complicated example,
(let ((x +)
(y two))
(let ((z three))
(x y z)))
is exactly equivalent to:
((lambda (x y)
((lambda (z)
(x y z))
three))
+ two)
Going totally overboard,
(let ((le
(lambda (length)
(lambda (l)
(if (null? l)
zero
(add1 (length (cdr l))))))))
(let ((mk-length
(lambda (mk-length)
(le (lambda (x)
((mk-length mk-length) x))))))
(mk-length mk-length)))
is equivalent to:
((lambda (le)
((lambda (mk-length) (mk-length mk-length))
(lambda (mk-length)
(le (lambda (x) ((mk-length mk-length) x))))))
(lambda (length)
(lambda (l)
(if (null? l) zero (add1 (length (cdr l)))))))
Write a function remove-lets which accepts an expression
possibly containing lets and returns an equivalent expression without lets.
Remember to recur into every subexpression.