
- •New to the Tenth Edition
- •Preface
- •Acknowledgments
- •About the Author
- •Contents
- •1.1 Reasons for Studying Concepts of Programming Languages
- •1.2 Programming Domains
- •1.3 Language Evaluation Criteria
- •1.4 Influences on Language Design
- •1.5 Language Categories
- •1.6 Language Design Trade-Offs
- •1.7 Implementation Methods
- •1.8 Programming Environments
- •Summary
- •Problem Set
- •2.1 Zuse’s Plankalkül
- •2.2 Pseudocodes
- •2.3 The IBM 704 and Fortran
- •2.4 Functional Programming: LISP
- •2.5 The First Step Toward Sophistication: ALGOL 60
- •2.6 Computerizing Business Records: COBOL
- •2.7 The Beginnings of Timesharing: BASIC
- •2.8 Everything for Everybody: PL/I
- •2.9 Two Early Dynamic Languages: APL and SNOBOL
- •2.10 The Beginnings of Data Abstraction: SIMULA 67
- •2.11 Orthogonal Design: ALGOL 68
- •2.12 Some Early Descendants of the ALGOLs
- •2.13 Programming Based on Logic: Prolog
- •2.14 History’s Largest Design Effort: Ada
- •2.15 Object-Oriented Programming: Smalltalk
- •2.16 Combining Imperative and Object-Oriented Features: C++
- •2.17 An Imperative-Based Object-Oriented Language: Java
- •2.18 Scripting Languages
- •2.19 The Flagship .NET Language: C#
- •2.20 Markup/Programming Hybrid Languages
- •Review Questions
- •Problem Set
- •Programming Exercises
- •3.1 Introduction
- •3.2 The General Problem of Describing Syntax
- •3.3 Formal Methods of Describing Syntax
- •3.4 Attribute Grammars
- •3.5 Describing the Meanings of Programs: Dynamic Semantics
- •Bibliographic Notes
- •Problem Set
- •4.1 Introduction
- •4.2 Lexical Analysis
- •4.3 The Parsing Problem
- •4.4 Recursive-Descent Parsing
- •4.5 Bottom-Up Parsing
- •Summary
- •Review Questions
- •Programming Exercises
- •5.1 Introduction
- •5.2 Names
- •5.3 Variables
- •5.4 The Concept of Binding
- •5.5 Scope
- •5.6 Scope and Lifetime
- •5.7 Referencing Environments
- •5.8 Named Constants
- •Review Questions
- •6.1 Introduction
- •6.2 Primitive Data Types
- •6.3 Character String Types
- •6.4 User-Defined Ordinal Types
- •6.5 Array Types
- •6.6 Associative Arrays
- •6.7 Record Types
- •6.8 Tuple Types
- •6.9 List Types
- •6.10 Union Types
- •6.11 Pointer and Reference Types
- •6.12 Type Checking
- •6.13 Strong Typing
- •6.14 Type Equivalence
- •6.15 Theory and Data Types
- •Bibliographic Notes
- •Programming Exercises
- •7.1 Introduction
- •7.2 Arithmetic Expressions
- •7.3 Overloaded Operators
- •7.4 Type Conversions
- •7.5 Relational and Boolean Expressions
- •7.6 Short-Circuit Evaluation
- •7.7 Assignment Statements
- •7.8 Mixed-Mode Assignment
- •Summary
- •Problem Set
- •Programming Exercises
- •8.1 Introduction
- •8.2 Selection Statements
- •8.3 Iterative Statements
- •8.4 Unconditional Branching
- •8.5 Guarded Commands
- •8.6 Conclusions
- •Programming Exercises
- •9.1 Introduction
- •9.2 Fundamentals of Subprograms
- •9.3 Design Issues for Subprograms
- •9.4 Local Referencing Environments
- •9.5 Parameter-Passing Methods
- •9.6 Parameters That Are Subprograms
- •9.7 Calling Subprograms Indirectly
- •9.8 Overloaded Subprograms
- •9.9 Generic Subprograms
- •9.10 Design Issues for Functions
- •9.11 User-Defined Overloaded Operators
- •9.12 Closures
- •9.13 Coroutines
- •Summary
- •Programming Exercises
- •10.1 The General Semantics of Calls and Returns
- •10.2 Implementing “Simple” Subprograms
- •10.3 Implementing Subprograms with Stack-Dynamic Local Variables
- •10.4 Nested Subprograms
- •10.5 Blocks
- •10.6 Implementing Dynamic Scoping
- •Problem Set
- •Programming Exercises
- •11.1 The Concept of Abstraction
- •11.2 Introduction to Data Abstraction
- •11.3 Design Issues for Abstract Data Types
- •11.4 Language Examples
- •11.5 Parameterized Abstract Data Types
- •11.6 Encapsulation Constructs
- •11.7 Naming Encapsulations
- •Summary
- •Review Questions
- •Programming Exercises
- •12.1 Introduction
- •12.2 Object-Oriented Programming
- •12.3 Design Issues for Object-Oriented Languages
- •12.4 Support for Object-Oriented Programming in Smalltalk
- •12.5 Support for Object-Oriented Programming in C++
- •12.6 Support for Object-Oriented Programming in Objective-C
- •12.7 Support for Object-Oriented Programming in Java
- •12.8 Support for Object-Oriented Programming in C#
- •12.9 Support for Object-Oriented Programming in Ada 95
- •12.10 Support for Object-Oriented Programming in Ruby
- •12.11 Implementation of Object-Oriented Constructs
- •Summary
- •Programming Exercises
- •13.1 Introduction
- •13.2 Introduction to Subprogram-Level Concurrency
- •13.3 Semaphores
- •13.4 Monitors
- •13.5 Message Passing
- •13.6 Ada Support for Concurrency
- •13.7 Java Threads
- •13.8 C# Threads
- •13.9 Concurrency in Functional Languages
- •13.10 Statement-Level Concurrency
- •Summary
- •Review Questions
- •Problem Set
- •14.1 Introduction to Exception Handling
- •14.2 Exception Handling in Ada
- •14.3 Exception Handling in C++
- •14.4 Exception Handling in Java
- •14.5 Introduction to Event Handling
- •14.6 Event Handling with Java
- •14.7 Event Handling in C#
- •Review Questions
- •Problem Set
- •15.1 Introduction
- •15.2 Mathematical Functions
- •15.3 Fundamentals of Functional Programming Languages
- •15.4 The First Functional Programming Language: LISP
- •15.5 An Introduction to Scheme
- •15.6 Common LISP
- •15.8 Haskell
- •15.10 Support for Functional Programming in Primarily Imperative Languages
- •15.11 A Comparison of Functional and Imperative Languages
- •Review Questions
- •Problem Set
- •16.1 Introduction
- •16.2 A Brief Introduction to Predicate Calculus
- •16.3 Predicate Calculus and Proving Theorems
- •16.4 An Overview of Logic Programming
- •16.5 The Origins of Prolog
- •16.6 The Basic Elements of Prolog
- •16.7 Deficiencies of Prolog
- •16.8 Applications of Logic Programming
- •Review Questions
- •Programming Exercises
- •Bibliography
- •Index

