Expressions
Expressions compute values. Visript supports a full expression system with arithmetic, comparison, boolean logic, arrays, and function calls.
Literals
42 // Integer
3.14 // Float
"hello" // String
true // Boolean
false // Boolean
Arithmetic
1 + 2 // 3
10 - 3 // 7
4 * 5 // 20
10 / 3 // 3.333... (division is always float)
10.0 / 3.0 // 3.333...
10 \ 3 // 3 (integer division)
10 % 3 // 1 (modulus keeps integers)
Mixed types are promoted to float:
1 + 2.5 // 3.5 (integer promoted to float)
Division always gives a float
+, - and * keep two integers as an integer, but / always produces a
float — 1 / 2 is 0.5, not 0.
This matters because a lot of what you divide is an integer without looking
like one. $TIME_MS and $FRAME_COUNT are integers, so under truncating division
something like
on_frame {
// Would step 0, 1, 2, 3 … one whole radian every 5 seconds
angle = math::sin(rad: $TIME_MS / 5000)
}
would snap between whole values instead of moving smoothly.
% is left alone, because integer modulus is usually what you want:
on_frame {
if $FRAME_COUNT % 60 == 0 {
flash = true
}
}
Integer division with \
When you want a whole number, use \. It divides and throws away the
fraction, always giving an integer:
let columns = $WIDTH \ 100 // how many 100px columns fit
let bucket = i \ 8 // group an index into eights
It accepts floats as well as integers — $WIDTH and $HEIGHT are floats, and
requiring a conversion first would defeat the point:
7.5 \ 2 // 3
\ truncates toward zero, so -7 \ 2 is -3. If you want it to round
down instead, use math::floor:
math::floor(value: -7 / 2) // -4
Dividing by zero is an error, the same as with /.
Comparison
1 == 1 // true
1 != 2 // true
3 < 5 // true
5 > 3 // true
3 <= 3 // true
5 >= 4 // true
Boolean Logic
true && false // false
true || false // true
!true // false
&& and || short-circuit: if the left side settles the answer, the right
side is never worked out at all. That is what lets one side guard the other:
on_frame {
// The division only happens when n is non-zero
if n != 0 && total \ n > 5 {
flash = true
}
}
Both sides must be booleans. 1 && true is an error rather than treating
non-zero as true — a number is not a truth value here.
Bitwise Operators
&, | and ^ work on the bits of whole numbers:
6 & 3 // 2 — bits set in both
6 | 3 // 7 — bits set in either
6 ^ 3 // 5 — bits set in exactly one
They are useful for packing several on/off flags into one property, or for cycling through a power-of-two range:
prop flags = 0
on_frame {
flags = flags | 4 // turn a flag on
if (flags & 4) > 0 { // test it — note the parentheses
pulse = 1.0
}
}
Whole numbers only. 6.5 & 3 is an error rather than a silent truncation,
since a bit pattern is not a meaningful notion for a float — use value \ 1 to truncate, or
math::floor(value: value) \ 1 to round down and convert.
Note that & is a different operator from &&, and | from ||. The
doubled forms are the boolean ones.
String Operations
"hello" + " " + "world" // "hello world"
Arrays
[1, 2, 3]
[[100.0, 200.0], [300.0, 400.0]]
Indexing
Read a single element with [...], counting from zero:
let first = audio::detect::get_spectrum()[0]
let bass = audio::detect::get_spectrum()[4]
The index can be any whole-number expression, and indexing chains:
let v = audio::detect::get_spectrum()[i * 2]
let y = points[1][0]
Reading past the end gives 0 rather than failing. That is deliberate: an index may be outside an array’s bounds, so an error would break every audio-reactive script the moment it fell silent. A negative index also reads as 0.
The index must have integer type. Use value \ 1 to truncate or
math::floor(value: value) \ 1 to round down:
let v = audio::detect::get_spectrum()[$WIDTH \ 40]
Grouping
Use parentheses to control precedence:
(1 + 2) * 3 // 9
1 + (2 * 3) // 7
Color Constructors
color::rgb(r: 1.0, g: 0.5, b: 0.0)
color::hsl(h: 0.5, s: 0.8, l: 0.5)
See Color Constructors for details.
Function Calls
my_func(x: 1.0, y: 2.0)
See Functions for details.
Operator Precedence
From lowest to highest. This follows C, which is what most languages use:
||(logical or)&&(logical and)|(bitwise or)^(bitwise xor)&(bitwise and)==,!=<,<=,>,>=+,-*,/,\,%!,-(unary)[...](indexing)()(grouping)
One consequence is worth knowing, because it surprises people in every language
that inherits it: the bitwise operators bind more loosely than ==. So
flags & 4 == 4
groups as flags & (4 == 4), which is a type error rather than the test you
meant. Parenthesise when you mix them:
(flags & 4) == 4
Assignment is a statement, not an operator, so it does not appear here. See
Variables & Assignment for =, the compound forms and ++.
Creating and updating arrays
Engine 2.4.0 adds array::filled(count:, value:) and indexed assignment:
on_init {
let values = array::filled(count: 4, value: -1)
values[0] = 10
values[1] += 2
let rows = array::filled(count: 2, value: [0, 0])
rows[0][1] = 7
}
Both constructor arguments are required. count must be a finite whole number
from 0 to 65,536; value is evaluated once and copied into each element. Empty
arrays ([]) are supported. Nested arrays are independent copies, so changing
one row leaves the others unchanged. Assigning an array to another variable or
passing it into a function also copies it; return the modified array to update
the caller’s value.
Writes support =, +=, -=, *=, /= and %=. Each index is evaluated once,
from left to right, followed by the right-hand expression. A write index must
have integer type and be within the existing bounds. To convert a float use
value \ 1 (or math::floor(value: value) \ 1 to round down). A failed bounds
check or arithmetic operation leaves the target element unchanged and reports
the statement’s source location. Writes never grow arrays. Audio snapshots
such as audio::detect::get_spectrum() remain read-only; copy selected samples
into an array
created with array::filled or a literal.
The constructor also limits the total copied value size to 1,000,000 units: array nodes and scalar values each count as one, and strings additionally count by UTF-8 bytes. Copying consumes the existing execution budget, including when constructors are nested or called repeatedly in a loop. Large nested values can therefore reach the budget before the element-count limit.