Huey, An Extended Language for Colors

Motivation

This document specifies an extended version of the Huey language we created for Homework 9, which was motivated by a common web programming task. When you know how to write language interpreters, a language becomes a practical option for solving such problems.

To write more complex programs in Huey, it would be convenient if we were able to evolve colors through a sequence of expressions. This requires the ability to change the state of a local variable and have the new value reflected in subsequent expressions.

This version of Huey includes sequences of assignments as color expressions. They will be useful for writing programs that compute more ambitious colors using Huey's operators.

Language Grammar

The extended version of the language consists of RGB values, variable references, unary expressions, two-color binary expressions, one-color binary expressions, blocks with local variables, and blocks with assignment statements. Here is the BNF description of the language:

     <color> ::= (rgb <byte> <byte> <byte> )
               | <varref>
               | ( <unary-op> <color> )
               | ( <color> <2color-op> <color> )
               | ( <color> <1color-op> <number> )
               | ( color <var> = <color> in <color> )
               | ( do <assignment>* <color> )          ; new

<assignment> ::= ( <varref> <= <color> )               ; new

  <unary-op> ::= invert | darker
 <2color-op> ::= + | - | mix
 <1color-op> ::= * | shift

A <number> can be any real number, positive or negative, including integers. As noted Homework 9, a <byte> is an 8-bit integer in the range [0..255]. The semantics of the values and operators is defined below.

The symbols rgb, color, =, in, do, and <= are keywords. They cannot be used as the names of variables.

Example Expressions

These are all still legal Huey expressions:

white
(rgb 0 255 0)
(invert (rgb 4 4 4))
((rgb 0 255 0) + (rgb 4 4 4))
(rgb 255 0 255) mix ((rgb 0 255 0) + (rgb 4 4 4))
((rgb 255 0 255) * 1.2)
((rgb 255 0 255) shift -10)
(color purple = ((rgb 255 0 0) mix (rgb 0 0 255)) in
  (darker purple))
(color green = (rgb 0 255 0) in
  (color blue-green = (green mix (rgb 0 0 255)) in
    (invert blue-green)))

These are now also legal Huey expressions: — new

(do (rgb 255 0 0))

(color c = (rgb 0 255 0) in
  (do (c <= (c mix (rgb 0 0 255)))
      (c <= (invert blue-green))
      (c shift 5)))

(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))))

As the grammar indicates, expressions nest arbitrarily deeply.

Semantics

RGB Values
An RGB value is a triple of three bytes, each an integer between 0 to 255, inclusive. These values can be implemented in any way that supports the operations of the language. From the users' perspective, these are like literals in that they evaluate to themselves.

Racket does not provide an 8-bit integer type, but we can use Racket's byte? type predicate to validate the components of an RGB value. The constructor for an RGB value should ensure that all values fall in the legal range. Any value larger than 255 maps to 255, and any value less than 0 maps to 0.
Unary Expressions
A unary operator takes a color as its operand and returns a new color.
  • (invert operand) returns a color whose RGB components are 255 minus the corresponding components of operand.
    (invert (rgb 150 99 42)) equals (rgb 105 156 213).
  • (darker operand) returns a color whose RGB components are one-half the corresponding components of operand.
    (darker (rgb 150 99 42)) equals (rgb 75 49 21).
Note that Huey uses integer division and thus rounds down.
Two-Color Expressions
These binary operators take two colors as their operands.
  • (color1 + color2) returns a color whose RGB components are the sums of the corresponding components of its two operands.
    ((rgb 150 99 42) + (rgb 50 18 241)) equals (rgb 200 117 255).
  • (color1 - color2) returns a color whose RGB components are the differences of the corresponding components of its two operands.
    ((rgb 150 99 42) - (rgb 50 108 21)) equals (rgb 100 0 21).
  • (color1 mix color2) returns a color whose RGB components are 50-50 blends of the corresponding components of its two operands.
    ((rgb 150 99 42) mix (rgb 50 108 21)) equals (rgb 100 103 31).
Note that if an operation produces a component value larger than 255 or less than 0, the values are bounded at their limits. (The RGB constructor function should ensure this behavior.) Also note that, as before, Huey uses integer division and thus rounds down.
One-Color Expressions
This kind of binary operator takes a color as its left operand and a number as its right operand.
  • (color * number) returns a color whose RGB components are number times the corresponding components of its color operand.
    ((rgb 150 99 42) * 1.6) equals (rgb 240 158 67).
  • (color shift number) returns a color whose RGB components are number plus the corresponding components of its color operand.
    ((rgb 150 99 42) shift -50) equals (rgb 100 49 0).
Note again that (a) component values are bounded at 0 from below and 255 from above and (b) Huey rounds all component values down to the nearest integer.
Primitive Values
The initial environment of a Huey program contains two primitive colors:
  • white, bound to the value (rgb 255 255 255)
  • black, bound to the value (rgb 0 0 0)
Blocks with Local Variables
A variable reference is meaningful only within the body of the color/in expression that declares the variable. It is not meaningful in the value being assigned to the variable.

A reference to a variable that has not been defined either in the initial environment or in a containing color/in expression is an error.

A color/in expression behaves as follows:

  • First, the value of the variable is evaluated in the current environment.
  • Then, a new environment is created by extending the current environment with the new variable/value pair.
  • Finally, the body of the expression is evaluated in the new environment.
... just as local variables work in Racket.
Sequences with State — new
A do expression consists of 0 or more assignment statements followed by a color expression. The value of a do expression is the value of that final color.

An assignment statement changes the value of an existing variable. An attempt to assign a value to a variable that has not been defined by a containing color/in expression is an error.

Note that the <= operator does not behave like other operators in the language, because the variable on the lefthand side of the expression is not evaluated. Instead, it is the target of the assignment. The color expression on the righthand side of the statement is evaluated, including any variables it contains.

A do expression behaves as follows:
  • Evaluate the assignment statements in order. For each:
    • evaluate the color expression on the righthand side of the statement, and
    • update the value of the variable on the lefthand side in the current environment.
  • Evaluate the final color expression in the current environment and return that value as the value of the do expression.

Syntactic Sugar

All of the features defined above are part of the core of the Huey language except:

In Huey, syntactic abstractions are preprocessed away before expressions are evaluated.