vision
Install

Language reference

Control flow

Vision branches with when, repeats work with three loop forms, and rejects expressions whose order is not visible. This page gives the exact surface and the failure rules behind it.

Every rule below describes the current Vision compiler. For a shorter introduction to values and list iteration, read Values, text, and lists first.

Conditional branches

An ordinary conditional starts with when condition:. Follow it with zero or more else when condition: branches and an optional else:. Each header except else: requires a yes/no condition. The keyword if does not open a branch; it is reserved for question-verb declarations such as to ask if.

Conditions combine with and, or, and prefix not. A flat chain of only and or only or is accepted. Mixing them without parentheses is a compile error because Vision will not choose precedence for the reader. Group the intended order: (ready and valid) or forced.

A branch introduces a scope. Names created inside it do not remain available after the branch. Presence facts are also branch-local; the values reference gives the exact join and reassignment rules for optional values.

Sequential loops

while condition repeats while a yes/no condition holds. The header ends at the newline and has no colon. loop: is the unconditional form. Both accept an indented statement block.

each item in collection: visits a list in position order and a map or set in insertion order. A map binds each key; a set binds each element. The loop name exists only inside the body. Ordinary collection iteration is spelled each, not for each or foreach. The separate for each lead belongs to channel receive loops and servers.

skip and stop

skip abandons the current iteration and continues with the next one. It is valid inside each, while, and loop:; using it outside a loop is a compile error. In nested loops it applies to the nearest enclosing loop.

stop is not a loop break. It ends the current verb after cleaning up its live resources. In to begin:, it exits the program successfully. In a parallel-each body, it ends only that strand. A verb that promises a value cannot reach a bare stop in its own body because that path produces no answer.

reference-control-flow.viscompiler checked
to describe(number):
    when the number is greater than 10:
        say "large"
    else when the number is 10:
        say "ten"
    else:
        say "small"

to scan until three(numbers):
    each number in the numbers:
        when the number is 3:
            stop
        say "seen {number}"
    say "unreachable after stop"

to count forever:
    the counter is 0
    loop:
        the counter is the counter plus 1
        when the counter is less than 3:
            skip
        say "loop count: {the counter}"
        stop

to begin:
    describe(10)

    the labels are "north", "east", "west"
    the heading is "northbound"
    when the heading starts with "north" and the heading contains "bound" and the heading ends with "bound":
        say "text comparisons: yes"
    when 5 is at least 5 and 5 is at most 5:
        say "numeric bounds: yes"
    when the heading is not "southbound":
        say "inequality: yes"
    when "east" is one of the labels:
        say "membership: yes"
    the empty labels are a list of text
    when the empty labels is empty:
        say "empty list: yes"
    the blank is ""
    when the blank is empty:
        say "empty text: yes"
    the empty map is a map from text to whole
    when the empty map is empty:
        say "empty map: yes"
    the empty goods are a set of text
    when the empty goods is empty:
        say "empty set: yes"
    the same labels are "north", "east", "west"
    when the labels is the same labels:
        say "list equality: yes"

    the numbers are 1, 2, 3, 4, 5, 6
    the odd total is 0
    each number in the numbers:
        when the remainder of the number divided by 2 is 0:
            skip
        the odd total is the odd total plus the number
    say "odd total: {the odd total}"

    the counter is 0
    while the counter is less than 3
        the counter is the counter plus 1
    say "while count: {the counter}"

    scan until three(the numbers)
    say "scan returned"
    count forever()
    say "loop returned"
to describe(number):
    when the number is greater than 10:
        say "large"
    else when the number is 10:
        say "ten"
    else:
        say "small"

to scan until three(numbers):
    each number in the numbers:
        when the number is 3:
            stop
        say "seen {number}"
    say "unreachable after stop"

to count forever:
    the counter is 0
    loop:
        the counter is the counter plus 1
        when the counter is less than 3:
            skip
        say "loop count: {the counter}"
        stop

