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