Previous

Contents

Next

Chapter 3:
Statements

The statements was interesting, but tough.
— Mark Twain, The Adventures of Huckleberry Finn


3.1 If statements
3.2 Assignment statements
3.3 Compound conditions
3.4 The case statement
3.5 Range tests
3.6 The null statement
3.7 Loops
3.8 The calculator program revisited
3.9 Exception handling
Exercises

3.1 If statements

Let’s try a slightly more elaborate example than the ones we’ve seen so far. This one asks the user whether it’s morning or afternoon and then replies ‘Good morning’ or ‘Good afternoon’ as appropriate. Here’s what it looks like:

    with Ada.Text_IO;  use Ada.Text_IO;
    procedure Greetings is
        Answer : Character;                             -- 1
    begin
        Put ("Is it morning (m) or afternoon (a)? ");   -- 2
        Get (Answer);                                   -- 3
        if Answer = 'm' then                            -- 4
            Put_Line ("Good morning!");                 -- 5
        else                                            -- 6
            Put_Line ("Good afternoon!");               -- 7
        end if;                                         -- 8
    end Greetings;                                      -- 9

Line 1 declares a variable called Answer to hold the answer that the user types in response to the question asked on line 2. The answer will be a single character ('m' or 'a'), so I’ve declared it to be a variable of type Character, which is another standard data type. Character variables are capable of holding a single character. The answer is read in using a version of Get defined in Ada.Text_IO which has an output parameter of type Character.

Lines 4 through 8 are an if statement which allows us to choose between two alternative courses of action. Like a procedure definition it is a compound construction with a semicolon at the very end of it on line 8 and no semicolons on either line 4 or 6. Compound constructions like this always end with ‘end whatever-it-is’ in Ada, so that ‘procedure X’ ends with ‘end X’ and ‘if’ ends with ‘end if’. The statements on lines 5 and 7 are indented to make it visually obvious that they are enclosed by the if statement.

When the if statement is executed, the condition after the word if is tested and, if it is true, the statements between then and else are executed, which in this case is the single statement on line 5. If the condition is false, the statements between else and end if (in this case the single statement on line 7) will be executed. The effect is that if the value of Answer is the letter m, the message ‘Good morning!’ is displayed, otherwise the message ‘Good afternoon!’ is displayed. Once the if statement has been executed (i.e. either line 5 or line 7 has been executed), execution continues with whatever follows the if statement. In this case it is line 9, which is the end of the program.

Note that you have to use single quote marks to enclose characters; 'm' is the letter m as a value of type Character, whereas "m" is a string which is one character in length. The compiler takes this apparently trivial difference quite seriously, since the particular style of quote is used to distinguish values of type Character from values of type String, which in turn determines what operations can legitimately be performed on them.

In its present form the program doesn’t quite do what we really want. If you type anything other than the letter m the program will respond with a cheery ‘Good afternoon!’. Ideally we would like to check that the answer is in fact an a if it is not an m and display a less cheery error message if it isn’t. We can do this by using another if statement instead of the existing line 7:

    if Answer = 'a' then
        Put_Line ("Good afternoon!");
    else
        Put_Line ("Please type 'm' or 'a'!");
    end if;

The complete if statement starting at line 4 now looks like this:

    if Answer = 'm' then
        Put_Line ("Good morning!");
    else
        if Answer = 'a' then
            Put_Line ("Good afternoon!");
        else
            Put_Line ("Please type 'm' or 'a'!");
        end if;
    end if;

This shows that the statements contained within an if statement can be any statements at all, including further if statements. Note that each if statement has its own corresponding end if. If you have a lot of if statements nested inside one another you can end up with an awful lot of end ifs, as well as indentation problems as the if statements are going to be indented further and further to the right. To get around this, we can write the if statement above a different way:

    if Answer = 'm' then
        Put_Line ("Good morning!");
    elsif Answer = 'a' then
        Put_Line ("Good afternoon!");
    else
        Put_Line ("Please type 'm' or 'a'!");
    end if;

The reserved word elsif allows you to specify a secondary condition as part of the same if statement. Since it’s all a single if statement now, only a single end if is required at the very end and there is no problem with indentation. You can have as many elsif parts in an if statement as you want, but there can only ever be one else part which must come at the very end and is only executed if all the conditions specified after if and elsif are false. You can also leave out the else part completely if you don’t want to do anything when the conditions you specify are all false.

