Operators
Operators are functions that perform actions on their operands. They provide concise syntax for common operations like arithmetic, comparison, logical operations, and assignment.
Operator Formats
Verse operators come in three formats based on their position relative to their operands:
Prefix Operators
Prefix operators appear before their single operand:
not Expression- Logical negation-Value- Numeric negation+Value- Numeric positive (for alignment)
Infix Operators
Infix operators appear between their two operands:
A + B- AdditionA * B- MultiplicationA = B- Equality comparisonA and B- Logical AND
Postfix Operators
Postfix operators bind to the expression on their left. While some (like .) appear between two elements, they're classified as postfix because they operate on the left-hand expression:
Value?- Query operator for logic valuesObject.Member- Member access (the.operates on the object to its left)Array[Index]- Array indexing (the[]operates on the array to its left)Function()- Function call (the()operates on the function to its left)Constructor{}- Object construction (the{}operates on the type to its left)
Although . appears between Player and Respawn in Player.Respawn(), it's considered postfix because it binds to Player and selects a member from it. The right side (Respawn) is not a separate operand but a member selector
Precedence
When multiple operators appear in the same expression, they are evaluated according to their precedence level. Higher precedence operators are evaluated first. Operators with the same precedence are evaluated left to right (except for assignment and unary operators which are right-associative).
The precedence levels from highest to lowest are:
| Precedence | Operators | Category | Format | Associativity | Example |
|---|---|---|---|---|---|
| 11 | ., [], (), {}, ? (postfix) | Member access, Indexing, Call, Construction, Query | Postfix | Left | BossDefeated?, Player.Respawn() |
| 10 | +, - (unary), not | Unary operations | Prefix | Right | +Score, -Distance, not HasCooldown? |
| 9 | *, / | Multiplication, Division | Infix | Left | Score * Multiplier |
| 8 | +, - (binary) | Addition, Subtraction | Infix | Left | X + Y, Health - Damage |
| 7 | = (relational), <>, <, <=, >, >= | Relational comparison | Infix | Right | Player <> Target, Score > 100 |
| 5 | and | Logical AND | Infix | Left | HasPotion? and TryUsePotion[] |
| 4 | or | Logical OR | Infix | Left | IsAlive? or Respawn() |
| 3 | .. | Range | Infix | Left | 0..100, -15..50 |
| 2 | ~~Lambda expressions~~ | ~~Function literals~~ (not yet supported) | Special | N/A | N/A |
| 1 | :=, set = | Assignment | Infix | Right | X := 15, set Y = 25 |
The = symbol serves two distinct purposes in Verse: - Relational comparison (precedence 7): When used as an operator in expressions, A = B tests equality and returns a logic value - Assignment (precedence 1): When used with the set keyword, set X = Value assigns a new value to an existing variable
This is different from :=, which always means "define and initialize" for new variables. The context determines which meaning of = applies.
Arithmetic Operators
Arithmetic operators perform mathematical operations on numeric values. They work with both int and float types, with some special behaviors for type conversion and integer division.
Basic Arithmetic
| Operator | Operation | Types | Notes |
|---|---|---|---|
+ | Addition | int, float | Also concatenates strings and arrays |
- | Subtraction | int, float | Can be used as unary negation |
* | Multiplication | int, float | Converts int to float when mixed |
/ | Division | int (failable), float | Integer division returns rational |
# Basic arithmetic
Sum := 10 + 20 # 30
Diff := 50 - 15 # 35
Prod := 6 * 7 # 42
Quot := 20.0 / 4.0 # 5.0
# Unary operators
Negative := -42 # -42
Positive := +42 # 42 (for alignment)
# Integer division (failable, returns rational)
if (Result := 10 / 3):
IntResult := Floor(Result) # 3
# Type conversion through multiplication
IntValue:int = 42
FloatValue:float = IntValue * 1.0 # Converts to 42.0
Compound Assignments
Compound assignment operators combine an arithmetic operation with assignment:
| Operator | Equivalent To | Types |
|---|---|---|
set += | set X = X + Y | int, float, string, array |
set -= | set X = X - Y | int, float |
set *= | set X = X * Y | int, float |
set /= | set X = X / Y | float only |
var Score:int = 100
set Score += 50 # Score is now 150
set Score -= 25 # Score is now 125
set Score *= 2 # Score is now 250
var Health:float = 100.0
set Health /= 2.0 # Health is now 50.0
# Arrays can use += with both arrays and tuples
var Items:[]int = array{1, 2, 3}
set Items += array{4, 5} # Items is now array{1, 2, 3, 4, 5}
set Items += (6, 7) # Items is now array{1, 2, 3, 4, 5, 6, 7}
# Note: set /= doesn't work with integers due to failable division
# var IntValue:int = 10
# set IntValue /= 2 # Compile error!
Comparison Operators
Comparison operators test relationships between values and are failable expressions that succeed or fail based on the comparison result.
Relational Operators
| Operator | Meaning | Supported Types | Example |
|---|---|---|---|
< | Less than | int, float | Score < 100 |
<= | Less than or equal | int, float | Health <= 0.0 |
> | Greater than | int, float | Level > 5 |
>= | Greater than or equal | int, float | Time >= MaxTime |
Equality Operators
| Operator | Meaning | Supported Types | Example |
|---|---|---|---|
= | Equal to | All comparable types | Name = "Player1" |
<> | Not equal | All comparable types | State <> idle |
# Numeric comparisons
if (Score > HighScore):
Print("New high score!")
if (Health <= 0.0):
HandlePlayerDeath()
# Example with other comparable types
if (PlayerName = "Admin"):
EnableAdminMode()
if (CurrentState <> game_state.Playing):
ShowMenu()
# Comparison in complex expressions
if (Level >= 10 and Score > 1000):
UnlockAchievement()
The following types support equality comparison operations (= and <>):
- Numeric types:
int,float,rational - Boolean:
logic - Text:
string,char,char32 - Enumerations: All
enumtypes - Collections:
array,map,tuple,option(if elements are comparable) - Structs: If all fields are comparable
- Unique classes: Classes marked with
<unique>(identity equality only)
Comparisons between different types generally fail:
0 = 0.0 # Fails: int vs float
"5" = 5 # Fails: string vs int
Logical Operators
Logical operators work with failable expressions and control the flow of success and failure.
Query Operator (?)
The query operator checks if a logic value is true (see Failure for how ? works with other types):
var IsReady:logic = true
if (IsReady?):
StartGame()
# Equivalent to:
if (IsReady = true):
StartGame()
Not Operator
The not operator negates the success or failure of an expression:
if (not IsGameOver?):
ContinuePlaying()
# Effects are not committed with not
var X:int = 0
if (not (set X = 5, IsGameOver?)):
# X is still 0 here, even though the assignment "tried" to happen
Print("X is {X}") # Prints "X is 0"
And Operator
The and operator succeeds only if both operands succeed:
Player:player = player{Level:=10, HasItem:=option{1}}
if (HasKey? and DoorUnlocked?):
EnterRoom()
# Short-circuit evaluation - second operand not evaluated if first fails
if (QuickCheck[] and ExpensiveCheck[]):
ProcessResult()
Or Operator
The or operator succeeds if at least one operand succeeds:
if (HasKeyCard? or HasMasterKey?):
OpenDoor()
# Short-circuit evaluation - second operand not evaluated if first succeeds
if (QuickCheck[] or ExpensiveCheck[]):
ProcessResult()
Truth Table
Consider two expressions P and Q which may either succeed or fail, the following table shows the result of logical operators applied to them:
| Expression P | Expression Q | P and Q | P or Q | not P |
|---|---|---|---|---|
| Succeeds | Succeeds | Succeeds (Q's value) | Succeeds (P's value) | Fails |
| Succeeds | Fails | Fails | Succeeds (P's value) | Fails |
| Fails | Succeeds | Fails | Succeeds (Q's value) | Succeeds |
| Fails | Fails | Fails | Fails | Succeeds |
Assignment and Initialization
When initializing constants and variables, both = and := can be used if an explicit type is provided. For type inference (no type annotation), you must use :=.
# Constant initialization with explicit types - both = and := work
MaxHealth:int = 100
PlayerName:string := "Hero"
# Variable initialization with explicit types - both = and := work
var CurrentHealth:int = 100
var Score:int := 0
# Type inference requires := (no type annotation)
AutoTyped := 42 # Inferred as int
# Note: var requires explicit type - var X := value is not allowed
The set = operator updates variable values:
var Points:int = 0
set Points = 100
var Position:vector3 = vector3{X := 0.0, Y := 0.0, Z := 0.0}
set Position = vector3{X := 10.0, Y := 20.0, Z := 0.0}
Special Operators
Indexing
The square bracket operator is used for multiple purposes in Verse:
- Array/Map indexing - Access elements in collections
- Function calls - Call functions which may fail
# Array indexing (failable)
MyArray := array{10, 20, 30}
if (Element := MyArray[1]):
Print("Element at index 1: {Element}") # Prints 20
# Map lookup (failable)
Scores:[string]int = map{"Alice" => 100, "Bob" => 85}
if (AliceScore := Scores["Alice"]):
Print("Alice's score: {AliceScore}")
# String indexing (failable)
Name:string = "Verse"
if (FirstChar := Name[0]):
Print("First character: {FirstChar}") # Prints 'V'
# Function call that can fail
Result1 := MyFunction1[Arg1, Arg2] # Can fail
Result2 := MyFunction2[?X:=Arg1, ?Y:=Arg2] # Named arguments
EmptyCall := MyFunction2[] # and optional values
Member Access
The dot operator accesses fields and methods of objects:
Player.Health
Player.GetName()
MyVector.X
Config.Settings.MaxPlayers
Range
The range operator creates ranges for iteration:
# Inclusive range
for (I := 0..4):
Print("{I}") # Prints 0, 1, 2, 3, 4
Object Construction
Verse provides multiple syntaxes for constructing objects. All of the following are equivalent:
# Curly braces with commas
Point1 := point{X:= 10, Y:= 20}
# Curly braces with semicolons
Point2 := point{X:= 10; Y:= 20}
# Colon syntax with newlines (no braces)
Point3 := point:
X:= 10
Y:= 20
# Colon syntax with commas and newlines
Point4 := point:
X:= 10,
Y:= 20
# Fields can be separated by newlines inside braces
Player := player_data {
Name := "Hero"
Level := 5
Health := 100.0
}
# Trailing commas are not allowed
Config := game_config{
MaxPlayers := 100,
EnablePvP := true # , -- comma not allowed here
}
# Dot syntax for single field (requires defaults for other fields)
Point5 := point . X:=10 # Y gets default value 0
Point6 := point . Y:=20 # X gets default value 0
Tuple Access
Round braces when used with a single argument after a tuple expression, accesses tuple elements:
MyTuple := (10, 20, 30)
FirstElement := MyTuple(0) # Access first element
SecondElement := MyTuple(1) # Access second element
Type Conversions
Verse has limited implicit type conversion. Most conversions must be explicit:
# No implicit int to float conversion
MyInt:int = 42
# MyFloat:float = MyInt # Error!
MyFloat:float = MyInt * 1.0 # OK: explicit conversion
# No implicit numeric to string conversion
Score:int = 100
# Message:string = "Score: " + Score # Error!
Message:string = "Score: {Score}" # OK: string interpolation
When operators work with mixed types, specific rules apply:
# int * float -> float
Result := 5 * 2.0 # Result is 10.0 (float)
# Comparisons must be same type
if (5 = 5): # OK
if (5.0 = 5.0): # OK
# if (5 = 5.0): # Fails