Modifying Evaluators

MCS-178: Introduction to Computer Science II

Due: October 26, at the beginning of class

Introduction

You will work on this lab as a member of a two-person team. Each team is to submit a single jointly-authored lab report. For the first part of this lab, you will work with section 10.3 of the text, which is an implementation of the ``Micro-Scheme'' programming language. You will extend the language with new features which make it less like Scheme. Then, in the second part of the lab, you will work with a ``Mini-Scheme'' implementation, similar to section 10.4, but simplified through the use of state. Finally, in the third part of the lab, you will modify this Mini-Scheme implementation analogously to section 10.5, so as to make it generate explanatory output as it does its evaluation.

Advice

It's critical (especially in this lab) to test each change you make to the code as soon as possible: Write a few lines of code, test them, write a few more, test them, repeat. By coding this way, when you find a bug, you'll usually know exactly where the bug is, saving you time and frustration.

Prelab

Before coming to lab, you should have read and thought about all of sections 10.1, 10.2 and 10.3, and this handout. If possible, identify a team-mate; otherwise, team-mates will be assigned at the beginning of the first lab period.

In Lab

  1. You should open the file ~mc28/labs/evaluators-w-state/micro-scheme.scm and use the "Save Definitions As..." command to save a private copy in your own directory, which you will modify. Use the ``Execute'' button to evaluate all the definitions in that file. This file contains the Micro-Scheme implementation from section 10.3 of the text, along with some necessary definitions from section 10.2 and earlier chapters of the text.
  2. Next, evaluate (read-eval-print-loop) in Scheme and then try evaluating various Micro-Scheme expressions in the resulting Micro-Scheme read-eval-print loop. When you are done trying out Micro-Scheme and want to return to real Scheme, click on the ``eof'' button at the end of the input window.
  3. Now that you are familiar with Micro-Scheme as it is provided, it is time to add your own extensions. First, add more pre-defined names by modifying look-up-value, as described in exercises 10.11 and 10.12 on page 299 of the textbook. For 10.11, you could add cons, car, cdr, and null?, so that you can do list processing in Micro-Scheme. For 10.12, you can add anything you've wished Scheme had built in, perhaps square. Try out Micro-Scheme again, making sure that your new pre-defined names work.
  4. Next it is time to add a new kind of expression to Micro-Scheme. Do exercise 10.16 from page 303 of the textbook, which calls for adding with expressions to Micro-Scheme. This will involve modifying the definition of micro-scheme-parsing-p/a-list. To declare the symbol with and the symbol compute as keywords, modify the keyword? predicate accordingly. (Question: What should we do with the = symbol?)

    Here is a somewhat tricky with expression within a lambda; think carefully about what the value should be, and make sure your implementation produces that value:

    ((lambda (x y)
        (with x = (+ x y) compute (* x y)))
     3 5)
    

    If you have that right, you are then likely to be able to properly handle nested with's such as:

    (with x = (+ 2 3) compute (with y = (+ x 4) compute (* x y)))
    
    (with x = (+ 2 3) compute (with x = (+ x 4) compute (* x x)))
    
    Again, be sure to hand calculate what the above should evaluate to before testing them on your implementation.

    Be sure to try to test every change to the code you make as soon as possible. This way, if there is a bug, you'll know exactly where to look. For this exercise, for example, you first may wish to make a temporary pattern/action for the with statement that allows you to test the pattern matching, before concerning yourself with how these expressions should actually be turned into ASTs. In particular, you can temporarily use the following action:

     (lambda (variable value-expression body)
       (display "with expression parsed with variable ")
       (write variable)
       (display ", value expression ")
       (write value-expression)
       (display ", and body ")
       (write body)
       (newline)
       (make-constant-ast 7))
    
    This will of course not behave at all as with expressions should: it will make all with expressions always evaluate to 7. However, this "testing stub" will allow you to see that with expressions are being correctly pattern matched. Once you have checked that, you can replace the action with a real one.
  5. Next you'll turn Micro-Scheme into Mini-Scheme. You'll need to have read section 10.4 to understand this, although we will be deviating from section 10.4's approach.
    1. Open the file ~mc28/labs/evaluators-w-state/mini-additions.scm; copy all the definitions from that file, and paste them at the end of your copy of micro-scheme.scm. Save the modified version of as micro-scheme.scm out as mini-scheme.scm, to make clear what it is now becoming.
    2. Also, these additions contain a new version of the read-eval-print-loop. To avoid confusion, you should delete (or at least comment out) the earlier micro-scheme version of read-eval-print-loop.
    3. Flesh out the definition of make-initial-global-environment along the lines of your look-up-value from the first part of the lab.
    At this point, you are ready to turn micro-scheme into mini-scheme by making the following changes:
    1. Modify make-name-ast so the name ASTs use look-up-value-in rather than look-up-value. They should get the global environment from the repl-state.
    2. Modify the mini-scheme read-eval-print-loop so that each time the procedure loop is invoked, the first thing it does it to set the global-environment into the repl-state.
    Test that mini-scheme works, both for the sorts of things micro-scheme could do, and for its new abilities.
  6. Now you are ready to start adding explanatory output to mini-scheme, analogously to section 10.5. To start with, copy onto the end of your mini-scheme.scm the further additions given in the file ~mc28/labs/evaluators-w-state/explanatory-additions.scm. Because these additions include a new version of the evaluate procedure, you should delete or comment out the earlier version, to avoid confusion. At this point, you have what you need to complete the next few steps of the lab before mini-scheme will again be working, with its new explanatory output.
  7. Do exercise 10.22 on pages 312-313; you will need to also add the unparse operation to make-application-ast as shown on page 312 above the exercise. To facilitate testing, start with the the types of AST that go at the leaves of parse trees (names and constants) and then move up to the other kinds of AST. Test each AST constructor immediately after you've changed it by unparsing the result of parsing an appropriate expression. That is, you can do a test like the following, but with the box replaced by an actual mini-scheme expression:
    (unparse (parse 'some-mini-scheme-expression)) => some-mini-scheme-expression

    As the exercise says, some constants need to be quoted in order to turn them into mini-scheme expressions, whereas others do not. This quoting, which is part of the mini-scheme language, would go inside the boxes in the above illustration. It should not be confused with the quoting of the first box, which is happening in Scheme, not mini-scheme.

    Most of you aren't yet comfortable enough with Scheme to figure out how to quote the constant. To gain more confidence in Scheme semantics, play around with expressions which have quote and/or ' symbols, and then try the following puzzle. In Scheme, type

    (define value '(2 3))
    If you now type value, Scheme returns (2 3) Now, write a Scheme expression which contains the name value, but does not contain the constants 2 nor 3; your goal is for the expression to evaluate to either '(2 3) or, equivalently, (quote (2 3)).

    Lastly, if you have troubles handling quoted constants properly, you can continue on without loss of continuity, so long as the numerical constants unparse properly.

  8. Just like we were able to use the repl-state to avoid the extra global environment argument of section 10.4's evaluate-in, we can use it to avoid the level argument of section 10.5's evaluate-in-at. Modify make-read-eval-print-loop-state so that the repl-state has room to hold a level as well as a global environment. Add a selector, get-level and a mutator, set-level!, to operate on repl states. Test these operations in isolation, by setting a global environment and a level into the repl-state and then getting them back out. Then put the code into read-eval-print-loop to initialize the level to 0 before the loop is entered the first time.
  9. As pages 321-322 of the textbook explain, three calls to evaluate should be changed to evaluate-additional. Change them.
  10. To make the output look as shown, it is also necessary to provide a blank line before the value of a built-in procedure. Put the appropriate call to blank-line into the procedures generated by make-mini-scheme-version-of.
  11. Now you should be able to test your mini-scheme. Be sure you exercise every kind of AST.
  12. Optional: do exercise 10.29 and/or 10.30.

Postlab

You need not write a formal lab report. For this lab, you should organize the final mini-scheme code, omitting any procedures you didn't change. In the next section, clearly explain how I would test each of the exercises you solved to be confident that they work (or don't work). In this section you should clearly identify any bugs in your solutions.

Choose your test cases carefully so that they exercise all lines of code and all cases without overburdening the reader with redundant tests. Explain the reasoning behind why you chose the test cases you did. What problems didn't you test for?

The gradesheet for the project is available in PostScript or PDF format. (If you print a copy out, you can staple it to the front of your project report to save paper.)