Huey, A Little Language for Colors

Motivation

All links in this section are optional.

The light we see can be thought of as a wave of energy. What we perceive as different colors are light waves with different wavelengths. Our perception of color is determined by three sensors that trigger at wavelengths corresponding roughly to what we think of as red, green, and blue. All other colors are composites created by our brains based on the values returned by these three sensors.

We can thus record a color by encoding the values returned by these sensors in a triple, known as an RGB value. The first value represents the amount of red in the color, as returned by the 'red' sensor. The second and third represent the amount of green and blue in the color, respectively, as returned by the corresponding sensors.

Each of these values is a byte, an eight-bit integer in the range [0..255]. When all three values are set to 255, we perceive white. When all three are set to 0, we perceive black. All other colors correspond to other combinations of red, green, and blue values.

This RGB color model is used in many systems to represent and manipulate colors, including HTML and CSS. You can experiment with RGB values and their corresponding colors using MDN's color wheel or W3's color picker.

When you know how to write language interpreters, they become a practical option for solving a larger set of programming problems, including ones like manipulating colors. Over the next few homework assignments, we will put this idea into practice. We will make a language called Huey that is designed for the express purpose of computing solutions to this problem. The language turns out to be useful enough that we could extend it for use in a large set of graphical programs.

Language Grammar

BNF Description

The initial version of the language consists of RGB values, unary expressions, two-color binary expressions, and one-color binary expressions. Here is the BNF description of the language:

    <color> ::= (rgb <byte> <byte> <byte> )
              | ( <unary-op> <color> )
              | ( <color> <2color-op> <color> )
              | ( <color> <1color-op> <number> )

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

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

Example Expressions

These are all legal Huey expressions:

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

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.

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.