Scope
The scope of a name determines where it is visible, and how the language finds the binding the name refers to.
Two namespaces
Names live in two separate namespaces that never collide:
- The command namespace holds the built-in commands and the commands
you define with
def. A bareword in command position — the first word of a statement, or the first word inside[...]— resolves here. - The variable namespace holds block parameters,
let/constlocals, the handler-injected names ($self,$actor, …), and top-levelconst. A$namereference resolves here.
Because they are separate, let count 0 does not shadow the
count builtin, and def make { … } does not create a
variable $make. The bridge between them is the & sigil, which
takes a command as a value usable in the variable namespace:
def greet { <c> do "say hello [name $c]" }
greet $alice # bareword -> command namespace -> run greet
let g &greet # & takes greet as a value
[$g $alice] # $g -> variable namespace; [...] invokes it
Top-level declarations
On the top level, a script may contain only three things: handlers, a
function definition usingdef, and a top-level assignment using
const.
def — named commands
def name { … } binds name in the command namespace. The value must
be a block. Afterwards you call name like a builtin, or take
it as a value with &name.
def classify { <c>
if [ge [level $c] 20] { return [upper "elite"] }
return [upper "common"]
}
after command (say) {
do "say you are [classify $actor]"
}
Against a level-26 actor it reports "you are ELITE." def is
top-level only. For a callable held in a variable, use let f { … }
and invoke it as [$f …].
A def may not reuse a builtin or reserved command name, and two defs
may not share a name.
const — top-level named values
const name value binds an immutable value in the variable namespace,
read as $name from anywhere in the script.
const HOME 3001
Hoisting and order
Before any code runs, every top-level def and const name is made
visible to the whole script, then the top-level forms run in source
order to fill in their values. Two consequences:
- A block body may call any top-level
defregardless of source order, so mutually-recursive helpers need no forward declaration. - A top-level
constinitializer that reads anotherconstwhose own initializer has not run yet finds it unset, which will refuse to compile.
Lexical scope and lookup
Blocks are lexically scoped: a free variable resolves up the chain of scopes where the block was defined, not where it was called. Each block invocation has its own locals; its parent is the scope it sits inside in the source.
each $rooms { <room>
each [people $room] { <person>
do "say [name $person] is in [name $room]"
}
}
The inner block sees $room because its enclosing chain reaches the outer
each's scope.
A $name read searches the current scope first (its parameters, injected
names, and let/const), then each enclosing scope outward, and finally
the top-level consts. The search is resolved when the script compiles;
an unresolved name fails to load.
One important consequence: a top-level def sits at the outermost scope,
not inside any handler, so it does not see a handler's injected
names like $actor. A helper that needs the actor must take it as a
parameter:
def greet { <c> do "say hello [name $c]" }
after command (say) {
greet $actor # pass the actor in
}
Within one scope all names share a flat space: a let that reuses a
parameter or an earlier local's name is a compile-time error.
The counter idiom
Because set crosses scope boundaries, a returned block
keeps and can mutate the locals of the scope that made it:
def make_counter { let n 0 ; { set n [+ $n 1] ; return $n } }
after command (say) {
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
call advances the same n. Produces 1 2 3.
Lifetime
A block's locals are fresh for each call and gone once it returns — unless a nested block still references them, as above. Locals therefore do not persist between separate handler invocations; for that, use persistent state.
See also
- Variables —
let,set,const, and$name. - Blocks — the code values that scopes wrap.
- Handlers — where injected names like
$actorcome from. - Persistent state — state across invocations.