Interpreter Implementation

Loading a Program File into a Grid

How to read a source file into a two-dimensional grid: splitting lines, padding short rows, and handling tabs, carriage returns and unusual characters.

On this page
  1. From text to a rectangle
  2. Reading the lines
  3. Padding short rows
  4. Programs larger than the field
  5. Tabs
  6. Odd characters
  7. Programs that read their own source
  8. Command-line sources and other inputs
  9. What to test

From text to a rectangle

A grid program is stored as an ordinary text file. Each line is a row, each character a cell. The interpreter’s first job is to turn that text into a rectangular structure it can index by column and row. It sounds simple, and the happy path is only a few lines, but the odd cases are where programs quietly break.

Reading the lines

Read the whole file as text and split it into lines. Two details matter right away.

Line endings. Files made on different systems end lines with a newline, a carriage return followed by a newline, or (rarely, on very old systems) just a carriage return. If you split only on the newline and leave a stray carriage return at the end of each row, that character becomes a cell in the grid. It is invisible when printed and can alter the program, for instance as an unexpected instruction at the right edge. Most languages have a helper that handles all common endings; use it.

The final newline. Many editors add a newline at the end of the file. A naive split then creates an extra empty row. This is usually harmless but changes the grid height, which matters if the program relies on wrap-around or on reading a cell near the bottom.

Padding short rows

People rarely write every row to the same length. Rows end where the last visible character ends. The grid, though, must be rectangular, so shorter rows are filled with spaces up to the width of the widest row, or up to the field width the language defines.

In Python this is a few lines:

def load_grid(text, width=None, height=None):
    rows = text.splitlines()
    w = width or max((len(r) for r in rows), default=1)
    h = height or max(len(rows), 1)
    grid = [list(r[:w].ljust(w)) for r in rows[:h]]
    while len(grid) < h:
        grid.append([" "] * w)
    return grid

Here the optional width and height let you impose a fixed field, as in a language with a set size. When no size is given, the grid fits the program.

Trailing spaces in the file are meaningless to the program and are often removed by editors. Because spaces are empty cells, either way is fine, but do not trim leading spaces. They are positions.

Programs larger than the field

What if the file has more rows or columns than the language allows? You have a few reasonable policies:

  • Report an error and stop.
  • Ignore the extra rows and columns, perhaps with a warning.
  • Grow the field, if the language supports unbounded space.

Silent truncation is the worst option, because the program will run but with part of its code missing. The choice also depends on the storage design, since an array fixed at load time cannot grow later; see the discussion of the playfield data structure.

Tabs

A tab character is a single character in the file but looks like several columns on screen. If your interpreter treats it as one cell, a program that looks aligned in an editor will not line up in the grid, and the pointer will take a path different from the one the author drew. Options include rejecting tabs, treating them as an unknown instruction, or expanding them to spaces at a fixed width. Rejecting or warning is the most honest approach, because any expansion is a guess at what the author meant.

Odd characters

Source files may contain characters outside plain ASCII. Decide on an encoding when you open the file, commonly UTF-8, and decide whether a cell holds a byte or a decoded character. This matters because a multi-byte character would otherwise occupy several cells and shift everything after it.

Other special cases:

  • Control characters such as form feeds or null bytes: treat them as unknown instructions, or reject the file.
  • A byte order mark at the start of the file: some editors add one. If you do not strip it, the first cell of the program is not what the author wrote.
  • Characters in unused cells: a stray symbol in a corner of the file may never run, which is dead code, but it is still data a program can read.

What an unknown character does at runtime is a separate question. Some definitions say the instruction is ignored; Funge-98 describes reflect behavior for unknown instructions, which turns the pointer around. Loading should preserve the character exactly and leave that decision to the execution loop.

Programs that read their own source

Since grid programs can read cells as data, the loaded characters are visible to the program. A quine-style program, for example, relies on seeing the characters exactly as written. That is another reason to avoid silently changing the text during loading. Keep the original characters, and apply padding only with spaces.

Command-line sources and other inputs

You may also want to accept source from a string, standard input or a built-in sample. If the loader takes text instead of a path, tests can call it directly without touching the file system. A small loader function that takes text and returns a grid is easy to cover with tests.

What to test

  • a one-row program and an empty file
  • rows of different lengths
  • a file with Windows-style line endings
  • a file with a trailing newline and one without
  • a file with a tab
  • a row longer than the field

Checking these early prevents puzzling failures later, and they fit well in the ladder of small programs recommended under interpreter testing.

The complete guide to interpreter implementation

Building Interpreters Step by Step
Read the guide

Keep exploring

Guides from other topics

All topics →

What Is Funge-98?

Funge-98 generalizes Befunge into a larger, better-defined family of grid languages. Learn its goals, its main differences from Befunge-93, and why it matters.

Funge-98 5 min read