Note the missing ‘e’ in elsif! A common beginner’s mistake is to spell it elseif, but the spelling was deliberately chosen so that if the words else and if accidentally get run together as elseif as a result of missing out the space between the two words, the compiler will immediately spot it as an error.


3.2 Assignment statements

At the moment you have to type in the letter m or a in lower case in response to the prompt. If you type in a capital M instead, the program will respond ‘Please type ‘m’ or ‘a’!’. We can modify the program to test for upper case letters and convert them to the lower case equivalents by adding an extra if statement:

    with Ada.Text_IO;  use Ada.Text_IO;
    procedure Greetings is
        Answer : Character;
    begin
        Put ("Is it morning (m) or afternoon (a)? ");
        Get (Answer);
        if Answer = 'M' then            -- 1
            Answer := 'm';              -- 2
        elsif Answer = 'A' then         -- 3
            Answer := 'a';              -- 4
        end if;                         -- 5
        if Answer = 'm' then
            Put_Line ("Good morning!");
        elsif Answer = 'a' then
            Put_Line ("Good afternoon!");
        else
            Put_Line ("Please type 'm' or 'a'!");
        end if;
    end Greetings;

The if statement on lines 1 to 5 checks for the letter M or A and changes the value of Answer to m or a as appropriate. It does this by assigning a new value to Answer on lines 2 and 4. The assignment statement

    Answer := 'm';

stores the letter m into the variable Answer, replacing the existing value of Answer. The symbol ‘:=’ is usually pronounced ‘becomes’, so we can read this statement as ‘Answer becomes m’. You must give a variable name on the left of ‘:=’, but you can have any expression you like on the right hand side as long as it produces a value of the correct type when it’s evaluated.

Note that there is no else part in this if statement. If Answer is the letter M, line 2 is executed; if it is the letter A, line 4 is executed; and if it is anything else, we don’t do anything. As I mentioned earlier, leaving out the else part of an if statement simply means that you do nothing if none of the conditions specified after if and elsif are true.


3.3 Compound conditions

There is another way to do the same thing. We can eliminate the extra if statement and alter the second one to test if Answer is either an M or an m as follows:

    if Answer = 'm' or Answer = 'M' then
        Put_Line ("Good morning!");
    elsif Answer = 'a' or Answer = 'A' then
        Put_Line ("Good afternoon!");
    else
        Put_Line ("Please type 'm' or 'a'!");
    end if;

The first line of this revised if statement checks if Answer is an m and also checks if it is an M. If either condition is true the message ‘Good morning!’ is displayed.

The or operator allows us to combine more than one condition into a single compound condition which is true if either or both of the subconditions are true. It is tempting to try to write the first line of the if statement as follows:

    if Answer = 'm' or 'M' then ...

but the compiler will complain if you do.

The reason is that or requires something which evaluates to either true or false (a Boolean expression) on both its left and its right hand sides. Boolean is another one of Ada’s built-in types, and is named after the English logician George Boole who first formalised the notion of an algebra of truth values. The ‘=’ operator compares two values of the same type and produces a Boolean result (true or false) so all will be well as long as you use an expression of the form ‘A = B’ on both sides of the or operator. The condition above has a Boolean expression on its left but a value of type Character on its right, so the compiler will be justifiably sceptical about it. Ada compilers are very strict about type-checking since confusion about types generally indicates muddled thinking on the part of the programmer; the Ada view of data types will be explored in more detail later on.


3.4 The case statement

When there are a lot of alternative values of the same variable to be dealt with, it’s generally more convenient to use a case statement than an if statement. Here’s how the previous program could be rewritten using a case statement:

    with Ada.Text_IO;  use Ada.Text_IO;
    procedure Greetings is
        Answer : Character;
    begin
        Put ("Is it morning (m) or afternoon (a)? ");
        Get (Answer);
        case Answer is
            when 'M' | 'm' =>                    -- 1
                Put_Line ("Good morning!");
            when 'A' | 'a' =>                    -- 2
                Put_Line ("Good afternoon!");
            when others =>                       -- 3
                Put_Line ("Please type 'm' or 'a'!");
        end case;
    end Greetings;

Depending on the value of Answer, one of the three alternatives of the case statement will be executed. The vertical bar ‘|’ can be read as meaning ‘or’ so that choice 1 will be executed if the value of Answer is M or m and choice 2 will be executed if the value of Answer is A or a. Choice 3 (others) is executed if all else fails. The others choice must be the last one and is equivalent to the else part of an if statement. It is only executed if none of the other choices apply. A case statement must have a choice for every possible value of the controlling expression between case and is, so an others clause is usually necessary.


