Numbers, text, and logic
Behind the world-touching commands is ordinary computation: arithmetic,
comparisons, boolean logic, random rolls, and text manipulation. Every
operator is a prefix command call, normally written inside [...] so its
result becomes an argument.
Literals and types
A bare token is typed by its lexical form: digits make an int, true
and false make a bool, and anything else is a string:
let n 5 # int
let ok true # bool
let word hello # string (a bareword)
Quoting forces a string: 2 is the int two, but '2' is the one-character
string. Integer and boolean tokens cannot be used where a name is expected
(a parameter, a let/const name). Converting between types at run time is
always explicit — see Conversions for the int built-in.
Arithmetic — int → int
[+ 3 4] # 7
[- 10 3] # 7
[* 2 5] # 10
[/ 20 4] # 5 (integer quotient)
[% 17 5] # 2 (remainder)
Both operands must be ints; dividing or taking the remainder by zero is a run-time error. Nest calls to build expressions:
after command (loot) {
let gold [+ [* 2 [level $actor]] [randrange 1 6]]
echo $actor "You find $gold gold pieces."
}
Comparison → bool
[eq 2 2] # true [ne 2 3] # true (any comparable values)
[gt 5 3] # true [lt 3 5] # true (ints only)
[ge 5 5] # true [le 4 9] # true (ints only)
eq follows the type's equality rule and
does not cross types: [eq '1' 1] is false (string vs int). The
ordered comparisons require integers. For case-insensitive text use
streqi, and for prefix matching one string
against another, isabbrev:
after command (password) {
if [streqi $arg "open sesame"] {
echo $actor "The vault clicks open."
}
}
after command (compass) {
if [isabbrev $arg "north"] { # "n", "nor", "north" all match
do "say That way lies the mountains."
}
}
Logic → bool
[not a] # negation
[and a b …] # true when every operand is true
[or a b …] # true when any operand is true
All operands must be bool: a string, int, or null in a boolean position
is a type error. and and or take two or more operands:
after command (admit) {
if [and [isplayer $actor] [ge [level $actor] 10] [lt [level $actor] 50]] {
do "say You are exactly the calibre we need."
}
}
or is true when any operand holds:
after command (greet) {
if [or [streqi [class $actor] "Warrior"] [streqi [class $actor] "Knight"]] {
do "say A fighting type — well met!"
}
}
For an inverted condition, wrap the test: if [not [isplayer $actor]] { … }.
Random rolls
Two built-ins introduce chance. random is a
percentile predicate — [random 25] is true a quarter of the time — and
randrange returns a random integer in an
inclusive range:
after command (gamble) {
if [random 50] {
echo $actor "Heads — you win [randrange 10 100] chips!"
} else {
echo $actor "Tails. Better luck next time."
}
}
(For picking a random element of a collection, use
choose.)
Conversions
The one automatic conversion is to text, for interpolation; nothing is
silently parsed the other way. To turn a numeric string into an int — for
instance a number the player typed — use int:
after command (setcount) {
let n [int [first [words $arg]]] # "say 5" -> the int 5
do "say Counting to [+ $n 1]."
}
A non-numeric string is a run-time error, so validate input you do not control.
Transforming text
upper and lower
change case; substr takes up to len
characters from a 0-based start:
after command (shout) {
do "say [upper $arg]!" # SAY IT LOUD
}
after command (whisper) {
do "say [lower $arg]..." # quietly, all lower case
}
after command (initial) {
echo $actor "Your name begins with [upper [substr [name $actor] 0 1]]."
}
Assemble a string from pieces by interpolating them into a double-quoted string (see Talking and messaging).
Building lists with ( … )
A literal list is written with parentheses; its elements are any argument-position forms separated by whitespace. A list is iterable, so it flows straight into the consumers from Finding things:
after command (cardinal) {
let dirs (north east south west)
do "say I might go [choose $dirs]."
}
A list can even hold predicates — a command reference or a block:
let checks (&isplayer { <c> [ge [level $c] 5] })
The list built-in builds the same kind of list
from its arguments — the command-form counterpart of ( … ), convenient
when the elements are computed:
after command (profile) {
let facts [list [name $actor] [class $actor] [level $actor]]
do "say I know [count $facts] things about you."
}
if as an expression
if is not just control flow — it yields the value of the branch that
runs, so it can sit anywhere a value is expected. With no match and no
else, it yields null:
after command (rank) {
let title [if [ge [level $actor] 20] { [upper "elite"] } else { [upper "novice"] }]
do "say You are $title."
}
Note the branch bodies are command calls ([upper "elite"]): a branch's
value is its last statement's value, and a bare "elite" in statement
position would be read as a command and fail.
See also
- Operators · Values — equality, boolean contexts, text conversion.
- Variables, scope, and memory — storing the numbers and strings you compute.
- Control flow —
if/elif/elsein full.