Appearance
Loops, Arrays, Numbers & Strings
Iterating over collections and engine namespaces, and the value macros for working with arrays, numbers, and text.
Loops & Namespace Queries
Ana provides a loop construct and three collection macros ((ids:), (filter:), and (query:)) that work across all engine namespaces: $npc, $inv, $container, and any custom namespace registered with (ns-nested:). These macros always return arrays, so they pair naturally with (each:).
All three macros return arrays you can iterate with (each:).
(each: _var in collection)[body]
Loops over every item in an array, binding the current item to a temp variable. The block renders once per iteration.
ana
(set: _items to (ids: $inv))
(each: _id in _items)[
(get: $item, _id, "name")
]The collection can be any expression that returns an array: a variable, a filter result, or any other expression.
(break:) / (continue:)
Loop control inside an (each:) body. (break:) stops the loop immediately; (continue:) skips the rest of the current iteration and moves to the next. Both affect only the innermost enclosing loop, and both work inside (if:) branches within the loop. Used outside a loop, they are ignored (with a dev-build warning and a validator warning).
ana
// First weapon in a large inventory — stop as soon as we find one
(each: _id in (ids: $inv))[
(if: (get: $item, _id, "type") is "weapon")[
You ready your (get: $item, _id, "name").
(set: $world.drawnWeapon to _id)
(break:)
]
]
// Skip locked entries
(each: _id in (ids: $container.chest))[
(if: (get: $item, _id, "locked"))[(continue:)]
You take the (get: $item, _id, "name").
](ids: namespace)
Expression macro. Returns an array of all IDs in the given namespace.
ana
(ids: $npc) // all NPC IDs
(ids: $inv) // all item IDs in carry inventory
(ids: $container.chest) // all item IDs in a specific container
(ids: $faction) // all IDs in a custom nested namespace(filter: ns.key is value [, ns.key2 is value2 ...])
Expression macro. Returns an array of IDs where all predicates match (AND logic). Multiple predicates are supported for 2-part ns.key forms.
ana
// NPC filter
(set: _atBar to (filter: $npc.location is "bar"))
(each: _id in _atBar)[
(get: $npc, _id, "name") nurses a drink.
]
// Multi-predicate AND
(set: _targets to (filter: $npc.location is "bar", $npc.gender is "female"))
// Inventory filter — returns item IDs matching a property
(set: _weapons to (filter: $inv.type is "weapon"))
(each: _id in _weapons)[
You're carrying a (get: $item, _id, "name").
]
// Multi-predicate inventory filter
(set: _heavy to (filter: $inv.type is "weapon", $inv.weight > 5))
// Container filter — 3-part form ($container.id.key), single predicate only
(set: _potions to (filter: $container.shop_goods.type is "potion"))
// Custom namespace
(set: _friendly to (filter: $faction.standing > 50))Filter operators: is, is not, >, <, >=, <=.
npc.locationhere is a predicate, not a variable.$npcis a nested namespace keyed by id; the actual stored values live at$npc.bartender.location,$npc.guard.location, and so on. Thenpc.location is "bar"form inside(filter:)/(query:)means "for every npc id, test itslocationkey"; it does not read a single$npc.locationvalue (there is no such variable). This is how you iterate a nested namespace:(filter:)/(query:)walk every id for you, or use(each: _id in (ids: $npc))and read each with(get: $npc, _id, "location").
(query: namespace [, where, ns.key is value] [, sort, "key" [desc]] [, limit, N])
Expression macro. A composable query with optional filtering, sorting, and a result cap. Returns the same array format as (ids:) and (filter:).
The first argument is the namespace. where predicates use the same form as (filter:).
ana
// All NPCs in the bar, sorted by name
(set: _bar to (query: $npc, where, $npc.location is "bar", sort, "name"))
// Top 3 highest-level hostile NPCs
(set: _enemies to (query: $npc, where, $npc.faction is "hostile", sort, "level", desc, limit, 3))
// All NPCs sorted, no filter
(set: _roster to (query: $npc, sort, "name"))
// Inventory: highest-damage weapons, cap 5
(set: _top to (query: $inv, where, $inv.type is "weapon", sort, "damage", desc, limit, 5))(ns-nested: "name") (GameInit only)
Registers a new nested namespace that behaves identically to npc: it supports (declare: $name.id, ...), (ids: name), (filter: name.key is value), and (query: name, ...).
ana
(ns-nested: "faction")
(declare: $faction.guild, name, "Merchant Guild", standing, 50)
(ids: $faction) // ["guild"]
(filter: $faction.standing > 40) // ["guild"]
(query: $faction, where, $faction.standing > 40, sort, "name")Note: there is no
(ns-flat:)macro. Flat namespaces ($world.timeOfDay,$player.gold) work automatically the moment you(declare:)a variable in them; no registration call is needed. See Variable depth for the three namespace depths.
Quick reference by namespace
| Namespace | (ids:) | (filter:) predicate | Notes |
|---|---|---|---|
npc | (ids: $npc) | $npc.key is value | Any (npc-define:) / (declare: $npc.id, ...) entry |
| inventory | (ids: $inv) | $inv.key is value | Item template IDs |
| container | (ids: $container.chest) | $container.chest.key is value | 3-part, single predicate |
| custom | (ids: $faction) | $faction.key is value | After (ns-nested: "faction") |
Events are plain array variables; loop directly with (each: _ev in $npc.bartender.events)[...]
Arrays
Arrays are declared with (declare: $variable, []) in GameInit. Use arrays for ordered lists of strings or numbers: visited locations, collected clues, event flags.
(add: $arrayVariable, value)
Appends a value to the end of an array. This is the same (add:) macro used for numeric variables; it detects the array type automatically.
ana
(declare: $player.events, [])
(add: $player.events, "met_kyle")
(add: $player.events, "found_note")Array aliases: (arr-push:), (arr-remove:), (arr-count:)
These are aliases of the polymorphic verbs; prefer the unprefixed form, which is the engine's house style:
| Prefixed alias | Preferred form |
|---|---|
(arr-push: $arr, v) | (add: $arr, v); append to an array |
(arr-remove: $arr, v) | (remove: $arr, v); remove first occurrence (no-op if absent) |
(arr-count: $arr) | (count: $arr); array length |
The aliases are retained for readability in array-heavy code where the array nature is worth signalling, but they do exactly what (add:)/(remove:)/(count:) do on an array variable.
ana
(declare: $visited, [])
(add: $visited, "bar") // preferred
(arr-push: $visited, "mill") // alias — identical effect
(remove: $visited, "bar")
(if: (count: $visited) >= 5)[You've been around.]contains / not contains / does not contain operators
Built into the condition syntax. Checks whether an array or string includes a value. No macro needed.
ana
(if: $visited contains "mill")[
You know the layout.
]
(if: $visited not contains "mill")[
You've never been out there.
]
(if: $visited does not contain "mill")[
You've never been out there.
]Event flags via arrays: declare an event array, push tags to it, check with contains:
ana
// GameInit
(declare: $player.events, [])
(add: $player.events, "found_note")
(if: $player.events contains "found_note")[
You remember that report.
]Traits: just a string array on the character namespace:
ana
// GameInit
(declare: $player.traits, [])
(add: $player.traits, "charming")
(if: $player.traits contains "charming")[
Your smile puts them at ease.
]
(if: $player.traits does not contain "fearless")[
Something gives you pause.
]Array Macros
Functional array operations; all return new arrays or single values. None of these mutate the original variable. For array mutation (push, remove), use (add:) and (arr-remove:) (see Arrays above).
All are expression macros.
(arr: val1, val2, ...)
Builds a new array from a series of inline values. (arr:) with no arguments returns an empty array.
ana
(set: $player.inventory to (arr: "sword", "shield", "potion"))
(set: _empty to (arr:))(pick: $array) / (pick: val1, val2, ...)
Returns a random value. Pass a single array to pick one of its elements, or a series of inline values to pick among them. Returns null for an empty array or empty series. (either:) is an alias.
ana
(set: _greeting to (pick: $npc.bartender.greetings)) // from an array
(pick: "He nods.", "He shrugs.", "He grunts.") // from inline values(either: val1, val2, ...)
Alias of (pick:); reads naturally for one-off random prose where no array variable is needed.
ana
(either: "He nods.", "He shrugs.", "He grunts.")
(set: _mood to (either: "cheerful", "neutral", "gruff"))(arr-first: $array) / (arr-last: $array)
Returns the first or last element. Returns null for an empty array.
ana
(set: _latest to (arr-last: $player.events))(arr-nth: $array, n)
Returns the element at 1-indexed position n. Returns null if out of range.
ana
(set: _second to (arr-nth: $player.events, 2))(arr-shuffle: $array)
Returns a new randomly shuffled copy. The original array is unchanged.
ana
(set: _deck to (arr-shuffle: $game.deck))(arr-sort: $array)
Returns a new sorted copy in ascending order. Numbers sort numerically; strings sort lexicographically. The original array is unchanged.
ana
(set: _sorted to (arr-sort: $player.scores))(arr-reverse: $array)
Returns a new reversed copy. The original array is unchanged.
ana
(set: _reversed to (arr-reverse: $player.inventory))(arr-unique: $array)
Returns a new copy with duplicate values removed, preserving first occurrence order.
ana
(set: _visited to (arr-unique: $player.locations))(arr-slice: $array, from, to?)
Returns a sub-array from 1-indexed position from to to (inclusive). If to is omitted, slices to the end of the array. The original array is unchanged.
ana
(set: _last3 to (arr-slice: $player.events, (arr-count: $player.events) - 2))
(set: _middle to (arr-slice: $player.events, 2, 4))(sort: ...) / (shuffle: ...)
Polymorphic versions of (arr-sort:) / (arr-shuffle:). Each accepts either a single array argument or a series of inline values, and returns a new sorted/shuffled copy.
ana
(set: _ranked to (sort: $player.scores)) // array form
(set: _ranked to (sort: 3, 1, 2)) // inline series → [1, 2, 3]
(set: _order to (shuffle: $game.deck))(length: value) / (arr-length: $array)
(length:) returns the character count of a string or the entry count of an array. (arr-length:) is the array-only form (an alias of (arr-count:)); use it when you want to assert the argument is an array.
ana
(set: _chars to (length: $player.name)) // string → character count
(set: _items to (length: $player.inventory)) // array → entry count
(set: _items to (arr-length: $player.inventory))(join: ...) / (concat: ...) / (arr-join: ...)
Concatenates strings, or merges arrays into one new array. If every argument is an array, the result is a merged array; otherwise the arguments are coerced to strings and concatenated. (concat:) and (arr-join:) are aliases.
ana
(set: _full to (join: "Hello, ", $player.name, "!")) // strings → one string
(set: _all to (join: $party, $reserves)) // arrays → one merged arrayThis is distinct from (collapse:), which joins the elements of a single array into a string with a separator.
Number Macros
Utility math functions. All are expression macros; use them inside (set:), conditions, or anywhere a value is expected.
(min: a, b) / (max: a, b)
Returns the smaller or larger of two numbers.
ana
(set: _capped to (min: $player.health, 100))
(if: (max: $rel.bartender, 0) > 50)[
He's friendly.
](clamp: value, min, max)
Constrains a value to the range [min, max]. Returns min if below, max if above, otherwise value unchanged.
ana
(set: _safe to (clamp: $player.health, 0, 100))
(set: _vol to (clamp: _rawVol, 0.0, 1.0))(round: value) / (floor: value) / (ceil: value)
Rounding functions. (round:) rounds to the nearest integer (0.5 rounds up). (floor:) rounds toward negative infinity. (ceil:) rounds toward positive infinity.
ana
(set: _display to (round: $player.health))
(set: _slots to (floor: $player.stamina / 10))(abs: value)
Returns the absolute value of a number.
ana
(set: _diff to (abs: $player.health - $player.maxHealth))(sum: ...) / (avg: ...)
(sum:) totals its numbers; (avg:) returns their mean (0 for an empty series). Each accepts either a single array argument or a series of inline numbers.
ana
(set: _total to (sum: $player.scores)) // array form
(set: _total to (sum: 10, 20, 30)) // inline → 60
(set: _mean to (avg: $player.scores))(convert: value, fromUnit, toUnit)
Converts a numeric value between units in the same category. Supported categories:
- Length:
mm,cm,m,km,in,ft,yd,mi - Mass:
mg,g,kg,oz,lb,st - Volume:
ml,l,floz,cup,pt,qt,gal - Temperature:
c,f,k
Unit names are case-insensitive and accept common spellings (e.g. "feet", "pounds", "celsius"). Converting between incompatible categories throws an error.
ana
(set: _cm to (convert: 6, "ft", "cm")) // → 182.88
(set: _f to (convert: 100, "c", "f")) // → 212String Macros
All are expression macros. Use inside (set:) or anywhere a value is expected. The dash-notation names (str-length, str-from, etc.) use hyphens to distinguish them from variable paths.
(lowercase: str) / (uppercase: str) / (proper: str)
Case conversion. (proper:) capitalizes the first letter of each word.
ana
(set: _name to (proper: $player.name)) // "the old mill" → "The Old Mill"
(set: _tag to (lowercase: $world.event))(trim: str)
Strips leading and trailing whitespace.
ana
(set: _clean to (trim: _rawInput))(str-length: str)
Returns the number of characters in the string.
ana
(if: (str-length: $player.name) is 0)[
You haven't entered a name.
](str-from: str, n) / (str-to: str, n)
Substring operations using 1-indexed positions.
(str-from: str, n): returns from positionnto the end.(str-to: str, n): returns from the start up to and including positionn.
ana
(str-from: "hello", 3) // → "llo"
(str-to: "hello", 3) // → "hel"
(str-from: "hello", 1) // → "hello"(str-split: str, sep)
Splits a string into an array by the separator.
ana
(set: _parts to (str-split: "a,b,c", ","))
// → ["a", "b", "c"]
(each: _tag in (str-split: $player.tagString, " "))[
_tag
](collapse: $array, sep?)
Collapses an array's elements down into one string, joined by sep. If sep is omitted, elements are concatenated with no separator. This is the inverse of (str-split:).
ana
(set: _arr to ["a","b","c"])
(set: _out to (collapse: _arr, "-")) // → "a-b-c"
(set: _out to (collapse: _arr)) // → "abc"(Not to be confused with (join:), which concatenates separate string/array arguments rather than the elements of one array.)
(repeat: count, value)
Returns a string with value repeated count times. count is floored and clamped to a minimum of 0.
ana
(set: _bar to (repeat: 10, "=")) // → "=========="
(set: _dots to (repeat: $level, "."))(str: value) / (num: str)
Type coercion. (str:) converts any value to its string representation. (num:) parses a string as a number, returning NaN if the string is not a valid number.
ana
(set: _label to (str: $player.gold) + " gold")
(set: _parsed to (num: "3.14"))