Homework 11
Adding State to Huey

Due: Friday, May 8, at 11:59 PM

Introduction

For this assignment, we extend Huey, our RGB color language, one last time. You will modify the interpreter and other tools that process Huey programs so that they support mutable variables and assignment statements.

Before going any farther, read the updated Huey language specification, especially the sections marked new. These sections define the new features being added to the language.

Optional Reading: The instructions below include all the requirements for the assignment, but they assume that you are comfortable with adding new features to our language and interpreter. If you would like a little more guidance on how to approach the assignment, read this outline for adding features to a language and its processors. The reading walks through the process we have used before to extend a language in a bit more detail, offering more explicit instructions and hints for implementation, including some Racket you might want to use.

Code and Files

For this assignment, you will extend the Huey interpreter to handle mutable local variables and assignment statements. These changes will affect the syntax procedures that define the new expressions, the pre-processor, and the evaluator. You will organize your code in the same way as Homework 10.

Here are a few changes specific to this assignment:

Please comment your source code so that you and I can easily find the various pieces.

A Data Abstraction for Cells

In order to support mutable variables, our interpreter must map names to locations that hold values, rather than to the values themselves. To provide this behavior, we need an abstract data type called a cell. The cell ADT is an example of a mutable object.

The interface of the cell ADT consists of three operations:

Problems

  1. Implement the cell ADT as a function or as a pair of procedures that share data.
    This means that the cell constructor will return either a lambda or a cons.

    Of course, users of the ADT won't know which implementation you use! For example:

    > (define foo (cell 0))
    > (cell-value foo)
    0
    > (cell-set! foo 10)
    10
    > (cell-value foo)
    10
    > (cell-set! foo (+ (cell-value foo) 5))
    15
    > (cell-value foo)
    15
    

    We saw examples of several different implementations for mutable objects in Sessions 24 and 25, including functions and lists.

  2. Define the syntax procedures for do expressions and for the assignment statements they contain.
    For each of the new expression types, define a type predicate, a constructor, and the corresponding access procedures. As always, follow the BNF description for each kind of expression.

    Reminders and hints:

    • For do expressions, you will want two accessors:
      • one that returns a list of the 0 or more assignment statements that immediately follow the keyword do at the beginning of the expression, and
      • one that returns expression's value, which is the Huey expression at the end of the list.
    • You can use the built-in Racket function last to implement the value accessor. You will want to write a helper function to implement the assignments accessor.
    • Be sure to extend the general type predicate color? (or whatever you named it) to recognize the new types of expression, too. Remember, though: assignment expressions are not legal Huey expressions by themselves. They occur only within do expressions.
  3. Extend (preprocess sugared-exp) to handle do expressions.
    do expressions are a core feature of Huey, so preprocess needs only de-sugar the sub-expressions of the do and then create a new do expression.

    For example:

    > (preprocess '(do (darker (rgb 255 0 0))))
    '(do ((rgb 255 0 0) * 0.5))
    
    > (preprocess '(color c = (rgb 0 255 0) in
                      (do (c <= (c mix (rgb 0 0 255)))
                          (c <= (invert c))
                          (darker (c shift 5)))))
    '(color c = (rgb 0 255 0) in
        (do (c <= ((c * 0.5) + ((rgb 0 0 255) * 0.5)))
            (c <= (invert c))
            ((c shift 5) * 0.5)))
    

    Reminders and hints:

    • assignment statements are not legal Huey expressions, so preprocess will not have a case for them.
    • The case for preprocessing do expressions can call a helper function to preprocess the assignment statements it contains. To preprocess a single assignment statement, preprocess the value on its right hand side and return a new assignment statement.
  4. Modify the evaluator's environment to map variables to cells that contain values, not values.
    Use the cell ADT you implemented for Problem 1.

    This requires three changes:

    • change the initial environment to bind white and black to cells that hold their values,
    • change the part of the evaluator that extends an environment to bind the new variable to a cell that holds its value, and
    • change the part of the evaluator that looks up the value of a variable in the environment to ask the cell it finds for the actual value.

    After this step, our evaluator should work exactly as it did after Homework 10, even though the environment now maps variables to cells that hold values. All the same tests should pass. We should not notice any changes!
    Run your existing tests to make sure this is true.

  5. Extend (eval-exp exp) to handle do expressions.
    eval-exp returns the value of exp, which now may be or contain the new kind of expression, as specified by the language spec.

    What Racket will display as a result of eval-exp still depends on how you represent RGB values internally. If you use lists of size three, for example, it might display values such as these:

    > (eval-exp 'white)
    '(255 255 255)
    
    > (eval-exp '(do (rgb 255 0 0)))
    '(255 0 0)
    
    > (eval-exp '(color c = (rgb 0 255 0) in
                    (do (c <= (c mix (rgb 0 0 255)))
                        (c <= (invert c))
                        (darker (c shift 5)))))
    '(127 66 66)
    
    > (eval-exp '(color c = (rgb 0 255 0) in
                    (color d = (rgb 0 0 255) in
                      (do (c <= (c mix d))
                          (d <= (c mix d))
                          ((c mix d) shift 5)))))
    '(5 99 163)
    
    > (eval-exp '(color c = (rgb 0 255 0) in
                    (do (c <= (invert d))       ;; notice the 'd'
                        c)))
    eval-exp: undefined variable d
    

    Reminders and hints:

    • Again: assignment statements are not legal Huey expressions, so eval-exp will not have a case for them.
    • Write a new helper function that evaluates a single assignment statement. It updates the value of the variable in the current environment. It does not add a new variable to the environment.
    • For each assignment statement in the do expression, eval-exp can call that helper. You can do that with map or with another helper function that handles all of the assignment statements.