636 |
Chapter 14 Exception Handling and Event Handling |
somewhat philosophical, particularly in the case of predefined error conditions. For example, suppose a language has a predefined exception that is raised when a subscript range error occurs. Many believe that subscript range errors should always be detected, and therefore it should not be possible for the program to disable detection of these errors. Others argue that subscript range checking is too costly for production software, where, presumably, the code is sufficiently error free that range errors should not occur.
The exception-handling design issues can be summarized as follows:
•How and where are exception handlers specified, and what is their scope?
•How is an exception occurrence bound to an exception handler?
•Can information about an exception be passed to the handler?
•Where does execution continue, if at all, after an exception handler completes its execution? (This is the question of continuation or resumption.)
•Is some form of finalization provided?
•How are user-defined exceptions specified?
•If there are predefined exceptions, should there be default exception handlers for programs that do not provide their own?
•Can predefined exceptions be explicitly raised?
•Are hardware-detectable errors treated as exceptions that may be handled?
•Are there any predefined exceptions?
•Should it be possible to disable predefined exceptions?
We are now in a position to examine the exception-handling facilities of three contemporary programming languages.
14.2 Exception Handling in Ada
Exception handling in Ada is a powerful tool for constructing more reliable software systems. It is based on the good parts of the exception-handling design of two earlier languages with exception handling—PL/I and CLU.
14.2.1Exception Handlers
Ada exception handlers are often local to the code in which the exception can be raised (although they can be propagated to other program units). Because this provides them with the same referencing environment, parameters for handlers are not necessary and are not allowed. Therefore, if an exception is handled in a unit different from the unit that raised the exception, no information about the exception can be passed to the handler.3
3.Not quite true. It is possible for the handler to retrieve the exception name, a short description of the exception, and the approximate location where the exception was raised.

