Pebble Documentation

Pebble is a simple, fast scripting language with a clean syntax and powerful built-in functions.

Language Basics

Variables

let name = "Pebble"
let version = 1.0
let active = true

Functions

def greet(name):
    return "Hello, " + name
end

def add(a, b):
    return a + b
end

Control Flow

# If statements
if x > 10:
    io_print("Large")
elif x > 5:
    io_print("Medium")
else:
    io_print("Small")
end

# While loops
let i = 0
while i < 10:
    io_print(i)
    i = i + 1
end

Comments

# Single-line comment

## Documentation comment
## Used for documenting functions and variables
def important():
    # Implementation here
end

Records

Records are C-style structs that group related data together. They provide O(1) field access through compile-time index resolution.

record Person:
    field name
    field age
end

# Create a record instance
let person: Person = Person
person.name = "Alice"
person.age = 30

io_print(person.name)  # Alice

Type Annotations for Records

Type annotations are required for record types in three situations:

  • Variable declarations: When assigning a record to a variable
  • Function parameters: When a function parameter is a record type
  • Record fields: When a field contains another record (enables nested access)

Type annotations tell the compiler which record type is being used, enabling compile-time field index resolution. This is only required for record types - other types (int, float, string, array, map) do not need annotations.

# Variable with type annotation (required for records)
let p: Point = Point
p.x = 10
p.y = 20

# Function with typed record parameter
def calculate_distance(p1: Point, p2: Point):
    let dx = p2.x - p1.x  # Works because p2 is typed
    let dy = p2.y - p1.y
    return dx * dx + dy * dy
end

Nested Records

For nested field access, the field must have a type annotation specifying which record type it contains:

record Point:
    field x
    field y
end

record Circle:
    field center: Point  # Type annotation enables nested access
    field radius
end

let circle: Circle = Circle
let p: Point = Point
p.x = 100
p.y = 200
circle.center = p
circle.radius = 50

# Nested access works because center is typed as Point
io_print(circle.center.x)  # 100
io_print(circle.center.y)  # 200

When Type Annotations Are NOT Needed

Type annotations are only required for record types. You do not need them for:

  • Primitive types (int, float, string, bool, nil)
  • Arrays
  • Maps
# No type annotations needed (not records)
let x = 42                # int
let name = "Alice"        # string
let arr = [1, 2, 3]        # array
let map = {"a": 1}        # map

def sum_array(numbers):
    # No type annotation needed for array parameter
    let total = 0
    let i = 0
    while i < type_len(numbers):
        total = total + numbers[i]
        i = i + 1
    end
    return total
end

Imports

import "libs/math"
import "utils/helpers"

Note: The .pbl extension is automatically added by the compiler.

I/O Functions

io_print(value, ...)

Prints values to standard output.

io_print("Hello, World!")
io_print("Value:", 42, "Result:", result)

Type Functions

typeof(value) -> string

Returns the type of a value as a string.

io_print(typeof(42))        # "int"
io_print(typeof(3.14))      # "float"
io_print(typeof("hello"))   # "string"
io_print(typeof([1, 2, 3]))  # "array"
io_print(typeof({a: 1}))    # "map"

type_int(value) -> int

Converts a value to an integer.

io_print(type_int(3.14))    # 3
io_print(type_int(true))     # 1
io_print(type_int(false))    # 0

type_float(value) -> float

Converts a value to a floating-point number.

io_print(type_float(42))     # 42.0
io_print(type_float(true))   # 1.0

type_str(value) -> string

Converts a value to a string.

io_print(type_str(42))        # "42"
io_print(type_str(3.14))      # "3.14"
io_print(type_str(true))      # "true"

type_len(value) -> int

Returns the length of a string, array, or map.

io_print(type_len("hello"))     # 5
io_print(type_len([1, 2, 3]))   # 3
io_print(type_len({a: 1, b: 2})) # 2

Array Functions

array_push(array, value) -> nil

Appends a value to the end of an array.

let arr = [1, 2, 3]
array_push(arr, 4)
io_print(arr)  # [1, 2, 3, 4]

array_pop(array) -> value

Removes and returns the last element from an array.

let arr = [1, 2, 3]
let last = array_pop(arr)
io_print(last)  # 3
io_print(arr)   # [1, 2]

array_slice(array, start, end) -> array

Returns a new array containing elements from start to end (exclusive). Supports negative indices.

let arr = [1, 2, 3, 4, 5]
io_print(array_slice(arr, 1, 4))    # [2, 3, 4]
io_print(array_slice(arr, 0, -1))   # [1, 2, 3, 4]
io_print(array_slice(arr, -3, -1))  # [3, 4]

