Dictionaries
A dictionary (or dict) is a collection that stores key-value pairs. Think of a phonebook: you look up a person's name (the key) and find their phone number (the value). Unlike a list, where you use a numbered position, a dict lets you use a descriptive name.
Creating a Dict
Put key-value pairs between curly braces {}, with a colon : between each
key and value:
An empty dict has nothing between the braces:
Keys must be strings. Values can be any type — numbers, strings, booleans, lists, or even other dicts:
If you want Pebble to check the types of keys and values, you can use a
parameterized type annotation
like Dict[String, Int].
Reading a Value
Use square brackets with the key (a string) to look up a value:
If you use a key that doesn't exist, Pebble stops with an error:
Changing or Adding Values
Assign to a key to change its value, or create a new key if it doesn't exist yet:
let d = {"name": "Alice"}
d["name"] = "Bob" # change existing key
d["age"] = 13 # add a new key
print(d) # prints: {name: Bob, age: 13}
This is called an upsert — it updates if the key exists, or inserts if it doesn't.
Finding the Size
The built-in len() function tells you how many key-value pairs a dict has:
Getting Keys and Values
Two built-in functions let you pull out just the keys or just the values:
let d = {"name": "Alice", "age": 12}
print(keys(d)) # prints: [name, age]
print(values(d)) # prints: [Alice, 12]
Both return a list, so you can use them with len(), indexing, or loops:
let d = {"x": 10, "y": 20, "z": 30}
let k = keys(d)
for i in range(len(k)) {
print("{k[i]}: {d[k[i]]}")
}
Checking the Type
Dicts with Other Features
String Interpolation
You can use dict values inside {...} strings:
Functions
Dicts can be passed to and returned from functions:
fn greet(person) {
return "Hi, {person["name"]}!"
}
print(greet({"name": "Bob"})) # prints: Hi, Bob!
Nested Dicts
A dict value can itself be a dict:
How It Works Under the Hood
When the compiler sees {"a": 1, "b": 2}, it pushes each key and value
onto the stack in order, then emits a BUILD_DICT 2 instruction (where 2 is
the number of key-value pairs). The VM pops 2 * 2 = 4 values off the
stack, pairs them up, and creates a dictionary.
For d["name"], the compiler pushes the dict and the key string, then emits
INDEX_GET. The VM checks whether the target is a list or a dict and handles
it accordingly — for a dict it looks up the key and pushes the value.
For d["name"] = "Bob", the compiler pushes the dict, key, and value, then
emits INDEX_SET. For a dict, the VM performs an upsert — it creates the key
if it's missing, or updates it if it already exists.
Think of BUILD_DICT like filling in a phonebook — you hand over names and
numbers in pairs, and get a finished phonebook back. INDEX_GET is looking up
a name in the phonebook, and INDEX_SET is writing a new entry or updating an
existing one.
The Full Journey
Here's how let d = {"a": 1} travels through the pipeline:
- Lexer — produces tokens:
LET,IDENTIFIER("d"),EQUAL,LEFT_BRACE,STRING("a"),COLON,INTEGER(1),RIGHT_BRACE - Parser — builds an
Assignmentnode whose value is aDictLiteralcontaining one key-value pair - Analyzer — checks that all variables in keys and values are declared
- Compiler — emits
LOAD_CONST 0("a"),LOAD_CONST 1(1),BUILD_DICT 1,STORE_NAME "d" - VM — pushes "a" and 1, packs them into a dict
{"a": 1}, stores it asd