3.5 Range tests

In situations where you want any one of a consecutive range of values to select a particular choice, you can specify a range of values in a case statement using ‘..’ to indicate the extent of the range. For example, if you wanted to test if Answer was a letter you could do it like this:

    case Answer is
        when 'A' .. 'Z' | 'a' .. 'z' =>
            Put_Line ("It's a letter!");
        when others =>
            Put_Line ("It's not a letter!");
    end case;

This says that if the value of Answer is in the range A to Z or the range a to z, the message ‘It’s a letter!’ will be displayed. If it isn’t, the message ‘It’s not a letter!’ will be displayed instead.

You can also test if a value is in a particular range using the operators in and not in. The case statement above could be rewritten as an if statement like this:

    if Answer in 'A' .. 'Z' or Answer in 'a' .. 'z' then
        Put_Line ("It's a letter!");
    else
        Put_Line ("It's not a letter!");
    end if;

Not in is the opposite of in:

    if Answer not in 'A' .. 'Z' then
        Put_Line ("It's not a capital letter!");
    end if;

3.6 The null statement

Case statements must cover all the possible values of the expression between case and is. This means that there has usually to be a when others clause, but sometimes you don’t want to do anything if the value doesn’t match any of the other selections. The solution is to use the null statement:

    when others =>
        null;       -- do nothing

The null statement is provided for situations like this one where you have to say something but don’t want to do anything. A null statement has no effect at all except to keep the compiler happy by telling it that you really do want to do nothing and that you haven’t just forgotten something by accident.


3.7 Loops

At the moment you only get one chance to answer the question that the program asks. It would be nicer if you were given more than one attempt. Here is a program that does that:

    with Ada.Text_IO;  use Ada.Text_IO;
    procedure Greetings is
        Answer : Character;
    begin
        loop                                    -- 1
            Put ("Is it morning (m) or afternoon (a)? ");
            Get (Answer);
            if Answer = 'm' or Answer = 'M' then
                Put_Line ("Good morning!");
                exit;                           -- 2
            elsif Answer = 'a' or Answer = 'A' then
                Put_Line ("Good afternoon!");
                exit;                           -- 3
            else
                Put_Line ("You must type m or a!");
            end if;
        end loop;                               -- 4
    end Greetings;                              -- 5

This program contains a loop statement which starts at line 1 and ends at line 4. Again, it’s a compound statement; it starts with loop on line 1 and ends with end loop and a semicolon on line 4. The sequence of statements it encloses will be repeated over and over again when it’s executed. It will only stop repeating when you execute one of the exit statements on lines 2 and 3. The exit statement terminates the loop and execution of the program continues at the point after end loop. In this case it is line 5, the end of the program.

Quite a lot of the time you want to exit from a loop when a particular condition becomes true. You could do this using an if statement:

    if This_Is_True then
        exit;
    end if;

but it’s a common enough requirement that Ada provides a special form of the exit statement:

    exit when This_Is_True;

Here’s another way of writing the same program as before which illustrates the use of an exit when statement:

    with Ada.Text_IO;  use Ada.Text_IO;
    procedure Greetings is
        Answer : Character;
    begin
        loop
            Put ("Is it morning (m) or afternoon (a)? ");
            Get (Answer);
            exit when Answer = 'm' or Answer = 'M'
                   or Answer = 'a' or Answer = 'A';
            Put_Line ("You must type m or a!");
        end loop;
        if Answer = 'm' or Answer = 'M' then -- 1
            Put_Line ("Good morning!");
        else
            Put_Line ("Good afternoon!");
        end if;
    end Greetings;

The previous version displayed the message ‘Good morning!’ or ‘Good afternoon!’ from within the loop; here, the loop just checks whether the answer is valid. As soon as it is valid, the exit statement terminates the loop and execution continues at the next statement, namely the if statement at line 1 which is now responsible for displaying ‘Good morning!’ or ‘Good afternoon!’. By the time we get to line 1, we know that Answer is either an m or an a in either upper or lower case and so the if statement only has to test if it’s an m or an M; if it isn’t it must be an a or an A.

In many cases the exit statement is the first statement in a loop; you will often want to test that everything’s all right before you do anything else:loop exit when End_Of_File; -- i.e. when there is no more input -- get some input and process itend loop;