to begin:
    describe(10)

    the labels are "north", "east", "west"
    the heading is "northbound"
    when the heading starts with "north" and the heading contains "bound" and the heading ends with "bound":
        say "text comparisons: yes"
    when 5 is at least 5 and 5 is at most 5:
        say "numeric bounds: yes"
    when the heading is not "southbound":
        say "inequality: yes"
    when "east" is one of the labels:
        say "membership: yes"
    the empty labels are a list of text
    when the empty labels is empty:
        say "empty list: yes"
    the blank is ""
    when the blank is empty:
        say "empty text: yes"
    the empty map is a map from text to whole
    when the empty map is empty:
        say "empty map: yes"
    the empty goods are a set of text
    when the empty goods is empty:
        say "empty set: yes"
    the same labels are "north", "east", "west"
    when the labels is the same labels:
        say "list equality: yes"

    the numbers are 1, 2, 3, 4, 5, 6
    the odd total is 0
    each number in the numbers:
        when the remainder of the number divided by 2 is 0:
            skip
        the odd total is the odd total plus the number
    say "odd total: {the odd total}"

    the counter is 0
    while the counter is less than 3
        the counter is the counter plus 1
    say "while count: {the counter}"

    scan until three(the numbers)
    say "scan returned"
    count forever()
    say "loop returned"
The fixture checks a branch chain, text and numeric comparisons, list membership, each with skip, colon-free while, and stop returning from the verb that contains a loop.

Parallel each

all at once each item in list: starts one isolated strand per list item and waits for all of them before continuing. The input must be a list. Result order is not defined, even when the input list is ordered. Do not depend on the order in which strands append.

A strand may read its item and values created inside its own body. It may add to an outer list used as a collect target; those appends are locked. It may not read that collect list, read an unrelated outer local, collect into a set, or open another parallel-each block. Those shapes are compile errors rather than unsynchronized races.

The parallel construct itself does not require >> when its body is pure, as in the fixture. A world-touching line inside the body still needs its own >>, and that effect makes the enclosing verb world-touching, so its declaration also needs >>.

reference-control-parallel.viscompiler checked
to begin:
    the inputs are 1, 2, 3, 4, 5, 6
    the outcomes are a list of wholes

    all at once each input in the inputs:
        the square is the input times the input
        add the square to the outcomes

    the total is 0
    each outcome in the outcomes:
        the total is the total plus the outcome

    say "results: {how many are in the outcomes}"
    say "sum: {the total}"
to begin:
    the inputs are 1, 2, 3, 4, 5, 6
    the outcomes are a list of wholes

    all at once each input in the inputs:
        the square is the input times the input
        add the square to the outcomes

    the total is 0
    each outcome in the outcomes:
        the total is the total plus the outcome

    say "results: {how many are in the outcomes}"
    say "sum: {the total}"
Six strands append squares under the compiler's collect lock. The test checks count and sum, not nondeterministic append order.

Arithmetic and grouping

The binary arithmetic forms are plus or +, minus or -, times or *, divided by or /, and to the power of. Prefix minus and a negative numeric literal both negate one value.

Vision does not assign precedence among binary operators. A chain of only additions or only multiplications is unambiguous and compiles. Mixed operators, repeated subtraction or division, and a bitwise operation combined with arithmetic must use parentheses. For example, write (base shifted left by 2) plus 1, not the ungrouped chain.

Whole-number addition, subtraction, multiplication, and negation trap on overflow. divided by gives a decimal result and exits with status 70 when its divisor is zero. Decimal arithmetic follows the selected float or double mode and can produce infinity after overflow.

Named math forms

Two-value selection uses the larger of A, B and the smaller of A, B. Variadic selection uses the largest of (A, B, ...) and the smallest of (A, B, ...), with max(...) and min(...) as shorter aliases. The defined use is numeric; mixing a whole and decimal promotes the result to decimal.

Unary decimal forms are the square root of value, the tangent of value, and the hyperbolic tangent of value. the whole part of value floors a decimal toward negative infinity and exits with status 70 if the result is not finite or does not fit a whole number.

the whole of A divided by B performs whole-number division. Both operands must be whole numbers, and a zero divisor exits with status 70. Current limitation: the signed 64-bit minimum divided by -1 has no overflow guard. That edge reaches undefined signed C division behavior and must be avoided.

