Previous | Next | Table of Contents | Index | Program List | Copyright

6.5 System Structures: Robust Exception Handling

A good program should be written so as to anticipate likely input errors and behave accordingly, retaining control instead of "crashing" or just returning control to the operating system. Such a program is called a robust program; the property of robustness is advantageous in a program. A robust Ada program is one that retains control and behaves predictably even when exceptions are raised.

Program 5.22 was written with an exception-handling section at the end, so that it would display an appropriate message if an input value was out of range or badly formed or if a result would overflow the computer's arithmetic system. This is only a partial solution, because the program terminates without giving the user another chance to enter an acceptable value. There are many techniques for solving this problem; the one we consider here is the use of Ada exception handlers.

We will get user input by entering a loop that exits only when the input value is acceptable. We will detect out-of-range or badly formed input values using an exception handler form similar to that in Program 5.22. It is necessary to associate the exception handler with the input statement rather than with the entire program. A pseudocode description of the process follows.

Template for a robust input loop, initial version

    LOOP
        Prompt the user for an input value
        Get the input value from the user
        EXIT the loop if and only if no exception was raised on input
        If an exception was raised, notify the user
    END LOOP;

The first two lines in the loop body should present no problem to you at this point. The last line is coded using an exception-handler section like that in Program 5.22. Ada's rules require that an exception handler be associated with a block or frame, that is, a sequence of statements between a BEGIN and an END. A procedure or function has a block as part of its body; the exception handler in Program 5.22 is associated with that block. Luckily, we can build a block wherever we need one within a program, just by enclosing a group of statements between BEGIN and END. In the pseudocode below (a refinement of the pseudocode above), the entire loop body is considered a block because it is enclosed between BEGIN and END. The structure beginning EXCEPTION is associated with this block.

Template for a robust input loop, refined version

    LOOP
        BEGIN
        	Prompt the user for an input value
        	Get the input value from the user
        	EXIT the loop;  -- valid data
        EXCEPTION             -- invalid data
    	    Determine which exception was raised and notify the user
        END; 
    END LOOP;

The EXIT statement is associated with the LOOP structure. If control reaches the EXIT--that is, if the input is correct--loop exit occurs. Control passes to the exception handler if the input is incorrect; after execution of the exception handler, control flows to the END LOOP, which of course causes the loop to be repeated. The only code permitted between EXCEPTION and END is a sequence of one or more exception handlers.

SYNTAX DISPLAY
Exception Handler

Form:
WHEN exception name => sequence of statements

Example:
WHEN Constraint_Error =>
    Ada.Text_IO.Put(Item => "Input number is out of range");
    Ada.Text_IO.New_Line;
    Ada.Text_IO.Put(Item => "Please try entering it again.");
    Ada.Text_IO.New_Line;

Interpretation:
This structure is valid only in the exception-handler part of a BEGIN/END block. If exception name was raised in the block, sequence of statements is executed, after which control passes to the next statement after the block's END.

Note: Exception name can be a predefined exception or a programmer-defined exception. We will introduce programmer-defined exceptions in Chapter 9. The predefined exceptions most commonly used follow:

SYNTAX DISPLAY
Block with Exception Handler

Form:
BEGIN

    normal sequence of statements

EXCEPTION

    WHEN exception-name1 =>
        sequence-of-statements1

     WHEN exception-name2 =>
        sequence-of-statements2
    ...

    WHEN exception-nameN =>
	    sequence-of-statementsN

END;

Example:
An example is given in Program 6.7.

Interpretation:
If an exception is raised by any statement in normal-sequence-of-statements, execution of the statement causing the exception is immediately halted, and control passes to the appropriate exception handler. If the block has no exception-handler part, or no exception handler is appropriate (i.e., the exception that was raised is not named in any of the handlers), control passes out of the block to the statement following the END, and the exception is reraised at that point.

Note:
The last sentence means that if an exception is raised in executing the statements of a function or procedure, and that function or procedure has no exception-handler part, the exception is "passed back" to the program that called the function or procedure, and an attempt is made to find an appropriate handler there. If the procedure was the main program, the program ends and control passes to the Ada run-time system, which reports the exception to the user.

Example 6.11

Program 6.7 shows a robust input handler. The purpose of the program is to add five integers in the range -10..10. A subtype SmallInt is declared with this range, then Ada.Integer_Text_IO.Get is used to get input in this range, storing the value in the variable InputValue of type SmallInt. If the value entered is out of range, the attempt to store it in InputValue raises Constraint_Error. The exception handler for Constraint_Error notifies the user that the input is out of range.