14.2 Exception Handling in Ada |
637 |
Exception handlers have the following general form, given here in EBNF:
when exception_choice {| exception_choice} => statement_sequence
Recall that the braces are metasymbols that mean that what they contain may be left out or repeated any number of times. The exception_choice has the form
exception_name | others
The exception_name indicates a particular exception that this handler is meant to handle. The statement sequence is the handler body. The reserved word others indicates that the handler is meant to handle any exceptions not named in any other local handler.
Exception handlers can be included in blocks or in the bodies of subprograms, packages, or tasks. Regardless of the block or unit in which they appear, handlers are gathered together in an exception clause, which must be placed at the end of the block or unit. For example, the usual form of an exception clause is shown in the following:
begin
-- the block or unit body -- exception
when exception_name1 => -- first handler --
when exception_name2 =>
--second handler --
--other handlers --
end;
Any statement that can appear in the block or unit in which the handler appears is also legal in the handler.
14.2.2Binding Exceptions to Handlers
When the block or unit that raises an exception includes a handler for that exception, the exception is statically bound to that handler. If an exception is raised in a block or unit that does not have a handler for that particular exception, the exception is propagated to some other block or unit. The way exceptions are propagated depends on the program entity in which the exception occurs.
When an exception is raised in a procedure, whether in the elaboration of its declarations or in the execution of its body, and the procedure has no handler for it, the exception is implicitly propagated to the calling program unit at the point of the call. This policy is reflective of the design philosophy that exception propagation from subprograms should trace back through the control path (dynamic ancestors), not through static ancestors.
If the calling unit to which an exception has been propagated also has no handler for the exception, it is again propagated to that unit’s caller. This

638 |
Chapter 14 Exception Handling and Event Handling |
continues, if necessary, to the main procedure, which is the dynamic root of every Ada program. If an exception is propagated to the main procedure and a handler is still not found, the program is terminated.
In the realm of exception handling, an Ada block is considered to be a parameterless procedure that is “called” by its parent block when execution control reaches the block’s first statement. When an exception is raised in a block, in either its declarations or executable statements, and the block has no handler for it, the exception is propagated to the next larger enclosing static scope, which is the code that “called” it. The point to which the exception is propagated is just after the end of the block in which it occurred, which is its “return” point.
When an exception is raised in a package body and the package body has no handler for the exception, the exception is propagated to the declaration section of the unit containing the package declaration. If the package happens to be a library unit (which is separately compiled), the program is terminated.
If an exception occurs at the outermost level in a task body (not in a nested block) and the task contains a handler for the exception, that handler is executed and the task is marked as being completed. If the task does not have a handler for the exception, the task is simply marked as being completed; the exception is not propagated. The control mechanism of a task is too complex to lend itself to a reasonable and simple answer to the question of where its unhandled exceptions should be propagated.
Exceptions can also occur during the elaboration of the declarative sections of subprograms, blocks, packages, and tasks. When such exceptions are raised in procedures, packages, and blocks, they are propagated exactly as if the exception were raised in the associated code section. In the case of a task, the task is marked as being completed, no further elaboration takes place, and the built-in exception Tasking_Error is raised at the point of activation for the task.
14.2.3Continuation
In Ada, the block or unit that raises an exception, along with all units to which the exception was propagated but that did not handle it, is always terminated. Control never returns implicitly to the raising block or unit after the exception is handled. Control simply continues after the exception clause, which is always at the end of a block or unit. This causes an immediate return to a higher level of control.
When deciding where execution would continue after exception handler execution was completed in a program unit, the Ada design team had little choice, because the requirements specification for Ada (Department of Defense, 1980a) clearly states that program units that raise exceptions cannot be continued or resumed. However, in the case of a block, a statement can be retried after it raises an exception and that exception is handled. For example, suppose a statement that can raise an exception and a handler for that exception are both enclosed in a block, which is itself enclosed in a loop. The following

