Structuring larger scripts
A handful of handlers can sit side by side, but once a script repeats logic
or grows past a screen, you will want to factor it. This chapter covers the
tools for that: named helpers, named constants, blocks passed as values,
the threading operator, and the flow-control forms that let a routine exit
early or span ticks. Throughout, a line beginning with # is a comment.
Named helpers — def
def name { … } binds a block to a name in the command namespace, so
you call it like a built-in. Use it to name a computation you repeat:
# yields x + 1; the bare [+ $x 1] is an expression statement,
# so its value becomes the block's value
def inc { <x> [+ $x 1] }
after command (add) {
do "say [inc 4] and [inc 41]." # says: 5 and 42.
}
A block's value is its last statement's value, and that statement must be a
value-producing call — a [...] substitution or a query. Use
return to exit early or make intent
explicit:
def classify { <c>
if [ge [level $c] 20] { return [upper "elite"] }
return [upper "common"]
}
after command (rank) {
do "say You are [classify $actor]."
}
A helper can take several parameters — list them all inside the angle brackets:
def max { <a b>
if [gt $a $b] { return $a }
return $b
}
after command (bigger) {
do "say The larger is [max [level $actor] 20]."
}
def is top-level only, and a def sits at the outermost scope — it does
not see a handler's injected names like $actor. A helper that needs
the actor must take it as a parameter, as classify does. All defs are
hoisted before any code runs, so they may call one another regardless of
source order.
Named constants — top-level const
At the top level, const name value binds an immutable value in the
variable namespace, readable as $name anywhere in the script —
including inside a def:
const HOME 3001
after command (recall) {
walkto $HOME
}
Blocks as values
A block is a first-class value. Bind one with let and invoke it by
putting it in command position inside [...]:
after command (twice) {
let shout { <msg> do "say $msg!" }
[$shout "here"]
[$shout "and here"]
}
Closures and the counter idiom
Because a block keeps access to the scope it was defined in, and
set reaches outward, a block returned from a
def can carry private state:
def make_counter { let n 0 ; return { set n [+ $n 1] ; return $n } }
after command (count) {
let bump [make_counter]
do "say [$bump] [$bump] [$bump]" # says: 1 2 3
}
The returned block still reaches n through its enclosing scope, so each
[$bump] advances the same n. (Statements inside a block are separated by
newlines or semicolons, as in make_counter.) Note the explicit
return on the inner block: a bare { … } written as a statement is
invoked, not yielded as a value, so to hand a block back you must put it
in argument position — here, as the argument to return.
Passing a command by name — &
The & sigil takes a command as a value, bridging the command namespace
into the variable namespace. Its main use is handing a named predicate to a
consumer instead of writing an inline block. For a built-in predicate, pass
it directly — &isplayer is exactly the block { <c> [isplayer $c] },
only shorter:
after command (census) {
let players [select [/> $self room creatures] &isplayer]
do "say [count $players] players present."
}
The same works for a predicate you define yourself — useful when the test is more than a single built-in:
def is_veteran { <c> [and [isplayer $c] [ge [level $c] 20]] }
after command (veterans) {
let vets [select [/> $self room creatures] &is_veteran]
do "say [count $vets] veterans present."
}
&name resolves to your def if there is one, otherwise the built-in of
that name. In command position it is redundant — [is_veteran $c] and
[&is_veteran $c] are the same — but as an argument it is how you pass
the function itself.
Variadic helpers — the rest parameter
The last parameter may be prefixed with ..., collecting any extra
arguments into an iterable, each argument a separate element:
def announce { <prefix ...rest>
each $rest { <item> do "say $prefix: $item" }
}
after command (list) {
announce "Wares" sword shield "healing potion"
}
announce binds prefix to "Wares" and rest to an iterable of the
three remaining arguments — note "healing potion" stays one element
because the rest parameter splits on arguments, not whitespace. With no
extra arguments the rest binds an empty iterable, never null.
Threading a value — />
/> value step1 step2 … feeds a value left-to-right through a chain of
single-argument callables, each result becoming the next call's
argument. It is sugar for nested substitution:
after command (announce) {
do "say [/> $actor name upper]!" # = [upper [name $actor]]
}
This shouts the actor's name in capitals. A step may be any compatible
callable: a bareword naming a built-in, system routine, or def; an
&name reference; a $var holding a block; or a literal { … } block.
Each step takes exactly one argument — the running value. A step invoked
as a block value (a &ref, $var, or literal block) yields its result
the usual way, as the block's last expression.
Loop control — break and continue
Inside a loop body (each,
select, and friends),
continue skips to the next element and
break ends the loop. They affect only the
nearest enclosing loop:
after command (countoff) {
each [words "1 2 3 4 5"] { <n>
if [streqi $n "3"] { continue } # skip three
if [streqi $n "5"] { break } # stop before five
do "say $n"
}
}
This says 1, 2, 4. To leave a routine entirely, use
return; to stop the whole script on the
spot, use halt.
Spanning ticks — pause
A handler normally finishes within one tick. pause n suspends it for n
ticks and resumes right where it left off, locals intact — the one way a
script stretches across game time:
after enter {
do "bow $actor"
pause 3
do "emote straightens and points to a notice on the wall."
}
(This is the mechanism walkto
and driveto expand into.)
Taking sole control — nuke
nuke cancels every other script currently running on the same owner —
including ones suspended on a pause — while the script that called it
keeps going. Use it when a routine must seize the owner and stop competing
handlers, such as an alarm overriding a paused idle sequence:
handle command (alarm) {
nuke
do "shout Intruder! All routines suspended!"
}
See also
- Blocks · Scope · Control flow
- Variables and memory — the scope rules closures rely on.
- Coverage index — where every feature is shown.