This is a common enough situation that there’s a special form of the loop statement to cater for it:

    while not End_Of_File loop
        -- get some input and process it
    end loop;

The operator not inverts the sense of a condition; if a condition X is True then not X is False and vice versa. Note that the condition in a while loop tells you when to repeat the loop whereas the condition in an exit when statement tells you when to exit from it, so a loop that begins ‘exit when X’ gets rewritten as ‘while not X loop ...’.

Another common requirement is to repeat a loop a fixed number of times. This is a frequent enough situation that Ada provides another form of the loop statement to handle it (a for loop). Here’s an example which displays a line of 20 asterisks:

    for N in 1..20 loop
        Put ("*");
    end loop;

The range 1..20 specifies how many times the loop will be executed. The control variable N will take on successive values from 1 to 20 each time around the loop (i.e. in this case, it will always give the number of times the loop has executed so far). N doesn’t need to be declared elsewhere; it is automatically declared by its appearance in the loop heading. For loops are dealt with in more detail in chapter 6.

You are also allowed to name loops by attaching labels to them:

    Main_Loop:
    loop
        ...
    end loop Main_Loop;

The label is a name followed by a colon immediately before the loop statement. Note that if you use a loop label, the name must be repeated after end loop as in the example above.

The main reason for providing a loop label is so that you can specify which loop an exit statement should exit from. This allows you to exit several loops at once:

    Outer:
    loop
        Inner:
        loop
            ...
            exit Outer when Finished;   -- 1
        end loop Inner;
    end loop Outer;                     -- 2

The exit statement at line 1 will exit from both the inner and outer loops, and execution will continue at line 2 (after the end of the outer loop). This is something which is rarely required in practice; if it does seem to be necessary, it’s worth having a good think about your design since there are usually better ways to achieve the same effect.


3.8 The calculator program revisited

Armed with all this extra knowledge about Ada we are now in a position to improve somewhat on the calculator program from the previous chapter. It can be rewritten to accept expressions like ‘123+456’ or ‘32–5’ and display the answer. This is simply a matter of reading an integer, a character and another integer, and then performing the appropriate operation depending on the character between the two integers:

    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        First, Second : Integer;
        Operator      : Character;
    begin
        Put ("Enter an expression: ");
        Get (First);
        Get (Operator);
        Get (Second);
        case Operator is
            when '+' =>
                Put (First + Second, Width => 1);
            when '-' =>
                Put (First - Second, Width => 1);
            when '*' =>
                Put (First * Second, Width => 1);
            when '/' =>
                Put (First / Second, Width => 1);
            when others => Put ("Invalid operator '");
                Put (Operator);
                Put ("'");
        end case;
        New_Line;
    end Calculator;

One problem with this is that the operator is taken to be the first character after the first integer, which doesn’t allow for any separating spaces. The integers can be preceded by spaces because of the way that Get works for integers, so that ‘123+ 456’ will be accepted but ‘123 + 456’ won’t be. The answer is to use a loop to skip spaces between the first integer and the operator:

    Get (First);
    loop
        Get (Operator);
        exit when Operator /= ' ';
    end loop;
    Get (Second);

The operator ‘/=’ means ‘not equal to’, so the exit statement will be executed when Operator is not equal to a space. This means that as long as it is a space, we’ll go round the loop and get another character, thus ignoring all spaces.

An even better idea is to extend the program further so that it can read expressions involving more than one operator, e.g. ‘1+2+3’. This will involve reading the first integer and making it the result, then reading successive pairs of operators and integers and adding them (or whatever) to the result. This will give a strictly left-to-right evaluation, so that ‘1+2*3’ will come out as 9 rather than 7 as you might expect; in a later chapter I will show you how to deal with issues like doing multiplication and division before addition and subtraction. To simplify matters I will require the user to type in a full stop to terminate the expression.

This is a slightly larger program than the previous ones; rather than just showing you the complete program I’m going to take you through a step-by-step design process. Reading and understanding the programs I’ve shown you so far is important for getting to grips with the facilities that Ada provides, but at some point you have to start writing your own programs and for this you need some tips on how to get started. A lot of people find it easy to understand a program once it’s been written but find it hard to know where to start if they have to write it themselves.