array_append(array1, array2) -> array

Returns a new array with all elements from array2 appended to array1.

let a = [1, 2]
let b = [3, 4]
let c = array_append(a, b)
io_print(c)  # [1, 2, 3, 4]

array_insert(array, index, value) -> nil

Inserts a value at the specified index in an array.

let arr = [1, 3, 4]
array_insert(arr, 1, 2)
io_print(arr)  # [1, 2, 3, 4]

array_remove(array, index) -> value

Removes and returns the element at the specified index.

let arr = [1, 2, 3, 4]
let removed = array_remove(arr, 1)
io_print(removed)  # 2
io_print(arr)      # [1, 3, 4]

array_reverse(array) -> nil

Reverses the elements of an array in place.

let arr = [1, 2, 3, 4]
array_reverse(arr)
io_print(arr)  # [4, 3, 2, 1]

array_clear(array) -> nil

Removes all elements from an array.

let arr = [1, 2, 3]
array_clear(arr)
io_print(arr)  # []

Map Functions

map_keys(map) -> array

Returns an array of all keys in a map.

let person = {name: "Alice", age: 30}
let k = map_keys(person)
io_print(k)  # ["name", "age"]

map_values(map) -> array

Returns an array of all values in a map.

let person = {name: "Alice", age: 30}
let v = map_values(person)
io_print(v)  # ["Alice", 30]

map_has_key(map, key) -> bool

Checks if a map contains a specific key.

let person = {name: "Alice", age: 30}
io_print(map_has_key(person, "name"))   # true
io_print(map_has_key(person, "email"))  # false

map_delete(map, key) -> nil

Removes a key-value pair from a map.

let person = {name: "Alice", age: 30, city: "NYC"}
map_delete(person, "city")
io_print(person)  # {name: "Alice", age: 30}

String Functions

string_upper(str) -> string

Converts a string to uppercase.

let s = string_upper("hello")
io_print(s)  # "HELLO"

string_lower(str) -> string

Converts a string to lowercase.

let s = string_lower("WORLD")
io_print(s)  # "world"

string_trim(str) -> string

Removes whitespace from both ends of a string.

let s = string_trim("  hello  ")
io_print(s)  # "hello"

string_trim_prefix(str, prefix) -> string

Removes prefix from string if present.

let s = string_trim_prefix("hello_world", "hello_")
io_print(s)  # "world"

string_trim_suffix(str, suffix) -> string

Removes suffix from string if present.

let s = string_trim_suffix("file.txt", ".txt")
io_print(s)  # "file"

string_split(str, separator) -> array

Splits string by separator into array.

let parts = string_split("a,b,c", ",")
io_print(parts)  # ["a", "b", "c"]

string_join(array, separator) -> string

Joins array of strings with separator.

let s = string_join(["a", "b", "c"], "-")
io_print(s)  # "a-b-c"

string_replace(str, old, new) -> string

Replaces all occurrences of substring.

let s = string_replace("hello", "l", "r")
io_print(s)  # "herro"

string_contains(str, substr) -> bool

Checks if string contains substring.

io_print(string_contains("hello", "ell"))  # true

string_starts_with(str, prefix) -> bool

Checks if string starts with prefix.

io_print(string_starts_with("hello", "hel"))  # true

string_ends_with(str, suffix) -> bool

Checks if string ends with suffix.

io_print(string_ends_with("hello", "lo"))  # true

string_index_of(str, substr) -> int

Finds first index of substring (-1 if not found).

io_print(string_index_of("hello", "l"))  # 2

string_last_index_of(str, substr) -> int

Finds last index of substring (-1 if not found).

io_print(string_last_index_of("hello", "l"))  # 3

string_substr(str, start, [end]) -> string

Gets substring from start index to end index. If end is omitted, goes to end of string.

io_print(string_substr("hello", 1, 4))  # "ell"
io_print(string_substr("hello", 2))     # "llo"

string_repeat(str, count) -> string

Repeats string n times.

let s = string_repeat("ha", 3)
io_print(s)  # "hahaha"

string_reverse(str) -> string

Reverses a string.

let s = string_reverse("hello")
io_print(s)  # "olleh"

string_count(str, substr) -> int

Counts occurrences of substring.

io_print(string_count("hello", "l"))  # 2

string_pad_left(str, width, [pad_char]) -> string

Pads string on left to specified width. Optional third argument specifies pad character (default space).

let s = string_pad_left("5", 3, "0")
io_print(s)  # "005"

string_pad_right(str, width, [pad_char]) -> string

Pads string on right to specified width. Optional third argument specifies pad character (default space).

