Operators
This page provides a complete reference for all operators in Tova, organized by category and including a full precedence table.
Arithmetic Operators
| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 3 + 4 | 7 |
- | Subtraction | 10 - 3 | 7 |
* | Multiplication | 4 * 5 | 20 |
/ | Division | 10 / 3 | 3.333... |
% | Modulo | 10 % 3 | 1 |
** | Power | 2 ** 10 | 1024 |
For string concatenation, use + or string interpolation:
"hello" + " " + "world" // "hello world"
"{first} {last}" // string interpolation (preferred)The * operator supports string repetition when used with a string and a number:
"-" * 40 // "----------------------------------------"
"abc" * 3 // "abcabcabc"Unary - negates a number:
x = 42
y = -x // -42Comparison Operators
| Operator | Name | Example |
|---|---|---|
== | Equal | x == 10 |
!= | Not equal | x != 10 |
< | Less than | x < 10 |
<= | Less than or equal | x <= 10 |
> | Greater than | x > 10 |
>= | Greater than or equal | x >= 10 |
Chained Comparisons
Tova supports chained comparisons, which read naturally as mathematical inequalities:
1 < x < 10 // true when x is between 1 and 10 (exclusive)
0 <= score <= 100 // true when score is in [0, 100]
a < b < c < d // true when a < b AND b < c AND c < dEach intermediate value is evaluated only once. Chained comparisons desugar to a conjunction of pairwise comparisons.
Logical Operators
Tova provides both keyword and symbolic forms. The keyword forms are idiomatic:
| Keyword | Symbol | Meaning | Example |
|---|---|---|---|
and | && | Logical AND | x > 0 and x < 10 |
or | || | Logical OR | x == 0 or x == 1 |
not | ! | Logical NOT | not is_empty |
Both forms are fully interchangeable:
// Keyword style (preferred)
if is_valid and not is_expired {
process(item)
}
// Symbol style (also valid)
if is_valid && !is_expired {
process(item)
}Logical operators use short-circuit evaluation: and does not evaluate the right operand if the left is falsy, and or does not evaluate the right if the left is truthy.
Membership Operators
| Operator | Meaning | Example |
|---|---|---|
in | Contained in | "a" in list |
not in | Not contained in | x not in banned |
if item in allowed_items {
accept(item)
}
if user not in banned_users {
grant_access(user)
}Type Checking Operators
| Operator | Meaning | Example |
|---|---|---|
is | Type check (true if value is of type) | x is String |
is not | Negated type check | x is not Nil |
The is operator tests the runtime type of a value. It works with built-in types and ADT variants:
value = "hello"
value is String // true
value is Int // false
value is not Nil // true// With ADT variants
result = Ok(42)
if result is Ok {
print("Success!")
}
option = Some("data")
if option is not None {
print("Has value")
}Supported type checks: String, Int, Float, Bool, Nil, Array, Function, Number, and any custom ADT variant name.
Bitwise Operators
| Operator | Name | Example |
|---|---|---|
& | Bitwise AND | flags & mask |
| | Bitwise OR | flags | FLAG_READ |
^ | Bitwise XOR | a ^ b |
~ | Bitwise NOT | ~mask |
<< | Left shift | 1 << 4 |
>> | Right shift | n >> 2 |
>>> | Unsigned right shift | n >>> 1 |
// Bitwise operations
flags = 0b1010
mask = 0b1100
flags & mask // 0b1000 (AND)
flags | mask // 0b1110 (OR)
flags ^ mask // 0b0110 (XOR)
~flags // bitwise NOT
1 << 4 // 16 (left shift)
32 >> 2 // 8 (right shift)Assignment Operators
| Operator | Meaning | Equivalent |
|---|---|---|
= | Assign | x = 10 |
+= | Add and assign | x = x + 10 |
-= | Subtract and assign | x = x - 10 |
*= | Multiply and assign | x = x * 10 |
/= | Divide and assign | x = x / 10 |
&= | Bitwise AND and assign | x = x & mask |
|= | Bitwise OR and assign | x = x | flag |
^= | Bitwise XOR and assign | x = x ^ bits |
<<= | Left shift and assign | x = x << n |
>>= | Right shift and assign | x = x >> n |
var counter = 0
counter += 1 // counter is now 1
counter *= 5 // counter is now 5Pipe Operator
The pipe operator |> passes the result of the left expression as the first argument to the function on the right:
// Without pipes
result = format(filter(map(data, transform), predicate))
// With pipes -- reads left to right
result = data
|> map(transform)
|> filter(predicate)
|> format()Placeholder _
Use _ as a placeholder to control where the piped value is inserted:
10 |> add(_, 5) // add(10, 5)
"hello" |> replace(_, "l", "r") // replace("hello", "l", "r")Method Pipe
Use .method() syntax to call a method on the piped value:
items
|> .filter(fn(x) x > 0)
|> .map(fn(x) x * 2)
|> .join(", ")Range Operators
| Operator | Name | Example | Meaning |
|---|---|---|---|
.. | Exclusive range | 0..5 | 0, 1, 2, 3, 4 |
..= | Inclusive range | 0..=5 | 0, 1, 2, 3, 4, 5 |
for i in 0..10 { // 0 through 9
print(i)
}
for i in 1..=100 { // 1 through 100 (inclusive)
print(i)
}Spread Operator
The ... spread operator expands an iterable into individual elements:
a = [1, 2, 3]
b = [...a, 4, 5] // [1, 2, 3, 4, 5]
opts = {color: "red"}
full = {...opts, size: 10} // {color: "red", size: 10}It also works in function calls:
args = [1, 2, 3]
sum(...args) // sum(1, 2, 3)Optional Chaining
The ?. operator accesses a property only if the receiver is not nil:
user?.address?.city // nil if user or address is nil
items?.length // nil if items is nilNil Coalescing
The ?? operator returns the left operand if it is not nil, otherwise the right:
name = user?.name ?? "Anonymous"
port = config?.port ?? 3000Member Access and Subscript
| Operator | Name | Example |
|---|---|---|
. | Member access | user.name |
[] | Subscript / index | items[0] |
point.x // access field x
matrix[0][1] // nested indexing
map["key"] // string subscriptFat Arrow and Thin Arrow
| Operator | Name | Usage |
|---|---|---|
=> | Fat arrow | Match arms, route handlers, short lambdas |
-> | Return type | Function return type annotations |
// Fat arrow in match
match status {
200 => "OK"
404 => "Not Found"
_ => "Unknown"
}
// Thin arrow for return types
fn add(a: Int, b: Int) -> Int {
a + b
}Error Propagation Operator
The ? postfix operator unwraps a Result or Option, returning early if it contains an error or absence:
fn process(input: String) -> Result<Data, String> {
parsed = parse(input)? // returns Err early if parse fails
validated = validate(parsed)? // returns Err early if validation fails
Ok(validated)
}? on a Result returns the Err value from the enclosing function. ? on an Option returns None. See the Error Handling guide for details.
Await and Yield Operators
The await prefix operator suspends execution until an async expression resolves:
result = await fetch_data(url)The yield prefix operator produces a value from a generator function. Any function containing yield is automatically treated as a generator — no special syntax is needed. Use yield from to delegate to another generator:
fn range_gen(start, end) {
for i in start..end {
yield i
}
}
fn combined() {
yield from range_gen(1, 5)
yield from range_gen(10, 15)
}Both await and yield have the same precedence as unary - and ... (spread).
Other Operators
| Operator | Name | Usage |
|---|---|---|
: | Type annotation / object field | x: Int, {name: "Alice"} |
:: | Slice step (inside [] only) | items[::2], items[1::3] |
++ | String prefix matching (in match patterns only) | "api/" ++ rest |
? | Error propagation | parse(input)? |
Operator Precedence
Operators are listed from highest precedence (binds tightest) to lowest (binds loosest). Operators on the same row have equal precedence.
| Level | Operators | Associativity | Description |
|---|---|---|---|
| 17 | . ?. [] () ? | Left | Member access, optional chain, subscript, call, error propagation |
| 16 | - (unary) ... (spread) await yield | Right | Unary negation, spread, await, yield |
| 15 | ** | Right | Exponentiation |
| 14 | * / % | Left | Multiplication, division, modulo |
| 13 | + - | Left | Addition, subtraction |
| 12 | << >> >>> | Left | Bitwise shift |
| 11 | .. ..= | None | Range (exclusive, inclusive) |
| 10 | in not in is is not | None | Membership test, type checking |
| 9 | < <= > >= == != | Left | Comparison (chainable) |
| 8 | & | Left | Bitwise AND |
| 7 | ^ | Left | Bitwise XOR |
| 6 | | | Left | Bitwise OR |
| 5 | not ! ~ | Right | Logical NOT, bitwise NOT |
| 4 | and && | Left | Logical AND |
| 3 | or || | Left | Logical OR |
| 2 | ?? | Left | Null coalescing |
| 1 | |> | Left | Pipe |
Precedence Examples
// Unary - binds tighter than **
-2 ** 3 // (-2) ** 3 = -8
-2 ** 2 // (-2) ** 2 = 4 (not -(2 ** 2) = -4)
// * binds tighter than +
2 + 3 * 4 // 2 + (3 * 4) = 14
// and binds tighter than or
a or b and c // a or (b and c)
// |> has lowest precedence
data |> f() |> g() // g(f(data))
// ?? binds tighter than |> but looser than or
a ?? b |> f() // f(a ?? b)