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

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.

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.

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.