In general you can break any programming problem down into three main components: some initialisation to get things ready at the beginning, followed by the main processing involved, followed by some finishing off at the end. In this case the initialisation will involve displaying a prompt and reading an arithmetic expression, the main processing will involve evaluating the expression that the user has typed in, and the finishing off at the end might just involve displaying the result. We’ll need an integer variable for the result to be displayed. This gives the following as a first stab at the program:

    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result : Integer;
    begin
        Put ("Enter an expression: ");
        -- process the expression typed in by the user
        Put (Result, Width => 1);
        New_Line;
    end Calculator;

The next thing to do is to decide how to break down the main processing. A good way to start is to consider the structure of the input that the program will be expected to deal with. Here are some samples of the input I’d expect this program to accept:

    2.
    2+2.
    2+2*2.
    2+2*2-2.

What we have here is an integer followed by any number (zero or more) of arithmetic operators each of which is followed by an integer, followed finally by a full stop. An old programming rule of thumb says that the structure of a program tends to reflect the structure of its input; in this case we’ll need to read the first number, then repeatedly process operators and the numbers which follow them until we reach a full stop. So this gives us a sequence of two steps: read the first number and then process the rest of the expression. In the case where there are no operator/integer pairs after the first number, the result will be the first number. This leads to the conclusion that the first number should just be read into the variable Result:

    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result : Integer;
    begin
        Put ("Enter an expression: ");
        Get (Result);
        -- repeatedly process operator/integer pairs (if any)
        Put (Result, Width => 1);
        New_Line;
    end Calculator;

Processing operator/integer pairs is a repetitive activity, so we’ll need a loop statement. With any loop you need to ask yourself when the loop will terminate; in this case it is when the final full stop is read, or when something unexpected is read (which should be reported as an error). The result won’t need to be displayed if an error occurs, so we can display it when the full stop is encountered, rather than at the end of the program as I’ve got it above. This can be done by moving the call to Put into the loop and following it by an exit statement.

Inside the loop we’ll need to read the next character, which should be either an operator or the terminating full stop, so we’ll need a character variable (which I’ll call Operator) to store it in:

    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result   : Integer;
        Operator : Character;
    begin
        Put ("Enter an expression: ");
        Get (Result);
        loop
            Get (Operator);
            if Operator = '.' then
                -- display result and exit from the loop
                Put (Result, Width => 1);
                exit;
            else
               -- process the rest of an operator/integer pair
            end if;
        end loop;
        -- the call to Put has now been moved into the loop
        New_Line;
    end Calculator;

As in the previous program we’ll want to ignore spaces before the operator; to do that, I’ll just use the code which I showed you earlier without any further comment:

    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result   : Integer;
        Operator : Character;
    begin
        Put ("Enter an expression: ");
        Get (Result);
        loop
            loop
                Get (Operator);
                exit when Operator /= ' ';
            end loop;
            if Operator = '.' then
                Put (Result, Width => 1);
                exit;
            else
                -- process the rest of an operator/integer pair
            end if;
        end loop;
        New_Line;
    end Calculator;

Processing the rest of the operator/integer pair involves reading the integer (which means we need another Integer variable) and then applying the operator to the result so far and the integer that we’ve just read:

    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result   : Integer;
        Operator : Character;
        Operand  : Integer;
    begin
        Put ("Enter an expression: ");
        Get (Result);
        loop
            loop
                Get (Operator);
                exit when Operator /= ' ';
            end loop;
            if Operator = '.' then
                Put (Result, Width => 1);
                exit;
            else
                Get (Operand);
                -- apply the operator to Result and Operand
            end if;
        end loop;
        New_Line;
    end Calculator;

Applying the operator involves a choice between a number of alternatives: if it’s an addition operator we want to add the numbers together, if it’s a multiplication operator we want to multiply them, and so on. Since we have a choice between several alternatives we have to use either an if statement or a case statement. As you saw earlier, a case statement is a convenient solution in this case where all the choices depend on a particular value, in this case the value of Operator. The first number is in Result and the second is in Operand, so we’ll need to evaluate Result+Operand, Result-Operand or whatever. This will give us a new result which needs to be stored in Result so that it’s ready to be displayed when we get to the end of the program, so we need each choice to be an assignment statement along the lines of:

    Result := Result + Operand;

Note that the old value of Result is used on the right hand side of ‘:=’ to calculate the new value of Result. The right hand side of the assignment is evaluated by adding the old value of Result to Operand; this value is then stored in Result, replacing the old value. This idiom is commonly used to add 1 to the existing value of a variable, like this:

    Result := Result + 1;           -- add 1 to Result

