



3   Overview of the Ada Language



The previous chapter introduced the new highlights of Ada
9X.  In contrast, this chapter gives an overview of the
whole Ada language showing the overall framework within
which the new features fit. 
   Ada is a modern algorithmic language with the usual
control structures, and with the ability to define types and
subprograms.  It also serves the need for modularity,
whereby data, types and subprograms can be packaged.  It
treats modularity in the physical sense as well, with a
facility to support separate compilation.
   In addition to these aspects, the language supports real-
time programming, with facilities to define the invocation,
synchronization, and timing of parallel tasks.  It also
supports systems programming, with facilities that allow
access to system-dependent properties, and precise control
over the representation of data.
   Ada 9X is a natural evolutionary enhancement of Ada 83. The 
fact that the limited and predominantly upward compatible
enhancements to Ada allow it to support state-of-the-art
programming in the nineties and beyond, reconfirms the
validity of Ada's underlying principles, and is a proof of
the excellent foundation provided by the original design.


3.1  Objects, Types, Classes and Operations

This section describes two fundamental concepts of Ada:
types, which determine a set of values with associated
operations, and objects, which are instances of those types.
Objects hold values.  Variables are objects whose values can
be changed; constants are objects whose values cannot be
changed.


3.1.1  Objects and Their Types

Every object has an associated type.  The type determines a
set of possible values that the object can contain, and the
operations that can be applied to it.  Users write
declarations to define new types and objects.
   Ada is a block-structured language in which the scope of
declarations, including object and type declarations, is
static.  Static scoping means that the visibility of names
does not depend on the input data when the program is run,
but only on the textual structure of the program.  Static
properties such as visibility can be changed only by
modifying and recompiling the source code.
   Objects may be created when the executing program enters
the scope where they are declared (elaboration); they are
deleted when the execution leaves that scope (finalization).
In addition, allocators are executable operations that
create objects dynamically.  An allocator produces an access
value (a value of an access type), which provides access to
the dynamically created object.  An access value is said to
designate an object.  Access objects are only allowed to
designate objects of the type specified by the access type.
Access types correspond to pointer types or references in
other programming languages.
   A type, together with a (possibly null) constraint, forms
a subtype.  User-defined subtypes constrain the values of
the subtype to a subset of the values of the base type.
Subtype constraints are useful for run-time error detection,
because they show the programmer's intent.  Subtypes may
also allow an optimizing compiler to make more efficient use
of hardware resources, because they give the compiler more
information about the behavior of the program.
   User-defined types provide a finer classification of
objects than the predefined types, and hence greater
assurance that operations are applied to only those objects
for which the operations are meaningful.


3.1.2  Types, Classes and Views

Types in Ada can be categorized in a number of different
ways.  There are elementary types, which cannot be
decomposed further, and composite types which, as the term
implies, are composed of a number of components.  The most
important form of composite type is the record which
comprises a number of named components themselves of
arbitrary and possibly different types.
   Records in Ada 9X are generalized to be extensible and
form the basis for object-oriented programming.  Such
extensible record types are known as tagged types; values of
such types include a tag denoting the type which is used at
runtime to distinguish between different types.  Record
types not marked as tagged may not be extended and
correspond to the record types of Ada 83.
   New types may be formed by derivation from any existing
type which is then known as the parent type.  A derived type
inherits the components and operations of the parent type.
In the case of deriving from a tagged record type, new
components can be added thereby extending the type.  In all
cases new operations can be added and existing operations
replaced.
   The set of types derived directly or indirectly from a
specific type, together with that type form a derivation
class.  The types in a class share certain properties (they
all have the components of the common ancestor or root type
for example) and this may be exploited in a number of ways.
Treating the types in a class interchangeably by taking
advantage of such common properties is termed polymorphism.
   There are two means of using polymorphism in Ada.  Static
polymorphism is provided through the generic parameter
mechanism whereby a generic unit may at compile time be
instantiated with any type from a class of types.  Dynamic
polymorphism is provided through the use of so-called class-
wide types and the distinction is then made at runtime on
the basis of the value of a tag.
   A class-wide type is declared implicitly whenever a
tagged record type is defined.  The set of values of the
class wide type is the union of the sets of values of all
the types of the class.  Values of class-wide types are
distinguished at runtime by the value of the tag giving
class-wide programming or dynamic polymorphism.  The class-
wide type associated with a tagged record type T is denoted
by the attribute T'Class.  Objects and operations may be
defined for such class-wide types in the usual way.
   As well as derivation classes, Ada also groups types into
a number of predefined classes with common operations.  This
aids the description of the language and, moreover, the
common properties of certain of these predefined classes may
be exploited through the generic mechanism.
   A broad hierarchical classification of Ada types is
illustrated in Figure 3-1.  The following summary of the
various types gives their key properties.

*    A type is either an elementary type or a composite
     type.  Elementary types cannot be decomposed further
     whereas the composite types have an inner structure.
     The elementary types can be further categorized into
     the scalar types and access types.  The composite types
     comprise familiar array and record types plus the
     protected and task types which are concerned with
     multitasking.

*    Scalar types are themselves subdivided into the
     discrete types and the real types.  The discrete types
     have certain important common properties; for example,
     they may be used to index array types.  The discrete
     types are the enumeration types and the integer types.
     The integer types in turn comprise signed integer types
     and modular (unsigned) types.  The real types comprise
     the other forms of numeric types.

*    An enumeration type defines an ordered set of distinct
     enumeration literals, for example a list of states or
     an alphabet of characters.  The enumeration types
     Boolean, Character (the 8-bit ISO standard character
     set) and Wide_Character (a 16-bit character type) are
     predefined.
                               All Types
                                   |
               +--------------------------------+
               |                                |
       Elementary Types                  Composite Types
               |                                |
     +------------+                 +------------------------+
     |            |                 |       |       |        |
   access      scalar             array  record  protected  task
                  |
         +--------------------------+
         |                          |
         |    +---------------------+---------------+
     discrete |                   real              |
         |    |                     |               |
     +--------+-------+         +---------+         |
     |        |       |         |         |         |  Numeric Types
 enumeration  |    integer    float     fixed       |
              |       |                   |         |
              |   +--------+         +--------+     |
              |   |        |         |        |     |
              | signed  modular   decimal  ordinary |
              +-------------------------------------+

               Figure 3-1: Ada Type Hierarchy


