Interpreter Implementation

The Fetch and Execute Loop

How the core loop of a grid-language interpreter reads a cell, acts on it and moves the pointer, with a small Python example and the order-of-operations details.

On this page
  1. One loop does all the work
  2. A first version
  3. Order of operations
  4. String mode is a state, not an instruction
  5. Halting and unknown cells
  6. Skipping spaces
  7. Dispatch as it grows
  8. Where to go next

One loop does all the work

Almost every interpreter has a loop at its center. For a grid language it is especially compact, because the program counter is a point on a plane and the instruction set is single characters. On every pass the interpreter does three things:

  1. Fetch the character in the cell where the pointer is.
  2. Execute the action that character stands for.
  3. Advance the pointer one step in its current direction.

Everything else in the interpreter, such as arithmetic, input and output, exists to serve the second step.

A first version

Here is a minimal loop in Python. It handles a few instructions to keep the structure clear:

def run(grid):
    h = len(grid)
    w = len(grid[0])
    x, y = 0, 0
    dx, dy = 1, 0
    stack = []
    string_mode = False

    while True:
        cell = grid[y][x]

        if string_mode:
            if cell == '"':
                string_mode = False
            else:
                stack.append(ord(cell))
        elif cell == "@":
            return
        elif cell == ">":
            dx, dy = 1, 0
        elif cell == "<":
            dx, dy = -1, 0
        elif cell == "^":
            dx, dy = 0, -1
        elif cell == "v":
            dx, dy = 0, 1
        elif cell == '"':
            string_mode = True
        elif cell.isdigit():
            stack.append(int(cell))
        elif cell == ",":
            print(chr(stack.pop() if stack else 0), end="")

        x = (x + dx) % w
        y = (y + dy) % h

Row 0 is the top, so moving up means subtracting from y. The remainder at the end performs the wrap-around, and Python’s remainder returns a non-negative result for a positive width, so moving left from column 0 lands on the last column. In languages with truncating remainder, add the width before taking it.

Take the one-row program "ab",,@ as a trace. The quote switches on string mode, the next two cells push the codes for a and b, the closing quote switches it off, each comma pops and prints a character, and the stop instruction ends the run. It prints “ba”, since the last pushed code comes off first. That reversal is a good first check that the stack works as expected.

Order of operations

The sequence act-then-move is a choice you must keep consistent. The pointer starts on the first cell and that cell runs before any move. If you moved first, the opening cell would be skipped.

Direction changes take effect immediately, so the move that follows uses the new direction. That is why an arrow in a cell sends the pointer off the next step the right way.

Some instructions change the position itself, such as the trampoline instruction that skips the next cell. A simple way to build it is to perform the extra advance inside the instruction, then let the regular advance happen afterward. For a conditional, set the direction first and let the regular move follow.

String mode is a state, not an instruction

Inside quotes, characters are data and are not executed. The only character with special meaning is the closing quote. Because this changes how every cell is read, the check for string mode comes before the check for instructions. Forgetting to do so is a common mistake: the arrow characters inside a string start steering the pointer.

Halting and unknown cells

The loop needs a way out. The stop instruction is one; a step limit is another, useful during testing. Beyond that, the loop must decide what a space or an unknown character does. A space is normally a no-op: the pointer just keeps going. For other unknown characters, the choice depends on the language and version. Funge-98 reverses the pointer, while older descriptions vary. Whatever you pick, put it in a single final else branch, so unknown cells are never silently confused with real instructions.

Some Funge-98 details change the picture. Rather than moving one cell at a time along four directions, the pointer carries a general step, as covered in delta vectors, and a program can run several pointers at once, an idea explored in concurrent pointers. The loop for these features keeps the same shape, but the state per pointer is richer: position, step, stack and string mode become fields of an object, and the outer loop runs each pointer in turn.

Skipping spaces

In Funge-98, a run of spaces is treated as a single no-op move, which only matters for programs that, say, count steps or run multiple pointers in lockstep. If you are targeting that language, read its rules on this point carefully. For the older language, a plain one-cell move through spaces is the expected behavior.

Dispatch as it grows

A long chain of elif tests works for a dozen instructions, but a full instruction set is easier to manage with a dictionary from character to a function. Each function receives the machine state and changes it. The loop then looks up the character and calls the function, which keeps the loop short and each instruction separately testable. Performance considerations for this are described in the article on interpreter performance.

Where to go next

Once the loop works with arrows, digits, output and stop, add instruction groups gradually, as laid out in the staged plan for building interpreters. At each stage, run a few tiny programs and print a trace if anything looks strange. The loop itself rarely changes; the interesting work happens in the instructions it calls.

The complete guide to interpreter implementation

Building Interpreters Step by Step
Read the guide

Keep exploring

Guides from other topics

All topics →