Visual and 2D Languages

Piet: Programming With Colors

How Piet programs are images, how blocks of color are traversed, and how the change in hue and lightness between blocks selects each operation.

On this page
  1. A program that is a picture
  2. The building blocks
  3. Moving through the picture
  4. How colors choose operations
  5. What push pushes
  6. White and black
  7. The stack underneath
  8. Writing in Piet
  9. Why it matters

A program that is a picture

Piet is a programming language whose source code is an image. There is no text to type. A program is a grid of small colored squares, and the interpreter reads the picture instead of a file of characters. The language is named after the painter Piet Mondrian, and programs written in it can resemble abstract art, which is part of the appeal.

Like the grid languages around it, Piet has a pointer that travels across a two-dimensional space and a stack that holds data. What is unusual is that the operations are not written anywhere. They are implied by how colors relate to one another.

The building blocks

A few terms make the rest easier to follow.

Codel. The smallest unit of the picture is a codel, a tiny square of a single color. A program image is a grid of codels. In practice, people often draw each codel as a larger block of pixels so the picture is easy to see and edit.

Color block. Neighboring codels of the same color join into one color block. The block, not the individual codel, is what the pointer is “in” at any moment.

The palette. Piet defines a set of colors built from two ideas: a hue (red, yellow, green, cyan, blue, magenta) and a lightness (light, normal, dark). Six hues in three lightness levels give 18 colors. White and black are extra and have special meanings.

Moving through the picture

Two settings control where the pointer goes next. The first is a direction pointer, which faces right, down, left or up. The second is a codel chooser, which is set to left or right and decides which edge of the current block the pointer exits from when the block’s edge is wider than one codel. The next block is found by leaving the current block in the direction of the pointer, from the edge cell that the chooser selects.

Because these two settings can be changed by the program, the pointer direction in Piet is a state, as it is in Befunge. The difference is that nobody writes an arrow in a cell. The direction is rotated by an operation, and the grid itself stays a picture.

How colors choose operations

When the pointer moves from one color block to the next, the interpreter compares the two colors and works out two numbers:

  • how many steps the hue changed, going around the cycle red, yellow, green, cyan, blue, magenta and back to red;
  • how many steps the lightness changed, going around the cycle light, normal, dark and back to light.

The pair of numbers picks one operation from a table. The operations include push, pop, add, subtract, multiply, divide, mod, not, greater, pointer, switch, duplicate, roll, and input and output of both numbers and characters. There are 17 in all.

Here are two examples that can be checked against the cycles above. Moving from light red to normal red leaves the hue alone and moves the lightness by one step, which corresponds to the push operation. Moving from light red to light yellow shifts the hue by one step and leaves the lightness alone, which corresponds to add.

The table has a regular layout: each row is a hue step, each column is a lightness step. You do not need to memorize it, and Piet programmers usually keep a reference chart open.

What push pushes

Push is an interesting case, because Piet has no numeric literals. The value pushed is the number of codels in the block the pointer just left. A block of five codels pushes 5. To get a larger number, you draw a larger block, or combine smaller values with arithmetic. This means a program’s numbers are literally visible as areas, which is a pleasing property and a practical headache.

White and black

Two colors behave differently from the rest.

  • White is a free pass. The pointer slides across white without performing any operation. White is therefore useful for routing.
  • Black is a wall. The pointer cannot enter it, so black acts as a barrier and a way to make the pointer change direction when it hits an edge.

If the pointer is blocked and the interpreter has tried the possible turns without finding a way forward, the program ends. So a Piet program stops not because it reaches a specific stop instruction but because it runs out of places to go.

The stack underneath

Piet uses a program stack, like a stack machine. Most operations pop values from the stack and push results back. Add pops two values and pushes their sum. Duplicate copies the top value. Roll rotates a section of the stack. Understanding Piet is largely about keeping track of what is on the stack after each color change, so reading Piet code has something in common with tracing by hand in Befunge.

The way unusual operations get hidden in color changes is a good example of how code readability can fail: a viewer of a Piet program sees a painting, and unless they know the rules, they have no clue what it does. Some Piet programs go out of their way to look like specific pictures, which makes the program harder to write and produces some of the most admired examples.

Writing in Piet

The practical tools are a pixel editor and an interpreter that shows the pointer on the image. Working in Piet is slower than in text, partly because small changes can shift the colors of several blocks and partly because an error is not reported in words. A step-by-step view that shows the current block and the stack is more important here than in most languages.

A common approach is to sketch the program as a plan of operations first, then choose colors that produce those operations in sequence, then arrange the blocks so the pointer visits them in the right order. Starting with a tiny goal, such as adding two numbers and printing the result, is a good way to get a feel for it.

Why it matters

Piet is a clear demonstration that a language does not have to look like text. Its rules are small, but its form is different from anything with an instruction table of characters. For readers who like the idea that position and structure can carry meaning, it is one of the most memorable members of the group. Many details, such as the exact table layout and the way interpreters handle edge cases, vary or need to be checked against the specification, so treat this article as an orientation rather than a reference.

The complete guide to visual and 2d languages

Visual and Two-Dimensional Programming Languages
Read the guide

Keep exploring

Guides from other topics

All topics →