MASSACHVSETTS INSTITVTE OF TECHNOLOGY Department of Electrical Engineering and Computer Science 6.001 Structure and Interpretation of Computer Programs Spring semester, 2005 Project 5- The Meta-Circular Evaluator Issued Monday, April 25 To Be Completed By: Friday, May 6, 6: 00 pm Code to load for this project o Links to the system code files meval. scm, syntax. scm, and environment. scm are provided from the Projects link on the projects section You should begin working on the assignment once you receive it. It is to your dvantage to get work done early, rather than waiting until the night before it is due. You should also read over and think through each part of the assignment (as well as any project code) before you sit down at the computer. It is generally much more efficient to test, debug, and run a program that you have thought about beforehand, rather than doing the planning"online. "Diving into program development without a clear idea of what you plan to do generally ensures that the assignments will take much longer than necessary The purpose of this project is to familiarize you with evaluators. There is a fair amount to read and understand; we recommend that you first skim through the project to familiarize yourself with the format, before tackling problems Word to the wise: This project doesn't require a lot of actual programming. It does require understanding a body of code, however, and thus it will require careful preparation. You will be working with evaluators such as those described in chapter 4 of the text book, and variations on those evaluators If you dont have a good understanding of how the evaluator is structured, it is very easy to become confused between the programs that the evaluator is interpreting, and the procedures that implement the evaluator itself. For this project, therefore, we suggest that you do some careful preparation. Once you've done this, your work in the lab should be fairly straightforward Understanding the evaluator Load the code(meval. scm, then syntax. scm, finally environment. scm) for this project. These files contain a version of the meta-circular evaluator described in
MASSACHVSETTS INSTITVTE OF TECHNOLOGY Department of Electrical Engineering and Computer Science 6.001 Structure and Interpretation of Computer Programs Spring Semester, 2005 Project 5 - The Meta-Circular Evaluator • Issued Monday, April 25th. • To Be Completed By: Friday, May 6th, 6:00 pm • Code to load for this project: o Links to the system code files meval.scm, syntax.scm, and environment.scm are provided from the Projects link on the projects section . You should begin working on the assignment once you receive it. It is to your advantage to get work done early, rather than waiting until the night before it is due. You should also read over and think through each part of the assignment (as well as any project code) before you sit down at the computer. It is generally much more efficient to test, debug, and run a program that you have thought about beforehand, rather than doing the planning "online." Diving into program development without a clear idea of what you plan to do generally ensures that the assignments will take much longer than necessary. The purpose of this project is to familiarize you with evaluators. There is a fair amount to read and understand; we recommend that you first skim through the project to familiarize yourself with the format, before tackling problems. Word to the wise: This project doesn't require a lot of actual programming. It does require understanding a body of code, however, and thus it will require careful preparation. You will be working with evaluators such as those described in chapter 4 of the text book, and variations on those evaluators. If you don't have a good understanding of how the evaluator is structured, it is very easy to become confused between the programs that the evaluator is interpreting, and the procedures that implement the evaluator itself. For this project, therefore, we suggest that you do some careful preparation. Once you've done this, your work in the lab should be fairly straightforward. Understanding the evaluator Load the code (meval.scm, then syntax.scm, finally environment.scm) for this project. These files contain a version of the meta-circular evaluator described in
lecture and in the textbook. Because this evaluator is built on top of the underlying Scheme evaluator, we have called the procedure that executes evaluation m-eval(with associated m-apply) to distinguish it from the normal You should look through these files to get a sense for how they implement a version of the evaluator discussed in lecture(especially the procedure m-eval) You will be both adding code to the evaluator, and using the evaluator be careful, because it is easy to get confused. Here are some things to keep in mind: When adding code to be used as part of meval. scm, you are writing in Scheme, and can use any and all of the procedures of Scheme Changes you make to the evaluator are changes in defining the behavior you want your new evaluator to have After loading the evaluator(i.e, loading the file meval scm and any additions or modifications you make), you start it by typing(drive loop). In order to help you avoid confusion, weve arranged that each driver loop will print prompts on input and output to identify which evaluator you are typing at. For example, ;i M-Eval input (+34) M-Eval value shows an interaction with the m-eval evaluator To evaluate an expression, you type the expression and press ctrl-x ctrl-e. Dont use M-z to evaluate the expression; the presence of the prompt confuses the M-z mechanism. Also notice that if you have started up the evaluator in a scheme*buffer, you may go to any other buffer, write definitions or expressions and evaluate them from that buffer. This will cause the expressions to be evaluated in the new evaluator. On the other hand, if you have not started up the evaluator, evaluating expressions from a buffer will cause them to be evaluated in the normal Scheme evaluator The evaluator with which you are working does not include an error system If you hit an error you will bounce back into ordinary Scheme. You can restart the driver-loop by running the procedure driver-loop. Note that the driver loop does not re-initialize the environment, so any definitions you have made should still be available if you have to re-run driver n case you do want a clean environment, you should evaluate (refresh-global-environment) while in normal Scheme. It is instructive to run the interpreter while tracing m-eval (You will also probably need to do this while debugging your code for this assignment
7 lecture and in the textbook. Because this evaluator is built on top of the underlying Scheme evaluator, we have called the procedure that executes evaluation m-eval (with associated m-apply) to distinguish it from the normal eval. You should look through these files to get a sense for how they implement a version of the evaluator discussed in lecture (especially the procedure m-eval). You will be both adding code to the evaluator, and using the evaluator. Be careful, because it is easy to get confused. Here are some things to keep in mind: When adding code to be used as part of meval.scm, you are writing in Scheme, and can use any and all of the procedures of Scheme. Changes you make to the evaluator are changes in defining the behavior you want your new evaluator to have. After loading the evaluator (i.e., loading the file meval.scm and any additions or modifications you make), you start it by typing (driverloop). In order to help you avoid confusion, we've arranged that each driver loop will print prompts on input and output to identify which evaluator you are typing at. For example, ;;; M-Eval input: (+ 3 4) ;;; M-Eval value: shows an interaction with the m-eval evaluator. To evaluate an expression, you type the expression and press ctrl-x ctrl-e. Don't use M-z to evaluate the expression; the presence of the prompt confuses the M-z mechanism. Also notice that if you have started up the evaluator in a *scheme* buffer, you may go to any other buffer, write definitions or expressions and evaluate them from that buffer. This will cause the expressions to be evaluated in the new evaluator. On the other hand, if you have not started up the evaluator, evaluating expressions from a buffer will cause them to be evaluated in the normal Scheme evaluator. The evaluator with which you are working does not include an error system. If you hit an error you will bounce back into ordinary Scheme. You can restart the driver-loop by running the procedure driver-loop. Note that the driver loop does not re-initialize the environment, so any definitions you have made should still be available if you have to re-run driverloop. In case you do want a clean environment, you should evaluate (refresh-global-environment) while in normal Scheme. It is instructive to run the interpreter while tracing m-eval. (You will also probably need to do this while debugging your code for this assignment.)
Since environments are, in general, complex circular list structures, we have set Scheme's printer so that it will not go into an infinite loop when asked to print a circular list. (See the descriptions of *unparser-list depth-limit* and *unparser-list-breadth-limit* in the Scheme reference manual.) You will notice that when you have run driver-loop, the mode line(bar at the bottom of edwin with the buffer name along with (Scheme: listen)) will display (scheme Meval: listen) instead. This indicates that meval ill be receiving anything you c-x C-e, instead of (regular) MiTSche In order to give you further warning when you accidentally send things to Meval instead of mitscheme. the *warn-meval-define* variable controls whether Meval will print out warnings if the define you're evaluating is binding a variable that is also bound in mitscheme Computer Exercise 1: Exploring Meval Load the code files into your Scheme environment. To begin using the scheme nterpreter defined by this file, evaluate (driver-loop). Notice how it now gives you a new prompt, in order to alert you to the fact that you are now talking"to this new interpreter. Try evaluating some simple expressions in this interpreter. Note that this interpreter has no debugging mechanism--that is, if you hit an error, you will be thrown into the debugger for the underlying Scheme. This can be valuable in helping you to debug your code for the new interpreter but you will need to restart the interpreter You may quickly notice that some of the primitive procedures about which Scheme normally knows are missing in m-eval. These include some simple arithmetic procedures(such as *)and procedures such as cadr,cddr, newline Extend your evaluator by adding these new primitive procedures(and any others that you think might be useful). Check through the code to figure out where this is done. In order to make these changes visible in the evaluator you'll need to rebuild the global environment (refresh-global-environment Show your changes to the evaluator, and turn in a demonstration of your extended evaluator working correctly Computer Exercise 2: Changing style. In MITScheme, if expressions are allowed to lack an alternative expression, as in the following
Since environments are, in general, complex circular list structures, we have set Scheme's printer so that it will not go into an infinite loop when asked to print a circular list. (See the descriptions of *unparser-listdepth-limit* and *unparser-list-breadth-limit* in the Scheme reference manual.) You will notice that when you have run driver-loop, the mode line (bar at the bottom of edwin with the buffer name, along with (Scheme: listen)) will display (Scheme Meval: listen) instead. This indicates that meval will be receiving anything you C-x C-e, instead of (regular) MITScheme. In order to give you further warning when you accidentally send things to Meval instead of MITScheme, the *warn-meval-define* variable controls whether Meval will print out warnings if the define you're evaluating is binding a variable that is also bound in MITScheme. Computer Exercise 1: Exploring Meval. Load the code files into your Scheme environment. To begin using the Scheme interpreter defined by this file, evaluate (driver-loop). Notice how it now gives you a new prompt, in order to alert you to the fact that you are now "talking" to this new interpreter. Try evaluating some simple expressions in this interpreter. Note that this interpreter has no debugging mechanism -- that is, if you hit an error, you will be thrown into the debugger for the underlying Scheme. This can be valuable in helping you to debug your code for the new interpreter, but you will need to restart the interpreter. You may quickly notice that some of the primitive procedures about which Scheme normally knows are missing in m-eval. These include some simple arithmetic procedures (such as *) and procedures such as cadr, cddr, newline. Extend your evaluator by adding these new primitive procedures (and any others that you think might be useful). Check through the code to figure out where this is done. In order to make these changes visible in the evaluator, you'll need to rebuild the global environment: (refresh-global-environment) Show your changes to the evaluator, and turn in a demonstration of your extended evaluator working correctly. Computer Exercise 2: Changing style. In MITScheme, if expressions are allowed to lack an alternative expression, as in the following:
(define x 5 (if(=x0) (set!x0.000001)) At present, what happens if you evaluate this expression in m-eval? Modify the evaluator so that ifs may lack an alternative, but if they are lacking the alternative and the test evaluates to false then the value of the if should be Show your changes to the evaluator, and turn in a demonstration of your extended evaluator working correctly Adding new special forms to Scheme Computer Exercise 3: Adding a special form We have seen that our evaluator treats any compound expression as an application of a procedure, unless that expression is a special form that has been explicitly defined to obey a different set of rules of evaluation(e define, lambda, if). We are going to add some special forms to our evaluator in the next few exercises A common loop construct in other languages is the do.. while loop It has the following syntax (do expl while test) The behavior is as follows: expl through expN are evaluated in sequence. Then return the symbol done; otherwise the loop is repeated from the start. For and test is evaluated. If test evaluates to #f then we stop executing the loop example (1et((x())) (do (set! x (cons x)) while ( (length x)3))) evaluate this to get i Value: done
(define x 5) (if (= x 0) (set! x 0.000001)) At present, what happens if you evaluate this expression in m-eval? Modify the evaluator so that ifs may lack an alternative, but if they are lacking the alternative and the test evaluates to false, then the value of the if should be #f. Show your changes to the evaluator, and turn in a demonstration of your extended evaluator working correctly. Adding new special forms to Scheme Computer Exercise 3: Adding a special form. We have seen that our evaluator treats any compound expression as an application of a procedure, unless that expression is a special form that has been explicitly defined to obey a different set of rules of evaluation (e.g., define, lambda, if). We are going to add some special forms to our evaluator in the next few exercises. A common loop construct in other languages is the do...while loop. It has the following syntax: (do exp1 exp2 ... expN while test) The behavior is as follows: exp1 through expN are evaluated in sequence. Then test is evaluated. If test evaluates to #f then we stop executing the loop and return the symbol done; otherwise the loop is repeated from the start. For example, (let ((x '())) (do (set! x (cons '* x)) (write-line x) while (< (length x) 3))) ;evaluate this to get (*) (* *) (* * *) ; Value: done
Your task is to add this special form to m-eval, showing your changes to the code, and demonstrating that it works by providing some test cases Caveat: avoid mutation on the expression that you are desugaring; for example, when the expression which you are passed is the code pointer of a compound expression, mutating the expression will actually change what the procedure does To do this, you should include the followin Create a data abstraction for handling do. while loops, that is, selectors for getting out the parts of the loop that will be needed in the evaluation Add a dispatch clause to the right part of m-eval. We have actu included this clause, you simply need to uncomment it. Write the procedure(s) that handle the actual evaluation of the hile loop Be sure to turn in a listing of your additions and changes, as well as examples of your code working on test cases Computer Exercise 4: The let* special form. low let's try something a bit tougher. We have already seen the let special form. A variation on this is the let* special form. This form acts much like let however, the clauses of the let* are evaluated in order (rather than all at the same time as in a let), and any reference in a later clause to the variable of an earlier clause should use the new value of that variable. For example (define i 1) (1et((i3) (i (factorial i)) (11stij))) would return the value(3 1) because the let variables i, j are bound in parallel, and thus the argument to fact is the value of i outside the expression namely 1. on the other hand (define i 1 (1et*((i3) (i(factorial 1)) (list i 3))) would return the value (3 6) since the variable i is first bound to 3, and then the variable j is bound, and in this case the input to fact is the new value 3
Your task is to add this special form to m-eval, showing your changes to the code, and demonstrating that it works by providing some test cases. Caveat: avoid mutation on the expression that you are desugaring; for example, when the expression which you are passed is the code pointer of a compound expression, mutating the expression will actually change what the procedure does. To do this, you should include the following: • Create a data abstraction for handling do...while loops, that is, selectors for getting out the parts of the loop that will be needed in the evaluation. • Add a dispatch clause to the right part of m-eval. We have actually included this clause, you simply need to uncomment it. • Write the procedure(s) that handle the actual evaluation of the do...while loop. Be sure to turn in a listing of your additions and changes, as well as examples of your code working on test cases. Computer Exercise 4: The let* special form. Now let's try something a bit tougher. We have already seen the let special form. A variation on this is the let* special form. This form acts much like let, however, the clauses of the let* are evaluated in order (rather than all at the same time as in a let), and any reference in a later clause to the variable of an earlier clause should use the new value of that variable. For example (define i 1) (let ((i 3) (j (factorial i)) (list i j))) would return the value (3 1) because the let variables i, j are bound in parallel, and thus the argument to fact is the value of i outside the expression, namely 1. On the other hand: (define i 1) (let* ((i 3) (j (factorial i)) (list i j))) would return the value (3 6) since the variable i is first bound to 3, and then the variable j is bound, and in this case the input to fact is the new value 3