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 ;
letis immutable andvaris 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 bincludes both ends.aandbmust be integers that are the same for every pixel: numbers, params,width,heightand expressions of them.
Expressions#
From lowest to highest precedence:
Level |
Operators |
Notes |
|---|---|---|
1 |
|
|
2 |
|
|
3 |
|
|
4 |
|
cannot be chained |
5 |
|
|
6 |
|
|
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 |
|---|---|---|
|
32-bit signed integers |
arithmetic wraps around, as in numpy |
|
32-bit floats, always finite |
overflow saturates at ±3.4028235e38 |
|
|
from comparisons, |
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 |
|---|---|
|
the coordinates of the pixel (int); |
|
the size of the canvas of the kernel or measure (int) |
|
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 |
|
the index of the scenario inside a |
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).