Expressions

Everything that produces a value is an expression. A statement is a command call, which is itself an expression that produces a value (Blocks). This chapter defines the argument forms, command calls, command substitution, interpolation, the operator-like builtins, and the control-flow forms.

Argument-position forms

Wherever an argument is expected, the form is determined by the first token. These are the complete set of value-producing forms:

Argument = bareword            # literal: int / bool / string  (see Constants)
         | string              # '...'  literal string
         | InterpString        # "..."  interpolated string
         | CmdSubst            # [ cmd args ]
         | List                # ( ... )
         | Block               # { body } or { <params> body }
         | VarRef              # $name | ${name}
         | CommandRef .        # &name
First token Form
( list
{ block
' literal string
" interpolated string
[ command substitution
$ variable reference
& command reference
int integer literal
bool boolean literal
bareword string literal

Each form has one job; the form is determined unambiguously by the first token. Inside a block, a < immediately after { introduces the named-parameter subform (Blocks).

Literals and references

Lists

( … ) builds a list value (Types). Elements are any argument-position forms, separated by whitespace and/or newlines (no ;).

()                          # empty list
(1 2 3)
({ <n> [gt $n 0] } { <n> [lt $n 10] })   # a list of blocks

Interpolated strings

"..." evaluates its $var and [cmd] substitutions and concatenates the pieces, with every piece converted to string (Properties of types and values). The result is always a string.

"hello [name $actor], you have ${gold} coins"

Command calls

A statement, and the inside of a [...], is a command call: a command-position term followed by zero or more argument terms.

CommandCall = (bareword | VarRef | CommandRef | CmdSubst) { Argument } .

Resolution of the command-position term:

do 'wave'                 # bareword command (builtin)
greet $alice              # bareword command (def)
[$bump]                   # invoke a block held in $bump
[&is_dark $room]          # legal but redundant; same as [is_dark $room]

A command call always yields a value: a query builtin yields its result; a side-effecting command yields null.

Command substitution

[cmd args] evaluates the command call and substitutes its value. This is how a value-producing call is used as an argument.

require [eq [recall $self 'state'] 'gossip']
let target [choose [select [creatures [room $self]] { <c> isplayer $c }]]

Command substitution also appears inside interpolated strings, where the result is converted to string (Lexical elements).

A [expr] written as a whole statement is an expression statement: it evaluates expr and the body keeps its value as the statement's value (Blocks).

Operator builtins

The language has no infix operators; arithmetic, comparison, and logic are ordinary prefix command calls, normally used inside [...].

Form Result Notes
[+ a b] [- a b] [* a b] [/ a b] [% a b] int operands must be int; / and % by zero are run-time errors
[eq a b] [ne a b] bool structural equality (Properties)
[gt a b] [lt a b] [ge a b] [le a b] bool operands must be int
[not a] bool logical negation; operand must be bool
[and a b …] [or a b …] bool fold over arguments; every operand must be bool

and/or/not require bool operands and raise a type error otherwise; the ordered comparisons require integers under the same rule (Error conditions).

Invoking blocks

A block value is invoked by placing it in command position inside [...] (for a value result) or as a statement (for effect):

[$f $x $y]          # invoke the block in $f with two args
$f $x               # same, as a statement (value discarded unless last)

Argument binding and arity rules are in Blocks; the control-flow propagation rules are in Run-time.

The threading operator />

/> value cmd1 cmd2 … threads value left-to-right through a sequence of command names, feeding each command's result to the next. Every step must be a bareword naming a builtin or command. It is sugar for nested command substitution.

Control-flow forms

Control flow is not special syntax — if, each, select, every, some, and randomly are command calls that take block arguments and invoke them. Their argument shapes are validated at compile time; the Built-in functions chapter specifies their run-time behavior. Their call shapes:

if / elif / else

if [cond] { body }
if [cond] { body } elif [cond] { body } else { body }

Conditions are tested top to bottom; the first true one runs its branch and the rest are skipped. elif may appear zero or more times; else at most once, last. elif/else are not commands — they are literal-bareword markers recognized by the if form. Conditions must be bool — a non-bool raises a type error (Properties of types and values). For an inverted conditional body use if [not [cond]] { body }.

if is an expression: it yields the value of the branch that runs — that branch's block value, i.e. the value of the last command in the block. With no match and no else, it yields null. It may therefore be used in expression context like any other command:

let label [if [gt $hp 0] { 'alive' } else { 'dead' }]
do "say you are $label"

each

each $iter { <item> do "say got $item" }
each $iter { do "tick" }            # parameter-less: element ignored

select

select $iter { <c> [isplayer $c] }
select $iter &is_small

every / some

every $iter { <n> [gt $n 0] }
some  $iter { <n> [lt $n 0] }

randomly

randomly { do 'say hello' } or { do 'wave' } or { do 'smile' }

Chooses one branch uniformly at random and invokes it; yields that branch's value. or is a literal marker between branches, not a command.

Loop control and return

return, break, and continue may appear inside these forms. The propagation rules are specified in Run-time.

Handler guards

require and unless are statement-only guards that take a single boolean condition (Built-in functions, Program initialization and execution).