Written from a line-by-line source review; every example output is from a real run.
Introduction
The map plugin is a named, in-memory key-value store (a hash map or dictionary). Several independent maps can exist at once, each identified by a unique name. The key is always a string; the value can be one of three kinds: text (string), integer (i64), or binary data (blob). The store is thread-safe: every operation is protected by a per-map mutex, so it is safe to use from multiple threads.
How it works inside
The implementation is a chained hash table (a bucket array with a linked list per bucket), using a 32-bit FNV-1a hash that is fast and distributes well for short string keys. When the load exceeds 75%, the table automatically doubles and rehashes. The store is ephemeral: it lives for the duration of the process and is never written to disk. It has no external dependencies (pthreads on Linux, CRITICAL_SECTION on Windows).
Loading the plugin
At the top of your script, load the plugin by a path to its file, relative to the script, without the extension. From the Examples folder this is of the form ../plugins/...:
plugin "../plugins/print/PrintPlugin"; plugin "../plugins/map/MapPlugin";
A typical lifecycle
Usage almost always follows the same arc: create (Create), populate (Put*), query (Get*/HasKey), and finally clear or destroy (Clear/Destroy). A key may not be empty and may not exceed the built-in length limit.
map.Create("config", 16);
map.PutString("config", "host", "localhost");
printf("%s\n", map.GetString("config", "host"));
map.Destroy("config");What to know about every function (the basics)
The kind matters on lookup. Every entry has a kind (string, int, or blob). GetString returns only a string value, GetInt only an int, GetBlob only a blob — if the key has a different kind (or is missing), the function returns the "empty" result (empty string, 0, or empty blob), not an error.
A missing map is not an error. Most operations on a non-existent map simply return 0 (or an empty value) and the script keeps running. To be sure, call map.Exists first.
An error occurs only for a wrong argument count or type, an empty or too-long key, or out of memory — in those cases the script stops with a runtime error.
Put* overwrites. If the key already exists, Put* replaces its value (and its kind).
The key iteration order (Keys) follows the internal hash layout, NOT the insertion order — do not expect a sorted result.
The atomic operations (IncrInt, CompareAndSwapInt, AppendString, SetIfAbsentString) perform the whole read-modify-write in a single mutex-protected step, so no update is lost even with multiple threads.
How to read the signatures
name is always the map's name, key is the key. The type after the -> arrow is the return type. For example, map.PutInt(name, key, value) -> int means three arguments are required and the result is an integer (1 for success, 0 for failure).
Lifecycle
Creating, destroying, checking the existence of, and emptying a map.
map.Create
map.Create(name, initialCapacity) -> int
Creates a new, empty map with the given name. The initial capacity is only a performance hint (the table grows on its own when needed).
| Parameter | Type | Description |
|---|---|---|
| name | string | The unique name of the map. May not be empty, and has a built-in length limit. |
| initialCapacity | int | Initial bucket count (rounded up to the nearest power of two). 0 or negative is set to the minimum. |
1 if it was created; 0 if a map with that name already exists. (Too many maps or out of memory causes a runtime error.)
// a second Create with the same name does not create a new one
printf("%d\n", map.Create("m", 16));
printf("%d\n", map.Create("m", 16));1 0
Create the maps you need at the start of the program. The 0 return signals that a name is already taken — so several modules can share a map without clashing.
map.Destroy
map.Destroy(name) -> int
Destroys the map and frees all memory associated with it.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map to destroy. |
1 if it was destroyed; 0 if there was no map with that name.
map.Create("m", 8);
printf("%d\n", map.Destroy("m"));
printf("%d\n", map.Destroy("m"));1 0
Call it at the end of the program or when a task is done, so no leftover data stays in memory. Unlike Clear, it also frees the name.
map.Exists
map.Exists(name) -> int
Tells whether a map with the given name exists.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map to check. |
1 if it exists; 0 if not.
map.Create("m", 8);
printf("%d\n", map.Exists("m"));
printf("%d\n", map.Exists("nope"));1 0
For conditional initialization: create the map only if it does not yet exist (though Create's 0 return can serve the same purpose).
map.Clear
map.Clear(name) -> int
Removes all entries from the map but keeps the map itself (and its name).
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map to empty. |
1 if it was emptied; 0 if there was no map with that name.
map.Create("m", 8);
map.PutInt("m", "a", 1);
printf("%d\n", map.Clear("m"));
printf("%d\n", map.Size("m"));1 0
For emptying a reusable map between runs when you do not want to pay the cost of Create/Destroy again.
Inserting
Storing a value under a key. All three functions overwrite if the key already exists.
map.PutString
map.PutString(name, key, value) -> int
Stores a text value under the key.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key (may not be empty). |
| value | string | The text to store. |
1 on success; 0 if there is no map with that name.
map.Create("m", 8);
printf("%d\n", map.PutString("m", "name", "Ada"));1
For storing configuration values, names, messages. GetString reads back the same value.
map.PutInt
map.PutInt(name, key, value) -> int
Stores an integer value (i64) under the key.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key. |
| value | int | The integer to store. |
1 on success; 0 if there is no map with that name.
map.Create("m", 8);
printf("%d\n", map.PutInt("m", "age", 36));1
For storing counters, identifiers, settings. If you would increment a counter, the atomic IncrInt is safer than a Get+Put pair.
map.PutBlob
map.PutBlob(name, key, value) -> int
Stores binary data (a blob) under the key. The whole blob content is copied, including any 0 bytes.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key. |
| value | blob | The binary data to store. |
1 on success; 0 if there is no map with that name.
blob $B[4];
map.Create("m", 8);
$B[0] = 1; $B[1] = 2; $B[2] = 3;
$B.Length = 3;
printf("%d\n", map.PutBlob("m", "raw", $B));1
For storing raw bytes (a hash, an image, a network packet) where a 0 byte inside the data must be preserved. GetBlob reads it back.
Lookups
Reading a value or checking whether a key exists. Both a kind mismatch and a missing key produce the "empty" result.
map.HasKey
map.HasKey(name, key) -> int
Tells whether the key exists in the map (regardless of its kind).
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key to look for. |
1 if the key exists; 0 if not (or if the map does not exist).
map.Create("m", 8);
map.PutString("m", "name", "Ada");
printf("%d\n", map.HasKey("m", "name"));
printf("%d\n", map.HasKey("m", "xxx"));1 0
For an existence check when the content does not matter. It distinguishes "no key" from "key present but value is empty/0", which Get* cannot.
map.GetString
map.GetString(name, key) -> string
Returns the text stored under the key.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key to look for. |
The stored text; an empty string if the key is missing OR is not a string kind.
map.Create("m", 8);
map.PutString("m", "name", "Ada");
map.PutInt("m", "age", 36);
printf("%s\n", map.GetString("m", "name"));
// 'age' is an int, so as a string it is empty
printf("[%s]\n", map.GetString("m", "age"));Ada []
For reading text values. If you must tell an empty string apart from a missing key, call HasKey first.
map.GetInt
map.GetInt(name, key) -> int
Returns the integer value stored under the key.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key to look for. |
The stored integer; 0 if the key is missing OR is not an int kind.
map.Create("m", 8);
map.PutInt("m", "age", 36);
map.PutString("m", "name", "Ada");
printf("%d\n", map.GetInt("m", "age"));
// 'name' is a string, so as an int it is 0
printf("%d\n", map.GetInt("m", "name"));36 0
For reading numeric values. To tell a real 0 from "missing", use HasKey, or store values so that 0 is never a valid one.
map.GetBlob
map.GetBlob(name, key) -> blob
Returns the binary data stored under the key.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key to look for. |
A copy of the stored blob; an empty blob if the key is missing OR is not a blob kind.
blob $B[4];
blob $Out[16];
map.Create("m", 8);
$B[0] = 1; $B[1] = 2; $B[2] = 3; $B.Length = 3;
map.PutBlob("m", "raw", $B);
$Out = map.GetBlob("m", "raw");
printf("%d\n", $Out.Length);3
For reading binary data back. The returned blob is an independent copy, so later changes to the map do not affect it.
Modifying
Removing an entry.
map.Remove
map.Remove(name, key) -> int
Removes the key and its associated value.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key to remove. |
1 if it was removed; 0 if the key did not exist (or the map does not exist).
map.Create("m", 8);
map.PutInt("m", "age", 36);
printf("%d\n", map.Remove("m", "age"));
printf("%d\n", map.Remove("m", "age"));1 0
For deleting a single entry. To empty the whole map, Clear is faster than removing keys one by one.
Atomic operations
These functions perform the read-modify-write in a single mutex-protected step. With multiple threads, no update can be lost — unlike a manual Get+Put pair.
map.IncrInt
map.IncrInt(name, key, delta) -> int
Atomically adds delta to the key's integer value and returns the NEW value. A missing key starts at 0; a non-int key is overwritten as an int (0 + delta).
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The counter's key. |
| delta | int | The value to add (may be negative). |
The new integer value after the operation. (If the map does not exist, it returns delta itself but stores nothing.)
map.Create("m", 8);
printf("%d\n", map.IncrInt("m", "hits", 1));
printf("%d\n", map.IncrInt("m", "hits", 5));1 6
For counters, accumulators, identifier generation. It replaces the dangerous "read, increment, write back" pattern, which can lose updates with multiple threads.
map.CompareAndSwapInt
map.CompareAndSwapInt(name, key, expected, new) -> int
Replaces the key's value with new only if it is currently exactly expected (and of int kind). An optimistic locking primitive.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key. |
| expected | int | The expected current value. |
| new | int | The new value if the check succeeds. |
1 if it was swapped; 0 if not (key missing and expected is not 0, wrong kind, or value is not expected). Special case: if the key is missing and expected=0, it creates it with new.
map.Create("m", 8);
map.IncrInt("m", "hits", 6);
// 6 -> 100 succeeds; the second (expects 6 again) does not
printf("%d\n", map.CompareAndSwapInt("m", "hits", 6, 100));
printf("%d\n", map.CompareAndSwapInt("m", "hits", 6, 200));1 0
For optimistic locking: read a value, compute with it, then write it back with CAS; if someone changed it meanwhile, CAS returns 0 and you can retry. expected=0 is good for the "initialize exactly once" pattern.
map.AppendString
map.AppendString(name, key, suffix) -> int
Atomically appends suffix to the key's existing text. If the key is missing, suffix becomes the new value.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key. |
| suffix | string | The text to append. |
1 on success; 0 if the key is a non-string kind (int/blob) — in which case it does NOT overwrite — or if the map does not exist.
map.Create("m", 8);
printf("%d\n", map.AppendString("m", "log", "a"));
printf("%d\n", map.AppendString("m", "log", "b"));
printf("%s\n", map.GetString("m", "log"));1 1 ab
For building logs, comma-separated lists, or append-only events, where several threads may append to the same key.
map.SetIfAbsentString
map.SetIfAbsentString(name, key, value) -> int
Writes the (key, value) pair only if the key does not already exist.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
| key | string | The key. |
| value | string | The value to write if the key is missing. |
1 if it was written; 0 if the key already existed (with any kind), or if the map does not exist.
map.Create("m", 8);
printf("%d\n", map.SetIfAbsentString("m", "mode", "x"));
// already exists, the second does not overwrite
printf("%d\n", map.SetIfAbsentString("m", "mode", "y"));
printf("%s\n", map.GetString("m", "mode"));1 0 x
For one-shot initialization ("init once"): several threads may race to set the same default, but only one wins (gets 1), the others get 0.
Inspection
Querying the size and the keys of a map.
map.Size
map.Size(name) -> int
Returns the number of entries stored in the map.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
The number of entries; 0 if the map is empty or does not exist.
map.Create("m", 8);
map.PutInt("m", "a", 1);
map.PutInt("m", "b", 2);
printf("%d\n", map.Size("m"));2
For a size check, an emptiness test, or estimating the number of items a Keys walk will yield.
map.Keys
map.Keys(name) -> blob
Returns all keys in a single blob, separated by NUL (0) bytes. The language has no array type, so this is how you iterate the keys.
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of the map. |
A blob in which the keys are laid out separated by 0 bytes. An empty blob for an empty or non-existent map. The order follows the internal hash layout, not the insertion order.
blob $K[256];
string $Key[64];
i32 $Pos;
i32 $End;
i32 $j;
map.Create("m", 8);
map.PutString("m", "name", "Ada");
map.PutInt("m", "age", 36);
$K = map.Keys("m");
$Pos = 0;
while ($Pos < $K.Length)
{
$End = $Pos;
while (($End < $K.Length) && ($K[$End] != 0)) { $End =
$End + 1; }
$j = $Pos;
while ($j < $End) { $Key[$j - $Pos] = $K[$j]; $j = $j + 1; }
$Key.Length = $End - $Pos;
printf("%s\n", $Key);
$Pos = $End + 1;
}age name
For iterating over all entries (since the language has no foreach). The walk pattern: find the next 0 byte, copy the stretch before it as a key, then jump past the 0. Do not expect a sorted order.
Practical notes
What the map is good for
Storing configuration, settings, values reachable by name (a "config" map).
Collecting counters and statistics (IncrInt), especially with multiple threads.
Cache-like data that needs fast access by key.
Shared state across threads, since every operation is thread-safe.
Thread safety done right
Individual calls are atomic on their own, but another thread can slip in between two separate calls (for example a Get followed by a Put). If you read-modify-write the same key, do not use a Get+Put pair; use the atomic operation that fits the purpose: IncrInt for a counter, CompareAndSwapInt for a conditional swap, AppendString for appending text, SetIfAbsentString for one-shot initialization.
Common pitfalls
Kind mismatch: if you stored a key as an int, GetString returns an empty string for it (and vice versa). Keep the kind consistent per key, or check with HasKey.
0 and the empty string are not errors: Get* returns these for a missing key too. Where the distinction matters, use HasKey.
Keys does not sort: the key order reflects the hash table layout. If you need sorted output, sort the keys in the script.
Ephemeral store: the map disappears when the process stops. For persistence, save the data elsewhere (a file or a database).
Error handling
A wrong argument count or type, an empty/too-long key, and out of memory are reported by the runtime as a runtime error, and the script stops. Content-level cases (no such map, no such key, kind mismatch), however, are handled by the plugin not as errors but with a neutral return value, so in the usual cases you do not need extra guard checks.
The csv table plugin
Reading and writing CSV/TSV — complete function reference