*    The numeric types do not exactly fit the hierarchy as
     presented, but there are certain properties that are
     common to all numeric types (such as the availability
     of arithmetic operations), so we have indicated this by
     a box surrounding the numeric types.  Numeric
     types provide a means of performing approximate or
     exact numerical computations.  Approximate computations
     may be performed using either fixed point types with
     absolute error bounds, or floating point types with
     relative error bounds.  Exact computations may be
     performed with either integer types, which denote sets
     of consecutive integers, or (for addition and
     subtraction) with fixed point types.  In Ada 9X,
     decimal types, which denote sets of scaled decimal
     numbers, are implemented as a subset of the fixed
     types.  The numeric types Integer, Float and Duration
     are predefined.

*    Access types, the remaining form of elementary type,
     allow the construction of linked data structures.  The
     value of an access type is, in essence, a pointer to an
     object of another type, the accessed type.  The
     accessed type may be any type including a further
     access type.  In particular the accessed type may be a
     class-wide type thereby allowing the construction of
     heterogeneous linked data structures.  Access types may
     be used to designate objects created by allocators,
     declared objects (provided they are marked as aliased)
     and subprograms.

*    Array types allow definitions of composite objects with
     indexable components all of the same subtype.  Array
     types may be of one or more dimensions.  The types of
     the indexes must be discrete.    The array types String
     and Wide_String are predefined.

*    Record types are composite types with named components
     not necessarily of the same type.  Record types may be
     tagged or untagged.  A tagged record type may be
     extended upon derivation and gives rise to a class-wide
     type which forms the basis for dynamic polymorphism.

*    Protected types are composite types that provide
     synchronized access to their inner components via a
     number of protected operations.  Objects of protected
     types are passive and do not have a distinct thread of
     control; the mutual exclusion is provided
     automatically.

*    Task types are composite types which are used to define
     active units of processing.  Each object of a task type
     has its own thread of control.

   Record, protected and task types may be parameterized by
special components called discriminants.  A discriminant may
be either of a discrete type or an access discriminant. A
discriminant of a discrete type may be used to control the
structure or size of an object.  More generally, an access
discriminant may be used to parameterize a type with a
reference to an object of another type.  A discriminant may
also be used in the initialization of an object of a
protected or task type.
   Ada provides a special syntax for defining new types
within the various categories as illustrated by the
following examples.

   type Display_Color is -- an enumeration type
      (Red, Orange, Yellow, Green, Blue, Violet);
   
   type Color_Mask is -- an array type
      array (Display_Color) of Boolean;
   
   type Money is -- a decimal fixed type
      delta 0.01 digits 18;
   
   type Payment is -- a record type
      record
         Amount : Money;
         Due_Date : Date;
         Paid : Boolean;
      end record;
   
   task type Device is -- a task type
      entry Reset;
   end Device;
   
   type Dev is access Device; -- an access type
   
   protected type Semaphore is -- a protected type
      procedure Release;
      entry Seize;
   private
      Mutex: Boolean := False;
   end Semaphore;

   The following example illustrates a tagged type and type
extension.  A tagged type declaration takes the form

   type Animal is tagged
      record
         Species : Species_Name;
         Weight : Grams;
      end record;

and we may then declare

   function Image(A : Animal) return String;
      -- Returns a human-readable identification of an
   Animal

   The type Animal could then be extended as follows

   type Mammal is new Animal with
      record
         Hair_Color : Color_Enum;
      end record;

and a corresponding

   function Image(M : Mammal) return String;
      -- Returns a human-readable identification of a Mammal

   The type Mammal has all the components of Animal and adds
an additional component to describe the color of the
mammal's hair.  The process of extension and refinement
could continue with other types such as Reptile and Primate
leading to a tree-structured hierarchy of classes as
depicted in Figure 3-2.

                           Animal'Class
                                |
                         +-------------+
                         |             |
                   Reptile'Class   Mammal'Class
                                       |
                                       |
                                       |
                                  Primate'Class


          Figure 3-2: A Derivation Class Hierarchy


   It is important to observe that we have shown the
hierarchy in terms of the class-wide types such as
Mammal'Class rather than the specific types such as Mammal.
This is to emphasize the fact that the type Mammal is not a
subtype of Animal; Mammal and Animal are distinct types and
values of one type cannot be directly assigned to objects of
the other type.  However, a value can be converted using a
type conversion to another type of the same derivation
class.
   There are a number of other important concepts concerning
types regarding the visibility of their components and
operations which may be described as providing different
views of a type.
   For example, a private type is a type defined in a
package whereby the full details of the implementation of
the type are only visible inside the body and private part
of the package.  Private types are of fundamental importance
in providing data abstraction.
   Furthermore, a limited type is a type for which certain
operations such as predefined assignment are not available.
A type may be inherently limited (such as a task type or a
record type marked explicitly as limited) or it may be that
just a certain view is limited.  Thus a private type may be
declared as limited and the full type as seen by the body
need not be limited.


3.1.3  Operations and Overloading

There is a set of operations associated with each type.  An
operation is associated with a type if it takes parameters
of the type, or returns a result of the type.  Some
operations are implicitly  provided when a type is defined;
others are explicitly declared by the user.
   A set of operations is implicitly provided for each type
declaration.  Which operations are implicitly provided
depends on the type, and may include any of the following

*    Basic operations, such as assignment, component
     selection, literals and attributes.  Basic operations
     use special syntax specific to the operation.

     For example, an attribute takes the form X'Attr, where
     X is the name of an entity, and Attr is the name of an
     attribute of that entity.  Thus, Integer'First yields
     the lower bound of the type Integer.

*    Predefined operators.  These are taken from this set of
     21 operator symbols

          * Logical operators:     and or xor

          * Relational operators:  = /= < <= > >=

          * Binary adding operators:    + - &

          * Unary adding operators:     + -

          * Multiplying operators: * / mod rem

          * Highest precedence operators:    ** abs not

*    Enumeration literals.

*    Derived operations, which are inherited by a derived
     type from its parent type (as explained below).
   
   The explicitly declared operations of the type are the
