Zipper
Zipper is a small text-replacement programming language. Its complete program state is one string called the tape; the interpreter scans to the right of a visible cursor (▮), finds the next reducible call, replaces it in place, and repeats. The tape can be printed after every replacement, turning evaluation into a readable sequence of steps.
A Zipper program looks like nested function calls:
▮seq(
define(
greet,
who,
q{
seq(
emit("Hello, "),
who(),
emit("!\n")
)
}
),
greet(emit("Jeremy")),
emit("Done.\n")
)
It produces:
Hello, Jeremy!
Done.
The interesting part is how it gets there. A simple expression changes directly on the tape:
▮seq(emit("A"), emit("B"))
▮emit("A") seq(emit("B"))
▮A seq(emit("B"))
A ▮emit("B")
A ▮B
AB
seq exposes one expression at a time, emit replaces itself with literal output, and define replaces itself with a stored function definition. Bodies inside q{...} are delayed until called. Function arguments become zero-argument thunks, so they are substituted and evaluated only where the body asks for them; definitions also capture the definitions already on the tape, which provides lexical closures.
The cursor always moves into the newest replacement. Printing the formatted tape at each step makes function expansion, argument substitution, output, and closure capture visible instead of hiding them inside the interpreter.
The source and example program are in the racketprogs repository.