Language Specification
Detailed specification of the Pebble programming language
Comments
Comments help you document your code and make it easier to understand. In Pebble, you can add comments using the single quote character '. Everything after the ' on a line is ignored by the compiler.
' This is a comment
count = 0 ' This is also a comment
Variables and Constants
Variables store data that can change during program execution. In Pebble, all variables are accessible throughout your entire program — there’s no scoping or shadowing. A variable is defined the first time it’s encountered.
Type Inference
Most of the time, Pebble can figure out what type your variable should be:
count = 0 ' Inferred as int
speed = 2.5 ' Inferred as float
active = true ' Inferred as bool
name = "Alice" ' Inferred as string
Type Annotations
You can explicitly specify a variable’s type using a colon : followed by the type name:
score: int = 0
position: float = 10.5
data: byte[4] = [1, 2, 3, 4]
data2: byte[] = [1, 2, 3, 4] ' Size inferred from initializer
Type Stability
Once a variable’s type is set (either through inference or annotation), it can never change type. However, you can mutate the variable’s value:
count = 0 ' count is now an int
count = 5 ' OK: same type
count = 2.5 ' ERROR: can't change type from int to float
Constants
Constants are defined using const and must be initialized with a value. Once defined, constants cannot be changed:
const MAX_SPEED = 100
const PI = 3.14159
MAX_SPEED = 150 ' ERROR: cannot mutate a constant
Identifiers (variable and constant names) can contain letters, numbers, and underscores (a-zA-Z0-9_). By convention, constants are written in SCREAMING_SNAKE_CASE, but any valid identifier is allowed.
Keywords and Identifiers: Keywords (like if, while, type, const) are case-insensitive, so IF, If, and if are all treated the same. However, identifiers (variable names, type names, etc.) are case-sensitive: score, Score, and SCORE are three different variables.
Data Types
Pebble has several built-in data types for different kinds of values.
Primitive Types
int- Whole numbers (e.g., 0, 42, -10)float- Decimal numbers (e.g., 3.14, -0.5, 2.0)byte- Small integers 0-255, useful for raw data. (8-bit unsigned)bool- Boolean values:trueorfalsestring- Text enclosed in double quotes (e.g., “Hello”)
Numeric Literals: Numbers can be written in decimal (e.g., 42, 3.14) or hexadecimal with 0x prefix (e.g., 0xFF, 0x1A). Hexadecimal literals are useful for byte values and colors.
health: int = 100
temperature: float = 98.6
pixel: byte = 255
gameOver: bool = false
message: string = "Ready!"
Custom Types
Custom types let you group related data together, making your code more organized and easier to understand.
Defining Types
Define a type using type:
type Position
x: int
y: int
end
type Player
pos: Position
health: int
score: float
end
Using Custom Types
Create variables of your custom type and access their fields:
player: Player
player.pos.x = 100
player.pos.y = 50
player.health = 100
player.score = 0.0
Custom types can contain other custom types and arrays:
type Game
player: Player
enemies: Player[10]
level: int
end
Enumerations
Enumerations (enums) define a set of named constants, useful for representing states or categories.
Defining Enums
enum Direction
North
South
East
West
end
enum GameState
Menu
Playing
Paused
GameOver
end
Using Enums
Access enum values using the double colon :: operator:
currentDirection = Direction::North
state = GameState::Playing
if state == GameState::GameOver then
print "Game Over!"
end
Arrays
Arrays store multiple values of the same type. All arrays in Pebble have a fixed size that cannot change. Declare them with square brackets:
scores: int[5] ' Array of 5 integers
colors: byte[3] = [255, 0, 128] ' Initialized array
colors: Player[3] ' Initialized array of Player (custom type)
You can omit the size in the declaration, and it will be inferred from the number of elements in the initializer:
SPRITE_DATA: byte[] = [
0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07
] ' Size is 8, inferred from initializer
Access array elements using square brackets with an index (starting from 0). All array elements are automatically initialized to 0 (or false for bool arrays), so it’s safe to read any element:
colors[0] = 255 ' Set first element
value = colors[1] ' Get second element (returns 0 if not set)
Bounds Checking: Accessing an array index outside its valid range (0 to size-1) causes a runtime error and halts the program. Always ensure your indices are within bounds.
Strings
Strings represent text and are enclosed in double quotes:
name = "Player One"
message = "Hello, world!"
Note: String manipulation functions like substring or concatenation are not yet available. Use string interpolation to combine values.
Expressions and Operators
Expressions combine values and operators to produce new values.
Arithmetic Operators
result = 10 + 5 ' Addition
result = 10 - 5 ' Subtraction
result = 10 * 5 ' Multiplication
result = 10 / 5 ' Division
result = 10 % 3 ' Modulo (remainder)
Compound Assignment
You can combine arithmetic operators with assignment for concise updates:
count = 0
count += 1 ' Same as: count = count + 1
score -= 10 ' Same as: score = score - 10
speed *= 2 ' Same as: speed = speed * 2
lives /= 2 ' Same as: lives = lives / 2
Compound assignments work with member access and array indexing:
player.x += velocity
game.sprites[42].y -= 5
health[index] *= 1.1
Comparison Operators
x = 10
y = 20
x == y ' Equal to
x != y ' Not equal to
x < y ' Less than
x > y ' Greater than
x <= y ' Less than or equal to
x >= y ' Greater than or equal to
Logical Operators
active = true
ready = false
active and ready ' Logical AND
active or ready ' Logical OR
not active ' Logical NOT
Member Access
Use the dot . to access fields in custom types:
player.x = 10
player.position.y = 20
Control Flow
Control flow statements let you make decisions and repeat actions.
Conditional Statements
The if statement executes code when a condition is true. It comes in two forms:
Single-line if: Use then for a single statement on the same line:
if health <= 0 then gameOver = true
if score > 100 then bonus = 50
Block if: For multiple statements, use if/end (optionally with else):
if score > highScore then
highScore = score
print "New high score!"
end
if health <= 0 then
gameOver = true
print "Game Over!"
else
print "Keep playing!"
end
While Loops
The while loop repeats code as long as a condition is true:
count = 0
while count < 10
print "Count: {count}"
count = count + 1
end
You can create an infinite loop with while true:
while true
' Game loop runs forever
update()
render()
end
For Loops
The for loop iterates over a range of numbers. The range is inclusive, meaning both the start and end values are included in the iteration. The default step is 1:
for i = 0 to 10
print "i: {i}" ' Prints 0, 1, 2, ... 10 (11 times)
end
for i = 10 to 0 step -1
print "i: {i}" ' Counts down: 10, 9, ... 0
end
for i = 0 to 20 step 2
print "i: {i}" ' Only even numbers: 0, 2, 4, ... 20
end
Subroutines
Subroutines let you organize your code into reusable sections. Use gosub to jump to a label and return to come back.
Labels and Gosub
Define a label by putting a name followed by a colon. Jump to it with gosub:
gosub initialize
gosub update
gosub render
initialize:
score = 0
level = 1
return
update:
score = score + 1
return
render:
print "Score: {score}"
return
The program will execute the subroutine and return to the line after the gosub call.
String Interpolation
String interpolation lets you embed expressions directly in strings using curly braces {}:
name = "Alice"
score = 1000
print "Player: {name}"
print "Score: {score}"
print "Total: {score * 2}"
You can interpolate variables, expressions, and even member access:
print "Position: ({player.x}, {player.y})"
print "Active: {gamepad.a}"
Select Statements
The select statement (also known as switch/case in other languages) lets you match a value against multiple options. It works with integers and enums (and may support strings in the future).
Each case can have either a single statement on the same line, or multiple statements as a block on following lines.
Execution Rules:
- The first matching case is executed, then control exits the select (no fallthrough)
- Only one case ever executes per select statement
- The
elseclause is required unless all possible values are covered
Select with Integers
level = 2
select level
1: print "Easy mode"
2: print "Normal mode"
3: print "Hard mode"
else: print "Unknown level"
end
Select with Enums
When matching enum values, use the variant name directly without the enum type prefix:
enum Direction
North
South
East
West
end
currentDir = Direction::North
select currentDir
North: y = y - 1
South: y = y + 1
East:
x = x + 1
y = 2
West:
x = x - 1
end
With statements
If you want to operate on the same custom type multiple times, you can use a with block.
Inside a with block, field access to the selected value is written using the . prefix. All normal statements are allowed inside the block; with only affects how field names are resolved.
with game.sprites[2]
.pos.x = 3
print "hello"
.color = Color::Green
end