Arrays
An array (Pebble calls them lists) is an ordered collection of values. Think of it like a row of numbered boxes — each box holds one value, and you can look up a box by its number.
Creating a List
Put values between square brackets, separated by commas:
An empty list has nothing between the brackets:
Lists can hold any mix of types:
If you want Pebble to check that every element is the same type, you
can use a parameterized type annotation
like List[Int].
You can even put lists inside lists:
You can also build a list from a loop using a list comprehension:
Reading an Element
Use square brackets after the list name with an index — the position number. Positions start at 0, not 1:
let fruits = ["apple", "banana", "cherry"]
print(fruits[0]) # prints: apple
print(fruits[1]) # prints: banana
print(fruits[2]) # prints: cherry
You can also use a variable as the index:
If you use an index that is too big (or too negative), Pebble stops with an error:
Negative Indexing
Sometimes you want to grab items from the end of a list without
knowing exactly how long it is. Negative indices let you count backwards:
-1 is the last element, -2 is the second-to-last, and so on.
Think of a queue of people. Positive indices count from the front of the line — 0 is the first person, 1 is the second. Negative indices count from the back — -1 is the last person, -2 is the one just before them.
let colours = ["red", "green", "blue"]
print(colours[-1]) # prints: blue
print(colours[-2]) # prints: green
print(colours[-3]) # prints: red
You can also assign using negative indices:
If you go too far back, you get an error — just like going too far forward:
Under the hood, the VM simply converts the negative index:
-1 becomes len - 1, -2 becomes len - 2, and so on. Then it looks up
that position the normal way.
Slicing
Slicing lets you grab a portion of a list (or string) in one step — like
tearing a page out of a notebook. You write xs[start:stop], where start
is the first index to include and stop is the first index to exclude.
xs = [10, 20, 30, 40, 50]
0 1 2 3 4 ← indices
xs[1:4]
start ↓ ↓ stop (excluded)
[10, 20, 30, 40, 50]
──────────
result: [20, 30, 40]
xs[::2] (step = 2, take every other element)
[10, 20, 30, 40, 50]
✓ ✓ ✓
result: [10, 30, 50]
You can leave out parts:
print(xs[:3]) # first three: [10, 20, 30]
print(xs[2:]) # from index 2 onward: [30, 40, 50]
print(xs[:]) # a full copy: [10, 20, 30, 40, 50]
Add a step after a second colon to skip elements:
A negative step reverses the direction:
Negative indices work too — just like regular indexing:
If your indices go past the end, Pebble silently clamps them — no error:
But a step of zero is an error (you'd never move forward!):
Slicing strings
Slicing works on strings just the same way:
Slicing creates a copy
Slicing always produces a new list (or string). The original stays untouched:
Under the hood the compiler pushes the target, start, stop, and step onto
the stack, then emits a SLICE_GET instruction. The VM uses Python's built-in
slice machinery to do the heavy lifting — handling negative indices, clamping,
and stepping all in one go.
List Methods
Lists have their own set of methods you can call using dot notation —
the same value.method() pattern used by strings.
push()
Add a value to the end of the list:
Note
The functional form push(xs, 4) still works too.
pop()
Remove and return the last element:
Popping from an empty list is an error.
Note
The functional form pop(xs) still works too.
contains()
Check if a value is in the list:
reverse()
Reverse the list in place:
sort()
Sort the list in place:
Sorting works on lists of all integers or all strings. Mixing types is an error.
Quick Reference
| Method | Args | Returns | Description |
|---|---|---|---|
push(value) |
1 | null |
Append to end |
pop() |
0 | value | Remove and return last |
contains(val) |
1 | bool | Is val in the list? |
reverse() |
0 | null |
Reverse in place |
sort() |
0 | null |
Sort in place |
Changing an Element
Assign to a specific position to replace the value in that box:
Finding the Length
The built-in len() function tells you how many elements a list has:
len() also works on strings:
Looping Over a List
Combine for + range + indexing to visit every element:
This prints each colour on its own line.
Lists with Other Features
String Interpolation
You can use list elements inside {…} strings:
Functions
Lists can be passed to functions and returned from them:
How It Works Under the Hood
When the compiler sees [1, 2, 3], it emits instructions to push each
element onto the stack, then a BUILD_LIST 3 instruction. The VM pops
three values and bundles them into a single list value.
For xs[0], the compiler emits code to push the list and the index, then an
INDEX_GET instruction. The VM pops both, looks up the element, and pushes
it back.
For xs[0] = 42, the compiler pushes the list, index, and new value, then
emits INDEX_SET. The VM pops all three and changes the element in place.
Think of BUILD_LIST like packing items into a backpack — you hand over
three separate things and get one backpack back. INDEX_GET is reaching into
a specific pocket by number, and INDEX_SET is swapping what's in that
pocket for something new.
The Full Journey
Here's how let xs = [1, 2] travels through the whole pipeline:
- Lexer — produces tokens:
LET,IDENTIFIER("xs"),EQUAL,LEFT_BRACKET,INTEGER(1),COMMA,INTEGER(2),RIGHT_BRACKET - Parser — builds an
Assignmentnode whose value is anArrayLiteralcontaining twoIntegerLiteralnodes - Analyzer — checks that the variable name is valid
- Compiler — emits
LOAD_CONST 0,LOAD_CONST 1,BUILD_LIST 2,STORE_NAME "xs" - VM — pushes
1and2, packs them into a list, stores the list asxs
Every feature in Pebble follows this same path from text to running code.