Previous Table of Contents Next


In the original Gadget_Report, the monetary type was a subtype of Float. This is not the best way to represent money because Float is just an approximation of the real numbers and there is always a danger of accumulated rounding errors. For monetary quantities, it is better to use a decimal type that stores quantities exactly within a given accuracy. Accordingly, I write (line 7)

   type Money is delta 0.01 digits 12;

which specifies a new numeric type with 12 decimal digits of representation, accurate to the nearest 0.01. This gives us, according to the standard, a range of ±1010 - 0.01 or ±999,999,999.99. Lines 8-11 create instances of the two generic packages Ada.Text_IO.Decimal_IO (which I use for input) and Ada.Text_IO.Editing (which I use for edit-directed output).

In Gadget_Report, I discarded each transaction after reading it. Here I choose to retain all the transactions in an array, so that, for example, I could extend the program to support queries. Accordingly, I declare (lines 17-20) a transaction record type, and (lines 22-23) an array type I shall call Database. This is an example of an unconstrained array type; the range expression

   Integer range <>

indicates that the bounds of each object will be some integer subrange, but that I choose not to specify the array bounds in the type declaration, preferring instead to pin down the bounds in the object declarations. This would allow me to declare a number of array objects, each with different bounds. In this example, I declare just one array object, namely (line 32)

   TodaysActivity: Database(1..100);

which allocates one array with bounds 1..100.

Moving now to the program statements, I retrieve and display the program name (as actually seen in the file system) using (line 42) Ada.Command_Line.Command_Name and then (lines 44-50) ascertain that the user has supplied an input file, using Ada.Command_Line.Argument_Count (similar to argc). If a file is supplied, I open it, using Ada.Command_Line.Argument (similar to argv) to retrieve its name.

Suppose the filename is incorrect so that Open cannot find the proper file and thus fails. Ada.Text_IO raises an exception, generally Ada.Text_IO.Name_Error. At this point, control passes to the exception handler for Name_Error if the programmer has coded one; otherwise, control passes to the runtime system and the program terminates.

Syntactically, exception handlers in Ada are associated with “frames,” that is, with begin-end blocks. The structure of a begin-end block is

   begin
     --sequence of statements
   [exception
     --one or more exception handlers]
   end

where the brackets indicate that the exception handler part is optional. exception is a reserved word.

The example contains two exception-handler sections. The one governing the file-open operation begins at line 105. In this simple program, if the file open fails, there is no point in continuing execution, so I associate the handler with the main begin-end pair. If the file open fails, control immediately passes here. There is only one handler here, for Name_Error. The handler code displays a message and then control passes out of the frame. In this case, because the frame is the main frame, the program terminates.

If the file can be opened successfully, the main while loop (lines 56-77) reads transactions from the file, storing them this time in the array TodaysActivity. For example,

   Gadget_IO.Get(File => InputData,
                 Item => TodaysActivity(TransactionCount).Kind);

reads a gadget kind from the file, storing it in the Kind field of the transaction record at TodaysActivity(TransactionCount).

Now suppose that in a given transaction, the first token does not represent a valid gadget, or the monetary amount is ill-formed or out of range. This condition results in either Ada.Text_IO.Date_Error or Constraint_Error being raised. The invalid transaction should not be recorded, but the program should continue to process other transactions. I need to handle the exception locally and therefore provide a begin/end frame (lines 59-76) with its own exception-handler part (lines 65-75).

If either of the Get calls raises an exception, the statement containing the call is abandoned, control immediately passes to this handler section, and the relevant handler (the appropriate when clause) is selected. The appropriate actions are taken and then control passes out of this frame, namely, just below the end at line 76. Now control—still in the while loop—passes back to the top of this loop to read the next transaction.

Language-provided exception-handling models are of two varieties: resumption or termination. In a resumption model, control passes from a handler directly back to the failed statement. Ada uses the termination model. The designers observed that it is often fruitless and unreliable to automatically resume a failed statement; in any case, the programmer can provide for resumption using loops and local exception handlers, much as I have done here.

As another example of this, an Ada idiom commonly used for interactive input is

   loop
     begin
       Get(...);
       exit;
     exception
       when ...
       when ...
     end;
   end loop;


Previous Table of Contents Next