Getting Started

Learn Vexel

This guide walks you through Vexel from your first program all the way to structs, pattern matching, and SDL2 graphics. It assumes you have written some code before but does not assume any specific language background.

Download Vexel Full Reference

1. Installation

Download vexel.exe from the Downloads page and place it somewhere on your PATH, or just put it in your project directory and call it with ./vexel. No installation wizard, no package manager, no prerequisites. It is a single self-contained executable.

Verify the installation by running:

vexel --version

You should see something like Vexel 0.1.0 printed to stdout.

2. Hello, World

Create a file called hello.vx with the following content:

fn main():
    print("Hello, World!")

Run it with:

vexel run hello.vx

Vexel programs always start in a function called main. The fn keyword declares a function. Indentation (4 spaces or 1 tab) defines the function body. No curly braces.

3. Variables and Types

Use let to declare a mutable variable and const for a value that cannot be reassigned. Every variable has an explicit type annotation after a colon.

fn main():
    let name: str = "Vexel"
    let version: float = 0.1
    let count: int = 42
    let active: bool = true
    const PI: float = 3.14159

    count = count + 1     # ok, let is mutable
    # PI = 3.0          # error: cannot assign to const

    print(name)
    print(count)

The four primitive types are int (64-bit integer), float (64-bit double), str (string), and bool. There is also null for unset references.

4. Functions

Functions are declared with fn followed by a name, a typed parameter list, and an optional return type after ->.

fn add(a: int, b: int) -> int:
    return a + b

fn greet(name: str):
    print("Hello, " + name + "!")

fn main():
    print(add(3, 4))     # 7
    greet("Vexel")          # Hello, Vexel!

Functions without a return type annotation implicitly return nothing (equivalent to void). Recursive functions work as expected. Functions are first-class: you can store them in variables and pass them as arguments.

5. Control Flow

Vexel has if / elif / else, while, three forms of for, and break / continue.

fn main():
    # if / elif / else
    let score: int = 82
    if score >= 90:
        print("A")
    elif score >= 80:
        print("B")
    else:
        print("C or below")

    # for range: 0, 1, 2, 3, 4
    for i in range(5):
        print(i)

    # for-each over an array
    let words: [str] = ["hello", "world"]
    for w in words:
        print(w)

    # while loop
    let n: int = 0
    while n < 3:
        print(n)
        n = n + 1

6. Arrays

Arrays are homogeneous and dynamically sized. The type syntax is [T]. Use push to append, pop to remove the last element, and len to get the length.

fn main():
    let nums: [int] = [10, 20, 30]
    push(nums, 40)
    print(len(nums))    # 4
    print(nums[0])     # 10
    print(nums[3])     # 40

    for i, v in enumerate(nums):
        print(i)
        print(v)

7. Structs

Structs group related fields together. Methods are defined inside the struct body using fn with self as the first parameter.

struct Vec2:
    x: float
    y: float

    fn length(self) -> float:
        return (self.x * self.x + self.y * self.y) * 0.5

fn main():
    let v: Vec2 = Vec2(x: 3.0, y: 4.0)
    print(v.x)          # 3.0
    print(v.length())   # 12.5

8. Enums and Pattern Matching

Enums define a type that can be one of several named variants. Use match to dispatch on the active variant.

enum Direction:
    North
    South
    East
    West

fn describe(d: Direction):
    match d:
        case North: print("Going north")
        case South: print("Going south")
        default:   print("Going east or west")

9. Interfaces

Interfaces define a set of method signatures. A struct satisfies an interface by implementing all of its methods using impl InterfaceName for StructName.

interface Drawable:
    fn draw(self)

struct Circle:
    radius: float

impl Drawable for Circle:
    fn draw(self):
        print("Drawing circle")

10. Error Handling

Use assert for invariants that should never be violated. Use try / catch to recover from runtime errors.

fn main():
    assert 1 + 1 == 2, "math is broken"

    try:
        let x: int = risky()
        print(x)
    catch err:
        print("Caught: " + err)

11. Modules and Imports

Split code across files using import. All top-level declarations from the imported file become available in the current scope. Use import ... as name to access them through a namespace.

# utils.vx
fn clamp(v: int, lo: int, hi: int) -> int:
    if v < lo: return lo
    if v > hi: return hi
    return v

# main.vx
import "utils.vx" as utils

fn main():
    print(utils.clamp(150, 0, 100))   # 100

12. Your First Game: Bouncing Square

With SDL2 support you can open a window, draw shapes, and run a game loop in about 25 lines. Compile with --sdl2:

fn main():
    let ok: int = sdl2_init("Bouncing Square", 800, 600)
    if ok == 0: return

    let x: int = 100
    let y: int = 100
    let vx: int = 4
    let vy: int = 3
    let running: int = 1

    while running == 1:
        running = sdl2_poll()
        sdl2_clear(20, 20, 40)
        sdl2_set_color(255, 80, 80, 255)
        sdl2_draw_rect(x, y, 40, 40)
        sdl2_present()
        sdl2_delay(16)
        x = x + vx
        y = y + vy
        if x > 760 or x < 0: vx = -vx
        if y > 560 or y < 0: vy = -vy

    sdl2_quit()
# Run it
vexel compile game.vx --sdl2 -o game
./game
Full Language Reference SDL2 Reference FAQ