Funge-98 in Depth

Running Several Pointers with the Split Instruction

How the Funge-98 split instruction creates extra instruction pointers, how they interleave on a shared grid, and how each one ends, with design cautions.

On this page
  1. One program, several pointers
  2. What the new pointer inherits
  3. How the pointers interleave
  4. Ending pointers and ending the program
  5. A small illustration
  6. Design cautions
  7. When splitting is a good idea

One program, several pointers

In the original Befunge, one pointer runs through the program from start to finish. Funge-98 allows many. The instruction t, called split, creates a new pointer at the cell where it was executed. After the split there are two pointers sharing one program and one grid, both continuing from that point.

This is concurrency in a very literal form. There are no threads or locks, just several markers moving across the same cells. The result is a style of programming that feels more like choreography than like a to-do list.

What the new pointer inherits

When a pointer splits, the new one starts as a copy of the original in most respects:

  • It begins at the same position.
  • It gets its own copy of the stack of stacks, so the two pointers do not share a stack after the split. Values pushed by one are not visible to the other.
  • It carries the same storage offset.
  • Its movement is reversed. The new pointer starts by heading in the opposite direction from the parent. A parent moving east produces a child moving west.

Each pointer also has an identifier, which can be read with the system information instruction. A program can use that value to behave differently depending on which pointer is running it, which is how one piece of code can serve two roles.

What the two pointers do share is the grid. If one pointer writes to a cell, the other sees the change when it reads or executes there. This is useful for communication and is also the main source of bugs.

How the pointers interleave

Funge-98 describes execution in ticks. In each tick, every active pointer executes one instruction. Within a tick the pointers are generally taken in a consistent order, such as by identifier, but the details are the interpreter’s to define, and you should not depend on a particular order for correctness.

Some consequences:

  • Two pointers on the same cell in the same tick each run that cell’s instruction, in turn.
  • One output instruction per tick per pointer means output from different pointers tends to alternate rather than be grouped.
  • A single instruction counts as one step even if it is a complex one. Pointers move in lockstep per instruction, not per unit of real time.
  • The jump or skip instructions still count as a single step, so a pointer that skips ahead gets “ahead” of one that does not.

If you write a program where two pointers print, expect interleaved output, and design for it by letting only one pointer print at a time or by making the order irrelevant.

Ending pointers and ending the program

The two ways of stopping behave very differently:

  • The @ instruction ends only the pointer that executes it. Others continue. The program ends when no pointers remain.
  • The q instruction ends the whole program immediately, with an exit code.

A frequent beginner mistake is to expect the program to stop when the first pointer finishes, and then to get an apparently frozen program because another pointer is running in a loop forever. Another is to use q carelessly and cut off output that other pointers had not yet produced. Decide on one clear way of stopping, and arrange for it deliberately.

A small illustration

Consider a pointer running east that meets a t. After the split there are two pointers: the original continues east along the same row, and the child heads west, back along the cells the parent just crossed. If those cells contain instructions, the child will execute them in reverse order. Usually, you want the child to turn onto a different row immediately, so the cell after the split often holds a turn instruction that only makes sense for one of them. A common trick is to put a direction-changing cell where the child will land, and to let the parent pass through a layout that the child’s turn avoids. Working this out on paper with two markers and a list of ticks is the best way to learn it.

Design cautions

Shared cells. If pointers modify the same cell, the order matters. Use separate regions of the grid for separate pointers unless sharing is the whole point.

Self-modifying code. Code that rewrites itself becomes much harder to reason about when more than one pointer might be reading it. Be especially careful with self-modifying code in a multi-pointer program.

Unbounded splitting. A split placed in a loop creates a new pointer on every pass. A program can quickly create many pointers and consume memory. Keep splits out of loops unless the number is bounded, or have the extra pointers end themselves.

Testing. A multi-pointer program is difficult to test by looking at output alone. A trace that prints each pointer’s position and the tick number is more informative, and building a trace visualizer helps. Interpreters also differ in subtle scheduling details, so test on more than one if you can.

Reproducibility. If the program also uses the question-mark instruction, the random direction choices add another source of variation. Combining randomness with concurrency makes bugs hard to repeat, so add one at a time.

When splitting is a good idea

Concurrency suits problems where independent searches proceed together, such as exploring several paths of a maze, or where a pattern naturally repeats across the grid. It also makes for entertaining puzzles, since each pointer behaves like a small actor. For most everyday programs a single pointer is simpler and just as good, and it is wise to treat the feature as a specialist tool.

The complete guide to funge-98 in depth

Funge-98 Features in Depth
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