Co

Co is an esoteric programming language where everything is a coroutine and the only non-IO operation is coroutine transfer.

Contents

Lexer stuff

# starts a comment that lasts until the end of the line.

The characters []:<>|#" 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).

Coroutines

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 dup, deep, poll, and . 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 main.

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 var1 > var2 at the beginning of the routine or directly after a coroutine transfer. This creates or reassigns local variables var1 and var2; var1 is set to the coroutine that just transferred control to this coroutine, and var2 is set to the argument that was passed. The variables refer to the same instance of the coroutine as what was passed (so, Java/JavaScript/Python/etc.-style pass-a-reference-by-value). If . 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).

The syntax | 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 foo and 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.

Input and output

You can output a string by putting a double-quoted string literal before a coroutine transfer. Escapes \n, \\, and \" 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

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 b.

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 poll.

Duplication

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

then 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.

Translation from

A program (with no restrictions on recursion or parentheses, but without let expressions) can be translated into Co as follows:

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.

Interpreter