14.2 Exception Handling in Ada |
639 |
example code segment, which gets four integer values in the desired range from the keyboard, illustrates this kind of structure:
...
type Age_Type is range 0..125;
type Age_List_Type is array (1..4) of Age_Type; package Age_IO is new Integer_IO (Age_Type); use Age_IO;
Age_List : Age_List_Type;
...
begin
for Age_Count in 1..4 loop
loop -- loop for repetition when exceptions occur
Except_Blk:
begin -- compound to encapsulate exception handling
Put_Line("Enter an integer in the range 0..125");
Get(Age_List(Age_Count));
exit; exception
when Data_Error => -- Input string is not a number Put_Line("Illegal numeric value");
Put_Line("Please try again");
when Constraint_Error => -- Input is < 0 or > 125 Put_Line("Input number is out of range"); Put_Line("Please try again");
end Except_Blk;
end loop; -- end of the infinite loop to repeat input -- when there is an exception
end loop; -- end of for Age_Count in 1..4 loop
...
Control stays in the inner loop, which contains only the block, until a valid input number is received.
14.2.4Other Design Choices
There are four exceptions that are defined in the default package, Standard:
Constraint_Error
Program_Error
Storage_Error
Tasking_Error
Each of these is actually a category of exceptions. For example, the exception Constraint_Error is raised when an array subscript is out of range, when there is a range error in a numeric variable that has a range restriction, when a

640 |
Chapter 14 Exception Handling and Event Handling |
reference is made to a record field that is not present in a discriminated union, and in many other situations.
In addition to the exceptions defined in Standard, other predefined packages define other exceptions. For example, Ada.Text_IO defines the End_Error exception.
User-defined exceptions are defined with the following declaration form:
exception_name_list : exception
Such exceptions are treated exactly as predefined exceptions, except that they must be raised explicitly.
There are default handlers for the predefined exceptions, all of which result in program termination.
Exceptions are explicitly raised with the raise statement, which has the general form
raise [exception_name]
The only place a raise statement can appear without naming an exception is within an exception handler. In that case, it reraises the same exception that caused execution of the handler. This has the effect of propagating the exception according to the propagation rules stated previously. A raise in an exception handler is useful when one wishes to print an error message where an exception is raised but handle the exception elsewhere.
An Ada pragma is a directive to the compiler. Certain run-time checks that are parts of the built-in exceptions can be disabled in Ada programs by use of the Suppress pragma, the simple form of which is
pragma Suppress(check_name)
where check_name is the name of a particular exception check. Examples of such checks are given later in this chapter.
The Suppress pragma can appear only in declaration sections. When it appears, the specified check may be suspended in the associated block or program unit of which the declaration section is a part. Explicit raises are not affected by Suppress. Although it is not required, most Ada compilers implement the Suppress pragma.
Examples of checks that can be suppressed are the following: Index_ Check and Range_Check specify two of the checks that are normally done in an Ada program; Index_Check refers to array subscript range checking; Range_Check refers to checking such things as the range of a value being assigned to a subtype variable. If either Index_Check or Range_Check is violated, Constraint_Error is raised. Division_Check and Overflow_ Check are suppressible checks associated with Numeric_Error. The following pragma disables array subscript range checking:
pragma Suppress(Index_Check);
There is an option of Suppress that allows the named check to be further restricted to particular objects, types, subtypes, and program units.

