Language reference#

Small language. Strict rules. No data races.

The grammar of .vis, in EBNF. Line breaks are not significant: no statement starts with a symbol that could continue the previous expression, so ; is never required (it is accepted and ignored).

Lexical structure#

comment    = "//" { any character except newline } ;
number     = digits [ "." [ digits ] ] [ exponent ] | "." digits [ exponent ] ;
exponent   = ( "e" | "E" ) [ "+" | "-" ] digits ;
string     = '"' { any character except '"' and newline } '"' ;
name       = ( letter | "_" ) { letter | digit | "_" } ;   (* Unicode letters are accepted *)

A number with . or an exponent is a float; otherwise it is an int.

The keywords, the contextual words and the operators, with their meaning and precedence, are in Vocabulary; the normative rules in Language specification.

Reserved names: x, y, width, height, scenario and the names of the built-in functions.

Program#

program    = { param | canvas | kernel | measure | run } ;          (* at most one run *)

param      = "param" name "=" [ "-" ] number ;
canvas     = "canvas" name "[" expr "," expr [ "," expr ] "]" [ "edge" ( "clamp" | "wrap" | "zero" ) ]
             "{" { name [ ":" ( "f32" | "i32" ) ] [ "," ] } "}" ;
kernel     = "kernel" name "on" name block ;
measure    = "measure" name "=" reduction "(" expr ")" "on" name [ "where" expr ] ;
reduction  = "sum" | "mean" | "min" | "max" | "count" ;

The canvas size is [width, height], or [width, height, depth] for a 3-D canvas (depth up to 65535). In a 3-D canvas every read gives three coordinates, t[x, y, z], and the edge applies on the three axes. A channel is f32 (the default) or i32, and starts at 0. An i32 channel holds exact integers: it reads as an int, takes only int values (convert with int(...)), and with flux() (see the functions) a simulation in integer units conserves its totals exactly.

Kernel statements#

block      = "{" { statement } "}" ;
statement  = ( "let" | "var" ) name "=" expr
           | name ( "=" | "+=" | "-=" | "*=" | "/=" ) expr
           | "if" expr block [ "else" ( if | block ) ]
           | "for" name "in" expr "to" expr block ;
  • let is immutable and var is mutable; both must be initialised.

  • Assigning to a channel name writes the pixel of the kernel. Writing to channel[...] is a compile error (the pixel rule).

  • for i in a to b includes both ends. a and b must be integers that are the same for every pixel: numbers, params, width, height and expressions of them.

Expressions#

From lowest to highest precedence:

Level

Operators

Notes

1

or

2

and

3

not

4

== != < <= > >=

cannot be chained

5

+ -

6

* / %

/ is always real division

7

unary -

8

^

right-associative; binds tighter than unary -

primary    = number | "true" | "false" | name
           | name "[" expr "," expr "]"                  (* a pixel of this canvas *)
           | name "." name "[" expr "," expr "]"         (* a pixel of another canvas *)
           | name "(" [ expr { "," expr } ] ")"          (* a built-in function *)
           | "(" expr ")" ;

Types#

Type

Values

Notes

int

32-bit signed integers

arithmetic wraps around, as in numpy

float

32-bit floats, always finite

overflow saturates at ±3.4028235e38

bool

true, false

from comparisons, and, or, not; convert with float() or select()

An arithmetic operation is int only when both operands are int. x ^ n with an integer literal n up to 16 is an exact product and keeps the type of x; any other power is pow.

Coordinates#

Name

Value

x, y

the coordinates of the pixel (int); x grows to the right, y downwards

width, height

the size of the canvas of the kernel or measure (int)

z, depth

in a 3-D canvas: the layer of the cell (int, from 0) and the number of layers; in a 2-D program they are free names

scenario

the index of the scenario inside a scenarios block, from 0 (int, the same for every pixel); 0 outside

Reads outside the canvas follow its edge.

Run block#

run        = "run" "{" { command } "}" ;
command    = name                                            (* a kernel or a measure *)
           | "repeat" expr "{" { command } "}"
           | "scenarios" expr "{" { command } "}"                (* not nested *)
           | "load" name "." name string [ "nodata" [ "-" ] number ]
           | "save" name "." name string                     (* .png, .npy or .vti; a 3-D canvas: .npy or .vti *)
           | "save" name string                              (* a measure series, .csv; a whole canvas, .vti *)
           | "print" name                                    (* a measure *)
           | "print" name "." name [ "[" expr "," expr [ "," expr ] "]" ] ;

Running a kernel advances every pixel by one step: all reads see the state from before the kernel, and all writes appear after it. Running a measure appends one value to its series.

scenarios n { ... } runs its body n times, every time from the state at the start of the block, with scenario = 0, 1, …, n - 1 in the kernels; the measures inside collect one value per scenario, and print then shows their distribution (mean, 5th percentile, median, 95th percentile).