A when others choice will be needed in the case statement to cope with the fact that Operator might be any Character value, not just one of the four operators we’re looking for. The good news is that the compiler would complain if we forgot this little detail. If we get to the when others choice it means there’s an error in the input. An appropriate response to this is to display an error message and get out of the loop with an exit statement. So here at last is the final program:

    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result   : Integer;
        Operator : Character;
        Operand  : Integer;
    begin
        Put ("Enter an expression: ");
        Get (Result);                                   --  1
        loop                                            --  2
            loop                                        --  3
                Get (Operator);
                exit when Operator /= ' ';
            end loop;
            if Operator = '.' then                      --  4
                Put (Result, Width => 1);               --  5
                exit;                                   --  6
            else
                Get (Operand);                          --  7
                case Operator is
                    when '+' =>
                        Result := Result + Operand;     --  8
                    when '-' =>
                        Result := Result - Operand;
                    when '*' =>
                        Result := Result * Operand;     --  9
                    when '/' =>
                        Result := Result / Operand;
                    when others =>
                        Put ("Invalid operator '");     -- 10
                        Put (Operator);
                        Put ("'");
                        exit;                           -- 11
                end case;
            end if;
        end loop;
        New_Line;
    end Calculator;

If you type ‘1+2*3.’ in response to the prompt, what will happen is that line 1 will read the value 1 into Result. Line 2 is the start of the main loop; the first thing inside this loop is another loop (line 3) to skip over any spaces in front of the operator character. We will end up at line 4 with Operator holding the character '+'. Lines 5 and 6 will display the result and exit the main loop when Operator is a full stop, but we haven’t got to that stage yet. So line 7 will read the value 2 into Operand, and then the case statement will execute line 8 based on the value of Operator. Line 8 calculates the value Result+Operand (i.e. 1+2) and stores the result (i.e. 3) in Result. The upshot of this is that Result has been altered from 1 to 3.

After line 8 has been executed we go round the main loop a second time. Operator ends up holding the character '*' and Operand ends up holding the value 3. The case statement executes line 9, which multiplies Result (3) by Operand (also 3) to give a new value of 9 for Result. Around the loop again, and Operator ends up holding a full stop at line 4. The result (9) is then displayed by line 5 before exiting from the main loop at line 6.

If an invalid operator character is typed in (e.g. ‘1&2.’) the section of the case statement at line 10 gets executed, which displays an error message. Line 11 then exits from the main loop. Notice how important it is to think about what can possibly go wrong and to deal with it in a sensible way; it’s easy to write a program that gives the right answer for valid input, but it’s much harder to write a program that can cope sensibly with bad input as well.


3.9 Exception handling

Of course, the program is still not completely bulletproof. If you type gibberish like XYZZY at the point where the program expects an integer, the program will halt with an error message. If you’re unlucky it will just say something like ‘unhandled exception’; some compilers are more helpful, and will also tell you that the error was an exception called Data_Error, and possibly tell you which line of the program you were at when it happened. A Data_Error means that the input is in the wrong format; another common one is Constraint_Error, which you’ll get if you go outside the range of values allowed for Integer on your system (try 1000000*1000000*1000000, which will almost certainly be too big to handle).

A properly designed program should be able to cope with any input at all, not just correct input. To manage this we need to trap Constraint_Error and Data_Error exceptions and deal with them sensibly. Ada allows us to provide exception handlers to specify what happens if an exception occurs. This is a topic I’ll return to in more detail later, but it’s worth a brief introduction before we go any further so that you’ll be able to start making the programs you write more robust.

You can put an exception handler into any block of statements enclosed by begin and end, e.g. a procedure body:

    procedure X is
    begin
        -- your code goes here as usual
    exception
        when Some_Exception =>
            Do_This;
    end X;

where Some_Exception is the name of an exception you want to handle and Do_This is the action you want to take. The action can be any sequence of statements; it can be a null statement which does nothing, which will have the effect of ignoring the exception, or it can be something more elaborate. In this case a sensible action might be to print out an error message when a Constraint_Error or a Data_Error occurs. Here’s how to do it:

    procedure Calculator is
        Result   : Integer;
        Operator : Character;
        Operand  : Integer;
    begin
        Put ("Enter an expression: ");
        ... code to process the expression as before
    exception
        when Constraint_Error =>
            Put_Line ("Value out of range");
        when Data_Error =>
            Put_Line ("Error in input -- integer expected");
    end Calculator;

