Assignment #12: Toy CompilerOut: Wednesday, April 7th, Due: Friday, April 16th, 9:00pm |
This assignment is only required for Master students. Undergrads
can do it too, and you will be graded as usual — but you will
not be able to withdraw a graded homework grade, so submit only
if you think that your work is good enough to improve your grade. (Note:
the homework grades are all weighted to compute your overall grade, so
adding another grade means that you get smaller portions for more grades,
which can serve as protection against local disasters.)
Note that while undergrads are not required to submit a solution, it will
be a very good idea to read the homework and go through the solution when
it is later posted — since you are expected to know the material.
The language for this homework is:
#lang pl 12 |
In this assignment you will build on the interpreter that was extended in the previous homework, so begin by retrieving the posted solution to be used as a basis for this one.
In this homework, you will turn the extended TOY evaluator into a compiler. Do this in small steps, using what we’ve seen in class to guide your work. Dividing the work into small steps where the code still runs and passes all tests after each test will make this homework significantly easier.
Start with the posted solution.
;; convenient helper for running compiled code
(: runner : ENV -> ((ENV -> VAL) -> VAL))
(define (runner env)
(lambda (compiled) (compiled env))) |
(: compiler-enabled? : (Boxof Boolean))
;; a global flag that can disable the compiler
(define compiler-enabled? (box #f)) |
(: run : String -> Any)
;; compiles and runs a TOY program contained in a string
(define (run str)
(set-box! compiler-enabled? #t)
(let ([compiled (compile (parse str))])
(set-box! compiler-enabled? #f)
(let ([result (compiled global-environment)])
(cases result
[(ScmV v) v]
[else (error 'run
"the program returned a bad value: ~s"
result)])))) |
(unless (unbox compiler-enabled?)
(error 'compile "compiler disabled")) |
Your compile function will throw a “compiler disabled” error if the compiler is disabled, and you will need to test this to get complete coverage. However, this is an intenral check, and if an error is raised, it should be considered an internal error of your own code. In other words, when things run as they should, you will not have a way to test this error, so it is impossible to write a test case that simply uses run. You should therefore write a test case (for compile and for additional compilation functions that will be described below) using direct calls to the compile function to test the error.
One problem that you will need to deal with is the fact that the extended language has several places were multiple expressions are evaluated one by one, using eval-body. There are several possible ways to deal with compiling such multiple-expression bodies:
When you pull out all dependencies on the input syntax, pay attention to the case of applying an rfun (a FunV value with the flag on) — it doesn’t use compile, but it does depend on the source expressions, so we still want to do the source-related work at compile-time. Actually, we can consider the get-boxes function as being yet another part of the compiler, so we need to deal with it in a similar way to eval and eval-body: we want to turn it to a compilation function.
Begin doing this by renaming it to compile-get-boxes, and currying it, then move the call to this function to the compile-time part in the compile function. The type for this function should be
(: compile-get-boxes : (Listof TOY) -> (ENV -> (Listof (Boxof VAL)))) |
Use the following template for this function:
(: compile-get-boxes : (Listof TOY) -> (ENV -> (Listof (Boxof VAL))))
;; utility for applying rfun
(define (compile-get-boxes exprs)
(: compile-getter : TOY -> (ENV -> (Boxof VAL)))
(define (compile-getter expr)
(cases expr
[(Id name)
(lambda: ([env : ENV]) —«fill-in»—)]
[else
(lambda: ([env : ENV]) —«fill-in»—)]))
(unless (unbox compiler-enabled?)
(error 'compile "compiler disabled"))
(let ([getters (map compile-getter exprs)])
(lambda (env)
—«fill-in»—))) |
Note that when we do all of this, what happens is that each function call gets compiled as a possible rfun call: there is no way for us to know at compile time whether the function value is going to be a plain function or a by-reference function, so all relevant code is getting compiled in advance. This is similar to compiling an if expression — we don’t know which branch will need to run, so we compile both.