This page walks through writing and running a real Chasm program, explaining decisions along the way.
Hello, World
Create a file called hello.chasm:
print("Hello, Chasm!")Run it:
chasm run hello.chasm
# Hello, Chasm!
Chasm is a scripting language — top-level statements run directly, no main() wrapper needed. print writes a value followed by a newline and works on int, float, bool, and string.
Adding Variables
name = "world"
x = 42
msg = "hello #{name}, x is #{x}"
print(msg)name, x, and msg are local variables. The compiler infers their types: string, int, and string.
The #{} syntax interpolates any expression directly into a string. No str(x) calls needed.
Functions
Use def for public functions and defp for private helpers. Private functions require an explicit return type annotation:
defp fib(n :: int) :: int do
if n <= 1 do
return n
end
return fib(n - 1) + fib(n - 2)
end
print(fib(10)) # 55Parameters always need a type annotation (n :: int). The :: int after the parameter list is the return type. return exits early; the last expression is also implicitly returned.
Structs
A struct groups related values into a single named type. Structs are value types — copying a struct copies all its fields:
defstruct Vec2 do
x :: float
y :: float
end
defp length(v :: Vec2) :: float do
sqrt(v.x * v.x + v.y * v.y)
end
pos = Vec2{ x: 3.0, y: 4.0 }
print(length(pos)) # 5
pos2 = pos with { x: 0.0 } # copy with x overridden
print(pos2.y) # 4, y is unchangedThe with keyword creates a modified copy of a struct. Only the listed fields change.
Arrays
An array is an ordered collection of values. The argument to array_new is the initial capacity — it starts empty (len = 0):
scores = array_new(4) # capacity 4, length 0
scores.push(100)
scores.push(85)
scores.push(92)
print(scores.len) # 3, elements stored
print(scores.cap) # 4, space reserved
print(scores.get(0)) # 100
total = 0
for i in 0..scores.len do
total = total + scores.get(i)
end
print(total) # 277len is how many elements are stored. cap is how much space is reserved before the array needs to grow.
A Game Loop Script
Chasm shines in game engines. Here is a minimal script that moves a player with the arrow keys:
@player_x = 0.0
@player_y = 0.0
@speed = 200.0
def on_tick(dt :: float) do
dx :: frame = 0.0
if key_down(key_code(:right)) do dx = @speed end
if key_down(key_code(:left)) do dx = 0.0 - @speed end
@player_x = copy_to_script(@player_x + dx * dt)
end
def on_draw() do
clear_background(0x181820ff)
draw_rectangle(@player_x, @player_y, 32.0, 32.0, 0x4488ffff)
endRun it with:
chasm run --engine raylib player.chasm
on_tick and on_draw are both required — the engine will not start without them. on_draw must call clear_background to clear the previous frame before drawing.
key_code(:right) converts the atom name to the integer keycode that key_down expects. See the Raylib API for the full list of supported key names.
The @ prefix marks a module attribute — unlike local variables, they survive across calls to on_tick. Attributes default to script lifetime: they persist until the script is hot-reloaded. See Attributes & Lifetimes for the full picture.
Next step:
Continue to Variables & Types for a complete tour of the type system.