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