Co is an esoteric programming language where everything is a coroutine and the only non-IO operation is coroutine transfer.
# starts a comment that lasts until the end of the line.
:<>|#" and whitespace are reserved. Any other character can be used anywhere in a name (including at the beginning).
Whitespace (including newlines) separates tokens but is otherwise not significant (except inside string literals).
A Co program consists of a sequence of coroutine definitions. A coroutine definition starts with a name followed by a colon, and ends when the next definition begins. The names
. are reserved and cannot be used as the name of a coroutine. A coroutine named
main must exist; the program starts by transferring control to
Each coroutine has its own set of local variables and its own instruction pointer. When control is transfered to a coroutine, it'll start where it left off.
A statement of the form
coroutine < argument transfers control to the coroutine coroutine, passing it the argument argument. coroutine and argument can be the name of a coroutine, a local variable, or
. refers to the currently-running coroutine.
A coroutine can receive arguments by putting
. is used instead of a variable name, then that value is discarded. Local variables can shadow global coroutine names (although shadowing
dup could cause problems).
| coroutine is a shorthand for
.>tmp coroutine<tmp (where
tmp is an unused variable name); i.e., it takes the argument that it was passed and immediately passes it to a different coroutine.
The last coroutine call in a definition replaces the current coroutine instance with the one that was called. In other words, if you have the coroutine
foo: # ... bar < whatever something-else: # ...
then after the
bar < whatever line executes, attempting to transfer control to
foo will instead transfer control to
bar, and for all intents and purposes
bar refer to the same object. If the last statement of a coroutine transfers control to itself (including by transferring to a coroutine that's been replaced by the current routine), then the program halts.
You can output a string by putting a double-quoted string literal before a coroutine transfer. Escapes
\" are recognized; a backslash before any other character will just cause the backslash to be removed.
Output is not a separate statement type; rather, there's just one statement type that can do multiple things at once. The general form of a statement is
var1 > var2 output input coroutine < argument, where the parts have to go in that order, although each part except the last is optional.
Input has the form
[var1] [var2] [var3] (any number of bracketed variables), where var1, var2, and var3 are coroutine names or local variables (anything that can be used as a coroutine or argument). The names of these variables are used as labels for buttons the user can click.
For synchronous input, the statement should transfer control back to the current coroutine (
.<. or something similar; the argument is discarded so it doesn't matter). The program will wait for the user to click a button, and then return which button the user clicked as the argument. For instance, if you have something like this:
[a] [b] [c] . < . . > var
and the user clicks "b", then
var will contain the value of variable
If control is transferred somewhere else, buttons are shown, the current coroutine is placed in a "waiting for input" state, and execution continues normally. Once control is passed back to a coroutine that's waiting for input, the program will wait for the next button press (or see the next button press in the queue) and transfer control to whichever coroutine generated the button that was pressed.
Transferring control to the
poll coroutine will check without waiting if a button has been pressed; if it has, it'll transfer control to whichever coroutine generated the button; if it hasn't, it'll transfer control back to whichever coroutine called
poll. Button presses can only affect the program when control is transferred to a waiting coroutine or
Transferring control to the
dup coroutine will create a duplicate of its argument and pass that duplicate back to the coroutine that called it. For instance, if you do something like
dup < foo . > bar
bar will contain a duplicate of
foo. The duplicate has the same code as the original and starts with the instruction pointer in the same place and the local variables with the same value. However, continuing to execute one coroutine will affect the instruction pointer and variables of only that coroutine and not the other one. Duplicates are never waiting for input.
deep works like
dup, except it creates a deep copy; that is, the coroutines in all local variables are also duplicated, as are the local variables in those coroutines, etc.
As a shortcut, if you try to use (not assign to) a nonexistent variable with a slash in its name, but the part before the last slash is an existing variable, then the interpreter will insert a call to
dup before the statement. For instance, if
foo exists but not
foo/x, then something like
foo/x < . will become
dup < foo . > foo/x foo/x < .
Further references to
foo/x will continue to refer to that same duplicate, since now
foo/x has a value. Note that
/ is not a reserved character, and names containing
/ can still be used as normal names.
Duplication is necessary for the language to be Turing complete; otherwise, there would be a fixed finite number of coroutines with a fixed finite number of local variables which can have a fixed finite number of possible values and so you couldn't have unbounded storage.
A Ⅎ program (with no restrictions on recursion or parentheses, but without
let expressions) can be translated into Co as follows:
prev > name prev < .at the top of the coroutine, in order (takes an argument, saves it in a local variable, then returns the current coroutine). The last parameter doesn't have the
prev < ..
f/n < x . > fx, where
fxare new, unused names. A complicated application like
f (x y) zwould become an application of
x yinto a variable
xy, then an application of
fxy, then an application of
prev < xwhere
xis whatever variable contains the result.
Any lambda calculus expression can be translated into Ⅎ by replacing each lambda expression with a named function that takes closure variables as parameters and then the lambda variable as its last parameter. This means that any lambda calculus expression can be translated into Co.