In this assignment you will make some modifications to the compiler presented in Chapter 5 of the text to make it suitable for compiling a larger and more respectable subset of Scheme. In particular, you will be able to compile the metacircular evaluator of Chapter 4.
Start by copying from your hw9 directory into your new hw10 directory the following files:
regsim.scm syntax.scm eceval-support.scm(You don't need eceval.scm.) Then copy from ~offner/cs450/hw10 into your hw10 directory the following files:
compiler.scm compiler-shell.scm machine-shell.scm
You may make modifications to any or all of these files. (Well actually, you won't need to make any changes at all to compiler-shell.scm, and I would be astonished if you needed to make any changes to machine-shell.scm. So you can pretty much forget about modifying those two files.) I will collect all of them, and in addition I will collect the usual file
In doing these problems, you will want to add more Scheme primitives to the ones already supported by the compiler. You add them simply in eceval-support.scm to primitive-procedures; each new primitive is a one-line addition to that list.
Just to be clear about this: the primitive procedures you add in this way are names of procedures that the compiler (which is, after all, compiling Scheme code) will recognize in the Scheme code it is compiling as being Scheme primitive procedures. So for instance, if you are compiling a Scheme program that includes the Scheme primitive not, then you would want to add not to the list of primitive-procedures in eceval-support.scm. And don't add them to primitive-procedures in s450.scm! s450.scm is just the program you are compiling. Dont make any changes to it.
Also please note that there are some changes in the compiler that I have given you from the compiler in the book, principally in the lists of modified registers that are passed around. We discussed this in class.
This is entirely analogous to the way the compiler handles cond by transforming it into a set of nested if clauses using the syntax procedure cond->if. The main dispatch procedure of the compiler recognizes each cond expression and invokes cond->if, passing the result to compile. Of course you may also need some supporting functions for let->appl and or->if in syntax.scm. Add whatever you need, and put them in syntax.scm.
And please look up the definitions of let and or in R5RS to make sure you have really implemented the Scheme definitions of these procedures. You will undoubtedly be surprised at what you find. If you don't do this, I can almost guarantee that your compiler will not pass my tests.
Extend the compiler so you can compile the metacircular evaluator from Chapter 4. I have placed a very slightly modified version of that evaluator in ~offner/cs450/hw10/s450.scm which you should link to. (The main difference is that after the metacircular evaluator is loaded it starts executing without waiting to be invoked. This is necessary because otherwise machine-shell.scm exits immediately to the underlying Scheme.)
This is essentially Exercise 5.50 on page 610 of the textbook. It's fairly difficult, so let me give you some advice:
An apply expression looks like this:
(apply proc_exp param_list)So generate code as follows:
proc-code operand-codes (compile-procedure-call ...)
I'm guessing that the most difficult part of this will be managing the saving and restoring of registers, using preserving and so on. Give yourself plenty of time for debugging.
The debugging facility that you added to regsim.scm can help you a lot. Adding "print" statements (i.e., display expressions) in the compiler or the generated code can also be useful.
One thing to keep in mind: If you put display expressions in your compiler.scm to aid in debugging, remember that you will not see their output on the monitor. All the output from the compiler goes to the file *.cmp. So that's where you should look for the output from those expressions. Of course that output will be intermixed with the rest of the compiler output, so be sure to identify the output in some easily recognized way.
In debugging a problem, spend whatever time it takes to cut the problem down to an absolutely minimal size. Usually once you do this, it is easy to see where the problem is coming from. Many times students throw up their hands and ask me for help debugging without having done this. So I end up spending half an hour or more cutting their problem down until it is quite obvious what is going wrong. You should learn to do this, if you haven't already; it's an important technique and can save you a lot of time. (And it will save me a lot of time also, so I would really appreciate it.)
Another piece of advice about debugging, assuming your compiler is actually generating code: once you have cut the problem down to an absolutely minimal size, look at the generated code (i.e., the ".cmp" file). You can often see what is going wrong pretty quickly when you see something strange in that file.
When you finish this, you should be able to compile s450.scm into s450.cmp and execute that file using machine-shell.scm to get a running Scheme interpreter. Of course, as the book points out, because of the multiple levels of interpretation going on, this interpreter will run rather slowly, but it should run well, and you should experiment with it to see that it does.