14.2 Exception Handling in Ada |
641 |
14.2.5An Example
The following example program illustrates some simple uses of exception handlers in Ada. The program computes and prints a distribution of input grades by using an array of counters. The input is a sequence of grades, terminated by a negative number, which raises a Constraint_Error exception because the grades are Natural type (nonnegative integers). There are 10 categories of grades (0–9, 10–19, . . . , 90–100). The grades themselves are used to compute indexes into an array of counters, one for each grade category. Invalid input grades are detected by trapping indexing errors in the counter array. A grade of 100 is special in the computation of the grade distribution because the categories all have 10 possible grade values, except the highest, which has 11 (90, 91, . . . , 100). (The fact that there are more possible A grades than B’s or C’s is conclusive evidence of the generosity of teachers.) The grade of 100 is also handled in the same exception handler that is used for invalid input data.
--Grade Distribution
--Input: A list of integer values that represent
--grades, followed by a negative number
--Output: A distribution of grades, as a percentage for
--each of the categories 0-9, 10-19, ...,
--90-100.
with Ada.Text_IO, Ada.Integer.Text_IO; use Ada.Text_IO, Ada.Integer.Text_IO; procedure Grade_Distribution is
Freq: array (1..10) of Integer := (others => 0);
New_Grade : Natural;
Index,
Limit_1,
Limit_2 : Integer; begin
Grade_Loop:
loop
begin -- A block for the negative input exception Get(New_Grade);
exception
when Constraint_Error => -- for negative input exit Grade_Loop;
end; -- end of negative input block Index := New_Grade / 10 + 1;
begin -- A block for the subscript range handler Freq(Index) := Freq(Index) + 1;
exception
-- For index range errors when Constraint_Error =>
if New_Grade = 100 then
Freq(10) := Freq(10) + 1;

642 |
Chapter 14 Exception Handling and Event Handling |
|
|
else |
|
|
Put("ERROR -- new grade: "); |
|
|
Put(New_Grade); |
|
|
Put(" is out of range"); |
|
|
New_Line; |
|
|
end if; |
|
|
end; -- end of the subscript range block |
|
|
end loop; |
|
|
-- Produce output |
|
|
Put("Limits |
Frequency"); |
|
New_Line; New_Line; |
|
|
for Index in 0..9 loop |
|
|
Limit_1 := 10 * Index; |
|
|
Limit_2 := Limit_1 + 9; |
|
|
if Index = 9 then |
|
|
Limit_2 := 100; |
|
|
end if; |
|
|
Put(Limit_1); |
|
|
Put(Limit_2); |
|
|
Put(Freq(Index + 1)); |
|
|
New_Line; |
|
|
end loop; |
-- for Index in 0..9 ... |
end Grade_Distribution;
Notice that the code to handle invalid input grades is in its own local block. This allows the program to continue after such exceptions are handled, as in our earlier example that reads values from the keyboard. The handler for negative input is also in its own block. The reason for this block is to restrict the scope of the handler for Constraint_Error when it is raised by negative input.
14.2.6Evaluation
As is the case in some other language constructs, Ada’s design of exception handling represents something of a consensus, at least at the time of its design (the late 1970s and early 1980s), of ideas on the subject. For some time, Ada was the only widely used language that included exception handling.
There are several problems with Ada’s exception handling. One problem is the propagation model, which allows exceptions to be propagated to an outer scope in which the exception is not visible. Also, it is not always possible to determine the origin of propagated exceptions.
Another problem is the inadequacy of exception handling for tasks. For example, a task that raises an exception but does not handle it simply dies.
Finally, when support for object-oriented programming was added in Ada 95, its exception handling was not extended to deal with the new constructs. For example, when several objects of a class are created and used in a block and