keep value between low and high replaces a numeric local with the nearer bound when it falls outside the range. It is a statement, not an expression. The compiler lowers it as the larger of the low bound and the smaller of the current value and high bound; use bounds in low-to-high order.

Current limitation: the checker does not reject text or yes/no operands passed to the larger/smaller, largest/smallest, max/min, or keep forms. Those inputs have no supported ordering rule and can reach unsafe or invalid generated C. Use these forms only with numeric values.

reference-control-math.viscompiler checked
to begin:
    the sum is 8 plus 5 plus 2
    the difference is 20 minus 3
    the product is 6 times 7
    the quotient is 9 divided by 2
    the power is 2 to the power of 5
    the integer quotient is the whole of 17 divided by 5
    the floor is the whole part of -2.7
    the high is the larger of 8, 13
    the pair low is the smaller of 8, 13
    the peak is the largest of (9, 4, 7)
    the low is the smallest of (9, 4, 7)
    the alias high is max(9, 4, 7)
    the alias low is min(9, 4, 7)
    the root is the square root of 81
    the wave is the tangent of 0
    the smooth is the hyperbolic tangent of 0
    the short remainder is 17 mod 5
    the negative remainder is -17 mod 5
    the negative divisor remainder is 17 mod -5
    the clipped is 12
    keep clipped between 0 and 10

    say "sum: {the sum}"
    say "difference: {the difference}"
    say "product: {the product}"
    say "quotient: {the quotient}"
    say "power: {the power}"
    say "integer quotient: {the integer quotient}"
    say "floor: {the floor}"
    say "pair range: {the pair low}..{the high}"
    say "list range: {the low}..{the peak}"
    say "alias range: {the alias low}..{the alias high}"
    say "functions: {the root}, {the wave}, {the smooth}"
    say "mod: {the short remainder}"
    say "negative mod: {the negative remainder}"
    say "negative divisor mod: {the negative divisor remainder}"
    say "clamped: {the clipped}"
to begin:
    the sum is 8 plus 5 plus 2
    the difference is 20 minus 3
    the product is 6 times 7
    the quotient is 9 divided by 2
    the power is 2 to the power of 5
    the integer quotient is the whole of 17 divided by 5
    the floor is the whole part of -2.7
    the high is the larger of 8, 13
    the pair low is the smaller of 8, 13
    the peak is the largest of (9, 4, 7)
    the low is the smallest of (9, 4, 7)
    the alias high is max(9, 4, 7)
    the alias low is min(9, 4, 7)
    the root is the square root of 81
    the wave is the tangent of 0
    the smooth is the hyperbolic tangent of 0
    the short remainder is 17 mod 5
    the negative remainder is -17 mod 5
    the negative divisor remainder is 17 mod -5
    the clipped is 12
    keep clipped between 0 and 10

    say "sum: {the sum}"
    say "difference: {the difference}"
    say "product: {the product}"
    say "quotient: {the quotient}"
    say "power: {the power}"
    say "integer quotient: {the integer quotient}"
    say "floor: {the floor}"
    say "pair range: {the pair low}..{the high}"
    say "list range: {the low}..{the peak}"
    say "alias range: {the alias low}..{the alias high}"
    say "functions: {the root}, {the wave}, {the smooth}"
    say "mod: {the short remainder}"
    say "negative mod: {the negative remainder}"
    say "negative divisor mod: {the negative divisor remainder}"
    say "clamped: {the clipped}"
The fixture covers every named math family, both variadic aliases, decimal division, whole division, floor, power, and the short remainder form.

Comparisons

Equality uses is; inequality uses is not. Ordering uses is greater than, is at least, is at most, and is less than. Ordering is numeric only. Text and lists have no ordering relation.

A tagged-union condition is the exception to ordinary equality: when result is a found: compares the active variant tag and narrows that branch so its payload can be read. A chain of variant branches must cover every variant or end in else:. See Tagged unions for construction and payload rules.

Text equality compares content. starts with, ends with, and contains are text predicates. The form value is one of list scans a list for equal content, and value is empty accepts text, lists, maps, and sets. The grammar still recognizes matches, but the checker always rejects it; Vision has no matching operator.

