Language reference
Concurrency
Vision runs bounded work in strands, moves values through typed channels, and provides two blocking event-loop forms. The compiler keeps strand-local state isolated and makes every join explicit.
Every rule below describes the current Vision compiler. For the shorter safety model, read Safe by design. The Control-flow reference covers parallel iteration, which uses the same isolated-worker model but has a different surface and collection rule.
Channels
the jobs is a channel of whole number creates a typed, bounded FIFO queue with room for 128 values. Add an explicit bound with the jobs is a channel of whole number, with room for 8. A literal capacity must be a positive whole number. A whole-number expression is also accepted; if its runtime value is zero or negative, the compiler silently uses the default 128 slots.
A channel may carry text, whole number, decimal, byte, or yes or no. Lists, maps, sets, described things, enums, unions, callables, secrets, and handles are not valid channel element types.
Send and receive
send value to channel waits while the channel is full. The value must match the declared element type. A receive has the exact form receive from channel returning name, immediately followed by but if the channel is closed, resolution. It waits while the channel is empty and open. Once the queue is both drained and closed, the clause runs with the specialized channelclosed failure.
There is no manual close statement. A channel closes when the last concurrent worker that sends to it exits. Creating a channel, or sending from the parent strand, does not establish that sender credit; after a parent-only queue drains, another receive waits rather than reporting closed. Put finite producers in an all at once: block when a receiver must observe completion.
Sends from one strand retain that strand's program order. Concurrent senders may interleave in any order. Each queued value is received once by one waiting receiver; a channel shared by several receivers is a work queue, not a broadcast channel.
Receive loops
for each item received from channel: receives repeatedly, binds one item inside the body, and ends normally after the channel is drained and closed. It needs no closed-channel clause. This specializedfor each spelling is not ordinary collection iteration.
>> to begin:
the work is a channel of whole number, with room for 2
>> all at once:
ask a strand called alpha:
send 1 to the work
send 2 to the work
send 3 to the work
ask a strand called beta:
send 10 to the work
send 20 to the work
send 30 to the work
ask a strand called counter, returning the packed:
the count is 0
the total is 0
for each item received from the work:
the count is the count plus 1
the total is the total plus the item
answer (the count times 1000) plus the total
say "count-and-sum proof: {the packed}">> to begin:
the work is a channel of whole number, with room for 2
>> all at once:
ask a strand called alpha:
send 1 to the work
send 2 to the work
send 3 to the work
ask a strand called beta:
send 10 to the work
send 20 to the work
send 30 to the work
ask a strand called counter, returning the packed:
the count is 0
the total is 0
for each item received from the work:
the count is the count plus 1
the total is the total plus the item
answer (the count times 1000) plus the total
say "count-and-sum proof: {the packed}"General strands
General work appears only inside all at once:. The side-effecting form is ask a strand called label: followed by an indented body; it cannot answer. The result form adds , returning name:. The checker accepts a result body when its last statement is answer, or when its last statement is an exhaustive conditional and every branch ends the same way. A branch that stops while another answers does not satisfy this structural rule. The answer must be a whole, decimal, yes or no, byte, or text; other result types are rejected.
Result names are created in the enclosing scope and become usable after the block joins. They do not imply a completion order. A text answer is copied or transferred into caller-owned storage; scalar answers cross through plain value slots.
All at once
all at once: starts every indented fetch strand or general strand, then joins every one before the next statement runs. The number of general strands is fixed by the source. A thread-start failure is a fatal runtime error with status 70; it is not caught by the block's failure clause.
A fetch strand has the exact form ask host for expression returning name. It fetches the expression from the named host into a text result, and can fail with unreachable. Add , or nothing after the result name to suppress that failure and leave the text absent. Because a fetch reaches the network, its block and containing verb require >>.
all at once each item in list: is the dynamic, result-collecting form. It may add to an outer list under the compiler's lock, limits live workers to batches based on available processors, and does not preserve append order. See the parallel-each rules for its exact isolation and effect behavior.
Concurrent failure
A fetch strand can fail when its host is unreachable. A general strand can pass a failure from one of its own statements. In both cases, the enclosing block handles the aggregate once with a final | any: resolution clause. The block waits for every strand before applying that resolution, and it does not expose which strand failed or how many did.
Add , or nothing to an individual fetch or result strand to suppress that strand's passed failure and leave its result absent. If every potentially failing strand is optional, a | any:clause is rejected because there is nothing for it to catch. Optional scalar strand results still meet the general presence limitation: whole, decimal, and byte values cannot currently be narrowed with the presence forms, while yes/no presence syntax tests truth rather than optional state. Optional text results can be tested with is something, is nothing, or when there is.
stop inside a strand ends that worker; the text receive-loop exception under Ownership and cleanup applies.pass marks the strand failed for the enclosing block. give up exits the whole process immediately. A block clause follows the ordinary resolution boundary: in a helper verb,| any: stop returns from that helper; in to begin:, it ends the program successfully.
>> to attempt work:
>> all at once:
ask a strand called parser, returning the number:
"not-a-number" as a number returning the parsed
| failed: pass
answer the parsed
| any: say "parallel work failed" and stop
say "unreachable"
>> to begin:
>> all at once:
ask a strand called optional, returning the message, or nothing:
"still-not-a-number" as a number returning the parsed
| failed: pass
answer "parsed {the parsed}"
when the message is nothing:
say "optional result is absent"
>> attempt work()
say "the caller continued">> to attempt work:
>> all at once:
ask a strand called parser, returning the number:
"not-a-number" as a number returning the parsed
| failed: pass
answer the parsed
| any: say "parallel work failed" and stop
say "unreachable"
>> to begin:
>> all at once:
ask a strand called optional, returning the message, or nothing:
"still-not-a-number" as a number returning the parsed
| failed: pass
answer "parsed {the parsed}"
when the message is nothing:
say "optional result is absent"
>> attempt work()
say "the caller continued"Isolation and effects
A general strand may use values it creates, kind and verb declarations available at file scope, and outer channels through send, receive, or a receive loop. Reading or writing an unrelated local from the enclosing verb is a compile error. Start another all at once: or parallel-each block inside a general strand and the compiler rejects the nested concurrency. Do not put serve or respond inside a general strand. Those shapes are not safely supported: some are rejected through mistaken outer-local checks, while an accepted serve can produce C that does not compile.
Channel construction, send, receive, and receive loops are not world touches by themselves. Their value or capacity expressions can still contain a world touch. The current marker model classifies a general all at once: block as world-touching even when all its work is local, so the block line and its containing verb declaration both need >>. World-touching statements inside a strand keep their own markers as well.
Frames and keypresses
respond to each frame with handler repeatedly calls an already-declared zero-parameter verb by name, then sleeps for 16,666,667 nanoseconds. It is a blocking loop with a fixed post-handler delay, no rate argument, and no completion value. Handler time is added to that delay, so the loop does not compensate to maintain 60 Hz. A stop inside the handler returns from that handler only; the frame loop continues. A SIGTERM or SIGINT is drained between frames and ends the process cleanly.
Current limitation: the handler check enforces arity but not its answer or failure signature. A value-returning handler's answer is discarded, and a fallible handler's passed failure is ignored by the generated frame loop. Use a zero-parameter handler that answers nothing and handles every failure internally.
respond to each keypress: is the indented block form. On a terminal it temporarily disables canonical input and local echo. Each input byte becomes the key for one turn; newline and carriage return become "enter", and a space becomes "space". Other bytes become one-byte text, so a multi-byte UTF-8 character arrives over several turns. End-of-input ends the loop normally. stop in this inline body returns from the containing verb. In to begin:, the resulting process exit restores terminal settings through the registered exit handler.
Each event source has one shape: frame requires a named handler, and keypress requires a block. Other source names, a frame block, a keypress handler, or a frame handler with parameters are compile errors. Both event-loop lines and their containing verb declarations require >>.
to tick:
say "tick"
>> to animate frames:
>> respond to each frame with tick
>> to begin:
say "keypress loop ready"
>> respond to each keypress:
when the key is "q":
stop
say "key: {the key}"to tick:
say "tick"
>> to animate frames:
>> respond to each frame with tick
>> to begin:
say "keypress loop ready"
>> respond to each keypress:
when the key is "q":
stop
say "key: {the key}"Ownership and cleanup
A channel is owned by the scope that creates it and is freed when that scope closes. Scalar sends copy the value. A text send makes a private copy owned by the channel; receive moves that copy to the receiver, and the receiver's scope frees it. Text still buffered when the channel is freed is reclaimed by the channel.
The enclosing scope owns joined text results and frees them normally. A keypress body borrows the key for the current turn; a normally completed turn releases it.
Current text-channel limitations: a receive-loop item is released only at the bottom of a completed iteration, so stop or skip in that iteration leaks the received text. Sending newly allocated text, such as an interpolation written directly in a send, also leaves the sender's temporary allocation unreclaimed after the channel makes its private copy. Prefer a named text value for sends, and let text receive-loop iterations reach their end.
Current limitation: stop or answer from a keypress block inside a helper, or pass from one of its failure clauses, returns before the loop's per-key free and immediate terminal restore. The terminal's exit handler restores it only when the process later exits, and that key allocation is not reclaimed by Vision first. Keep early keypress-loop exits in to begin:, as the fixture does; pass is rejected there because begin has no caller.