Program 6.7
An Example of Robust Numeric Input

WITH Ada.Text_IO;
WITH Ada.Integer_Text_IO;
PROCEDURE Exception_Loop IS
------------------------------------------------------------------------
--| Illustrates how to write a robust input loop that
--| prompts user to reenter invalid input and
--| refuses to continue until input is good.
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: July 1995
------------------------------------------------------------------------

  MinVal : CONSTANT Integer := -10;
  MaxVal : CONSTANT Integer :=  10;
  SUBTYPE SmallInt  IS Integer RANGE MinVal .. MaxVal;

  InputValue: SmallInt;
  Sum:        Integer;

BEGIN -- Exception_Loop

  Sum := 0;

  FOR Count IN 1..5 LOOP -- counts the five values we need to read

    LOOP      -- inner loop just to control robust input
      BEGIN   -- block for exception handler

        Ada.Text_IO.Put(Item => "Enter an integer between ");
        Ada.Integer_Text_IO.Put(Item => SmallInt'First, Width => 0);
        Ada.Text_IO.Put(Item => " and ");
        Ada.Integer_Text_IO.Put(Item => SmallInt'Last, Width => 0);
        Ada.Text_IO.Put(Item => " > ");
        Ada.Integer_Text_IO.Get(Item => InputValue);

        EXIT; -- leave the loop only upon correct input

      EXCEPTION
        WHEN Constraint_Error =>
          Ada.Text_IO.Put
            ("Value entered is out of range. Please try again.");
          Ada.Text_IO.New_Line;
        WHEN Ada.Text_IO.Data_Error =>
          Ada.Text_IO.Put 
            ("Value entered not an integer. Please try again.");
          Ada.Text_IO.New_Line;
          Ada.Text_IO.Skip_Line;

      END;    -- block for exception handler
    END LOOP;
    -- assert: InputValue is in the range MinN to MaxN

    Sum := Sum + InputValue; -- add new value into Sum
  END LOOP;

  Ada.Text_IO.Put (Item => "The sum is ");
  Ada.Integer_Text_IO. Put (Item => Sum, Width => 1);
  Ada.Text_IO.New_Line;

END Exception_Loop;
Sample Run
Enter an integer between -10 and 10 > 20
Value entered is out of range. Please try again.
Enter an integer between -10 and 10 > -11
Value entered is out of range. Please try again.
Enter an integer between -10 and 10 > x
Value entered not an integer. Please try again.
Enter an integer between -10 and 10 > 0
Enter an integer between -10 and 10 > -5
Enter an integer between -10 and 10 > y
Value entered not an integer. Please try again.
Enter an integer between -10 and 10 > 3
Enter an integer between -10 and 10 > 4
Enter an integer between -10 and 10 > -7
The sum is -5
Suppose the input entered is not an integer: for example, suppose it is a letter. In this case Ada.Text_IO.Data_Error is raised. In this situation the letter is not consumed from the input stream. If the program just loops around, it will try to read the same letter again and again and again, causing an "infinite loop." To prevent this unpleasant occurrence, the handler for Ada.Text_IO.Data_Error contains a statement,
Ada.Text_IO.Skip_Line;

which causes the bad input to be skipped, creating a fresh line for input. Actually, Ada.Text_IO.Skip_Line causes all input, up to and including the carriage return with which you end a line, to be skipped.

Suppose a floating-point value--say, 345.67--is entered when an integer is called for. An odd consequence of the design of Ada.Text_IO is that the 345 will be accepted as a valid integer, and the decimal point will raise Ada.Text_IO.Data_Error if you try to read another integer. When your program is reading an integer token with Ada.Integer_Text_IO.Get, input stops whenever a character is reached that is not part of an integer token. In this case the decimal point stops input. This is one reason for including the Ada.Text_IO.Skip_Line statement in the exception handler.

A Robust Menu-Driven Command Handler

A very important and common computer application is a command handler, which accepts and processes commands from the keyboard. The basic algorithm for a command handler is a loop which is not exited until the user enters a "quit" command.

1. LOOP

     2. Prompt the user to enter a command

     3. EXIT WHEN the command is to quit

     4. The command was not quit, so process it

END LOOP;

The program cannot proceed if the user enters an invalid command. This leads to a refinement of Step 2:

2.1 LOOP

     2.2 Prompt the user to enter a command

     2.3 EXIT if and only if the commad is valid

END LOOP;

and so the refined algorithm is a pair of nested general loops:

1. LOOP

     2.1 LOOP

          2.2 Prompt the user to enter a command

          2.3 EXIT if and only if the command is valid

     END LOOP;

     3. EXIT WHEN the command is to quit

     4. The command was not quit, so process it

END LOOP;

Program 6.8 shows an implementation of this algorithm using Ada exception handling to catch invalid input. This program uses Screen.MoveCursor to control the positioning of the cursor and DELAY to cause execution to be delayed for a brief period before clearing the screen and prompting the user again. Correct input results in the program exiting from the inner loop and performing the desired command. The program leaves the outer loop and terminates when the command entered is 6, which in this program represents "quit."

Program 6.8
Framework for a Menu-Driven Command Handler

WITH Ada.Text_IO;
WITH Ada.Integer_Text_IO;
WITH Screen;
PROCEDURE Menu_Handler IS
------------------------------------------------------------------------
--| Framework for a menu-handling program
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------

  SUBTYPE Commands IS Positive RANGE 1..6;
  MenuSelection : Commands;

BEGIN -- Menu_Handler

  LOOP   -- this is the outer loop that keeps the program running 
         -- until a "quit" command is entered.

    LOOP -- inner loop continues until valid input is entered
      BEGIN -- exception-handler block

        Screen.ClearScreen;
        Screen.MoveCursor (Row => 5, Column => 20);
        Ada.Text_IO.Put (Item => "Select one of the operations below.");
        Screen.MoveCursor (Row => 7, Column => 20);
        Ada.Text_IO.Put (Item => "1. Compute an Average");
        Screen.MoveCursor (Row => 8, Column => 20);
        Ada.Text_IO.Put (Item => "2. Compute a Standard Deviation");
        Screen.MoveCursor (Row => 9, Column => 20);
        Ada.Text_IO.Put (Item => "3. Find the Median");    
        Screen.MoveCursor (Row => 10, Column => 20);
        Ada.Text_IO.Put (Item => "4. Find smallest and largest values");
        Screen.MoveCursor (Row => 11, Column => 20);
        Ada.Text_IO.Put (Item => "5. Plot the data");     
        Screen.MoveCursor (Row => 12, Column => 20);
        Ada.Text_IO.Put (Item => "6. Quit the program");   

        Screen.MoveCursor (Row => 14, Column => 20);
        Ada.Text_IO.Put ("Enter a command, 1 through 6 > ");

        -- this statement could raise an exception if input 
        -- is out of range or not an integer value
        Ada.Integer_Text_IO.Get (Item => MenuSelection);

        -- these statements are executed only if command is valid  
        -- otherwise, control passes to exception handler
        Screen.MoveCursor (Row => 17, Column => 20);
        Ada.Text_IO.Put ("Thank you for correct input.");      
        EXIT;      -- valid command; go process it

      EXCEPTION    -- invalid command
      
        WHEN Ada.Text_IO.Data_Error =>
          Screen.MoveCursor (Row => 17, Column => 20);
          Ada.Text_IO.Put (Item => "Value entered is not an integer.");
          Ada.Text_IO.Skip_Line;
          Ada.Text_IO.New_Line;
          DELAY 2.0;
        WHEN Constraint_Error =>  
          Screen.MoveCursor (Row => 17, Column => 20);
          Ada.Text_IO.Put (Item => "Value entered is out of range.");   
          Ada.Text_IO.New_Line;
          DELAY 2.0;

      END;         -- of exception-handler block
    END LOOP;

    -- We come here if command was valid; exit if it was quit 
    Screen.MoveCursor (Row =>20, Column => 20);
    EXIT WHEN MenuSelection = 6;

    Ada.Text_IO.Put (Item => "Here we would carry out the command.");
    Ada.Text_IO.New_Line;
    DELAY 5.0;

  END LOOP;

  Ada.Text_IO.Put (Item => "Goodbye for today.");
  Ada.Text_IO.New_Line;

END Menu_Handler;
Sample Run
                   Select one of the operations below.

                   1. Compute an Average
                   2. Compute a Standard Deviation
                   3. Find the Median
                   4. Find the smallest and largest values
                   5. Plot the data
                   6. Quit the program

                   Enter a command, 1 through 6 > 6


                   Thank you for correct input.


                   Goodbye for today.


Previous | Next | Table of Contents | Index | Program List | Copyright

Copyright © 1996 by Addison-Wesley Publishing Company, Inc.