let s = string_pad_right("hello", 8)
io_print(s)  # "hello   "

string_parse_int(str) -> int or nil

Parses string to integer. Returns nil on failure.

let n = string_parse_int("42")
io_print(n)  # 42

string_parse_float(str) -> float or nil

Parses string to float. Returns nil on failure.

let n = string_parse_float("3.14")
io_print(n)  # 3.14

string_char_at(str, index) -> string or nil

Gets character at index. Returns nil if out of bounds.

let c = string_char_at("hello", 1)
io_print(c)  # "e"

File Functions

file_read_file(path) -> string or nil

Reads the entire contents of a file as a string. Returns nil if the file cannot be read.

let content = file_read_file("data.txt")
if content != nil:
    io_print(content)
end

file_write_file(path, content) -> bool

Writes a string to a file, overwriting existing content. Returns true on success, false on failure.

let success = file_write_file("output.txt", "Hello, World!")
io_print("Written:", success)

file_append_file(path, content) -> bool

Appends a string to a file. Creates the file if it doesn't exist. Returns true on success, false on failure.

file_append_file("log.txt", "New log entry\n")

file_exists(path) -> bool

Checks if a file exists.

if file_exists("config.txt"):
    let config = file_read_file("config.txt")
end

file_delete_file(path) -> bool

Deletes a file. Returns true on success, false on failure.

file_delete_file("temp.txt")

file_read_lines(path) -> array or nil

Reads a file and returns an array of lines. Line endings (\n and \r\n) are removed. Returns nil if the file cannot be read.

let lines = file_read_lines("data.txt")
if lines != nil:
    for line in lines:
        io_print(line)
    end
end

file_list_dir(path) -> array or nil

Lists all files and directories in a directory. Returns an array of filenames (strings). Returns nil if the directory cannot be read.

let files = file_list_dir(".")
if files != nil:
    io_print("Files:", type_len(files))
end

file_is_dir(path) -> bool

Checks if a path is a directory. Returns false if the path doesn't exist.

if file_is_dir("src"):
    io_print("src is a directory")
end

file_make_dir(path) -> bool

Creates a directory. Returns true on success, false on failure.

file_make_dir("output")

file_make_dir_all(path) -> bool

Creates a directory and all parent directories (like mkdir -p). Returns true on success, false on failure.

file_make_dir_all("path/to/deep/dir")

file_remove_dir(path) -> bool

Removes a directory (must be empty). Returns true on success, false on failure.

file_remove_dir("temp_dir")

file_get_cwd() -> string or nil

Gets the current working directory. Returns nil on failure.

let cwd = file_get_cwd()
io_print("Current directory:", cwd)

file_set_cwd(path) -> bool

Sets the current working directory. Returns true on success, false on failure.

file_set_cwd("/home/user/project")

JSON Functions

json_stringify(value) -> string

Converts a Pebble value to a JSON string.

let data = {name: "Alice", age: 30, active: true}
let json_str = json_stringify(data)
io_print(json_str)  # {"name":"Alice","age":30,"active":true}

json_parse(json_string) -> value or nil

Parses a JSON string and returns a Pebble value. Returns nil if parsing fails.

let json_str = "{\"name\":\"Alice\",\"age\":30}"
let data = json_parse(json_str)
if data != nil:
    io_print(data.name)  # Alice
end

Collision Detection & Math

Built-in functions for 2D game development and geometry calculations.

collision_point_in_rect(px, py, rx, ry, rw, rh) -> bool

Checks if a point is inside a rectangle.

if collision_point_in_rect(mouse_x, mouse_y, 100, 100, 50, 50):
    io_print("Clicked on button!")
end

collision_point_in_circle(px, py, cx, cy, radius) -> bool

Checks if a point is inside a circle.

if collision_point_in_circle(mouse_x, mouse_y, 200, 200, 50):
    io_print("Hovering over circular button")
end

collision_rect_rect(x1, y1, w1, h1, x2, y2, w2, h2) -> bool

Checks if two rectangles overlap using AABB (Axis-Aligned Bounding Box) collision detection.

if collision_rect_rect(player_x, player_y, 32, 32, enemy_x, enemy_y, 32, 32):
    io_print("Player hit enemy!")
end

collision_circle_circle(x1, y1, r1, x2, y2, r2) -> bool

Checks if two circles overlap.

if collision_circle_circle(ball1_x, ball1_y, 10, ball2_x, ball2_y, 10):
    io_print("Balls collided!")
end

collision_rect_circle(rx, ry, rw, rh, cx, cy, radius) -> bool

Checks if a rectangle and circle overlap.

