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 expressions in Huey, it would be convenient to be able to create new kinds of operators and colors. This requires the ability to name an expression that computes a particular value and use the name as a value in a larger expression.

This version of Huey includes named colors as expressions. They will be useful both 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, and blocks with local variables. Here is the BNF description of the language:

    <color> ::= (rgb <byte> <byte> <byte> )
              | <varref>                               ; new
              | ( <unary-op> <color> )
              | ( <color> <2color-op> <color> )
              | ( <color> <1color-op> <number> )
              | ( color <var> = <color> in <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, =, and in are keywords. They cannot be used as the names of variables.

Example Expressions

These are all still 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)

These are now also legal Huey expressions: — new

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

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 — new
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 — new
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.

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.