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
- An
int,bool, or bareword argument is a literal — an integer, a boolean, or a string respectively, typed by lexical form (Constants). $name/${name}reads the variable namespace — a parameter,let/constlocal, handler-injected name, or top-levelconst, all resolved lexically (Variables, Declarations and scope).&nameyields a callable value — adef'd function, or failing that the named builtin (Declarations and scope).
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:
Bareword. Resolved at compile time among the built-in commands and then the per-script
defnames; built-in commands take precedence. An unresolved bareword is a compile-time "unknown command" error.$var/&name/[...]in command position. The term is evaluated to a value, which must be a block, and invoked with the arguments. This is the standard way to call a block held in a variable or returned by an expression. (&nameis redundant in command position — see Declarations and scope.)
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).