Extra Credit Opportunities

If you finish the above tasks and would like the chance to earn a few extra points, try one or more of these exercises. If you do, be sure to document your attempt clearly in the header block of your interpreter.rkt file.

  1. Modify the REPL with an option to print colors in hex format. — 4 points
    After we create a useful color, we might want to use it in the CSS file for a website. Huey could display colors in hex format in order to make that easier.

    One way to support this would be to let the user toggle the display format with a REPL command:

    huey! > (hex on)
    huey! > (rgb 255 0 0)
    "#ff0000"
    huey! > ((black mix (rgb 255 0 0)) mix white)
    "#be7f7f"
    huey! > (hex off)
    huey! > (rgb 255 0 0)
    (255 0 0)
    

    Another option would be to make this an option when we create the REPL, say, by passing a flag as an argument to run-huey.

    > (run-huey 'hex)
    huey! > (rgb 255 0 0)
    "#ff0000"
    huey! > ((black mix (rgb 255 0 0)) mix white)
    "#be7f7f"
    

    If you want to offer even more flexibility, let the user pass any display function as an argument to a REPL generator.

    (define run-huey (make-repl display-hex))
    
  2. Define two or more new operators that are syntactic abstractions. — 4 points
    We have a preprocessor, so adding syntactic abstractions to Huey should be straightforward!

    Update Huey's syntax procedures to recognize your new operators, and then modify the preprocessor to translate them into core Huey syntax.

    Offhand, I think adding a unary operator named lighter would be a useful bit of sugar in tandem with darker. Can you think of other combinations of core Huey operations that would be useful shorthand when describing colors?

    Run your ideas by me before implementing them. That way, we can make sure that they are valid abstractions and that they are worth extra credit points.

  3. After completing this assignment, your Huey REPL will evaluate programs that have state. Unfortunately, though, the REPL itself does not support sequences of stateful expressions at the Huey prompt.

    Write a function run-huey-with-state that creates a REPL with top-level mutable variables. — 8 points

    Users should be able to define a variable and then use and change its value in subsequent expressions:

    huey! > (define c <= (white mix black))
    '(127 127 127)
    huey! > (c <= (c shift 1))
    huey! > c
    '(128 128 128)
    

    This REPL will need its own helper to evaluate the expressions that the user enters, which can be define statements, assignment statements, or Huey expressions. This helper can call the existing eval-exp to evaluate the Huey expressions, but it will have to evaluate the define statements and assignment statements on its own — with calls to existing helper functions where appropriate.

    This is a big extension, but the result is a handy little interpreter for creating and manipulating colors.

Deliverables

By the due time and date, use the course submission system to submit the following files electronically:

Be sure that your submission follows the submission requirements. As always, use the specified names for your files. This enables the autograder to find and run your code.