This chapter describes what a Mica program does: the statements a body is made
of, how they follow one another, assignment and calls, the choices if and
case, the loops while, repeat, for and for ... in, the names a loop
carries and the statements leave and next that address them, deferred
statements, and the release of a heap cell with dispose. Each construct names
its rule in the grammar, says what it means,
and quotes what the compiler answers when a source breaks the rule. The
expressions the statements compute are chapter 5,
and the routines a call enters are chapter 7.
Choices and repetition teaches the choices and the loops
from the start, and initializers, and the leave family
and defer, end to end the rest.
Every example is a complete program the website’s gate compiles with the compiler as it is written here, and the output shown beneath it is the output the program prints.
Statements and their sequence
A body is a compound statement, begin, a sequence of statements and end
(compound-statement), and
a compound statement stands wherever one statement may, so a branch or a loop
that does several things holds them in begin ... end. The statements of a
sequence are separated by semicolons and run one after the other
(statement-sequence). A
statement may be empty (statement),
so a semicolon before end does no harm and begin end does nothing. Where a
statement stands, a sequence may also declare a variable, which
chapter 3 describes
with its scope.
A statement is one of these (action):
| Statement | Written | Described |
|---|---|---|
| an assignment | total := total + price | below |
| a call | Report(), strings.UpperCase(name) | below |
| a compound statement | begin ... end | above |
| a choice | if, case | below, below |
| a loop | while, repeat, for, for ... in, each with an optional name | below |
| a leave or a next | leave, leave scan when found, next when skip | below |
| a deferred statement | defer dispose cell | below |
| a disposal | dispose cell | below |
| a failure | fail NotFound | error domains |
| an emit | emit reading | chapter 11 |
a schedule, a concurrent block, a select, a synchronized statement | schedule Worker(1) | chapter 11 |
| a tape window | tape ... end | the tape |
A call, an assignment from a call, a for ... in loop and a select may end
with a consumption, on fail and what to do when the call fails, which
the failure channel teaches.
Assignment
An assignment stores the value of the expression on the right in the place on
the left (assignment-statement).
The place is a variable, a field or an element of one, orders[i].price, or
what a pointer points to, value counter
(assignment-target). The
value takes the type of the place as
chapter 4
states, and an assignment of a record or an array copies the whole value, so a
change to the copy leaves the original as it was: after b := a; b[1] := 9;
the element a[1] keeps its value.
| Written | Code | The compiler’s words |
|---|---|---|
an integer assigned to a string, s := 3; | 5143 | incompatible data types in assignment to s: int32 to string |
a constant assigned, Limit := 4; | 5099 | 'Limit' is a constant, whose value is fixed as the program compiles, so it cannot be assigned: declare a variable for a value the program changes |
a loop’s control variable assigned in its body, i := i + 1; | 5147 | left side of an assignment cannot be a for-statement control variable: 'i' |
Calls
A call statement enters a procedure, or a function whose answer the program
does not need, by its name and its arguments in parentheses
(call-statement). The name may
be qualified by the unit it is imported from, strings.UpperCase(name) after
imp strings;, as chapter 2
describes. The arguments are evaluated from the left, each before the routine is
entered, and a function called as a statement runs and its answer is dropped.
The parentheses stand even where there is no argument, and
chapter 3
quotes the refusal of a call without them; chapter 7
states what a call hands over.
If
if, a condition, then and a statement runs the statement when the condition
holds, and an else with its statement runs that statement when it does not
(if-statement). The condition is
a bool. An else belongs to the nearest if without one, so in
if a = 1 then if b = 1 then ... else ... the else is the inner if’s, and
an if behind an else makes a chain, if ... then ... else if ... then ... else ..., whose first road with a holding condition runs. The statement before
else ends without a semicolon, since a semicolon would end the whole if.
| Written | Code | The compiler’s words |
|---|---|---|
a semicolon before else, a := 1; else | 2027 | semicolon before else is not allowed |
an integer as the condition, if n then | 5171 | statement condition requires boolean data type, found int64 |
while and repeat answer an integer condition with the same 5171.
Case
case, a selector, of, its arms and end runs the arm whose labels hold the
selector’s value (case-statement).
An arm is its labels, separated by commas, a colon and a statement
(case-element), and the arms are
separated by semicolons. An else and its statement may follow the last arm,
without a semicolon before it, and run when no label holds the value; a
semicolon may stand after the else’s statement, before end.
The selector is of an ordinal type, an integer, a character, a bool, an
enumeration or a subrange, or a bitset, or of a sum type. A label is one of
these (case-label):
| Label | Written | Over |
|---|---|---|
| a constant | 7, 'q', Sat, True | an ordinal selector |
| a range of constants | 9..17, 'a'..'m', Mon..Fri | an ordinal selector |
| a masked bit pattern | 0b????_??10 | an unsigned integer or a bitset, whose ? digits match either bit |
| a bitset constructor | [0, 2] | a bitset |
| a case of a sum type | Circle(*), Dot | a sum type, its fields unbound |
A label is computed while the program compiles, and two labels do not hold one value, so at most one arm takes a value. Masked patterns are the exception: two of them may match one value, the first arm that matches takes it, and a pattern that an earlier one already covers whole draws warning 5284, since it can never take a value. Bit records teaches masked patterns and sum types the cases of a sum type with the fields an arm reads.
A case says what happens to every value of its selector: its labels cover
every value, or it has an else. Over an enumeration, a subrange, a character, a
bool or a sum type, the compiler counts the values or the cases the labels
cover, so 'a'..'z' alone over a unicode is refused; over a
whole integer type the labels can rarely cover the domain, and a case over it
without an else is refused with 5458.
{
Choices - an 'if' with its 'else' roads, and 'case' over an integer, an enumeration and a sum type.
The 'if' chain takes the first road whose condition holds. The first 'case' reads a list, two ranges and an 'else',
the second covers every day of the week and needs no 'else', and the third names each case of a sum type with '(*)'.
}
program Choices;
imp
WriteLn : std;
const
HourLine = "at %lld the day is in its %ls";
Morning = "morning";
Afternoon = "afternoon";
Evening = "evening";
KindLine = "%lld counts as %ls";
Night = "night";
Early = "early hours";
Working = "working hours";
Late = "late hours";
WorkingDay = "Sat is a working day";
Weekend = "Sat is in the weekend";
CircleLine = "a circle";
SquareLine = "a square";
DotLine = "a dot";
AfternoonStart = 12;
EveningStart = 18;
type
Day = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
Shape = (Circle(radius : float64), Square(side : float64), Dot);
var
hour : int64;
kind : string;
today : Day;
shape : Shape;
begin
hour := 14;
if hour < AfternoonStart then
WriteLn(HourLine, hour, Morning)
else if hour < EveningStart then
WriteLn(HourLine, hour, Afternoon)
else
WriteLn(HourLine, hour, Evening);
case hour of
0..5: kind := Night;
6, 7, 8: kind := Early;
9..17: kind := Working
else
kind := Late
end;
WriteLn(KindLine, hour, kind);
today := Sat;
case today of
Mon..Fri: WriteLn(WorkingDay);
Sat, Sun: WriteLn(Weekend)
end;
shape := Square(2.5);
case shape of
Circle(*): WriteLn(CircleLine);
Square(*): WriteLn(SquareLine);
Dot: WriteLn(DotLine)
end;
end.It prints:
at 14 the day is in its afternoon
14 counts as working hours
Sat is in the weekend
a square| Written | Code | The compiler’s words |
|---|---|---|
a case over the three days of a Day that names two and has no else | 5285 | the case over 'Day' covers 2 of the 3 values of its domain and has no else: a case must say what happens to every value — cover the domain or add an else |
a case over an int64 without an else | 5458 | the case over 'int64' has no else and its arms do not provably cover the domain: a case must say what happens to every value — add an else |
a case over a unicode whose one arm is 'a'..'z', with no else | 5285 | the case over 'unicode' covers 26 of the 1114112 values of its domain and has no else: a case must say what happens to every value — cover the domain or add an else |
the label 1 twice, on lines 7 and 8 | 5166 | case statement has duplicate label value 1, first written at line 7, column 9 |
the ranges 1..5 and 4..9, on lines 7 and 8 | 5345 | case statement label covering 4..9 overlaps an earlier label, first written at line 7, column 9 |
a variable as a label, b: | 5162 | case statement label must be constant at compile time: 'b', a variable |
| a real as the selector | 5161 | case statement selector requires an ordinal or bitset data type, found float64 |
| a string as the selector | 5161 | case statement selector requires an ordinal or bitset data type, found string |
a masked pattern over an int64 | 5279 | a masked bit pattern cannot label a case over 'int64': the don't-care digits select bits, so the selector must be a bitset or an unsigned integer |
an integer as a label over a bitset, 0b0101: | 5281 | a case label over the bitset selector 'Flags' must be a constant bitset constructor or a masked bit pattern, found int32 |
a semicolon before else | 2027 | semicolon before else is not allowed |
Loops
while, a condition, do and a statement tests the condition before each
round and runs the statement while it holds, so a loop whose condition is false
at once runs no round (while-statement).
repeat, its statements, until and a condition runs the statements and then
tests, until the condition holds, so its body runs at least once
(repeat-statement). The body
between repeat and until is a sequence of its own, with no begin, and holds
at least one statement. A variable it declares where a statement stands lives
to the end of that sequence, which ends before until, so the condition cannot
read it: until k > 3 over a k declared in the body is refused with 5018,
identifier not found: k.
The counted for runs its statement once for every value from the first bound
to the second, upward with to and downward with downto
(for-statement). Its control
variable is a variable the routine or the program declares, of an ordinal type:
an integer, a character or an enumeration, for d := Mon to Fri do. The bounds
are evaluated once, before the first round, so a body that changes a variable
in the second bound does not change the number of rounds. A first bound past
the second runs no round and leaves the control variable as it was; after a
loop that ran, the control variable holds the value of its last round.
While the loop runs, nothing but the loop changes its control variable. The
body reads it and never stores into it: no assignment, no address of it, no
loop of its own over it, and no select arm or on fail handler that stores a
value into it. A routine declared inside the loop’s routine, at any depth, may
read the control variable and never assigns it, whether the loop calls that
routine or not, and neither the routine nor a routine inside it takes the
control variable’s address, except to hand it whole to a const pointer
parameter, which only reads through it. Each of these is refused at the loop
or at the statement that makes it, and the routine itself may assign the
control variable before and after the loop.
for, a variable, in, a container, do and a statement runs the statement
once for each element of the container, the variable holding the element. A
fixed array, a dynamic array, a vector, a matrix and a span give their elements
in index order, a matrix walking its rows one after the other, a string gives
its characters, a bitset the positions of its set bits in ascending order, and a
generator’s stream the values the generator emits, which
chapter 11
describes with the consumption and the on lost section such a loop may carry.
The loop walks the container in place, so the container is a variable or a
parameter, and the control variable, declared as the counted loop’s is, holds
each element and is never assigned. After the loop it holds the last element it
was given.
{
Loops - the three loops, a named loop that 'next' and 'leave' address, and a for-in over an array.
'while' tests before each round and 'repeat' after it, so the repeat's body runs at least once. The counted 'for'
reads its bounds once, before the first round, and counts down with 'downto'. The for-in walks the readings in index
order: 'next scan' skips a negative reading and 'leave scan' ends the loop at the first reading past the limit, and
the program goes on after the loop.
}
program Loops;
imp
WriteLn : std;
const
HalvedLine = "100 halves to 1 in %lld rounds";
RepeatLine = "the repeat stopped at %lld";
CountdownLine = "countdown %lld";
TotalLine = "the readings up to the first past %lld add up to %lld";
Start = 100;
Step = 3;
StepLimit = 10;
ReadingLimit : int64 = 20;
type
Readings = array[1..6] of int64;
var
readings : Readings := (4, 9, -1, 16, 25, 7);
reading, total, rounds, n : int64;
begin
n := Start;
while n > 1 do
begin
n := n / 2;
rounds := rounds + 1;
end;
WriteLn(HalvedLine, rounds);
n := 0;
repeat
n := n + Step;
until n > StepLimit;
WriteLn(RepeatLine, n);
for n := 3 downto 1 do
WriteLn(CountdownLine, n);
scan : for reading in readings do
begin
next scan when reading < 0;
leave scan when reading > ReadingLimit;
total := total + reading;
end;
WriteLn(TotalLine, ReadingLimit, total);
end.It prints:
100 halves to 1 in 6 rounds
the repeat stopped at 12
countdown 3
countdown 2
countdown 1
the readings up to the first past 20 add up to 29| Written | Code | The compiler’s words |
|---|---|---|
a control variable no section declares, for k := 1 to 3 do | 5018 | identifier not found: k |
a real control variable, for r := 1.0 to 3.0 do | 5157 | for statement control variable requires a supported ordinal data type, found float64 |
a for-in over a set of Day | 5257 | a for-in statement iterates a container — a bitset visits its set-bit positions in ascending order, a string its code points, a fixed array, a vector, a matrix, a dynamic array, or a span its elements in index order (a matrix walking its contiguous row-major block), and a generator's stream the values it emits — but the container expression has type: Days |
an int64 walking the bits of a bitset | 5258 | a for-in control variable holds raw bit positions, so 'n' must be a uint64: int64 |
a for-in over a typed constant array, for x in Readings do | 5490 | a for-in over 'Row' walks its elements in place, through a borrowed view of the array's own storage, so the container must be an array variable or parameter — give the value a name and iterate that name, or, when the elements come from a call, take the dynamic array the call answers |
the element assigned in the body, x := 1; | 5147 | left side of an assignment cannot be a for-statement control variable: 'x' |
a routine Reset inside the loop’s routine assigning the control variable on line 9, i := 0; | 5782 | 'Reset' assigns 'i', the index of this loop, at line 9, column 9, and nothing but the loop may change its index while it runs: give 'Reset' a variable of its own for what it stores, or count this loop with a variable no nested routine assigns |
the control variable’s address taken ahead of the loop on line 11, cell := address i; | 5783 | the address of 'i', the index of this loop, is taken at line 11, column 13, and a write through that pointer could change the index while the loop runs: take the address of another variable, or count this loop with a variable whose address is never taken |
a select arm in the body storing into the control variable, on i in numbers do | 5160 | for statement control variable 'i' is already protected in the current lexical scope |
a repeat with no statement before until | 2017 | expected the begin of a statement, found until |
Leave and next
A loop may carry a name, written before it with a colon, scan : for ...
(labeled-loop). The name belongs
to a while, a repeat or a for loop and to nothing else, and it is what
leave and next address.
leave with a loop’s name ends that loop, and the statement after the loop
runs next. leave without a name ends the routine it stands in, as
chapter 7 describes for a function’s
answer, in the program’s body it ends the program, which exits with status
zero (leave-statement), and in
the body of a library or an object unit it ends that body, as
deferred statements shows. A bare
leave therefore never ends only a loop: a loop that is left early carries a
name.
next ends the current round of a loop, which goes on with its next round: a
while and a repeat test their condition, and a for moves to its next value
(next-statement). next
without a name continues the innermost loop around it, and next with a name
the loop of that name. Both statements take a guard, when and a condition, and
then act only where the condition holds: leave scan when reading > Limit,
next when odd i (when-guard).
The example under loops uses both.
| Written | Code | The compiler’s words |
|---|---|---|
a name that no loop around the statement carries, leave outer; | 5227 | no enclosing loop is named 'outer': a loop name can only be targeted from inside the loop it names |
next outside every loop | 5226 | 'next' advances a loop iteration, so it requires an enclosing for, while, or repeat statement |
| a loop inside another loop of the same name | 5228 | loop name 'scan' is already carried by an enclosing loop, so a target spelling it would be ambiguous |
a name before begin, block : begin | 2045 | a loop name must introduce a for, while, or repeat statement, found begin |
Deferred statements
defer and a statement registers the statement to run when the routine it
stands in ends (defer-statement),
at its last statement or at a leave, and in the program’s body when the
program ends. The registered statements run newest first, so a teardown mirrors
its setup, and each reads the values that hold when it runs, not those of its
registration. A path that never reached a defer does not run it, so a leave
above a registration ends the routine without it. Leaving a loop is not the end
of the routine and runs nothing. A defer stands unconditionally in the body
of its routine, outside every choice and loop.
defer until and an owner, the name of a routine around it or program,
registers a dispose to run when that owner ends instead
(until-owner), and new takes the
same owner, new Node until program, as
heap end to end teaches.
In the body of a library or an object unit, defer until program and any
statement registers a finalizer of the unit, which runs once when the program
has ended (libraries).
A finalizer stands at the top level of that body, outside every choice and
loop, so every run of the body reaches it and the finalizers a program runs are
the ones its units write, newest first. A cleanup that depends on a condition
keeps its finalizer at the top level and puts the condition inside the
statement, which reads the unit’s state when the program has ended:
{
Ledger - a library whose finalizers stand at the top level of its body, one of them holding its condition.
The body runs once when a program that uses the library starts, and registers two finalizers. When the program has
ended they run, the last registered first: the guarded one closes the entries the program opened, or says there were
none, and the first one reports the count it reads then.
}
library ledger;
imp
WriteLn : std;
const
ClosedLine = "closed %lld entries";
EmptyLine = "nothing to close";
CountLine = "the ledger ends with %lld entries";
var
entries : int64;
{ Open adds one entry to the ledger. }
exp procedure Open();
begin
entries := entries + 1;
end;
begin
entries := 0;
defer until program WriteLn(CountLine, entries);
defer until program
if entries > 0 then
WriteLn(ClosedLine, entries)
else
WriteLn(EmptyLine);
end.A bare leave in such a body ends the body, as it ends a routine’s, and the
finalizers written above it run when the program has ended. A leave stands
below every finalizer of the body: one above a finalizer would skip its place
while the finalizer still ran, so the compiler refuses it and names the
finalizer. A body that may end early writes its finalizers first:
{
Index - a library whose body ends early when there is nothing to index, below its finalizer.
The body registers its finalizer first, then leaves when the index has no capacity, so the line below the 'leave'
does not run on that path. When the program has ended the finalizer reports the capacity it reads then, whichever
way the body ended.
}
library index;
imp
WriteLn : std;
const
BuiltLine = "the index holds %lld slots";
EndLine = "the index ends with %lld slots";
DefaultCapacity = 8;
var
capacity : int64;
{ Resize sets the capacity the index reports when the program has ended. }
exp procedure Resize(slots : int64);
begin
capacity := slots;
end;
begin
capacity := DefaultCapacity;
defer until program WriteLn(EndLine, capacity);
leave when capacity = 0;
WriteLn(BuiltLine, capacity);
end.{
Cleanup - deferred statements, run newest first when their routine ends, and a heap cell released by one.
'Work' registers three deferred statements. They run when 'Work' ends, the last registered first, and each reads
the values that hold then, so the report prints the weight the body set after it was registered. The cell is
released by the deferred 'dispose', which the routine registered right after allocating it.
}
program Cleanup;
imp
WriteLn : std;
const
WeightLine = "the cell weighs %lld";
EndLine = "Work ends, last of all";
ReportLine = "the report reads the weight at the end: %lld";
BackLine = "back in the program";
FirstWeight = 1;
FinalWeight = 42;
type
Node = record
weight : int64;
end;
{ The work of one routine whose cleanup is registered as it goes. }
procedure Work();
var
cell : pointer Node;
begin
defer WriteLn(EndLine);
cell := new Node;
defer dispose cell;
cell.weight := FirstWeight;
defer WriteLn(ReportLine, cell.weight);
cell.weight := FinalWeight;
WriteLn(WeightLine, cell.weight);
end;
begin
Work();
WriteLn(BackLine);
end.It prints:
the cell weighs 42
the report reads the weight at the end: 42
Work ends, last of all
back in the program| Written | Code | The compiler’s words |
|---|---|---|
a defer inside an if | 5192 | defer statement must be placed unconditionally in the body of its function, but found it inside one 'if statement' |
an owner on a statement that is not a dispose, defer until Outer WriteLn(...) | 5197 | a deferred statement with an 'until' owner must be a dispose statement |
an owner that names nothing, defer until Nowhere dispose p; | 5018 | identifier not found: Nowhere |
a finalizer inside a loop of a library’s body, for order := 1 to 3 do defer until program WriteLn(...) | 5799 | a 'defer until program' in an initialization block is a unit finalizer, which runs once when the program ends whether or not its place ran, so it must stand at the top level of the block, but found it inside one 'for statement' - move it to the top level of the initialization block and put the condition inside its statement, or, where the cleanup belongs to the conditional work, do that work in a routine whose own 'defer' runs when the routine ends |
a leave above a finalizer of a library’s body, if ready = 0 then leave; on line 18 above defer until program WriteLn(...) on line 20 | 5801 | a bare 'leave' ends the initialization block, so it skips the place of the unit finalizer at line 20, column 5 below it, which would still run when the program ends - move the finalizer above the 'leave', or put the work that leaves early in a routine the block calls, whose own body the 'leave' then ends |
Disposal
dispose and a pointer releases the heap cell it points to
(dispose-statement), and
dispose and a variable of the routine whose record or array type holds heap
cells releases those cells. Every cell a program allocates is released on every
path, by a dispose, a deferred one or the owner its new names, and the
compiler follows each cell to its release and refuses a program that reads a
cell after it, as heap end to end and
ownership teach.
| Written | Code | The compiler’s words |
|---|---|---|
an integer disposed, dispose n; | 5190 | dispose statement requires a pointer, or a variable of its own routine whose record or array type holds a heap cell, but found data type 'int64' |
a field read after its cell is disposed, dispose p; WriteLn("%lld", p.w); | 13105 | heap cell is dereferenced after it was already released on every path reaching here — the cell allocated at line 15 is used after its dispose; move the use before the release |