if collision_rect_circle(box_x, box_y, 50, 50, ball_x, ball_y, 20):
    io_print("Ball hit box!")
end

math_distance(x1, y1, x2, y2) -> float

Calculates the Euclidean distance between two points.

let dist = math_distance(player_x, player_y, enemy_x, enemy_y)
if dist < 100:
    io_print("Enemy is nearby!")
end

math_angle(x1, y1, x2, y2) -> float

Calculates the angle from point 1 to point 2 in radians (using atan2).

let angle = math_angle(player_x, player_y, target_x, target_y)
# Use angle to rotate sprite or move towards target

math_lerp(start, end, t) -> float

Linear interpolation between two values. The parameter t is clamped to [0, 1].

# Smooth camera movement
camera_x = math_lerp(camera_x, target_x, 0.1)

# Fade effect
alpha = math_lerp(0, 255, fade_progress)

math_normalize(x, y) -> array

Normalizes a 2D vector to unit length. Returns an array [nx, ny].

let dir = math_normalize(dx, dy)
velocity_x = dir[0] * speed
velocity_y = dir[1] * speed

math_clamp(value, min, max) -> float

Clamps a value between minimum and maximum bounds.

# Keep player within screen bounds
player_x = math_clamp(player_x, 0, screen_width)

# Limit health value
health = math_clamp(health, 0, 100)

Time Functions

time_now() -> float

Returns the current Unix timestamp in seconds.

let start = time_now()
# ... do some work ...
let elapsed = time_now() - start
io_print("Elapsed:", elapsed, "seconds")

Game Window Functions

Functions for creating and managing game windows.

window_create(width, height, title) -> nil

Creates a game window with the specified dimensions and title.

window_create(800, 600, "My Game")

window_close() -> nil

Closes the game window.

window_should_close() -> bool

Returns true if the window should close (user pressed X button).

while not window_should_close():
    # Game loop
end

window_set_fps(fps) -> nil

Sets the target frames per second.

window_set_fps(60)

window_set_title(title) -> nil

Changes the window title.

window_set_title("Score: " + type_str(score))

window_set_resizable(resizable) -> nil

Sets whether the window can be resized.

window_set_resizable(false)  # Lock window size

window_set_fullscreen(fullscreen) -> nil

Enables or disables fullscreen mode.

if input_key_pressed("f11"):
    window_set_fullscreen(true)
end

window_is_fullscreen() -> bool

Returns true if window is in fullscreen mode.

if window_is_fullscreen():
    io_print("Running in fullscreen mode")
end

window_get_width() -> int

Returns the current window width in pixels.

let center_x = window_get_width() / 2

window_get_height() -> int

Returns the current window height in pixels.

let center_y = window_get_height() / 2

window_get_fps() -> int

Returns the current frames per second.

draw_text("FPS: " + type_str(window_get_fps()), 10, 10, 20, 255, 255, 255)

window_get_time() -> float

Returns the time elapsed since window creation in seconds.

let wave = math_sin(window_get_time() * 2)  # Oscillating value

Game Drawing Functions

Functions for drawing shapes, text, and graphics.

draw_begin() -> nil

Begins drawing mode. Call before drawing operations.

while not window_should_close():
draw_begin() draw_clear(0, 0, 0) # ... drawing code here ... draw_end() end

draw_end() -> nil

Ends drawing mode and presents the frame.

draw_clear(r, g, b) -> nil

Clears the screen with the specified RGB color (0-255).

draw_clear(0, 0, 0)  # Black background

draw_circle(x, y, radius, r, g, b) -> nil

Draws a filled circle.

draw_circle(400, 300, 50, 255, 0, 0)  # Red circle

draw_circle_outline(x, y, radius, r, g, b) -> nil

Draws a circle outline.

draw_circle_outline(200, 200, 30, 0, 0, 255)  # Blue ring

draw_rectangle(x, y, width, height, r, g, b) -> nil

Draws a filled rectangle.

draw_rectangle(100, 100, 200, 150, 0, 255, 0)  # Green box

draw_rectangle_outline(x, y, width, height, r, g, b) -> nil

Draws a rectangle outline.

draw_rectangle_outline(50, 50, 100, 80, 255, 255, 0)  # Yellow border

draw_line(x1, y1, x2, y2, thickness, r, g, b) -> nil

Draws a line between two points.

draw_line(0, 0, 800, 600, 2, 255, 255, 255)  # White diagonal

draw_text(text, x, y, size, r, g, b) -> nil

Draws text on screen.

draw_text("Score: 100", 10, 10, 20, 255, 255, 255)

draw_pixel(x, y, r, g, b) -> nil