subprograms that are explicitly declared to take a parameter
of the type, or to return a result of the type.  There are
two kinds of subprograms: procedures and functions.  A
procedure defines a sequence of actions; a procedure call is
a statement that invokes those actions.  A function is like
a procedure, but also returns a result; a function call is
an expression that produces the result when evaluated.
Subprogram calls may be indirect, through an access value.
   Procedures may take parameters of mode in, which allows
reading of the parameters, mode out, which allows writing of
the parameters, and mode in out, which allows both reading
and writing of the parameters.  In Ada 9X, a parameter of
mode out behaves like a variable and both reading and
writing are allowed; however, it is not initialized by the
actual parameter.  All function parameters are of mode in.
An in parameter may be an access parameter.  Within the
subprogram, an access parameter may be dereferenced to allow
reading and writing of the designated object.
   The primitive operations of a type are the implicitly
provided operations of the type, and, for a type immediately
declared within a package specification, those subprograms
of the type that are also explicitly declared immediately
within the same package specification.  A derived type
inherits the primitive operations of its parent type, which
may then be overridden.
   The name of an explicitly declared subprogram is a
designator, that is, an identifier or an operator symbol
(see the list of operator symbols above).  Operators are a
syntactic convenience; the operator notation is always
equivalent to a function call.  For example, X + 1 is
equivalent to "+"(X, 1).  When defining new types, users can
supply their own implementations for any or all of these
operators, and use the new operators in expressions in the
same way that predefined operators are used.
   The benefits of being able to use the operator symbol "+"
to refer to the addition function for every numeric type are
apparent.  The alternative of having to create a unique name
for each type's addition function would be cumbersome at
best.
   The operators are just one example of overloading, where
a designator (e.g., "+") is used as the name for more than
one entity.  Another example of overloading occurs whenever
a new type is derived from an existing one.  The new type
inherits operations from the parent type, including the
designators by which the operations are named; these
designators are overloaded on the old and new types.
Finally, the user may introduce overloading simply by
defining several subprograms that have the same name.
Overloaded subprograms must be distinguishable by their
parameter and result types.
   The user can also provide new meanings for existing
operators.  For example, a new meaning of "=" can be
provided for all types (only for certain limited types in
Ada 83).
   Ada compilers must determine a unique meaning for every
designator in a program.  The process of making this
determination is called overload resolution.  The compiler
uses the context in which the designator is used, including
the parameter and result types of subprograms, to perform
the overload resolution.  If a designator cannot be resolved
to a single meaning, then the program is illegal; such
ambiguities can be avoided by specifying the types of
subexpressions explicitly.
   Many attributes are defined by the language.  Attributes
denote various properties, often defined for various classes
of Ada's type hierarchy.  In some cases, attributes are
user-specifiable via an attribute definition clause,
allowing users to specify a property of an entity that would
otherwise be chosen by default.  For example, the ability to
read and write values of a type from an external medium is
provided by the operations T'Read and T'Write.  By writing

   type Matrix is ...
   for Matrix'Read use My_Matrix_Reader;
   for Matrix'Write use My_Matrix_Writer;

the predefined operations are overriden by the user's own
subprograms.


3.1.4  Class Wide Types and Dispatching