The exception handler section goes at the very end; it’s ignored if there aren’t any errors. If a Constraint_Error or a Data_Error is reported (or raised, to use the correct terminology), you immediately end up at the appropriate exception handler and do what it says. Once you’ve done this, you’re at the end of the procedure and the program terminates.

If you want the program to give the user another chance rather than terminating you need to be a bit more subtle. Here’s how you can safely read a value into an Integer variable called X:

    loop
        begin
            Put ("Enter an integer: ");                           -- 1
            Get (X);                                              -- 2
            exit;                                                 -- 3
        exception
            when Constraint_Error | Data_Error =>
                Put_Line ("Error in input -- please try again."); -- 4
                Skip_Line;                                        -- 5
        end;
    end loop;                                                     -- 6

Note that you can’t put an exception handler directly between loop and end loop; you have to put begin and end around the section that you want to provide exception handling for, and then put the exception handler section immediately before end. This is the only case in Ada where end is not followed by something to say what it is the end of.

What happens here is that line 1 displays a prompt and line 2 attempts to read an integer. If an exception is raised by Get, you won’t get to line 3; instead, you’ll be whisked off to line 4 which displays an error message. Line 5 calls a procedure Skip_Line from Ada.Text_IO to ignore the rest of the current line of input so the user will have to type another line. If you don’t call Skip_Line after a Data_Error you’ll just end up reading the same bad data from the current line (which won’t have been read since it wasn’t valid).

After this you’ll be at line 6, the end of the loop, so you’ll go around and redisplay the prompt and get another line of input. When the user types in a valid value for X you’ll carry on past line 2 to line 3, which will exit from the loop.

Note also that you can handle several exceptions with a single handler by separating them by a vertical bar (‘|’) in the same way as you would specify multiple choices in a case statement. Also as in a case statement, you can provide a ‘catch-all’ handler by specifying when others:

    exception
        when others =>
            Do_Something;   -- handle every exception the same way

As in a case statement, when others must come last if you have more than one exception handler. It handles any exceptions not dealt with by the other handlers. If you use it as the only handler it will deal with any exception that occurs. I don’t recommend using when others unless you really need to; it might disguise any real errors in your program due to undiscovered bugs which would otherwise be reported as unhandled exceptions.

If you want an exception to be handled in different ways in different places, you need to enclose each such place in a begin ... end block. For example, if you have two assignment statements which could each raise a Constraint_Error:

    A := A ** 2;    -- might raise Constraint_Error
    B := B ** 2;    -- might raise Constraint_Error

you could enclose each one in a separate block with its own handler like this:

    begin
        A := A ** 2; -- might raise Constraint_Error
    exception
        when Constraint_Error =>
            Put_Line ("Assignment to A failed");
    end;

    begin
        B := B ** 2; -- might raise Constraint_Error
    exception
        when Constraint_Error =>
            Put_Line ("Assignment to B failed");
    end;

Exercises

3.1 Modify the Greetings program to say ‘Good evening!’ in the evenings as well.

3.2 Modify the calculator program so that after evaluating an expression it asks the user if he or she wants to evaluate another expression. If the answer is ‘y’ or ‘Y’ (yes), evaluate another expression; if it’s ‘n’ or ‘N’ (no) exit from the program.

3.3 Write a program which asks the user to pick an animal from a list that you display (cat, dog, elephant or giraffe) and then asks ‘Is it a household pet?’ to distinguish cats and dogs from elephants and giraffes. If the user says it’s a household pet, ask if it purrs; if not, ask if it has a long neck. Finally, tell the user which animal you think was chosen. Try extending the program to include a few more animals.

3.4 Write a program to count the number of vowels (A, E, I, O or U) in its input. Allow the user to type in a sequence of characters (as many as they like) ending with a full stop and then display the number of occurrences of each vowel as well as a grand total. This will involve using a set of integer variables which are set to zero at the start of the program. You will then need to add 1 to the appropriate variable whenever a vowel is typed in. Ignore case distinctions, so that ‘a’ is treated as meaning the same as ‘A’.



Previous

Contents

Next

This file is part of Ada 95: The Craft of Object-Oriented Programming by John English.
Copyright © John English 2000. All rights reserved.
Permission is given to redistribute this work for non-profit educational use only, provided that all the constituent files are distributed without change.
$Revision: 1.2 $
$Date: 2001/11/17 12:00:00 $