TinyMUX

Registers

Softcode

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:

  • %q0 through %q9 – single-digit registers.
  • %qa through %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() or trigger() call shares the same register space as the caller—changes propagate back.
  • ulocal(), localize(), and letq() 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