Registers
Overview
Registers are temporary named storage slots used during softcode evaluation.
They allow you to save the result of a function call or computation and refer
to it later without repeating the work. Registers exist for the duration of a
single queue entry—they are cleared at the start of each interactive
command but preserved across commands in an action list (such as those composed
by @switch, @trigger, and @dolist).
Setting Registers: setq() and setr()
setq(<register>, <value>[, <register2>, <value2>, ...])
setq() stores <value> into <register> and returns nothing (empty
string). Multiple register/value pairs can be set in a single call; the
number of arguments must be even.
setr(<register>, <value>[, <register2>, <value2>, ...])
setr() is identical to setq() except that it returns the last <value>
assigned. This makes it useful inline where you need both to store and
display a value:
say The answer is [setr(0, add(2, 3))].
You say, "The answer is 5."
Register Names
There are 36 single-character registers: 0 through 9 and A through Z
(case-insensitive). In addition, you may use named registers—alphanumeric
strings with underscores, up to 32 characters, also case-insensitive:
[setq(myvar, Hello World)]
[setq(player_score, 42)]
Reading Registers: r() and %q
The r() function returns the contents of a register:
say [setq(0, Hello)]r(0) is [r(0)]
You say, "r(0) is Hello"
say [setq(myvar, World)]r(myvar) is [r(myvar)]
You say, "r(myvar) is World"
The %q substitution is a shorthand that avoids the overhead of a function
call:
%q0through%q9– single-digit registers.%qathrough%qz– single-letter registers.%q<name>– named registers, e.g.%q<myvar>.
Both r() and %q produce the same result. %q is evaluated during
substitution before function parsing, so it is slightly more efficient
in tight loops.
Scoped Registers: letq()
letq(<name1>, <value1>[, <name2>, <value2>, ...], <body>)
letq() temporarily sets the listed registers, evaluates <body>, and then
restores every affected register to its prior value. It always takes an odd
number of arguments: one or more name/value pairs followed by a body
expression.
say [setq(b, OUTER)][letq(b, INNER, %qb)] %qb
You say, "INNER OUTER"
say letq(x, 5, y, 3, add(%qx, %qy))
You say, "8"
This is the preferred way to create scratch registers inside a helper function without disturbing the caller.
Protecting Registers: localize() and ulocal()
localize(<expression>) evaluates <expression> with its own private copy
of all registers. Any changes made inside are discarded when localize()
returns. Think of it as an inline version of ulocal().
ulocal([<obj>/]<attr>[, <arg>, ...]) works like u() but saves and
restores all registers around the call. Use it when calling shared or global
code that may set registers internally:
&SUB me=[setq(0, v(DATA))][extract(%q0, match(%q0, %0), 1)]
&TOP me=[setq(0, are delicious!)][ulocal(SUB, %0)] %q0
say u(TOP, b*)
You say, "bananas are delicious!"
Without ulocal(), the SUB function’s use of %q0 would overwrite the
caller’s value.
Register Lifetime and Scope
Registers belong to the queue entry, not to the object or the player. Key rules:
- Registers are cleared when a new interactive command begins.
- They persist across all commands in a single action list (
;-separated or triggered by@dolist,@switch,@trigger, etc.). - Each
u()ortrigger()call shares the same register space as the caller—changes propagate back. ulocal(),localize(), andletq()each create a protected scope that restores registers on exit.
Interaction with Common Functions
u() and ulocal(): A function called with u() shares the caller’s
registers. Use ulocal() or wrap the call in localize() when the called
function should not alter the caller’s registers.
iter(): Each iteration shares the same register space. A common pattern is to accumulate a result in a register across iterations.
switch() and @switch: Registers set before or during case evaluation remain available in the action that fires.
Performance Benefits
Registers avoid redundant evaluation. Consider an attribute that uses the
same get() result three times:
[if(get(%0/STATUS), get(%0/STATUS), default)]
With a register, the attribute is fetched once:
[setq(s, get(%0/STATUS))][if(%q0, %q0, default)]
The savings compound in loops and deeply nested code.
Common Patterns
Accumulating a list in iter():
[setq(out,)][iter(1 2 3, [setq(out, %q0 [mul(##,##)])])]%q0
Passing data between helper functions:
&FN_LOOKUP me=[setr(0, get(%0/SCORE))]
&FN_DISPLAY me=[u(FN_LOOKUP, %0)]—Score is %q0
Caching an expensive result:
[setq(loc, loc(%#))][if(match(get(%q<loc>/ZONE), %1),...)]
Limits
- 36 single-character registers (
0-9,A-Z). - Named registers up to 32 characters (alphanumeric and underscore).
- Register values are limited by the server’s buffer size (typically 8000 characters per value in default TinyMUX builds).
- There is no fixed cap on the number of distinct named registers, but practical memory and evaluation limits apply.
See Also
setq(), setr(), r(), letq(), localize(), ulocal(), u(), Substitution