Control blocks are an indented sequence of statements that are wrapped together in a shared control context and lexical scope for local variable declarations. Acorn offers several types of control blocks:
- method, closure, and yielder, which provide various ways to operate on parameters passed by its caller and eventually return a value back to the caller (described in an earlier chapter).
- do, which wraps a sequence of statements within a context of values that are begun at the start and stopped at the end.
- if ... elif .. else, which uses conditional expressions to determine which sequence of statements to perform.
- match, which matches a value against multiple patterns, each corresponding to a sequence of statements to perform if that match is successful.
- while, which repetitively performs a sequence of statements so long as a specific conditional expression remains true.
- this, which focuses the attention of a sequence of statements on a specific value referred to explicitly or implicitly by the pseudo-variable 'this' (described in the next chapter).
- each, which repetitively performs a sequence of statements for each value retrieved by an iterator until it is done (described in the next chapter).
Control clauses are a concise version of three of these control blocks (if, while, each). Control clauses make it possible for a single line to contain both a statement (such as an expression statement, break, continue, return or yield) and a control clause that operates on just that statement as if it were a one statement block. Multiple clauses may be specified on a statement; they are handled in reverse order (see each clause for examples). Any expression statement that uses a control clause should avoid declaring local variables; its variables are lexically scoped within the control clauses that follow.
'do'
A 'do' block without specified values may be used to separate out an indented sequence of statements that have a lexical scope for local variables.
do local max = a<b ? b : a Vm.Print(max)
A 'do' block that specifies one or more comma-separated expressions acts as a context manager (similar to Python's 'with' statement). Acorn will invoke the .Begin method on all those values before performing the first statement in the block. It will also invoke the .End method on all those values after performing the last statement in the block.
do timer # timer.Begin automatically performed here biglist.Sort # timer.End automatically performed here
'do' offers a safe, convenient way to handle resources that have a specific usage life-time, such as files, i/o, timers, transactions (where .End performs a commit), processes, locks, or yielders. It is perfectly fine for a resource's type to implement an 'End' method but not a 'Begin' method.
'if'
Acorn provides three techniques for using conditional expressions to determine which value to use or which code block to perform: the '?' operator, the 'if' clause, and the 'if' code block.
? operator
The ? operator is a concise way to pick one of two values within an expression. If the expression before the ? operator is true, the value after the '?' is picked. Otherwise the value after the 'else' is chosen:
x = val<5.0 ? 5.0 else val # sets x to the higher between 5.0 and val
Note: To avoid the possibility of a question-mark being treated as part of a method or property name, always be sure to put a space before the ? operator.
if clause
If you have a single statement that should execute (or not) based on a condition, append the statement with 'if' followed by the condition:
val = 5.0 if val<5.0 # if val is less than 5, set it to 5
if block
For more complex scenarios, use the 'if' block. The statements to perform on a true condition are tab-indented as a block.
if cmd=='snow' sky.weather = cmd $$.ambient Color.Gray
Use the 'else' block whenever you have alternative set of statements to perform should the condition evaluate as false.
if actor.mood == 'happy' actor.action = 'dance' else actor.eyes = 'cry'
One can insert one or more 'elif' blocks to sequentially check other conditions and perform the associated statement block if true. 'else', if specified, should always be last.
moving = true if x<=0.0 speed = 0.0 moving = false elif y<3.0 speed = 1.0 else speed = 5.0
match
The match block is similar to, but more powerful than, the 'switch' block found in other languages. The 'match' block is used to sequentially match a single value against multiple patterns. It will perform the first code block associated with a successful match, or the 'else' block, if there are no matches.
match term_speed with 0.0 .. 1.5 # equivalent to: if +Range(0.,1.5).'~~'(term_speed) ship.state = 'landing' with 1.5 .. 4.0 # equivalent to: if +Range(1.5,4.0).'~~'(term_speed) ship.state = 'crashing' else ship.state = 'flattened'
By default, the 'match' block uses the '~~' method to match each successive pattern against the value of the 'match' statement's expression, as the comments demonstrate. A match happens if the return value is anything other than false or null. The first 'with' matches if the value term_speed lies between 0 and 1.5. If it matches, the ship's state is set to 'landing'.
Several of Acorn's core types implement the '~~' method:
- Range matches whether an Integer or Float lies within its defined range.
- Type matches whether a value makes use of that type's defined traits.
- All ensures that a '~~' match is treated like '==' for any type that does not implement the ~~ method. This allows the 'match' block to be used the way one would use a 'switch' block in other languages.
Additional pattern matching types can be easily defined that implement the '~~' method, thereby extending the pattern matching capability of Acorn.
The 'using' clause
Append a 'using' clause to the 'match' statement to specify a specific method to use for matching (as opposed to using the default '~~'):
match number using '==' with 0 .turn with 1 .jump
The using clause can specify any method symbol (including the default '==' and '===' provided by All). Any executable method, closure or yielder may also be specified.
Multiple patterns on a 'when'
A 'with' statement can list multiple match patterns, separated by commas. A match on any of those match patterns activates that code block.
match number using '==' with 0,1 .turn with 2,3,4 .jump
Extracting match results
Some patterns do more than matching. They also extract or transform matched elements within a successfully matched source value, returning one or more new digested value (so long as the first returned value is not false or null, which indicates a match failure). Specify the variables names that hold these returned values with an 'into' clause at the end of the 'match' statement:
# Cmd is a custom-built pattern for decoding commands match command into thing with +Regex"fight [*]" combat: true opponent: thing with +Regex"use [*]" .use(thing)
Regex returns the portion of the Text that lies within the square brackets. So, if command was "fight Lux", the first match would succeed and thing would be set to "Lux".
Alternatively, the return variable name(s) may be specified on any specific 'with' statement:
match command with +Regex"fight [*]" into npc combat: true opponent: npc with +Regex"use [*]" into item .use(item)
while
while executes the same statement(s) over and over as long as its condition remains true. The condition is checked right away and every time after the statement(s) have been performed.
This example uses a while block:
x = 0 while x<=5 $.log "WARNING, WARNING! Will Robinson" x+=1
while may be used as a control clause:
$$.show(list.extract) while !list.empty?
The break statement, placed anywhere within a while block, provides a way to instantly and explicitly stop performance of the 'while' block statements. break is typically used with an 'if' control clause:
while true # without the break, this loop would run forever actor.walkstep # will happen at least once break if keypress=='space' # stops whenever the space bar is pressed
By contrast, the continue statement ignores all subsequent statements in the 'while' block, returning control back to the top of the while block where the conditional expression is re-evaluated.