Statements¶
Simple and Compound Statements¶
We can classify statements as either simple or compound. Simple statements don't contain other statements; think of them as "atomic units" that cannot be further divided. Compound statements, on the other hand, may contain other — simple or compound — statements.
Here are some examples from each category:
Category |
Examples |
|---|---|
Simple statements |
Null statement, assignment, subprogram call, etc. |
Compound statements |
If statement, case statement, loop statement, block statement |
In the Ada Reference Manual
Labels¶
We can use labels to identify statements in the code. They have the following
format: <<Some_Label>>. We write them right before the statement we want
to apply it to. Let's see an example of labels with simple statements:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Statement_Identifier is
pragma Warnings (Off, "is not referenced");
begin
<<Show_Hello>> Put_Line ("Hello World!");
<<Show_Test>> Put_Line ("This is a test.");
<<Show_Separator>>
<<Show_Block_Separator>>
Put_Line ("====================");
end Show_Statement_Identifier;
Here, we're labeling each statement. For example, we use the Show_Hello
label to identify the Put_Line ("Hello World!"); statement. Note that we
can use multiple labels a single statement. In this code example, we use the
Show_Separator and Show_Block_Separator labels for the same
statement.
In the Ada Reference Manual
Labels and goto statements¶
Labels are mainly used in combination with goto statements. (Although
pretty much uncommon, we could potentially use labels to indicate important
statements in the code.) Let's see an example where we use a goto label;
statement to jump to a specific label:
procedure Show_Cleanup is
pragma Warnings (Off, "always false");
Some_Error : Boolean;
begin
Some_Error := False;
if Some_Error then
goto Cleanup;
end if;
<<Cleanup>> null;
end Show_Cleanup;
Here, we transfer the control to the cleanup statement as soon as an error is detected.
Use-case: Continue¶
Another use-case is that of a Continue label in a loop. Consider a loop
where we want to skip further processing depending on a condition:
procedure Show_Continue is
function Is_Further_Processing_Needed
(Dummy : Integer)
return Boolean
is
begin
-- Dummy implementation
return False;
end Is_Further_Processing_Needed;
A : constant array (1 .. 10) of Integer :=
(others => 0);
begin
for E of A loop
-- Some stuff here...
if Is_Further_Processing_Needed (E) then
-- Do more stuff...
null;
end if;
end loop;
end Show_Continue;
In this example, we call the Is_Further_Processing_Needed (E) function to
check whether further processing is needed or not. If it's needed, we continue
processing in the if statement. We could simplify this code by just using
a Continue label at the end of the loop and a goto statement:
procedure Show_Continue is
function Is_Further_Processing_Needed
(Dummy : Integer)
return Boolean
is
begin
-- Dummy implementation
return False;
end Is_Further_Processing_Needed;
A : constant array (1 .. 10) of Integer :=
(others => 0);
begin
for E of A loop
-- Some stuff here...
if not Is_Further_Processing_Needed (E) then
goto Continue;
end if;
-- Do more stuff...
<<Continue>>
end loop;
end Show_Continue;
Here, we use a Continue label at the end of the loop and jump to it in
the case that no further processing is needed. Note that, in this example, we
don't have a statement after the Continue label because the label itself
is at the end of a statement — to be more specific, at the end of the loop
statement. In such cases, there's an implicit null statement.
Historically
Since Ada 2012, we can simply write:
loop
-- Some statements...
<<Continue>>
end loop;
If a label is used at the end of a sequence of statements, a null
statement is implied. In previous versions of Ada, however, that is not the
case. Therefore, when using those versions of the language, we must write at
least a null statement:
loop
-- Some statements...
<<Continue>> null;
end loop;
Labels and compound statements¶
We can use labels with compound statements as well. For example, we can label
a for loop:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Statement_Identifier is
pragma Warnings (Off, "is not referenced");
Arr : constant array (1 .. 5) of Integer :=
(1, 4, 6, 42, 49);
Found : Boolean := False;
begin
<<Find_42>> for E of Arr loop
if E = 42 then
Found := True;
exit;
end if;
end loop;
Put_Line ("Found: " & Found'Image);
end Show_Statement_Identifier;
For further reading...
In addition to labels, loops and block statements allow us to use a
statement identifier. In simple terms, instead of writing
<<Some_Label>>, we write Some_Label :.
We could rewrite the previous code example using a loop statement identifier:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Statement_Identifier is
Arr : constant array (1 .. 5) of Integer :=
(1, 4, 6, 42, 49);
Found : Boolean := False;
begin
Find_42 : for E of Arr loop
if E = 42 then
Found := True;
exit Find_42;
end if;
end loop Find_42;
Put_Line ("Found: " & Found'Image);
end Show_Statement_Identifier;
Loop statement and block statement identifiers are generally preferred over labels. Later in this chapter, we discuss this topic in more detail.
Exit loop statement¶
We've introduced bare loops back in the Introduction to Ada course. In this section, we'll briefly discuss loop names and exit loop statements.
A bare loop has this form:
loop
exit when Some_Condition;
end loop;
We can name a loop by using a loop statement identifier:
Loop_Name:
loop
exit Loop_Name when Some_Condition;
end loop Loop_Name;
In this case, we have to use the loop's name after end loop. Also,
having a name for a loop allows us to indicate which loop we're exiting from:
exit Loop_Name when.
Let's see a complete example:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Vectors;
procedure Show_Vector_Cursor_Iteration is
package Integer_Vectors is new
Ada.Containers.Vectors
(Index_Type => Positive,
Element_Type => Integer);
use Integer_Vectors;
V : constant Vector := 20 & 10 & 0 & 13;
C : Cursor;
begin
C := V.First;
Put_Line ("Vector elements are: ");
Show_Elements :
loop
exit Show_Elements when C = No_Element;
Put_Line ("Element: "
& Integer'Image (V (C)));
C := Next (C);
end loop Show_Elements;
end Show_Vector_Cursor_Iteration;
Naming a loop is particularly useful when we have nested loops and we want to exit directly from the inner loop:
procedure Show_Inner_Loop_Exit is
pragma Warnings (Off);
Cond : Boolean := True;
begin
Outer_Processing : loop
Inner_Processing : loop
exit Outer_Processing when Cond;
end loop Inner_Processing;
end loop Outer_Processing;
end Show_Inner_Loop_Exit;
Here, we indicate that we exit from the Outer_Processing loop in case a
condition Cond is met, even if we're actually within the inner loop.
In the Ada Reference Manual
If, case and loop statements¶
In the Introduction to Ada course, we talked about if statements, loop statements, and case statements. This is a very simple code example with these statements:
procedure Show_If_Case_Loop_Statements is
pragma Warnings (Off);
Reset : Boolean := False;
Increment : Boolean := True;
Val : Integer := 0;
begin
--
-- If statement
--
if Reset then
Val := 0;
elsif Increment then
Val := Val + 1;
else
Val := Val - 1;
end if;
--
-- Loop statement
--
for I in 1 .. 5 loop
Val := Val * 2 - I;
end loop;
--
-- Case statement
--
case Val is
when 0 .. 5 =>
null;
when others =>
Val := 5;
end case;
end Show_If_Case_Loop_Statements;
In this section, we'll look into a more advanced detail about the case statement.
In the Ada Reference Manual
Case statements and expressions¶
As we know, the case statement has a choice expression
(case Choice_Expression is), which is expected to be a discrete type.
Also, this expression can be a function call or a type conversion, for example
— in additional to being a variable or a constant.
As we discussed earlier on, if we use parentheses, the contents between those parentheses is parsed as an expression. In the context of case statements, the expression is first evaluated before being used as a choice expression. Consider the following code example:
package Scales is
type Satisfaction_Scale is (Very_Dissatisfied,
Dissatisfied,
OK,
Satisfied,
Very_Satisfied);
type Scale is range 0 .. 10;
function To_Satisfaction_Scale
(S : Scale)
return Satisfaction_Scale;
end Scales;
package body Scales is
function To_Satisfaction_Scale
(S : Scale)
return Satisfaction_Scale
is
Satisfaction : Satisfaction_Scale;
begin
case (S) is
when 0 .. 2 =>
Satisfaction := Very_Dissatisfied;
when 3 .. 4 =>
Satisfaction := Dissatisfied;
when 5 .. 6 =>
Satisfaction := OK;
when 7 .. 8 =>
Satisfaction := Satisfied;
when 9 .. 10 =>
Satisfaction := Very_Satisfied;
end case;
return Satisfaction;
end To_Satisfaction_Scale;
end Scales;
with Ada.Text_IO; use Ada.Text_IO;
with Scales; use Scales;
procedure Show_Case_Statement_Expression is
Score : constant Scale := 0;
begin
Put_Line ("Score: "
& Scale'Image (Score)
& Satisfaction_Scale'Image (
To_Satisfaction_Scale (Score)));
end Show_Case_Statement_Expression;
When we try to compile this code example, the compiler complains about missing
values in the To_Satisfaction_Scale function. As we mentioned in the
Introduction to Ada course, every possible
value for the choice expression needs to be covered by a unique branch of the
case statement. In principle, it seems that we're actually covering all
possible values of the Scale type, which ranges from 0 to 10. However,
we've written case (S) is instead of case S is. Because of the
parentheses, (S) is evaluated as an expression. In this case, the
expected range of the case statement is not Scale'Range, but the range of
its base type Scale'Base'Range.
In other languages
In C, the switch-case statement requires parentheses for the choice expression:
#include <stdio.h>
int main(int argc, const char * argv[])
{
int s = 0;
switch (s)
{
case 0:
case 1:
printf("Value in the 0 -- 1 range\n");
default:
printf("Value > 1\n");
}
}
In Ada, parentheses aren't expected in the choice expression. Therefore,
we shouldn't write case (S) is in a C-like fashion —
unless, of course, we really want to evaluate an expression in the case
statement.
Block Statements¶
We've introduced block statements back in the Introduction to Ada course. They have this simple form:
procedure Show_Block_Statement is
pragma Warnings (Off);
begin
-- BLOCK STARTS HERE:
declare
I : Integer;
begin
I := 0;
end;
end Show_Block_Statement;
We can use an identifier when writing a block statement. (This is similar to
loop statement identifiers that we discussed in the previous section.) In this
example, we implement a block called Simple_Block:
procedure Show_Block_Statement is
pragma Warnings (Off);
begin
Simple_Block : declare
I : Integer;
begin
I := 0;
end Simple_Block;
end Show_Block_Statement;
Note that we must write end Simple_Block; when we use the
Simple_Block identifier.
Block statement identifiers are useful:
to indicate the begin and the end of a block — as some blocks might be long or nested in other blocks;
to indicate the purpose of the block (i.e. as code documentation).
In the Ada Reference Manual
Extended return statement¶
A common idiom in Ada is to build up a function result in a local object, and then return that object:
procedure Show_Return is
type Array_Of_Natural is
array (Positive range <>) of Natural;
function Sum (A : Array_Of_Natural)
return Natural
is
Result : Natural := 0;
begin
for Index in A'Range loop
Result := Result + A (Index);
end loop;
return Result;
end Sum;
begin
null;
end Show_Return;
Since Ada 2005, a notation called the extended return statement is available: this allows you to declare the result object and return it as part of one statement. It looks like this:
procedure Show_Extended_Return is
type Array_Of_Natural is
array (Positive range <>) of Natural;
function Sum (A : Array_Of_Natural)
return Natural
is
begin
return Result : Natural := 0 do
for Index in A'Range loop
Result := Result + A (Index);
end loop;
end return;
end Sum;
begin
null;
end Show_Extended_Return;
The return statement here creates Result, initializes it to
0, and executes the code between do and end return.
When end return is reached, Result is automatically returned
as the function result.
In the Ada Reference Manual
Other usages of extended return statements¶
Note
This section was originally written by Robert A. Duff and published as Gem #10: Limited Types in Ada 2005.
While the extended_return_statement was added to the language
specifically to support
limited constructor functions,
it comes in handy whenever you want a local name for the function result:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_String_Construct is
function Make_String
(S : String;
Prefix : String;
Use_Prefix : Boolean) return String
is
Length : Natural := S'Length;
begin
if Use_Prefix then
Length := Length + Prefix'Length;
end if;
return Result : String (1 .. Length) do
-- fill in the characters
if Use_Prefix then
Result
(1 .. Prefix'Length) := Prefix;
Result
(Prefix'Length + 1 .. Length) := S;
else
Result := S;
end if;
end return;
end Make_String;
S1 : String := "Ada";
S2 : String := "Make_With_";
begin
Put_Line ("No prefix: "
& Make_String (S1, S2, False));
Put_Line ("With prefix: "
& Make_String (S1, S2, True));
end Show_String_Construct;
In this example, we first calculate the length of the string and store it in
Length. We then use this information to initialize the return object of
the Make_String function.