Draws a single pixel.

# Create starfield effect
let i = 0
while i < 100:
    draw_pixel(rand_x, rand_y, 255, 255, 255)
    i = i + 1
end

Game Input Functions

Functions for handling keyboard and mouse input.

input_key_pressed(key) -> bool

Returns true if the key was just pressed this frame.

if input_key_pressed("space"):
    # Player jumped
end

input_key_down(key) -> bool

Returns true while the key is being held down.

if input_key_down("right"):
    player_x = player_x + speed
end

input_key_released(key) -> bool

Returns true if the key was just released this frame.

if input_key_released("space"):
    # Player stopped charging attack
end

input_mouse_x() -> int

Returns the current mouse X position.

let mx = input_mouse_x()
let my = input_mouse_y()
draw_circle(mx, my, 5, 255, 0, 0)  # Draw crosshair

input_mouse_y() -> int

Returns the current mouse Y position.

input_mouse_pressed(button) -> bool

Returns true if mouse button was just pressed. Button: 0=left, 1=right, 2=middle.

if input_mouse_pressed(0):
    # Left click detected
end

input_mouse_down(button) -> bool

Returns true while mouse button is being held.

if input_mouse_down(0):
    # Continuous shooting while held
    spawn_bullet()
end

input_mouse_released(button) -> bool

Returns true if mouse button was just released.

if input_mouse_released(1):
    # Right click released
end

Game Texture Functions

Functions for loading and drawing textures/images.

texture_load(filepath) -> texture or nil

Loads a texture from file. Returns texture object or nil on failure.

let player_sprite = texture_load("player.png")
if player_sprite == nil:
    io_print("Failed to load texture!")
end

texture_unload(texture) -> nil

Frees a loaded texture from memory.

texture_unload(player_sprite)  # Clean up when done

texture_draw(texture, x, y) -> nil

Draws a texture at the specified position.

texture_draw(player_sprite, player_x, player_y)

texture_draw_ex(texture, x, y, rotation, scale, flip_x, flip_y) -> nil

Draws a texture with advanced options. Rotation in degrees, scale multiplier, flip booleans.

# Draw rotated and scaled
texture_draw_ex(sprite, x, y, 45, 2.0, false, false)

texture_get_width(texture) -> int

Returns the texture width in pixels.

let center_x = x - (texture_get_width(sprite) / 2)

texture_get_height(texture) -> int

Returns the texture height in pixels.

let center_y = y - (texture_get_height(sprite) / 2)

Game Audio Functions

Functions for playing sound effects and music.

audio_init() -> nil

Initializes the audio system. Call once at program start.

audio_init()  # Initialize before loading sounds

audio_close() -> nil

Closes the audio system. Call at program end.

audio_close()  # Clean up audio resources

sound_load(filepath) -> sound or nil

Loads a sound effect from file (WAV, MP3, OGG).

let jump_sound = sound_load("jump.wav")

sound_unload(sound) -> nil

Frees a loaded sound from memory.

sound_unload(jump_sound)

sound_play(sound) -> nil

Plays a sound effect.

if input_key_pressed("space"):
    sound_play(jump_sound)
end

sound_stop(sound) -> nil

Stops playing a sound.

sound_stop(alarm_sound)

sound_set_volume(sound, volume) -> nil

Sets sound volume (0.0 to 1.0).

sound_set_volume(explosion_sound, 0.5)  # 50% volume

sound_is_playing(sound) -> bool

Returns true if the sound is currently playing.

if not sound_is_playing(footstep_sound):
    sound_play(footstep_sound)
end

music_load(filepath) -> music or nil

Loads background music from file.

let bg_music = music_load("background.mp3")

music_unload(music) -> nil

Frees loaded music from memory.

music_unload(bg_music)

music_play(music) -> nil

Starts playing music (loops automatically).

music_play(bg_music)  # Starts looping

music_stop(music) -> nil

Stops playing music.

music_stop(bg_music)

music_pause(music) -> nil

Pauses music playback.

if game_paused:
    music_pause(bg_music)
end

music_resume(music) -> nil

Resumes paused music.

if !game_paused:
    music_resume(bg_music)
end

music_set_volume(music, volume) -> nil

Sets music volume (0.0 to 1.0).

music_set_volume(bg_music, 0.3)  # Quiet background music

music_is_playing(music) -> bool

Returns true if music is currently playing.

if music_is_playing(bg_music):
    io_print("Music is playing")
end

music_update(music) -> nil

Updates music streaming. Call once per frame if using music.

while not window_should_close():
    music_update(bg_music)  # Keep music streaming
    # ... rest of game loop ...
end