Spec chapter 04
Control Flow
If
If hp <= 0 Then Die() // single-line form: the only form that uses Then
If hp <= 0
Die()
ElseIf hp < 20
Flash()
Else
Regen()
End IfConditions must be Bool.
The single-line form is If cond Then stmt, optionally followed by Else stmt on the same
line. It has no ElseIf, and its statements can’t be block statements (If, loops,
Select):
If hp <= 0 Then Die() Else Regen()For
For i = 0 To 9 ... End For // inclusive
For i = 0 Until 10 ... End For // exclusive
For i = 0 Until 10 Step 2 ... End For
For i = 10 To 0 Step -1 ... End For
For e In enemies ... End For // values
For i, e In enemies ... End For // index + value
For key, val In scores ... End For // map entriesFordeclares its variables; they exist only inside the loop. A counting loop’s variable takes the type of its start value; aFor ... Invariable takes the element type.For ... Inevaluates the array once and holds it for the loop. It reads aT[]’sCountbefore each pass, so removing elements during the loop ends it sooner. The index is anInt, and both variables are read-only copies. AT[W, H]goes row by row, with one index that counts every element.- A literal start takes the end’s type instead:
For i = 0 Until nwithn: Longcounts with aLong. - The start, end, and step are each evaluated once, in that order, before the first pass.
- The loop never steps past its end, so a
Byteloop up to 255 ends instead of wrapping around. - When the step’s sign isn’t known at compile time, it picks the direction at run time. A step
of
0is a compile error when known and a panic at run time. - Assigning to a loop variable inside the loop is an error.
Iterators
For ... In also goes over an Iterator, and over a value whose Iterate() gives one, like a
collection. An Iterator<T> (std/core/iterator.fb) has a ByRef Method Next(): T?, which gives
the next element each time, and Null once there are no more.
Struct Countdown Implements Iterator<Int>
Field N: Int
ByRef Method Next(): Int?
If N == 0 Then Return Null
N -= 1
Return N + 1
End Method
End Struct
For n In Countdown(3) ... End For // 3, 2, 1- The loop holds the Iterator, evaluated once: a copy of a Struct, so the variable it came from
doesn’t change, or a Class’s object, which its Next changes. An
Iterate()takes no arguments. One that throws needs the loop in a Try block, or writtenFor x In Try items, which lets its errors go on like Try on a call. - Over an Iterator, two names take each element apart, so the elements must be pairs:
For key, val In scores. Only arrays give an index. - Over values that can be Null, like an
Iterator<String?>’s, Next gives aString??: its Null ends the loop, and a Null value is one of the values, which the loop goes over. What gives one of them, like First, Last, Find, and Reduce, gives aString??too. - An
Iterable<T>(std/core/iterator.fb) is what has anIterate(): Iterator<T>, for code that takes any collection: std’s collections are. A collection’s own Iterate gives a Struct of its own, which anIterable<T>’s value, or generic code, gets in a box, anIterator<T>object; a For loop over the collection itself makes no box. - An Iterator’s adapters are lazy, each giving an
Iterator<T>that asks the one before it for a value only when it’s asked for one:Map,Filter,Take,Skip,TakeWhile,SkipWhile,Enumerate,Zip,Chain,Flatten,StepBy,Chunks,Windows,OrderBy, andOrderByDescending. Its consumers go over the values once:ToArray,Count,Any,All,Find,First,Last,Fold,Reduce,ForEach,SumandProductwhere T is Numeric,MinandMaxwhere it’s Comparable,MinBy, andMaxBy.Repeated(value, count)andEmpty<T>()give values of their own. They’re Final defaults, so what a type has, and a call through anIterator<T>value runs them as written (see 06-user-types.md). On a Struct, each goes over a copy of it, so the Struct doesn’t move, while a Class’s object is used up, as a For loop over it is.
Ranges
For i In 1 To 3 ... End For // 1, 2, 3
Local evens = 0 Until 10 Step 2 // 0, 2, 4, 6, 8
Print(evens.Sum(), (1 To 5).Product())a To banda Until b, withStep sor without, are values of std.core’sRange<T>, a Struct that implementsIterator<T>and gives whatFor i = a To b Step scounts. T is the start’s type, or the end’s when the start is a literal, as a For loop’s is, or the T of theRange<T>expected. A range counts with integers: a Float range is an error, though a For loop counts Floats.- The start, end, and step are evaluated once, in that order, when the range is made. A Step of 0 is an error when it’s known, and a panic when the program runs otherwise.
ToandUntilbind looser than every binary operator but? :, so each end is a whole expression:0 Until n - 1,1 To count ?? 0, and the Step takes a whole expression,? :too, as a For loop’s does. A Method of the range needs parentheses:(1 To 5).Sum(). In a For loop’s bounds and a Case’s values, To and Until are their own, as they always were. A slice is stilla[from..to].- Ranges are
==when they give the same values, so every range that gives none is==to every other, one that Next has gone to the end of too, and an empty range is a Range’s zero. That==is an Operator, which a Const Function doesn’t run. A range is a value, copied like any Struct, so a loop over a stored range goes over a copy of it.
Parallel For is covered in 10-concurrency.md.
While, Repeat, Loop
While running
...
End While
Repeat // body runs at least once
...
Until done
Loop // infinite
...
End LoopUntilis outside theRepeatbody, so it can’t seeLocals declared inside it.While True,Repeat ... Until False, andLooponly end throughBreak(orReturn), so code after one without aBreakis never reached.
Break and Continue
Outer: For y = 0 Until h
For x = 0 Until w
If grid[x, y] == 0 Then Continue
If grid[x, y] < 0 Then Break Outer // labeled break
End For
End ForA label (Outer:) must come right before a loop.
Select
Select state
Case State.Idle, State.Walk
Animate()
Case 1 To 5 // inclusive range
...
Case e As Boss // type match + bind
e.Enrage()
Default
...
End Select- No fallthrough.
Selecton an enum must be exhaustive or includeDefault. An exhaustive one leaves no path past it, so a function can return from every Case. A@FlagsEnum’s values combine, and an@OpenEnum holds others, so aSelecton one always needs aDefault.- The subject is an integer,
Bool,String,Char, or enum value, unless everyCaseis a type match likeCase e As Boss. - A type match runs for an object of that Class, or of one extending it, with
enaming it as one. ACaseafter one for a Class it extends never matches, so it’s an error.Nullgoes toDefault.Case x As Talways matches a Class; a value cast in aCaseneeds parentheses. Casevalues are of the subject’s type and must be known at compile time.Toranges work on integers and Chars, likeCase 'a' To 'z'. The same value can’t appear in two cases.
Defer
Runs a statement when the enclosing block exits, however it exits: at its end, or through
Return, Break, Continue, or a thrown error.
Local f = File.Open(path)
Defer f.Close()- The statement is one call, assignment,
++or--, or a one-lineIfof those. It can’t leave the block or declare anything. - A block’s Defers run the last first, after those of the blocks inside it. A loop’s body is a block, so each pass runs its own. Variables declared before a Defer are alive when it runs.
- Its expressions are evaluated when it runs:
Defer Print("{n}")showsnas it is at the exit.Returncomputes its value first, so a Defer can’t change what’s returned. - It’s checked where it’s written, as if it ran there. A null check above it still holds unless the rest of the block changes the variable, and what it assigns isn’t assigned after it.
- A panic ends the program without running Defers.
Removed
- No
Goto, noGosub, no line labels other than loop labels.
Report an issue with this page or with FutureBlitz on GitHub.