Associated with each tagged type T is a class-wide type
T'Class.  Class-wide types have no operations of their own.
However, users may define explicit operations on class-wide
types.  For example

   procedure Print(A : in Animal'Class);
      -- Print human-readable information about an Animal

   The procedure Print may be applied to any object within
the class of animals described above.
   A programmer can define several operations having the
same name, even though each operation has a different
implementation.  The ability to give distinct operations the
same name can be used to indicate that these operations have
similar, or related, semantics.  When the intended operation
can be determined at compile time, based on its parameter
and result types, overloading of subprogram names is used.
For example, the predefined package Text_IO contains many
operations called Put, all of which write a value of some
type to a file.  The implementation of Put is different for
different types.
   Dispatching provides run-time selection of the proper
implementation in situations where the type of an argument
to an operation cannot be determined until the program is
executed, and in fact might be different each time the
operation is invoked.
   Ada 9X provides dispatching on the primitive operations
of tagged types.  When a primitive operation of a tagged
type is called with an actual parameter of a class-wide
type, the appropriate implementation is chosen based on the
tag of the actual value.  This choice is made at run time
and represents the essence of dynamic polymorphism.  (Note
that, in some cases, the tag can be determined at compile
time; this is simply regarded as an optimization.)
   Continuing the example from above, we demonstrate both
overloading and dispatching

   procedure Print(S : in String);
    -- Print a string
   
   procedure Print(A : in Animal'Class) is
      -- Print information on an animal
   begin
      Print(Image(A));
   end Print;

   The Print operation is overloaded.  One version is
defined for String and a second is defined for Animal'Class.
The call to Print within the second version resolves at
compile time to the version of Print defined on String
(because Image returns a String); no dispatching is
involved.  On the other hand, Image (see example in Section
3.1.2) is indeed a dispatching operation: depending on the
tag of A the version of Image associated with Animal or
Mammal etc, will be called and this choice is made at
runtime.


3.1.5  Abstraction and Static Evaluation

The emphasis on high performance in Ada applications, and
the requirement to support interfacing to special hardware
devices, mean that Ada programmers must be able to engineer
the low-level mapping of algorithms and data structures onto
physical hardware.  On the other hand, to build large
systems, programmers must operate at a high level of
abstraction and compose systems from understandable building
blocks.  The Ada type system and facilities for separate
compilation are ideally suited to reconciling these
seemingly conflicting requirements.
   Ada's support for static checking and evaluation make it
a powerful tool, both for the abstract specification of
algorithms, and for low-level systems programming and the
coding of hardware-dependent algorithms.  By static, we mean
computations whose results can be determined by analyzing
the source code without knowing the values of input data or
any other environmental parameters that can change between
executions of the program.
   Ada requires static type checking.  The "scope"
(applicability or lifetime) of declarations is determined by
the source code.  Careful attention is given to when the
sizes of objects are determined.  Some objects' sizes are
static, and other objects' sizes are not known until run
time, but are fixed when the objects are created.  The size
of an object is only allowed to change during program
execution if the object's size depends on discriminant
values, and the discriminants have a default value.  Other
variable-size data structures can be created using
dynamically allocated objects and access types.
   Ada supports users who want to express their algorithms
at the abstract level and depend on the compiler to choose
efficient implementations, as well as users who need to
specify implementation details but also want to declare the
associated abstractions to the compiler to facilitate
checking during both initial development and maintenance.


3.2  Statements, Expressions and Elaboration

Statements are executed at run time to cause an action to
occur.  Expressions are evaluated at run time to produce a
value of some type.  Names are also evaluated at run time in
the general case; names refer to objects (containing values)
or to other entities such as subprograms and types.
Declarations are elaborated at run time to produce a new
entity with a given name.
   Many expressions and subtype constraints are statically
known.  Indeed, the Ada compiler is required to evaluate
certain expressions and subtypes at compile time.  For
example, it is common that all information about a
declaration is known at compile time; in such cases, the
run-time elaboration need not actually execute any machine
code.  The language defines a mechanism that allows Ada
compilers to pre-elaborate certain kinds of units; i.e., the
actual actions needed to do the elaboration are done once
before the program is ever run instead of many times, each
time it is run.


3.2.1  Declarative Parts

Several constructs of the language contain a declarative
part followed by a sequence of statements.  For example, a
procedure body takes the form

   procedure P(...) is
      I : Integer := 1; -- this is the declarative part
      ...
   begin
      ...               -- this is the statement sequence
      I := I * 2;
      ...
   end P;

   The execution of the procedure body first elaborates all
of the declarations given in the declarative part in the
order given.  It then executes the sequence of statements in
the order given (unless a transfer of control causes
execution to go somewhere other than to the next statement
in the sequence).
   The effect of elaborating the declarations is to cause
the declared entities to come into existence, and to perform
other declaration-specific actions.  For example, the
elaboration of a variable declaration may initialize the
variable to the value of some expression.  Often, such
expressions are evaluated at compile time.  However, if the
declarations contain non-static expressions, then the
elaboration will need to evaluate those expressions at run-
time.
   Controlled types allow programmers a means to define what
happens to objects at the beginning and end of their
lifetimes.  For such types, the programmer may define an
initialization operation, to be automatically invoked when
an object of the type is elaborated, and a finalization
operation to be automatically invoked when the object
becomes inaccessible.  (Declared objects become inaccessible
when their scope is left.  Objects created by allocators
become inaccessible when Unchecked_Deallocation is called,
or when the scope of the access type is left.)  Controlled
types provide a means to reliably program dynamic data
structures, prevent storage leakage, and leave resources in
a consistent state.


3.2.2  Assignments and Control Structures

An assignment statement causes the value of a variable to be
replaced by that of an expression of the same type.
Assignment is normally performed by a simple bit copy of the
value provided by the expression.  However, in the case of
non-limited controlled types, assignment can be redefined by
the user.
   Case statements and if statements allow selection of an
enclosed sequence of statements based on the value of an
expression.  The loop statement allows an enclosed sequence
of statements to be executed repeatedly, as directed by an
iteration scheme, or until an exit statement is encountered.
A goto statement transfers control to a place marked with a
label.  Additional control mechanisms associated with
multitasking and exception handling are discussed below (see
3.4 and 3.5).


3.2.3  Expressions

Expressions may appear in many contexts, within both
declarations and statements.  Expressions are similar to
expressions in most programming languages: they may refer to
variables, constants and literals, and they may use any of
the value-returning operations described in Section 3.1.3.
An expression produces a value.  Every expression has a type
that is known at compile time.


3.3  System Construction

Ada was designed specifically to support the construction of
large, complex, software systems.  Therefore, it must allow
the composition of programs from small, understandable
building blocks, while still allowing programmers to
engineer the low-level mapping of algorithms and data
structures onto physical hardware.  Ada provides support for
modern software development techniques with the following
capabilities

*    Packaging, the grouping together of logically related
     entities into packages.

*    Information hiding, where the programmer defines the
     interface of a program unit for the users of that unit,
     and separately defines the implementation of the unit.

*    Object-oriented programming, where objects can be
     defined in terms of preexisting objects with possible
     extension, overload resolution can be used to select
     operations at compile time, and dispatching can be used
     to select operations at run time.

*    Construction of software systems from large numbers of
     separately compiled units stored in a library, with
     full compile-time checking of interfaces between
     separately compiled units.

*    Class-wide programming.

*    Construction of mixed language systems, that is
     programs written in more than one programming language.
   
   The following subsections describe this support in more
detail.


3.3.1  Program Units

Ada programs are composed of the following kinds of program
units

*    Subprograms - functions and procedures.

*    Packages - groups of logically related entities.

*    Generic units - parameterized templates for subprograms
     and packages.

*    Tasks - active entities that may run in parallel with
     each other.

*    Protected objects - passive entities that protect data
     shared by multiple tasks.
   
   Program units may be nested within each other, in the
same way as in other block-structured languages.
Furthermore, they may be separately compiled.
   As we shall see later, packages at the so-called library
level may have child units.  Ada has a hierarchical
structure both at the external level of compilation and
internal to a program unit.
   Each program unit may be given in two parts: The
specification defines the interface between the unit (the
"server") and its users ("clients").  The body defines the
implementation of the unit; users do not depend on the
implementation details.
   For packages, the specification consists of the visible
part and the private part.  The visible part defines the
logical interface to the package.  The private part defines
the physical interface to the package, which is needed to
generate efficient code, but has no effect on the logical
properties of the entities exported by the package.  Thus,
the private part may be thought of as part of the
implementation of the package, although it is syntactically
part of the specification in order to ease the generation of
efficient code.
   The various parts of a package take the following form

    -- this is a package specification:
   package Example is
      -- this is the visible part
      -- declarations of exported entities appear here
      type Count is private;
      function Double (C : Count) return Count;
      --
   private
      -- this is the private part
      -- declarations appearing here are not exported
      type Count is range 0 ..  100;
   end Example;
   
    -- this is the corresponding package body:
   package body Example is
   
      -- implementations of exported subprograms appear here
      -- entities that are used only in the implementation
      -- are also declared here
      Zero : constant Count := 0;
        -- declaration of constant only used in the body
   
      function Double (C : Count) return Count is
      begin
         return C * 2;
      end Double;
   
   end Example;



3.3.2  Private Types and Information Hiding

Packages support information hiding in the sense that users
of the package cannot depend on the implementation details
that appear in the package body.  Private types provide
additional information-hiding capability.  By declaring a
type and its operations in the visible part of a package
specification, the user can create a new abstract data type.
   When a type is declared in a package specification, its
implementation details may be hidden by declaring the type
to be private.  The implementation details are given later,
as a full type declaration in the private part of the
package.
   The user of a private type is not allowed to use
information about the full type.  Users may declare objects
of the private type, use the assignment and equality
operations, and use any operations declared as subprograms
in the visible part of the package.  The private type
declaration may allow users to refer to discriminants of the
type, or it may keep them hidden.  A private type may also
be declared as limited, in which case even assignment and
predefined equality operations are not available (although
the programmer may define an equality operation and export
it from the package where the type is declared).
   In the private part, in the body of the package, and in
the appropriate parts of child library units (see Section
3.3.6), the type is not private: all operations of the type
may be used in these places.  For example, if the full type
declaration declares an array type, then outside users of
the type are not allowed to index array components, because
these implementation details are hidden.  However, code in
the package body is allowed the complete set of array
operations, because it can see the full type declaration.


3.3.3  Object Oriented Programming

Modern software development practices call for building
programs from reusable parts, and for extending existing
systems.  Ada supports such practices through object
oriented programming features.  The basic principle of
object oriented programming is to be able to define one part
of a program as an extension of another, pre-existing, part.
The basic building blocks of object-oriented programming
were discussed in Section 3.1.
   Abstract data types may be defined in Ada using packages
and private types.  Types may be extended by adding new
packages, deriving from existing types, and adding new
operations to derived types.  For non-tagged types, such
extension is "static" in the sense that the compiler
determines which operations apply to which objects according
to the typing and overloading rules.  For tagged types,
however, operation selection is determined at run time,
using the tag carried by each such object.  This allows easy
extension of existing types.  To add a new tagged type, the
programmer derives from an existing tagged parent type,
possibly adding new record components.  The programmer may
override existing operations with new implementations.
Then, all calls to the existing operation will automatically
call the new operation in the appropriate cases; there is no
need to change or even to recompile such pre-existing code.
For tagged types, it is possible to write class-wide
operations by defining subprograms that take parameters of a
class-wide type.  Class-wide programming allows the
programmer to avoid redundancy in cases where an operation
makes sense for all types in a class, and where the
implementation of that operation is essentially the same for
all types in the class.


3.3.4  Generic Units

Generic program units allow parameterization of program
units.  The parameters can be types and subprograms as well
as objects.  A normal (non-generic) program unit is produced
by instantiating a generic unit; the normal program unit is
said to be an instance of the generic unit.  An instance of
a generic package is a package; an instance of a generic
subprogram is a subprogram.
   The instance is a copy of the generic unit, with actual
parameters substituted for generic formal parameters.
Generic units may be implemented by actually generating new
code for each instance, or by sharing the code for multiple
instances, and passing information about the parameters at
run time.
   An example of a generic package is a generic linked list
package that works for any element type.  The data type of
the elements would be passed in as a parameter.  The
algorithms for manipulating the lists are independent of the
actual element type.  Instances of the generic package would
support linked lists with a particular element type.  If the
element type is a tagged class-wide type, then heterogeneous
lists can be created, containing elements of any type in the
class.  Generic formal derived types permit generic units to
be developed for derivation classes.  Generic formal
packages allow a generic unit to be parameterized by an
instance of another generic package.


3.3.5  Separate Compilation

Ada allows the specifications and bodies of program units to
be separately compiled.  A separately compiled piece of code
is called a compilation unit.  The Ada compiler provides the
same level of compile-time checking across compilation units
as it does within a single compilation unit.  For example,
in a procedure call, the actual parameters must match the
types declared for the formal parameters.  This rule is
checked whether the procedure declaration and the procedure
call are in the same compilation unit, or different
compilation units.
   Ada compilers maintain a database called the program
library, which contains information about each compilation
unit that has been compiled.  This information is used in
part to check rules across compilation unit boundaries.
There are rules about order of compilation that ensure that
the compiler always has enough information to check all the
rules.  For example, a specification must be compiled before
all units that have visibility to names declared in that
specification.


3.3.6  Library Units

A program library contains a collection of compiled library
units.  The user creates new library units and updates old
ones by compiling new Ada source text into the program
library.  Library units may be packages, subprograms, or
generic units.
   Package library units may have child library units.
Thus, an entire hierarchy of library units may be created:
the root of the tree is called a root library unit; the tree
contains the root library unit, plus all of its children and
their descendants.
   A library unit specification and its body are compilation
units; that is, they may be compiled separately.
   Visibility among library units is achieved using context
clauses; a compilation unit can see a particular library
unit if it names that library unit in a context clause.
Both root library units and child units may be named in a
context clause.  In addition, the child library units of a
parent can see the parent, including the parent's private
declarations, and the body of a unit always has visibility
into its specification.
   Child units may be used to reduce recompilation costs.
Apart from dependences created by context clauses, the
immediate children of a given unit may be recompiled in any
order.  Therefore, if an existing library unit is extended
by adding a child unit, the existing unit need not be
recompiled; adding a child is accomplished without changing
the source code of the parent.  More importantly, other
units that depend on the existing parent unit will not need
to be recompiled.
   The root library units are considered to be children of
package Standard: context clauses and compilation ordering
rules work the same way.  Thus, child units are a
straightforward generalization of Ada 83 library units.
   As an example consider the following

   package Root is
      -- specification of a root library unit
      ...
   end Root;
   
   -------------------------------
   
   package Root.Child is
      -- specification of a child library unit
      ...
   end Root.Child;
   
   -------------------------------
   
   package body Root.Child is
      -- body of the child library unit
      ...
   end Root.Child;
   
   -------------------------------
   
   private package Root.Local_Definitions is
      -- a private child package specification
      ...
   end Root.Local_Definitions;
   
   -------------------------------
   
   package body Root is
       -- body of the root library unit
      ...
   end Root;

   The lines in the above example indicate the separate
compilation units; they may be submitted to the compiler
separately.  Note that the child library units are clearly
distinguishable by their expanded names (based on the
parent's name).  The example also shows a private child
package - a private child unit is visible only within the
hierarchy of units rooted at its parent.
   Sometimes, the body of a library unit becomes very large,
because it contains one or more nested bodies.  In such
cases, Ada allows the nested bodies to be separately
compiled as subunits.  The nested body is replaced by a body
stub.  The subunit, which is given separately, must name its
parent unit.  Visibility within the subunit is as if it had
appeared at the place where its body stub occurs.  Subunits
also support an incremental style of top-down development,
because a unit may be compiled with one or more body stubs -
allowing the development of those bodies to be deferred.


3.3.7  Program Composition

An executable software system is known in Ada as a program.
A program is composed of one or more compilation units.
   A program may be divided into separate partitions, which
may represent separate address spaces.  Implementations may
provide mechanisms for user-defined inter-partition
communication.  The Distributed Systems Annex defines a
minimal standard interface for such communication.
Partitions are intended to support distributed processing,
as explained in the Annex.  Of course, many programs will
not be partitioned; such programs consist of a single
partition.
   To build a partition, the user identifies a set of
library units to be included.  The partition consists of
those library units, plus other library units depended on by
the named units.  The Ada implementation automatically
constructs this set of units before run time.
   Each partition has an environment task, which is provided
automatically by the Ada implementation.  A partition may
have a main subprogram, which must be a library unit
subprogram.  The environment task elaborates all of the
library units that are part of the partition, and their
bodies, in an appropriate order, and then calls the main
subprogram, if any.  The library units and the main program
may create other tasks.  Thus, an executing partition may
contain a hierarchy of tasks, rooted at the environment
task.
   A program library may contain the library units of one or
more programs.  Individual library units may be part of more
than one program; each program has its own copy at run time.


3.3.8  Interfacing to Other Languages

Large programs are often composed of parts written in
several languages.  Ada supports this by allowing inter-
language subprogram calls, in both directions, and inter-
language variable references, in both directions.  The user
specifies these interfaces using pragmas.


3.4  Multitasking

Ada tasking provides a structured approach to concurrent
processing under the control of an Ada run-time system,
which provides services such as scheduling and
synchronization.  This section describes tasks and the
methods that are used for synchronizing task execution and
for communicating between tasks.
   Tasking is intended to support tightly coupled systems in
which the communication mechanisms may be implemented in
terms of shared memory.  Distributed processing, where the
processors are loosely coupled, is addressed in the
Distributed Systems Annex.


3.4.1  Tasks

Tasks are entities whose execution may proceed in parallel.
A task has a thread of control.  Different tasks proceed
independently, except at points where they synchronize.
   If there is a sufficient number of processors, then all
tasks may execute in parallel.  Usually, however, there are
more tasks than processors; in this case, the tasks will
time-share the existing processors, and the execution of
multiple tasks will be interleaved on the same processor.
   Tasks are designated by objects of task types.  There may
be more than one object of a given task type.  All objects
of a given task type have the same entries (interface), and
share the same code (body).  As a result they all execute
the same algorithm.  Different task objects of the same type
may be parameterized using discriminants.  Task types are
inherently limited types; assignment and equality operations
are forbidden.
   Task objects are created in the same ways as other
objects: they may be declared by an object declaration, or
created dynamically using an allocator.  Tasks may be nested
within other program units, in the same manner as
subprograms and packages.
   All tasks created by a given declarative part or
allocator are activated in parallel.  This means that they
can logically start running in parallel with each other.
The task that created these tasks waits until they have all
finished elaborating their declarative parts; it then
continues running in parallel with the tasks it created.
   Every task has a master, which is the task, subprogram,
or block statement which contains the declaration of the
task object (or an access type designating the task type, in
some circumstances).  The task is said to depend on its
master.  The task executing the master is called the parent
task.  Before leaving a master, the parent task waits for
all dependent tasks.  When all of those have been
terminated, or are ready to terminate, the parent task
proceeds.  Tasks may be terminated prematurely with the
abort statement.


3.4.2  Communication and Synchronization

For multiple tasks to cooperate, there must be mechanisms
that allow the tasks to communicate and to synchronize their
execution.  Synchronization and communication usually go
hand-in-hand.  Ada tasks synchronize and communicate in the
following situations

*    Tasks synchronize during activation and termination (as
     just explained).

*    Protected objects provide synchronized access to shared
     data.

*    Rendezvous are used for synchronous communication
     between a pair of tasks.

*    Finally, unprotected access to shared variables is
     allowed, but requires a disciplined protocol to be
     followed by the communicating tasks.
   
   This flexibility allows the user to choose the
appropriate synchronization and communication mechanisms for
the problem at hand.  They are depicted in Table 3-1.
 
Ada feature      Synchronization     Communication
  
  
Task Creation     (not needed)        Creator initializes discriminants
                                      of new task
  
Task Activation   Creator waits for   Activation failure might be
                  tasks being         reported
                  activated               
  
Task Termination  Master waits for    (none)
                  children
  
Rendezvous        Entry caller and    Entry parameters are passed
                  acceptor wait for   between the entry caller and the
                  each other          acceptor

Protected Object  Mutual exclusion    Tasks communicate indirectly by
                  during data access; reading and writing the
                  queued waiting for  components of protected objects
                  the entry barriers
  
Unprotected       User-defined, low-  Reading and writing of shared
Shared Variables  level synchroniza-  variables
                  tion 

  Table 3-1: Summary of Communication and Synchronization



3.4.3  Protected Objects

Protected types are used to synchronize access to shared
data.  A protected type may contain components.  Moreover, a
protected type may also contain functions, procedures, and
entries - the protected operations of the protected type.
The data being shared is declared either as components of
the protected type, or as global variables, possibly
designated by the components of the protected type.
Protected types are inherently limited.
   Calls to the protected operations are synchronized as
follows.  Protected functions provide shared read-only
access to the shared data.  Multiple tasks may execute
protected functions at the same time.  Protected procedures
and entries provide exclusive read/write access to the
shared data.  If any task is executing a protected procedure
or entry, then no other tasks are allowed to execute any
protected operation at the same time; if they try, they must
wait.
   Protected objects provide a safe and efficient method of
synchronizing shared data access.  They are safe, because
they perform the necessary synchronization operations
automatically, and because all synchronizing operations are
collected together syntactically.  (This is in contrast to
lower-level mechanisms such as semaphores, where the user of
the shared data must remember to lock and unlock the
semaphore.)  They are efficient, because their intended
implementation is close to the hardware: such as spin-locks
in multiprocessor systems.


3.4.4  Protected Operations and Entries

Protected types may export functions, procedures, and
entries as described above.  Tasks may export entries.  All
of these operations are called using similar syntax:
OBJ.OP(...), where OBJ is the name of the task or protected
object, OP is the name of the operation, and (...)
represents any actual parameters.  Information is passed
back and forth using in, in out, out parameters.  It is the
responsibility of the programmer to ensure that operations
of protected objects execute for a bounded and short period
of time.
   A client task which calls an entry of a server task or
protected object may be suspended and placed on a queue.
When a server task accepts the entry call from a client
task, we say that the two tasks are in rendezvous.  At the
beginning and the end of the rendezvous, data may be
exchanged via parameters.  When the rendezvous is over, the
two tasks each continue execution in parallel.
   Entries of protected objects are controlled by barrier
expressions.  When a task calls the entry, it can execute
the operation immediately if the barrier expression is true;
otherwise, the caller is placed on a queue until the barrier
has become true.  Protected functions and procedures do not
have barrier expressions and, therefore, calls on them need
not be queued.
   From within a rendezvous or the entry body of a protected
type it is possible to complete the interaction by requeuing
on a further entry; this avoids race conditions which might
occur with two quite distinct interactions.


3.4.5  Select Statements

Select statements are used to specify that a task is willing
to wait for any of a number of alternative events.  Select
statements take various forms.
   Select statements used by a server task may contain
*     One or more accept alternatives, which indicate that
     the task is willing to accept one of several entries;
     that is, the task waits for another task to call one of
     those entries.

*    An optional terminate alternative, which allows a
     server task to specify that it is willing to terminate
     if there is no more work to do (i.e., all other tasks
     that depend on the same master are either terminated or
     are waiting at terminate alternatives).
   
   Select statements used by a client task may contain

*    One entry call alternative; which indicates that the
     task is waiting for an entry to be executed.  That is,
     the task waits either for the server task to accept the
     entry, or for the  protected object barrier to become
     true.
   
   Both of these forms of select statement can also contain,
either

*    A delay alternative, which allows a task to specify an
     action to be taken if communication is not started
     within a given period of time, or

*    An else part, which allows a task to specify an action
     to be taken if communication is not immediately
     possible.

   Whichever alternative becomes available first is chosen.
Each alternative specifies an action to be executed if and
when the alternative is chosen.
   The final form of select statement provides for an
asynchronous transfer of control; it contains

*    A triggering alternative which may be a delay
     alternative or an entry call followed by a sequence of
     statements, and

*    An abortable part,  The triggering alternative and the
     abortable part both proceed.  If the triggering
     alternative completes before the abortable part
     completes then the abortable part is aborted.  Control
     is then asynchronously transferred from somewhere in
     the abortable part to the statements of the triggering
     alternative.  If the abortable part completes before
     being aborted, then the triggering alternative is
     cancelled and execution continues at the next statement
     after the select statement.


3.4.6  Timing

Ada provides features for measuring real time.  A task may
read the clock to find out what time it is.  A task may
delay for a certain period of time, or until a specific
time.
   As mentioned above, a delay alternative may be used to
provide a time-out for communication or as a triggering
event for initiating an asynchronous transfer of control.


3.4.7  Scheduling

Ada separates the concepts of synchronization and
scheduling.  Synchronization operations determine when tasks
must suspend, and when they are ready to run.  Scheduling is
the method of allocating processors to ready tasks.  The
default scheduling policy is defined by the language.  The
Real-Time Systems Annex defines another, priority-based,
scheduling policy, based on a dispatching model.  Finally,
implementations are allowed to add their own policies, which
can be specified by pragmas.


3.5  Exception Handling

Most programs need to recover gracefully from errors that
occur during execution.  Exception handling allows programs
to handle such error situations without ceasing to operate.
   An exception is used to name a particular kind of error
situation.  Some exceptions are predefined by the language;
others may be defined by the user.  Exceptions are declared
in the same way as other entities

   Buffer_Full_Error : exception;

This exception might be used to represent the situation of a
program trying to insert data into a buffer which is already
full.
   When the exceptional situation happens, the exception is
raised.  Language-defined exceptions are raised for errors
in using predefined features of the language.  These
exceptions correspond to run-time errors in other languages.
For example, the language-defined exception Constraint_Error
is raised when a subtype constraint is violated at run time.
User-defined exceptions are raised by the raise statement.
To continue the Buffer_Full_Error example above, the
implementation of the buffer data type might contain
statements such as

   if Buffer_Index > Max_Buffer_Size then
      raise Buffer_Full_Error;
   end if;

   Subprograms, package bodies, block statements, task
bodies, entry bodies, and accept statements may have
exception handlers.  An exception handler specifies an
action that should be performed when a particular exception
is raised.  When an exception is raised, the execution of
the current sequence of statements is abandoned, and control
is transferred to the exception handler, if there is one.
Thus, the action of the exception handler replaces the rest
of the execution of the sequence of statements that caused
the error condition.  If there is no exception handler in a
particular scope, then the exception is propagated to the
calling scope.  If the exception is propagated all the way
out to the scope of the environment task, then execution of
the program is abandoned; this is similar to the way in
which program execution is abandoned in other languages when
run-time errors are detected.
   The following example shows a block with two exception
handlers

   begin
      ...
   exception
      when Buffer_Full_Error =>
         Reset_Buffer;
      when Error : others =>
         Put_Line("Unexpected exception raised:");
   
   Put_Line(System.Exceptions.Exception_Message(Error));
   end;

   The handlers recognizes two situations: if
Buffer_Full_Error is raised, the buffer is reset.  If any
other exception is raised, information about that exception
is printed.  For many applications, it is useful to get such
information about an exception when it occurs.  A handler
may have a choice parameter (Error in the example above) of
type Exception_Occurrence.  The predefined function
Exception_Message takes a parameter of this type and returns
a String, providing a message (including the name) about the
exception.  These and other related facilities are defined
in a child of package System.


3.6  Low Level Programming

Although the majority of program text can be written in a
machine-independent manner, most large software systems
contain small portions that need to depend on low-level
machine characteristics.  Ada allows such dependence, while
still allowing the high-level aspects of the algorithms and
data structures to be described in an abstract manner.  Many
of an implementation's machine-specific characteristics are
accessible through the package System.  This defines
storage-related types, an Address type, and relational and
arithmetic operations on addresses.


3.6.1  Pragmas

A pragma is used to convey information to the compiler; it
is similar to a compiler directive supported by other
languages.  A pragma begins with the reserved word pragma,
an identifier which is the name of the pragma, and
optionally one or more arguments.
   Some pragmas are defined by the language.  For example,
pragma Inline indicates to the compiler that the code for a
subprogram is to be expanded inline at each call whenever
possible.  Most pragmas apply to a single object, type, or
program unit.  Configuration pragmas are used to specify
partition-wide or program-wide options.
   Implementations may provide additional pragmas, as long
as they do not syntactically conflict with existing ones or
use reserved words.  Unrecognized pragmas have no effect on
a program, but their presence must be signaled with a
warning message.


3.6.2  Specifying Representations

Normally, the programmer lets the Ada compiler choose the
most efficient way of representing objects.  However, Ada
also provides representation clauses, which allow the user
to specify the representation of an individual object, of
all objects of a subtype, or of all objects of a type.
Other representation clauses apply to program units.
   The programmer may need to specify that the
representation matches the representation used by some
hardware or software external to the Ada program, in order
to interface to that external entity.  Or, the programmer
may wish to specify a more efficient representation of
certain objects in cases where the compiler does not have
enough information to determine the best (most efficient)
representation.  In either case, data types, subtypes, and
objects are first declared in the normal manner, giving
their logical properties.  Later in the same declarative
part, the programmer gives the representation clauses.
   In addition to representation clauses, the language
defines certain pragmas that control aspects of
representation.  Implementations may provide additional
representation pragmas.
   There are predefined attributes that allow users to query
aspects of representation.  These are useful when the
programmer needs to write code that depends upon the
representation, although the user might not need to control
the representation.
   In the absence of representation clauses or pragmas, the
compiler is free to choose any representation.


3.6.3  Unprotected Shared Variables

In Ada, variables may be shared among tasks according to the
normal visibility rules: if two tasks can see the name of
the same variable, then they may use that variable as shared
data.  However, it is up to the programmer to properly
synchronize access to these shared variables.  In most
cases, data sharing can be achieved more safely with
protected objects; unprotected shared variables are
primarily used in low-level systems programming.  Ada allows
the user to specify certain aspects of memory allocation and
code generation that may affect synchronization by
specifying variables as volatile or atomic.


3.6.4  Unchecked Programming

Ada provides features for bypassing certain language
restrictions.  These features are unchecked; it is the
programmer's responsibility to make sure that they do not
violate the assumptions of the rest of the program.  For
example, there are mechanisms for manipulating access types
that might leave dangling pointers, and there is a mechanism
for converting data from one type to another, bypassing the
type-checking rules.
   The generic function Unchecked_Deallocation frees storage
allocated by an allocator.  It is unchecked in the sense
that it can leave dangling pointers.
   The generic function Unchecked_Conversion converts data
from one type to another, bypassing all type-checking rules.
The conversion is done simply by reinterpreting the bit
pattern as a value of the target type; no conversion
actually happens at run time (except possibly bit padding or
truncation).
   The attribute P'Unchecked_Access returns a typed access
value to any aliased object of the appropriate type,
bypassing the scope checking rules (but not the type rules).


3.7  Standard Library

All implementations provide a standard library of various
packages.  This includes the predefined package Standard
which defines the predefined types such as Integer, and the
package System which defines various entities relating to
the implementation.
   The standard library also includes packages for computing
elementary functions, manipulating complex numbers and
generating random numbers as well as general packages for
string handling and character manipulation,


3.7.1  Input Output

Input-output capabilities are provided in Ada by predefined
packages and generic packages.

*    Sequential files present a logical view of files as
     sequences of elements.  Successive read or write
     operations to a sequential file result in the transfer
     of consecutive elements.  The generic package
     Sequential_IO may be instantiated for any type to
     provide operations for creating, opening, closing,
     deleting, reading and writing sequential files.  All
     elements of a Sequential_IO file are of the same type,
     although if the type is a tagged class-wide type, the
     elements may have different tags.

*    Direct files present a logical view of files as indexed
     sets of elements.  The index allows elements to be read
     or written at any position within a file.  The generic
     package Direct_IO provides operations similar to
     Sequential_IO.  In addition, Direct_IO provides
     operations for determining the current position and
     size of a file, and setting the position in the file,
     in terms of element numbers.

*    The Text_IO package provides facilities for input and
     output in a human-readable form.

*    A stream presents a logical view of a file (or other
     external medium such as a buffer or network channel) as
     a sequence of storage elements.  Stream input and
     output are predefined as basic operations for all non-
     limited types.  Users may override these default
     implementations using the facilities of the package
     Stream_Support.


3.8  Application Specific Facilities

Previous sections of this Overview have focused on the
"core" of the Ada language.  Implementations may provide
additional features, not by extending the language itself,
but by providing specialized packages and implementation-
defined pragmas and attributes.  In order to encourage
uniformity among implementations, without restricting
functionality, the Annexes define standards for such
additional functionality for specific application areas.
Implementations are not required to support all of these
features.  For example, an implementation specifically
targeted to embedded machines might support the application-
specific features for Real-time Systems, but not the
application-specific features for Information Systems.
   The application areas discussed in the Annexes are

*    Systems Programming, including access to machine
     operations, interrupts, elaboration control, low-level
     shared variables, and task identification facilities.

*    Real-time Systems, including priorities, queuing and
     scheduling policies, monotonic time, delay accuracy,
     immediate abort and a simple tasking model.

*    Distributed Systems, including a model for Ada program
     distribution into partitions and inter-partition
     communication.

*    Language Interfaces, including means for communication
     with programs in other languages, in particular C and
     COBOL.

*    Information Systems, including detailed support for
     decimal types and picture formatting.

*    Safety and Security, including pragmas relating to the
     proof of correctness of programs.

*    Numerics, including a model of real arithmetic.


3.9  Summary

The goal of this chapter has been to provide a broad
overview of the whole of the Ada language.  It also
demonstrates that the changes to the language represent a
natural extension to the original design of Ada.  As a
consequence, the incompatibilities between Ada 83 and Ada 9X
are minimal.  Those of practical significance are described
in detail in the next chapter.
