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
WHEN
exception name =>
sequence of statements
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;
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:
Constraint_Error
--an attempt is made to store a value in a
variable that is out of range for that variable, that is, out of the range of
the variable's type or subtype
Ada.Text_IO.Data_Error
--an attempt is made to read a value
which is invalid for the variable being readSYNTAX DISPLAY
Block with Exception Handler
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;
END
, and the exception is reraised at that point.
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
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 -5Suppose 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 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
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.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.