Both comparison operands must have compatible types. Numeric widths may compare across whole, decimal, and byte. Lists compare by content only when their elements are comparable scalars or text. Described things, maps, and sets have no supported whole-value equality; compare fields or entries, or use set has value. The checker rejects described things and maps, but currently lets set is set reach invalid generated C. Avoid that gap.

Remainder / modulo

The full form is the remainder of A divided by B. The short form is A mod B. The percent sign is not a Vision operator. Both operands must be whole numbers, and the result is a whole number.

Division truncates toward zero, matching the generated C remainder. A negative result therefore follows the dividend: -17 mod 5 gives -2, while 17 mod -5 gives 2.

A literal zero divisor is a compile error. A divisor that becomes zero at runtime exits with status 70 and prints vision: the remainder by zero is undefined to standard error. Group a remainder before combining it with another operator. Like whole division, the signed 64-bit minimum modulo -1 has no overflow guard; it reaches undefined signed C remainder behavior and must be avoided.

Bitwise operations

The six prose forms are the bits of A and B, the bits of A or B, the differing bits of A and B, the flipped bits of A, A shifted left by B, and A shifted right by B. Their compact aliases are bit-and, bit-or, bit-xor, bit-not, shl, and shr.

Operands are whole numbers or bytes, and every result is a signed 64-bit whole number. Right shift is arithmetic, so it preserves the sign of a negative value. A negative shift count exits with status 70. A left shift by 64 or more gives 0; a right shift by 64 or more gives 0 for a non-negative value and -1 for a negative value.

reference-control-bitwise.viscompiler checked
to begin:
    the band is the bits of 12 and 10
    the bor is the bits of 12 or 10
    the bxor is the differing bits of 12 and 10
    the bnot is the flipped bits of 0
    the shl is 5 shifted left by 2
    the shr is 20 shifted right by 2

    the compact band is 12 bit-and 10
    the compact bor is 12 bit-or 10
    the compact xor is 12 bit-xor 10
    the compact inverted is bit-not 0
    the compact left is 5 shl 2
    the compact right is 20 shr 2

    the wide left is 1 shifted left by 64
    the negative is the flipped bits of 0
    the wide right is the negative shifted right by 64

    say "prose: {the band}, {the bor}, {the bxor}, {the bnot}, {the shl}, {the shr}"
    say "aliases: {the compact band}, {the compact bor}, {the compact xor}, {the compact inverted}, {the compact left}, {the compact right}"
    say "wide: {the wide left}, {the wide right}"
to begin:
    the band is the bits of 12 and 10
    the bor is the bits of 12 or 10
    the bxor is the differing bits of 12 and 10
    the bnot is the flipped bits of 0
    the shl is 5 shifted left by 2
    the shr is 20 shifted right by 2

    the compact band is 12 bit-and 10
    the compact bor is 12 bit-or 10
    the compact xor is 12 bit-xor 10
    the compact inverted is bit-not 0
    the compact left is 5 shl 2
    the compact right is 20 shr 2

    the wide left is 1 shifted left by 64
    the negative is the flipped bits of 0
    the wide right is the negative shifted right by 64

    say "prose: {the band}, {the bor}, {the bxor}, {the bnot}, {the shl}, {the shr}"
    say "aliases: {the compact band}, {the compact bor}, {the compact xor}, {the compact inverted}, {the compact left}, {the compact right}"
    say "wide: {the wide left}, {the wide right}"
The fixture checks all six prose operations, all six aliases, and both defined results for shifts by 64.

Ownership and effects

Branches and sequential loops do not transfer ownership. An each binding is scoped to its body, and the collection remains owned by its surrounding scope. Arithmetic, comparisons, remainder, and bitwise operations produce scalar values and allocate no managed heap result.

Parallel-each creates joined worker strands. Each strand owns its local temporaries; the surrounding scope keeps ownership of the input and any collect lists. The join completes before the next statement runs, so those collections remain valid after the block. Effects inside any control-flow body follow the same >> rules as straight-line code.