by Jim Rogers
This is part two (of five) of this article. Click here
to return to part one, or here to return to the contents.
Basic Ada Syntax
Every program must have a
starting place. In the C family of languages this starting place
is always a function called main. Ada has no rule about
the name of the program starting point. The program starting
point is a compilation unit containing only a procedure with no
parameters. The simplest Ada programs can be written entirely in
one procedure. The following "hello world" example
demonstrates how to write a very simple Ada program.
Hello World
-----------------------------------------------------------
-- Hello World program
-----------------------------------------------------------
with Ada.Text_Io;
use Ada.Text_Io;
procedure Hello is
begin
Put_Line("Hello World!");
end Hello;
The first three lines are
comments. Ada comments start with a pair of hyphens and continue
to the end of the line. The with clause declares that this
program requires access to a package named Ada.Text_IO.
This makes the package visible to the program, but does not make
its contents directly visible. The use clause makes the
contents of the Ada.Text_IO package visible to this
program.
Ada identifiers are not
case sensitive. Thus procedure and Procedure are
equivalent.
Hello is the name of
this procedure, which is the starting point for our little
program.
Put_Line is a procedure from the Ada.Text_IO package that
takes a string as a parameter and outputs to standard output.
Put_Line appends a newline character to the end of the output.
The combination of begin
and end defines the body of the procedure.
Ada does not provide a
preprocessor. Preprocessors were left out of the language on
purpose. The view of the Ada language developers was that
preprocessor macros are unsafe. Interestingly, many C++
developers also believe that preprocessor macros are unsafe and
should be avoided whenever possible. The Ada with clause establishes a
compilation dependency. It does not cause the build tools to modify the
source code like a preprocessor does. This means that you can with the
same module as many times as you want without causing compilation
or linking problems.
Ada Types
Ada provides a large number
of kinds of data types.
Ada does not have a
predefined inheritance hierarchy like many object oriented
programming languages. Ada allows you to define your own data
types, including numeric data types. Defining your own type in
Ada creates a new type. In C or C++, the typedef command
simply creates an alias for a type.
Elementary Types |
Access Types |
Used to
reference objects. Ada access types are similar
to reference types in C++ and Java. |
Scalar Types |
Discrete Types |
Enum- eration Types |
Ada provides some predefined enumeration types. The
Boolean type is an enumeration type with two values: True and
False. The Character type is another enumeration type. You are
allowed to define your own enumeration types.
type Colors is
(Red, Orange, Yellow, Green,
Blue, Indigo, Violet);
Enumeration types are not numeric types.
|
Integer Types |
Signed Integer Types |
The predefined type Integer
is a signed integer type. You are allowed to define
your own signed integer types.
type My_Int is range 1..100;
Even though the type definition above does not
allow negative values, the type My_Int is
implemented as a signed integer. All arithmetic
on signed integers must deal with overflow and
underflow issues. Ada will raise the exception Constraint_Error
if an attempt is made to assign an out of range
value to a signed integer object.
|
Modular Types |
You must define your own modular
types.
type byte is mod 2**8;
The above definition defines an unsigned integer
(modular) type named byte.
The range of values for byte
is 0 through 255, or the cycle of values derived
from the modulus of 256. The expression 2**8
is read as 2 to the 8th power.
All arithmetic on modular types results in a value
within the modular type’s valid range. For
instance, if you define an instance of the byte
type described above:
A : byte := 0;
This instance, A, is explicitly initialized to
0. Subtracting 1 from A will result in a value of
255. Modular types will not generate overflow or
underflow errors.
|
|
|
Real Types |
Floating Point Types |
Ada provides a predefined floating point type
called float. You are allowed to define your own
floating point types.
type Sin_Values is digits 10
range -1.0..1.0;
This type definition defines a type named Sin_Values with 10
decimal digits of precision and a valid range of values from -1.0
through 1.0.
type Double is digits 15;
This type definition defines a floating point type named
Double with 15 decimal digits of precision. This
type would correspond exactly to the double
primitive type in Java. The range restriction
shown in the Sin_Values type definition is optional.
|
Fixed Point Types |
Fixed point types are real types with a fixed
number of digits to the right of the radix character. These types
are very much like decimal types in Cobol. Fixed point types come
in two kinds. Fixed point types always have a predefined distance
between neighboring values.
Ordinary |
The distance between values
is implemented as a power of 2.
type Batting_Averages is
delta 0.001
range 0.0..1.0;
Batting_Averages is a fixed point type
whose values are evenly spaced real numbers in
the range from 0 through 1. The distance between
the values is no more than 1/1000. The actual
distance between values
may be 1/1024, which is 2-10.
|
Decimal |
The distance between values
is implemented as a power of 10.
type Balances is
delta 0.01 digits 9
range
0.0..9_999_999.99;
The important different in the definition of a
decimal fixed point type from an ordinary fixed
point type is the specification of the
number of digits of precision. This example
also shows how Ada allows you to specify numeric
literals with underscores separating groups of
digits. The underscore is used to improve readability.
|
|
|
|
|
Composite Types |
array |
All arrays in Ada are instances of some type. There are some
predefined array types in Ada. The string type is
defined as an array of characters. Array types
may be constrained or unconstrained types.
Constrained types have a predefined size.
Unconstrained types may have instances of
different sizes.
Constrained type definition
type Int_Buffer is array (1..10)
of Integer;
Int_Buffer is an array type. The valid range of index values is 1
through 10. Each element of the array is an Integer.
Unconstrained type definition
type String is array (Positive range <>)
of Character;
This is the actual definition of a string in Ada. A string is an
array of characters. The array may be any length,
but the index will be a subset of the predefined
integer subtype named Positive. Positive
is defined as all Integer values starting
at 1 through the highest value of Integer. Each instance of
an indefinite array type must have its size declared when the instance is
defined.
Last_Name : String(1..20);
Last_Name is defined as a 20 character string.
Array Index Values
Ada array types must specify the range of values for their array
index. Any discrete type can be used as an array
index. The purpose of this freedom is to allow
the programmer to cleanly model the real world in
code. For instance, if you want to collect the
frequency of occurrence of numbers spaced around
a normalized value you can define an array type as follows:
type Normalized_Distribution is array(-100..100)
of Natural;
Natural is a predefined subtype of Integer with a
minimum value of 0 and a maximum value of the
highest value of Integer . If your program
attempts to access an element outside the range
of index values specified for your array type the
exception Constraint_Error will be raised.
If you want to collect the sales of a store for each day of the week you
could provide the following declarations.
type Days is
(Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday, Sunday);
type Daily_Sales is array(Days) of Float;
This defines an enumeration type for days of the week. It then
defines an array type indexed by that enumeration
type. How could we deal with such an array? After
all, enumeration types are not numeric
types. Iteration would seem to be an issue. Ada
solves this problem with the syntax of its for loop.
function Weekly_Total(Shop_Sales : Daily_Sales)
return Float is
Sum : Float := 0.0;
begin
for index in Shop_Sales'Range loop
Sum := Sum + Shop_Sales(index);
end loop;
return Sum;
end Weekly_Total;
This function cleanly iterates through all values in the Daily_Sales
object passed through the function parameter. ‘Range
is an attribute of an array that returns the
range of values for the index. The for
loop iterates through that range.
|
record |
A record in Ada is equivalent to a struct in C, or a
record in Pascal. A record is a grouping of
possibly dissimilar data elements into a single
type. Ada defines two basic kinds of records,
ordinary records and tagged records. Tagged
records are extensible through inheritance.
Ordinary records are not extensible.
Ordinary Record Type
type Inventory_Item is record
UPC_Code : String(1..20);
Description : String(1..256);
Quantity : Natural;
Unit_Cost : Float;
end record;
Every instance of Inventory_Item
contains four fields: UPC_Code, Description,
Quantity, and Unit_Cost.
Tagged Record Type
type Person is tagged record
Name : String(1..20);
Gender : Gender_Type;
Age : Natural;
end record;
Like an ordinary record, a tagged record may contain many fields. The
difference is that a tagged record becomes the
root of an inheritance hierarchy.
type Employee is new Person with record
Id : Integer;
Salary : Float;
end record;
Employee extends Person
and add two additional fields, Id and Salary.
|
protected |
Protected types are designed to safely implement data
sharing between tasks. Protected types are
protected from inappropriate simultaneous access
by multiple tasks. The definition of a protected
type is split into two parts, the interface and
private definitions, and the implementation of
the operations associated with the protected
type.
protected type Counting_Semaphore is
procedure Release;
entry Acquire;
function Lock_Count return Natural;
private
Count : Natural := 0;
end Counting_Semaphore;
The code snippet above defines the public interface to a
Counting_Semaphore. It also defines the private
data member of that type. This code snippet is
called the specification of the protected type.
It provides a calling contract with any code
module that needs to interface with an instance
of Counting_Semaphore.
protected body Counting_Semaphore is
procedure Release is
begin
if Count > 0 then
Count := Count - 1;
end if;
end Release;
entry Acquire when Count < 11 is
begin
Count := Count + 1;
end Acquire;
function Lock_Count return Natural is
begin
return Count;
end Lock_Count;
end Counting_Semaphore;
There are three kinds of operations allowed on a protected type.
Procedures are used to modify the state of the
protected type. Procedures get exclusive access
to the instance of the protected type. Entries
also may update the state of the protected type,
but they have a boundary condition that must be
true before they can be executed. Entries get
exclusive access to the instance of the protected
type. Functions must not change the state of the
protected type. They can only report state.
Functions get shared access to the instance of
the protected type.
|
task |
Task types are used to define tasks with identical behavior.
A task is a concurrent thread of execution in an
Ada program. Tasks correspond to Threads in Java.
Task definitions are split into two parts. The
task specification defines the public interface
to a task. This interface consists of some set of
entry calls by which this task may be called from
another task.
task type Producer is
entry Start(Num_Items : in Positive);
end Producer;
Producer is a task type with a single entry. The Start entry
requires a calling task to pass in a parameter
with the formal name of Num_Items. The
actual behavior of a task is defined in the task body.
task body Producer is
Quantity : Positive;
begin
accept Start (Num_Items : in Positive) do
Quantity := Num_Items;
end Start;
for Iteration in 1..Quantity loop
Put_Line("Produced Item" &
Positive'Image(Iteration));
end loop;
end Producer;
The instance of Producer
will suspend at the accept call until some
other task calls this task’s Start entry.
When the call happens the value in the formal
parameter Num_Items is copied into the
local variable Quantity . The Producer
will then execute the loop for Quantity
iterations. In this example, when the loop
completes the task completes.
|
|
At Home on the Range
Ranges are used by Ada for many purposes.
- You specify a range when you define
your own scalar type. The range of the type defines the
set of valid values for the type.
type Rankings is new Integer range 1..10;
type Voltages is digits 12 range -12.0..12.0;
- Ranges are used to specify subsets
of types.
type Days is (Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday, Sunday);
subtype Weekends is Days range Saturday..Sunday;
- Ranges are used to specify
iteration limits in a for loop.
for Num in 1..10 loop
Put_Line(Integer'Image(Num));
end loop;
A range is specified with the notation
low_value .. high_value
You need to specify a range
when you define an array. Ada array indices may begin at any
integer or enumeration value. The set of index values used for an
array is determined by a range.
type Buffer is array (1..10) of Float;
type Normalized_Counts is array (-10..10) of Integer;
type Weekday_Sales is array(Days range Monday..Friday) of Float;
Ada provides a set of
special functions called attributes to help in dealing
with ranges. Attributes are evaluated by calling them with a
special notation sometimes called a tick notation.
Attribute Name |
Meaning |
Example |
First |
Evaluates to the
first or lowest value in a range. For scalar data types First
evaluates to the lowest valid value for the type. For
arrays First evaluates to the first or lowest
index value. |
Days'First
Normalized_Counts'First
|
Last |
Evaluates to the
last or highest value in a range. For scalar data types Last
evaluates to the highest valid value for the type. For
arrays Last evaluates to the last or highest index
value. |
Days'Last
Normalized_Counts'Last
|
Range |
Evaluates to the
range of values for a range. For an array the Range
attribute corresponds to Array’First..Array’Last. |
Buffer'Range
Voltages'Range
|
This ends part two of this article. Click here
to continue with part three, Ada Operators.