Programming in Ada a First Course
Programming in Ada a First Course
4 frst
cone
ROBERT G. CLARK
Programming in Ada
A FIRST COURSE
ROBERT G.CLARK
Department of Computing Science, University of Stirling
SE
CONTENTS
Preface ix
Problem solving with computers =
1.1 Introduction =
Arrays 108
9.1 Introduction 108
9.2 Two-dimensional arrays 111
9.3 Array type definition and array attributes 114
9.4 Arrays as parameters 116
9.5 Array aggregates 118
Exercises 120
Contents vii
17 Files 198
17.1 Introduction 198
17.2 Reading from a file 199
17.3 Creating and deleting files 201
17.4 Copying a file 203
Exercises 205
essof Lovelace. She was the daughter of Lord Byron and her work
with the nineteenth-century computer pioneer Charles Babbage
meant that she can be considered as the world’s first computer
programmer.
The initial version of the Ada Language Reference Manual was
published in 1980, but since then the language has undergone several
revisions. The language described in this book is the final version
(ANSI/MIL-STD-1815A-1983).
Ada is a very large language and no attempt is made in this book to
cover it all. Instead the emphasis is on the development of a good
programming style. The early parts of the book concentrate on
solving small problems and introducing the language features necess-
ary to write small programs. The description of type and object
declarations has been simplified, but this has been done in a way
which does not reduce the power of the language. The later parts of
the book show how packages can be used in constructing large reliable
programs and their use in information hiding and in the definition of
abstract data types. The last few chapters cover separate compilation,
exceptions and files. Generics, access types and tasks are not covered.
Throughout the book solutions to problems are developed by
means of stepwise refinement to give complete Ada programs. All the
programs were tested first using the University of York Ada compiler
and then using the validated Data General ROLM compiler. In the
tests using the ROLM compiler, package texz_io was used throughout
with type real defined as
type real is digits 6;
Packages int_io and real_io were instantiated as described in appendix
2. I would like to thank Iain Richmond of the Edinburgh Regional
Computing Centre for his help in arranging these tests.
I would also like to thank my colleagues Charles Rattray and Dave
Budgen in the Computing Science Department of Stirling University
for their helpful comments. Last, but not least, I would like to thank
my wife and children for their patience while the book was being
written.
Robert G Clark
Stirling, December 1984
1
1.1 Introduction
This book is about solving problems using a computer. A
major aim is to describe how we can systematically design solutions to
problems and how we can then implement these solutions on a
computer. The result should be computer programs which can be
easily understood and are straightforward, reliable and reasonably
efficient.
The programs are going to be written in a language called Ada,
which is well suited to this approach to problem solving. The second
major aim of the book is to give an introduction to the Ada language.
No previous knowledge of Ada or any other programming language
assumed, although itis expected that many readers will have had some
is
experience with computers in school or elsewhere.
As
they are used in so many different areas, most people have some
idea of what computers can do, but they may not be at all sure how
it
they manage to do it. To clear up any misconceptions, will be useful
to have a simple definition of a computer:
A computer isan electronic machine which can be given, and
which will then obey, a series of instructions.
to
Itis important realise that before a computer can carry out a task
such as sending you an electricity bill or playing chess, it must have
been given precise and unambiguous instructions of what to do. To be
understandable to the computer, the instructions must have been
written in some language which the computer can understand. A set
of instructions informing a computer how to carry out some task is
called a program.
If we want to use a computerto solve a problem, we do not just sit
down and write a program. We must first make sure that we fully
understand what the problem is and we must then design a suitable
2 Programming in Ada
solution. Only after this has been done do we start writing the actual
program, in some suitable language. The act of programming is
concerned with program design and not just with writing programs.
We shall discover that problem specification and the design of
solutions is much more difficult than implementing a solution in some
programming language.
Before we look at how we go about designing programs, let us
briefly consider the components which go to make up a computer
system.
and loaded into the main store before they can be obeyed.
4. Input and output devices
An input device is used to read information into a computer
and the results of running a program are presented to the user
by an output device. The most common device is the
interactive terminal, which can act both as an input and as an
output device. It usually consists of a screen (like a television
screen) and a typewriter keyboard and is often referred to as a
visual display unit or VDU. Information can be typed in at the
keyboard and both the information typed in and the results
produced by the program are displayed on the screen. In this
way the user can interact with the running of a program.
Another output device is the printer, which can be used to
print out results so that they can be taken away for further
study.
The main store, CPU, backing store and the input and output
devices are together known as the computer hardware. Programs, on
the other hand, are what is known as computer software.
guarantee that its results are correct, for there may be logical errors in
the attempted solution. Testing programs with carefully chosen test
data is therefore very important. Finding and correcting errors in this
way is called “debugging”.
You will find that compile-time errors are much easier to find and
correct than run-time errors, which are much easier to find and
correct than logical errors. Ada has been designed so that as many
errors as possible are picked up at compile-time. Moreover some
logical errors, which would remain undetected in programs written in
many other languages, will cause run-time errors to be produced.
1.4 Stepwise refinement
Let us now consider how we can go about solving a problem.
As has already been said, there are several important steps which must
be taken before we can start writing the actual program.
With complex problems finding out exactly what is required is
crucial and can be the most difficult part of the whole process. Any
mistakes made at this stage can be very expensive, as they will lead to
the wrong problem being solved. This step does not play a large part
in this book because the problems which we shall consider will already
be fairly well defined. It is, however, important to remember the
importance of this first step when problems in commerce, industry
and research are being dealt with.
Learning how to design the solutions to problems does play an
important part in this book. A way of solving a problem is called an
algorithm, which can be defined as
an unambiguous sequence of instructions which, when fol-
lowed, gives the required result in a finite time.
We
are therefore interested in designing algorithms. Let us look at
this through an example problem, such as that of finding the sum of
the squares of all the odd numbers from 1 to 99.
It is often best to start by forgetting about the computer and to
consider how we might go about tackling a problem with pencil and
paper. Our approach would most likely be to take each of the odd
numbers in turn, calculate its square and add this value to some
running total. This gives us the basis of our computer solution, which
we can write informally as
set running total to zero
take each odd number in the range 1 to 99 in turn and add its
square to the running total
write down the final value of the running total
6 Programming in Ada
This outline solution has ignored details such as how the sequence
of odd numbers is to be produced. Our next step is to expand the
outline solution to indicate how we are going to produce each of the
odd numbers in turn. A possible method is to start with the first odd
number and to always generate the next odd number by adding two to
the current one. Our algorithm then becomes
set running total to zero
set odd number to one
loop while the odd number is less than or equal to 99
add the square of the current odd number to the running
total
add two to the current odd number
end loop
write the final value of the running total
The idea is that the instructions between the words “loop” and
“end loop” are executed with each of the odd numbers between 1 and
99 in turn.
This approach to problem solving is called programming by
stepwise refinement. We first produce an outline solution which does
not go into detail. This solution is then expanded or refined in a
stepwise manner until it contains sufficient detail for us to be able to
implement it in some programming language. The outline solution
and its refinements are written in a restricted form of English so that
the design is not dependent on the features of a particular program-
ming language.
As this is a fairly simple problem, we are now in a position to write
the computer program. Provided the design stage has been done
properly, producing the program in Ada or some other language is
relatively straightforward. The principal intellectual challenge is in
producing the design, not in implementing the design in a program-
ming language.
So that we can see what a complete Ada program looks like, a
program corresponding to this design is given below. Do not worry
about following all the details; they are explained in the next few
chapters. The important thing to note is how closely the Ada program
follows our informal design in English and how, even with no knowl-
edge of Ada, it is possible for you to have a fair idea of what the
program is doing.
Problem solving with computers 7
PROGRAM 1.1
10
Ada programs 11
is
A simple Ada program consists of what called a procedure and the
second line of our program
procedure add is
introduces the procedure and gives it the name add. The choice of
names is left up to the programmer, but it
is always good practice to
choose names which help to explain what is happening in a program.
In programming, names are referred to as identifiers.
The third line
first, second, sum : integer;
is called the declarative part of the procedure. In it three variable
objects are introduced or declared and are given names (firsz, second
and sum) so that they can be referred to later in the program. Variable
objects, or variables, are used in programming languages to store, or
hold, values. They are called variables because during the execution
of a program the values which they store may be changed many times.
A variable object is restricted to being able to store values of only
one particular kind or type. In this declaration the three variables are
declared to be of type integer. This means that the variable objects
first, second and sum can only be used to store values which are whole
numbers. We refer to them as integer variables.
The type of a variable determines both the range of values which it
may hold and the kind of operations in which it may become involved.
Ada has several built-in types, such as integer and floar. Integer
variables can be used in the arithmetic operations of addition, sub-
traction, multiplication, division, etc. As we shall see later, float
variables are used to store fractional numerical values and can also be
used in arithmetic operations.
Avariable object therefore has aname, a value and a type. The name
and type are fixed when the variable is declared, but its value may
change during execution of the program.
Let us now look at the effect of executing the Ada program.
Programs are executed by obeying in turn the sequence of instruc-
tions or statements between the reserved words begin and end.
Immediately following the begin we have a comment:
--program to print the sum of two integers
Comments are introduced by two consecutive hyphens and are ter-
minated by the end of a line. They exist purely to help the human
reader and are ignored by a computer during the execution of the
program.
12 Programming in Ada
get(first); get(second);
are input statements and require two integers to be typed in as data.
Once they have been typed in, the two numbers are read by the
program and stored in the variables first and second respectively.
The statement
sum := first + second;
assigns the sum of the two values to sum as before. The answer is then
printed out, along with a suitable explanatory message, by the state-
ments
put(“‘Sum =");
put(sum);
new_line;
As we have now come to the end of the sequence of statements,
execution of the program stops.
~{ letter }
Lo
letter
__@m—
All possible paths through this diagram describe a valid Ada ident-
ifier. We therefore have an equivalent definition of an identifier to the
one given earlier in English.
In syntax diagrams, items in rounded boxes or circles such as the
underline character ““_"" actually appear in Ada programs while items
in rectangular boxes describe the kind of item which is to appear.
Items in rectangular boxes must be defined by further syntax
diagrams. A digit, for example, is defined by
thedtedity
16 Programming in Ada
This shows that a digit is one of the characters “0”, “17, “27, “3”,
seg «<5
cg cw
lower case letters.
«@ or «97 A letter is one of the 26 upper or 26
oso
declarative part
\—(begin }—={sequence of
tema
Ed)
— fer —0
Because they are in rectangular boxes, what exactly is meant by
“context clause”, “identifier”, “declarative part” and ‘“‘sequence of
statements” will be defined by other syntax diagrams. Words like
“procedure”, “is”, “begin” and “end”, on the other hand, are in
rounded boxes and will appear in the final program. They are the
reserved words and may not be used as ordinary identifiers.
Exercises
1. Which of the following are valid Ada identifiers?
total Total sum_1 suml sum__1 sum_1.5
SECOND begin _average “final” TotalCount
Total Count 2nd Begin END
2. Which identifiers in question 1 have we encountered as re-
served words?
3. Define the terms ‘variable’ and “‘type”.
4. In response to the prompt
two whole numbers please:
Ada programs 17
a user types in
15 28
as data for Program 2.2. What will be printed out by the
program in response?
.
. Write a program which will read two integers and print the
result of subtracting the first from the second.
second : integer;
sum : integer;
As well as being given a name and a type, variables may be given an
initial value when they are declared. Hence in the object declarations
table_size : integer := 20;
maximum, minimum : integer := 0;
the integer variable zable_size is created and given the initial value 20
and the integer variables maximum and minimum are created and given
the initial value 0.
is
If a variable not given a value when it is declared, as was the case
It
in our earlier examples, then we say that its value is undefined.
in
remains undefined until the variable is given a value an assignment
Types and values 19
integer variables and two integer constants are declared in this pro-
gram. The constant identifiers are used to make the program easier to
understand.
Execution starts with the prompt
Total number of hours worked is
being output to the user’s terminal. Execution of the program is
suspended at this point until an integer number such as 38 is typed in
at the terminal. This number isread by the program and stored in the
variable total_hours. A second prompt
Number of hours of overtime is
is then output and execution is again suspended until a second integer
number such as 3 is typed in. This is read and stored in the variable
overtime. The number of normal hours worked and the employee’s
Types and values 21
pay are then calculated and the program prints out the result
pay = § 316
Note that if the employee was given a wage rise, all we would have to
do is change the constant declarations. The actual program state-
ments would remain unchanged.
OE D
bers with fewer digits are printed with extra leading spaces. Negative
integers are preceded by a minus sign.
In our examples we have assumed that the maximum allowed
integer has 10 digits. Hence, in Program 3.1, the statements
put(“pay = $7); put(pay);
printed out
pay = $ 316
As this does not always lead to the best way of presenting results,it
is possible to have an optional “width parameter”. This is shown in
the examples
put('$"); put(pay, width = > 4); -- $ 316 printed
put('$"); put(pay, width => 3); -- $316 printed
It is not an error for the width parameter to be insufficient to print
the whole number; extra space is
just taken. In fact, we often use a
width parameter of 1 to ensure that the integer will be printed without
leading spaces, as in
put('$"); put(pay, width => 1); -- $316 printed
1 me
©
Sy
Types and values 23
begin
—-read radius and calculate circumference and area
put(“Radius?”);
get(radius);
circumference:= 2.0 * pi * radius;
*
radius * radius;
area := pi
put(““circumference = 7);
put(circumference); new_line;
put(“‘area = 7);
put(area); new_line;
end circle;
Three real variables and one real constant are declared in this
program. Execution starts with the prompt
Radius?
stored
being output. The number typed in at the terminal is read and
in the variable radius. The circumference and area of the circle are
then calculated using the well-known formulae and the results output
with suitable explanatory messages.
ry re
Examples are
rt AY mont
The space character is represented by a space between two apostrophe
characters. It is therefore represented in the same way as any
other
character.
What different characters that may be stored in a character
are the the built-in
variable, i.e. what is the allowable character set? In Ada,
type character allows the 128 characters
of what is
called the ASCII
Types and values 25
character set. Of these 128 characters, 95 are printable and the rest are
special control characters. Only the 95 printable characters can
ap-
pear in character literals. The printable characters are the upper and
lower case letters, the digits, most commonly used punctuation
characters such as comma and semi-colon, and arithmetic
operators
such as plus and minus. A complete list is given in appendix 3.
The following program gives an example of the use of character
variables. It will read in any three characters and write them out again,
first together and then separated by spaces.
PROGRAM 3.3
with student_io; use student_io;
procedure three_chars is
char_1, char_2, char_3 : character;
: constant character
space := '’;
begin
--read and print three characters
pur(“Three characters please:);
get(char_1); get(char_2); get(char_3);
--write out the characters
put(char_1); put(char_2); put(char_3);
new_line;
--write again, but include spaces
put(char_1); put(space);
put(char_2); put(space); put(char_3);
new_line;
end three_chars;
If the three characters
abc
are typed in as data for this program the result will be
abc
abc
Characters may be grouped together in a character
string, which is a
sequence of zero or more characters enclosed by quotation characters.
An example is
“This is a string.”
String is a built-in type like integer, float and character but
differs
from them in one important respect. Itis
composed of components of
a simpler type, namely characters, and so is called
a composite type.
26 Programming in Ada
The types integer, float, real and character are examples of what are
called scalar types.
Any printable ASCII character may be included in a string. One
problem is how to include the quotation character itself without the
string being automatically terminated. This problem is solved by
using the convention that it has to be written twice. Hence the
statement
put(“The variable “‘“‘sum”” has been declared.”);
will cause the message
The variable “sum” has been declared.
to be written out.
The empty string is allowed, i.e. a string which contains no charac-
ters, and it is written as ““”.
We have already used strings in our programs. We shall delay the
introduction of string variables until later, but deal now with string
constants. They are declared as in
greetings : constant string := “Hello”;
and are useful when a lengthy string is used several times in a
program. Consider for example the following program to print out a
triangle pattern surrounded by a border:
PROGRAM 3.4
with student_io; use student_io;
procedure rriangle is
border : constant string :=
CAKKKKKKI KKK KK KKK KKK kK AX
constant string : =
cc bh
spaces : 5
begin
--print a bordered triangle
put(border); new_line;
put(border); new_line;
new_line; --produce a blank line
put(spaces); put(“ *”); new_line;
put(spaces); put(‘* ***7); new_line;
put(spaces); put(‘“*****>); new_line;
new_line; --another blank line
put (border); new_line;
put(border); new_line;
end triangle;
Types and values 27
*
*% %
* kk kk
KAKA K AKA AK KKK K KAKA Kk
Kk kk kk
KAKA KAKA AK KAKA AAR AK kA KAA KX
enumeration ® a
) 2
(O
inumeration
® er 0)
4.1 Introduction
We have already used expressions and assignment statements
in our example programs. In Program 3.1 for example, we had the
assignment statement
pay := basic_pay + overtime_pay;
The effect of executing this statement is first to evaluate the
expression
basic_pay + overtime_pay
and then to assign the resulting value to the variable pay.
Expressions are the means by which new values are calculated. The
value of the expression
basic_pay + overtime_pay
is found by adding the value of the variable basic_pay to the value of
the variable overtime_ pay. Expressions consist of one or more of what
are called operands, whose values may be combined by operators such
as “+” or “*”’. The operands are usually constant or variable objects.
Some example expressions are
94 3 + 2 first 2%*second + 17 i
Note that an expression can be an operand on its own.
pay > 200
9: —=5:+-2 equals 6
9 —
(5+ 2) equals 2
3+4%*5 equals 23
B+ 4)*5 equals 35
16 /2*4*2 equals 64
16 / (2*4*2) equals
5%2*%x3 equals 40
4.3 Problem solving
Now that we know how to write assignment statements, let us
look at how we can go about solving a simple problem such as how to
read a distance in metres and convert
itinto yards, feet and inches.
As we saw in chapter 1, the first stage in producing a
computer
program is to construct a suitable outline solution. We call the outline
solution the “top-level algorithm”. Remember that the kind of al-
gorithm needed to produce a computer program is often exactly the
same as the one we would use to solve the problem by ourselves using a
pencil and paper. It is therefore useful to think how we would go
about solving the problem without a computer. This gives us some-
thing like the following solution:
read the number of metres
convert metres into inches
take the number of inches and find the whole number
and the number of inches left over
of feet
take the number of feet and find the whole number of yards
and the number of feet left over
write down the number of yards, feet and inches
The reason for producing a top-level algorithm is
to allow us to get
the main structure of our solution correct before we become involved
in too much detail. This approach to problem solving is called ““‘top-
down design”.
As this is a
very simple problem, we can go directly from this top-
level algorithm to its implementation in Ada. Let us first think about
the variables which will be required in the
program. We are going to
require variables to hold the numbers of metres, yards, feet and
inches. What is going to be the type of these variables?
It is clear that the number of feet and yards are going to be whole
numbers. For the problem to be general, it would seem desirable
to
allow a real number of metres, but allowing only whole
a number of
inches would seem suitable although a real number
of inches is a
possible alternative.
One thing which we have not yet considered is the
conversion factor
34 Programming in Ada
execute the rest of the program for yourself by obeying the assignment
statementsin the given order. Once you have done this, check that you
have produced the answer given above.
There are few restrictions on the kinds of object that can appearin a
relational expression. The relational operators can be used to com-
pare integers, reals, characters, strings or the values of any enumera-
tion type, but the usual strict type rules apply and objects may be
compared only with other objects of the same type.
The relational operators all have lower precedence than the
arithmetic operators. The effect of the expression
basic_pay + overtime_pay > 200
is therefore to add together the value of basic_pay and overtime_pay
and to compare the result with 200. Similarly, assuming that number
is an integer variable, the expression
number rem 2 = 0
is true if the value of number is exactly divisible by 2, i.e. if it is even,
and is false if it is odd.
When characters are compared the relative order of the character
values is given by their order in the ASCII character set given in
appendix 3. Most programmers will not be able, and have no need, to
remember the relative positions of characters such as :" and "?', but it
is useful to remember that the following relations are all true:
‘9’ < b’ b’ < 'c! C. y’ < A
‘A’ < B’ 'B’ < ‘Cc’
Fe Y' < 7!
0 < 1’ 1’ < ’ ral |’ < 9’
When strings of characters are compared, the value of one string is
greater than another string if the value of its first character is greater
than the first character of the other string. If both strings have the
same first character then the second characters are compared and so
on.
The following expressions all have the value true:
“Jane” < ‘Margaret’
“Julie” > “Julia”
“John” > “James”
The empty string is less than any other string and so the expression
“Anne’’ > “Ann”
is also true.
Being able to compare character strings in this way means that itis
possible to write Ada programs to manipulate pieces of text. We can,
for example, perform operations such as sorting a list of people’s
in a later
names into alphabetic order. We shall see how to do this
chapter.
Expressions and assignment 37
element in .. lower + 17
lower + 3
is evaluated by first evaluating the expressions lower + 3 and lower +
17 and then finding if the value of element is within this range.
The first value in a range is
the lower bound and the second value is
the upper bound. If the lower bound is greater than the upper bound
then we say that we have a null range. There are no values in a null
range.
4.6Logical operators
It is often useful to link together two or more Boolean expres-
sions. This can be done by means of the logical operators and, or and
Xor.
The expression
element rem = 0 and element > 0
2
is true if the value of element is both even and greater than 0;
otherwise it is false. The expression
element rem 2 = 0 or element > 0
is true if either ofthe two relational expressionsis true and is false only
if they are both false. The expression
element rem 2 = 0 xor element > 15
involves the “exclusive or” operator and is true if one of the two
relational expressions is true, but is false if they are both true or both
false.
The operators and, or and xor all have the same precedence, which
is lower than that of the relational operators. This is why no brackets
are required in the above examples. Extra brackets can be used,
however, to help readability as in
(element rem 2 = 0) and element > 0
The truth of a Boolean expression can be reversed by means of the
unary logical operator not. The value of the expression
not (element < 4)
is therefore the same as the value of
element > = 4
The not operator has higher precedence than multiplication or
division.
The following examples should help to show the correct order of
it
evaluation in Boolean expressions, but when in doubt is a good idea
Expressions and assignment 39
~~
and have the values sun and sar respectively.
Attributes can also be used to find the successor and predecessor of
an enumeration value. Examples of these attributes are
day'succ(mon)
card’ pred(queen)
which have the values tues and jack. After execution of the statement
appointment := day'succ(appointment);
the value of appointment will have been changed from wed to thurs.
The first value in a type has no predecessor and the last value has no
successor.
Attributes are defined not only for enumeration types, but exist for
all types in Ada. Some quantities such as the minimum and maximum
possible integer or float values vary from one implementation of Ada
to the next. You can find out what they are on the system you are using
by means of the attributes
integer'first integer'last float'first float'last
If you have to write a program which requires these values it is
always good practice to use the attributes rather than the actual
values. In this way your program will be less dependent on one
particular computer and can more easily be moved to a different
machine. On all machines the first integer and float values will be
large negative numbers and the last values will be large positive
numbers.
The successor and predecessor attributes can be used with integers
and characters. The expressions
:
integer'succ(320)
integer’ pred(322)
both give the value 321. However, they are not defined for real values.
What, after all, is the successor of 3.156? The integer, character,
Boolean and other enumeration types whose values have successors
and predecessors are called discrete types. All the scalar types we have
met except float and real are discrete.
Other attributes will be introduced at appropriate points in later
chapters.
Expressions and assignment 41
Exercises
1. We have the declarations
speed : real := 35.5;
time : real := 3.0;
start, stop : real := 0.2;
distance : real,
What will be the values of these variables after the following
assignment statements have been executed in the specified
order?
distance := speed * time;
time := start + distance | speed + stop;
speed := distance | time;
2. Assuming the declaration
centigrade : integer;
what are the values of the expressions
centigrade * 9 [5 + 32
real(centigrade) * 1.8 + 32.0
when centigrade has the values 12, 15, 24 and 100.
3. Write a complete Ada program which will read three num-
bers, calculate their sum and their average, and print the
results. Trace the execution of the program when the data is
25 173 12.1
4. Evaluate the expressions
5+ 4/3*%2 17-2 +6 17 — (2 + 6)
4/2
168 / (4/2) 168 / (168 / 4) |2
30%¥4/5 30* (4/5)
2+ 3<6andl5 <= 12 “Mary” > “Margaret”
card'succ(card'first) integer'first < 0
six not in ten .. card'last
5. Assume that you have two integer variables first and second
which have been assigned values. Write Boolean expressions
for each of the following situations:
(a) first is greater than second
(b) first is exactly divisible by second
(¢) both first and second are greater than or equal to zero
(d) either first or second is greater than zero but not both
(e) first is greater than or equal to 1 and is less than or equal
to 6
42 Programming in Ada
5.1 Selection
In the program which we developed in chapter 4 the same
statements are executed in exactly the same order each time the
programs run. Ifall programs were like this we would not have a very
powerful or flexible tool. What we need is to be able to select which
statement is to be executed next, with the decision depending on what
has happened so far.
In Ada the statement which allows us to do this is the if statement. It
uses a Boolean expression to select the appropriate path.
To see how this is done let us look again at Program 4.1, where we
converted a number of metres into the equivalent number of yards,
feet and inches. The results were printed out using the statements
put(yards); put(‘‘ yards”);
put(feet, width = > 2); put(‘‘ feet”);
put(inches, width = > 3); put(“‘ inches”);
In the sample run the result
2 yards O feet 11 inches
was printed out, but it might be thought that a better way of present-
ing the results would be to print the number of yards or feet only when
they are non-zero.
This can be achieved using the following statements:
if yards > 0 then
put(yards); pur(‘‘ yards”);
end if;
if feet > 0 then
put(feet, width = > 2); pur(‘““ feet”);
end if;
put(inches, width = > 3); pur(‘‘ inches”);
Here we have two examples of the if statement. Using our sample
data, execution occurs as follows. We first evaluate the Boolean
expression which follows the reserved word if. Because the number of
44 Programming in Ada
yards is 2, the value of yards > 01s true and the statements between the
reserved words then and end if
put( yards); put(‘‘ yards”);
are executed.
We now proceed to the next statement, which is another
ment. The Boolean expression feer > 0 is evaluated and
if state-
because it is
false the two statements between then and end if
put(feet, width = > 2); put(‘‘ feet”);
are skipped. Finally the statements
put(inches, width = > 3); put(‘‘ inches”);
are executed. Thus there is always some output even in the case where
we have zero metres. The net effect is to print
2 yards 11 inches
This has been our first example of a compound statement.
Although the if statement contains other statements, itis considered
to be a single statement.
The solution to a problem often involves the choice between alter-
native courses of action or, indeed, choosing one out of several
different possible courses. Other versions of the if statement allow us
to deal with such cases. If for example a has been declared to be of type
integer, the if statement
if a > 0 then
put(‘“‘greater than zero’);
else
put(“‘not greater than zero”);
end if;
is executed by evaluating the expression a > 0 and then either obeying
the statement after then or the statement after the reserved word else,
depending on whether or not the relational expression is true.
In the statement
if a > 0 then
put(‘“‘greater than zero’);
elsif a < 0 then
put(“‘less than zero”);
else
put(“‘equal to zero”);
end if;
we first evaluate the expression a > 0 and if it is true the statement
following then is obeyed. Only if
it is false do we go on to evaluate the
Selection and repetition 45
of
Boolean expression sequence of statements!
we
(elsif) expression]
[Boolean [sequence statements
statements]
[sequence of
[sequence
In the execution of an if statement, we start by evaluating the Boolean
statements]
expression following if. If it is true the sequence of statements
following then is executed. Only when it is false do we proceed to the
rest of the statement, where there may be zero or more “elsif parts’,
The Boolean expressions following any occurrences of elsif are
evaluated in turn until one is
true. The corresponding sequence of
is
statements then executed. If none of the Boolean expressions are
true then, if there is an optional “‘else part”, the sequence of state-
ments following else is executed.
The following example shows an if statement with more than one
elsif. The variable c# is assumed to have been declared to be of type
character.
if chin 'a’ .. 'Z’ then
put(“‘lower case’);
elsif ch in .. 'Z’ then
‘A’
put(“‘upper case’);
elsif cz in ‘0’ .. '9’ then
pur(“‘digit”);
else
put(‘“‘other character”);
end if;
The membership expressions are evaluated in turn until one is true.
The appropriate message is then printed. If they are all false
other characters
46 Programming in Ada
is printed.
Note that in the layout of the example statements, the reserved
words if,
elsif, else and end are written under one another and the
statements in the enclosed sequences of statements are indented. This
is to make the structure of the statement clear to a human reader.
do something else
end if
Repetition is dealt with in the next section. Indentation is used, as in
programs, to show the structure of the proposed solution.
Each statement in a top-level algorithm can be thought of as the
specification of some subproblem which has still to be solved. When
we write a top-level algorithm we are therefore splitting our initial
problem into several fairly independent subproblems, each of which
should be easier to solve than the original. This is the basic principle
of top-down design. It allows us to concentrate on the structure of the
solution as a whole without having to worry about how each sub-
problem is going to be solved. Detailed consideration of the
subproblems is postponed to a later stage. We have, for example,
shelved consideration of the problem of how to swap two values.
Our aim is therefore to get the overall structure of our solution
correct before we get bogged down in too much detail.
In our simple example it is straightforward to implement the
solution to most of the subproblems directly in Ada, but in general
some of the subproblems may have to be split into a series of yet
smaller problems. We go about producing the solution to a
subproblem in the same way as we produced the solution to the
original problem.
This approach to problem solving has been given several different
names, the most common of which are top-down design, structured
programming and programming by stepwise refinement.
Once we have an outline solution we should test that it works
correctly by manually tracing its execution with sample data. This
should be done with several different sets of data carefully chosen so
that each part of the algorithm is tested by at least one of the sets.
We should also have another look at our strategy to see if we have
overlooked any problems. You will find that it is often easier to spot
and deal with problems before they become hidden by detail. In this
example there is a problem. If a number is divided by zero the answer
is infinity and so our solution must be modified to guard against this
possibility.
Producing a solution is not therefore a simple linear process, but an
iterative one where earlier decisions have to be changed in the light
of fuller information. The outline algorithm now becomes
read two numbers
if the second is greater than the first then
swap first and second
end if
48 Programming in Ada
remainder integer;
:
begin
--find if one number can exactly divide another
put(“‘two positive integers please ”’);
get( first); get(second);
--ensure that first > = second
if second > first then
intermediate := first;
first := second;
second := intermediate;
end if;
if second = 0 then
put(““Cannot divide by zero’);
else
--calculate remainder
remainder := first rem second,
if remainder = 0 then
put( first, width = > 1);
Selection and repetition 49
else
put(“Not exactly divisible”);
end if;
new_line;
end divisible;
5.4 Repetition
Computers are ideally suited to the solution of problems
which involve repeating the same or similar operations a very large
number of times.
Letus consider the following problem. We have alist of one or more
positive numbers followed by a negative number, and we want to read
the positive numbers one by one and find their sum.
This is a straightforward problem to solve without a computer. We
take each number in turn and add it to the sum so far, i.e. we
repeatedly perform the operations
read a number
add the number to the sum
When we come across the negative number we know that we have
come to the end of our list and can stop. This approach forms the basis
of our computer solution.
In Ada a loop statement is used when we wish to execute the same
sequence of statements repeatedly. Each time round the loop we
perform a test to see whether we should continue or leave the loop.
This can be done by an exit statement which, as we might expect, uses
a Boolean expression to make the decision.
Assuming the declarations
sum, number : real
the following Ada statements will solve our problem:
sum := 0.0; --the sum is initially set to zero
loop
get(number);
exit when number < 0.0;
sum :=
sum + number;
--sum contains the total so far
end loop;
The value of sum is set to zero and we then enter the loop. A number
isread and then tested in the exit statement to see if it is less than zero.
If itis we leave the loop and execute the statement following end
loop. Otherwise we continue on to the next statement, where the
52 Programming in Ada
number is added to the current value of the sum. The reserved words
end loop are then encountered and we return to the beginning of the
loop, where the next number is read and tested. We continue round
the loop in this way until the exit statement is executed with a negative
number.
Hence if the data read was
14.6 15.3 75.2 9.7 67.3 —1.2
we would sum the first five numbers and leave the loop after reading
and testing the sixth number.
It is possible to write a program in which the exit condition never
becomes true. This results in what is called an “infinite loop” and is
an error normally caused by faulty logic in the development of the
program.
The general form of a basic loop is
(@xi0)—~(hen)—{Boolean
expressiont-+Q
There are two forms of the exit statement, the conditional and the
unconditional exit. The following example shows how they are re-
lated. The meaning of the statement
exit when number < 0.0;
is exactly the same as
if number < 0.0 then
exit;
end if;
but the former is to be preferred. It is important that programs are
read and understand, and significant events, such as where the
easy to
loop may be left, should be as prominent as possible. Otherwise,
because a loop may contain more than one exit statement, one could be
overlooked.
The following program uses a loop to read in a sentence and
calculate how many upper case letters, lower case letters, blanks and
Selection and repetition 53
ch : character;
begin
--Program to read and analyse the characters in a sentence
put(““Type in a sentence please **);
loop
--get and classify the next character
get(ch);
if chin ‘a’
.. 'Z' then
lower_count := lower_count + 1;
elsif ch in ‘A’ .. 'Z’ then
capital_count := capital_count + 1;
elsif ch = ’'’ then
blank_count := blank_count + 1;
else
punct_count := punct_count + 1;
end if;
--the counts contain the totals so far
exit when ch =
end loop;
';
put(““The number of capitals is >);
put(capital_count, width = > 1); new_line;
put(““The number of lower case letters is *);
put(lower_count, width = > 1); new_line;
put(““The number of blanks is >);
put(blank_count, width = > 1); new_line;
put(*““The number of punctuation characters
put( punct_count, width = > 1); new_line;
is °);
end sentence;
If the sentence typed in as data is
It is a long, long way
to Tipperary.
the following results will be obtained by running the program:
The number of capitals is 2
The number of lower case letters is 25
The number of blanks is 6
The number of punctuation characters is 2
54 Programming in Ada
5.5Loop invariants
Once you have written several programs you will find that
when presented with a new problem we do not have to start from
scratch but can make use of our experience. This is essential if we are
ever to solve large problems. Quite a few problems, although they
might appear to be distinct, have solutions which are essentially the
same. As an example of this we may again consider the list of one or
more positive numbers terminated by a negative number, but this
time we wish to find which number is the largest.
Let us see if we can use our experience to help solve this problem.
When we found the sum of the numbers we used a loop and each time
round the loop we calculated the “sum so far’’. When we analysed the
sentence we used a loop and each time round we updated the “counts
so far”. The solution to our problem might therefore be to use a loop
and each time round calculate the ‘largest number so far”.
The following sequence of statements uses this strategy to solve the
problem. The declarations
number, largest : real;
are assumed.
get(largest);
--largest contains the largest value so far
loop
get(number);
exit when number < 0.0;
--if necessary update largest
if number > largest then
largest : = number;
end if;
--largest contains the largest value so far
end loop;
--largest contains the largest value
As there is no competition, the first number read must be the largest
Selection and repetition 55
so far. Each time round the loop we read the next number, exit if the
number is less than zero and otherwise update, when necessary, the
value of largest.
The comment
--largest contains the largest value so far
is an assertion which is true before the loop starts and remains true
after each iteration. We call this assertion the “loop invariant’. When
designing a loop as part of the solution to a problem it is of great help
to identify the loop invariant so that we know what we have to achieve
each time round the loop.
If the final program contains a statement of the loop invariant as a
comment this can greatly help our understanding of what the program
is trying to achieve.
round the loop and will eventually exceed the limit. Once this has
happened, the next time the Boolean expression is evaluated it will be
false and so the loop will be terminated.
A while loop can always be rewritten in terms of a basic loop and an
exit statement. The effect of the above loop, for example, is exactly the
same as
get(limit);
loop
exit when odd_number > limit;
sum_odd := sum_odd + odd_number;
odd_number := odd_number + 2;
--sum_odd contains the sum of the odd numbers so far
end loop;
if
Note that the value read in for the limit is less than one then the
statements within the loop are not executed at all.
A while loop can contain an exit statement, but this is not advisable.
The reason for there being a special while loop is to help program
readability and reliability. If a loop starts with
while odd_number <= limit loop
we are, in a way, making a promise that we are going to go round the
loop while evaluation of this condition gives the value true. If an exit
statement is included within the loop, this promise will be broken
because there will then be an alternative way of leaving the loop. This
is very likely to mislead someone reading the program or, indeed, to
mislead the person who wrote the program when he or she comes to
read it some time later.
is
Ifitis felt that the use of an exit statement the best way of solving a
particular problem then the fact that the loop can be left in more than
one way should be advertised in a comment at the beginning of the
loop.
initialise counts
read size of class
loop for each person in the class
deal with this student’s exam results
update the count for the relevant student category
end loop
write results
58 Programming in Ada
merit_flag : Boolean;
merit_ passes, ord_passes, fails : integer := 0;
no_of_exams : constant integer := 3;
begin
--gather statistics on students’ performance
put(“Number in class?”’);
get(class_size);
for student in 1
.. class_size loop
--deal with the next student’s exam results
merit_flag = true;
:
Selection and repetition 59
exam_total := 0;
for exam in 1
.. no_of_exams loop
get(exam_result);
if exam_result < = 60 then
merit_flag := false;
end if;
exam_total := exam_total + exam_result;
end loop;
--if merit flag still true all results > 60
if merit_flag then
merit_passes := meril_passes + 1;
elsif exam_total | no_ofexams > 50 then
ord_passes := ord_passes + 1;
else
fails := fails + 1;
end if;
--the counts give information on the students so far
end loop;
put(‘“Number of merit passes = »);
put(merit_passes, width = > 1); new_line;
put(“Number of ordinary passes = °);
put(ord_passes, width = > 1); new_line;
put(“‘number of
fails = *°);
put(fails, width = > 1); new_line;
end statistics;
If there are 25 students in the class we shall go round the outer loop
25 times. Each time round we come across the inner loop
for exam in
.. no_of_exams loop
1
The statements within this inner loop are executed three times before
we continue on to the statements where the relevant count is updated.
The program therefore reads and processes 25 * 3 = 75 exam results.
There is a form of the for loop in which the loop parameter takes the
values in the range in reverse order. For example, the statement
for ch in reverse ‘a’ .. 'z' loop
put(ch);
end loop;
will print the alphabet in reverse order.
The general form of the for loop can be shown by a syntax diagram:
(for )—={loop
parameter}=(in) Ly] discrete range
(ero
60 Programming in Ada
We must use a discrete range in a for loop because only the values of
discrete types have successors and predecessors. A loop parameter
cannot therefore be of type real.
The lower and upper bounds in the discrete range are expressions
which are evaluated once, before the loop is started. If, assuming the
relevant declarations, we have
for student in base + offset .. size * 2 loop
the values of the expressions base + offset and size * 2 are evaluated
before the loop is entered. If any of these variables are changed during
execution of the loop this will have no effect on the number of loop
iterations. If the lower bound is initially greater than the upper
bound, the statements within the loop are not executed at all.
As you would expect, variables of an enumeration type can be
used to control a for loop. Given the declarations
type month is (jan, feb, march, april, may, june, july, aug, sep,
oct, nov, dec);
no_of_visitors : integer;
income : integer := 0;
summer_price : constant integer := 3;
other_price : constant integer := 2;
we can calculate the annual income from the sale of visitors’ tickets at
an ancient monument. The admission charges are higher in the
summer months.
for this_month in jan .. dec loop
get(no_ofvisitors);
if this_month in june .. sep then
income := income + no_of_visitors * summer_price;
else
income = income + no_of_visitors * other_price;
end if;
end loop;
To emphasise that in the loop we are going from the first to the last
possible value in the enumeration type we could have written
for this_month in month'first .. month'last loop
As a further alternative we could have just written the type name, as in
for this_month in month loop
In the same way, if we wanted a loop in which all the characters were
considered in turn, we should write
for ch in character loop
Selection and repetition 61
We can have exit statements within a for loop but, as with their use
in while loops, this is not advisable because it tends to obscure the
basic structure of a program and may therefore make it harder to read
and understand.
no_of_days := 30;
when feb = >
if leap_year then
62 Programming in Ada
no_of_days := 29;
else
no_of_days : 28;
end if;
—
when jan march | |
may |
july aug oct dec = >
| | |
no_of_days := 31;
end case;
Each possible value which can be taken by the expression after case
must correspond to one, and only one, of the choices. In our example
there are 12 values in type month and they are all catered for in the case
statement. To deal with types which have a large range of values the
reserved word others can be used to specify all alternatives which
have not been given explicitly. The others alternative must always be
the last of the alternative sequences.
Although the above example could be re-written with the final
alternative labelled with others, as in
when others = >
no_of_days := 31;
then much clearer
the first version is to be preferred because it is
which months have 31 days.
The syntax of the case statement is best summarised by its syntax
diagram,
(Gao) {expression} —(®)
sequence of statements
expression}—————
L
(others) J
Exercises
1. Trace the execution of the following Ada program
presented with the data
when
it is
. You have $500 in your bank account and each week your
income is $100 and your expenditure is $110. At the end of
each 12-week period, interest of 2.5 9, of your current balance
is added to your account. Write a program to calculate how
long it will be before you run out of money.
. Re-write the program in question 6 so that it will be able to
cope with any value of initial sum, weekly income, weekly
expenditure and rate of interest.
. What would be the effect of executing the following program?
with student_io; use student_io;
procedure mowing is
begin
for verses in 2 .. 10 loop
put(verses, width = > 2);
put(‘“‘ men went to mow, went to mow a meadow,”’);
new_line;
formen in reserve 2 .. verses loop
put(men, width = > 2);
put(‘‘ men”); new_line;
end loop;
put(“‘ 1 man and his dog, went to mow a meadow.”);
new_line; new_line;
end loop;
end mowing;
6
Input and output
The statement
put(number, 2);
has the same effect as
put(number, width = > 2);
and has the advantage of brevity. The longer form is recommended
however, for the meaning of the statement is then much clearer.
The output of real values is more complicated. They are normally
is
printed in what called “floating point form”. Let us look at this with
an example. If we have the declaration
amount : real := 12345.678;
then the statements
put(‘“‘number ="); put(amount);
will print out
number = 1.2345678E + 04
Positive real values are preceded by a space and negative values by a
minus sign. There is then a single non-zero digit before the decimal
point, except in the case where 0.0 is being output. The number of
places after the decimal point is one less than the number of signifi-
cant digits given in the definition of type real. Finally we have the
exponent part, which is always output with an initial plus or minus
sign followed by two digits. The value printed above should be read as
€1.2345678 times 10 to the power 4”.
Just as it was possible to control the spacing of integer values, we
can also control the layout of real values. The number of print
positions before the decimal point is controlled by the fore parameter,
the number of digits printed after the decimal point by the aft
parameter and the number of print positions used in the exponent by
the exp parameter. Hence
put(“number ="); put(amount, fore => 3); new_line;
put(‘“‘number ="); put(amount, aft => 5); new_line;
put(‘“‘number ="); put(amount, fore => 1, aft => 3,
exp = > 2); new_line;
put(“‘number ="); put(amount, 1, 3, 2); new_line;
will print out
number = 1.2345678E + 04
number 1.23457E + 04
number =1.235E +4
number =1.235E +4
Input and output 67
begin
--tabulate properties of sphere as radius changes
put_line(“‘radius circumference volume surface”);
put_line(* of sphere”);
for rin 1... 10 loop
put(r, width = > 6);
rr := real(r);
--write circumference
pur(2.0 * pi * rr, fore => 5, aft => 3, exp => 0);
--write volume
68 Programming in Ada
begin
--tabulate properties of sphere as radius changes
put(“‘radius’);
set_col(10); pur(“‘circumference”);
set_col(25); put(*‘ volume’);
set_col(39); put_line(‘‘ surface’);
set_col(39); put_line(‘‘ of sphere’);
for rin 1... loop
10
put(r, width => 6);
rr := real(r);
--write circumference
set_col(10)
put(2.0 * pirr, fore => 2, aft => 3, exp => 0);
*
--write volume
set_col(25); put(4.0 | 3.0 * pi * rr ** 3, aft => 3);
--write surface area
set_col(39); put(4.0 * pi * rr * rr, aft = > 3);
new_line;
end loop;
end tabulate;
create new ones of our own. All we need to know at present is that they
can be used to hold lots of useful declarations, which can then easily be
made available to Ada programs.
The package student_io is not part of the Ada language, but
likely that any Ada system which is used for teaching will
it
contain
is
a
package like it. You should find out what it is called on the system you
are using and how, if at all, it differs from studenz_io. The package
student_io is based on a more fundamental, but more difficult to use,
package called texz_io which is part of the Ada language. A description
of student_io is given in appendix 2, together with details of the small
additions which you will need to make to your programs if such a
package is not available.
this value to the variable position. The function col does not have a
parameter, but many functions do.
Another useful function is end_of_line. This is a Boolean function,
which gives the value true when we have read the last piece of
information in a line and the value false at all other times.
Information typed in as data for a program and results output from
a program can be considered to be a series of characters organised into
lines. This line structure is modelled in Ada by means of line termin-
ators. They are not characters in the normal sense, but they can be
written and read by special procedures and recognised by the end_of_
line function. The effect of a call of the new_line procedure is to write a
line terminator and then to set the current column number to one. A
line terminator can be read by calling the skip_line procedure. It
causes any remaining information on the current line to be skipped
and then reads the line terminator.
The following sequence of statements gives an example of how
these procedures and functions can be used. We want to be able to
read in five lines of data and write them out again with a line number
followed by a space at the beginning of each line. It is assumed that
char has been declared to be a character variable.
for line_number in 1
.. 5 loop
put(line_number); put’);
--read and write contents of a line
while not end_of_line loop
get(char); put(char);
end loop;
--deal with the line terminator
skip_line; new_line;
end loop;
There is one point which may have been worrying you. The
procedures ger and put are sometimes called with character par-
ameters, sometimes with integer parameters and at other times with
real parameters. The reason why this is possible is that not just one put
procedure or one ger procedure is defined in student_io but several of
each. The Ada system knows which one is
to be used by the type of the
parameter in the procedure call.
Using the same identifier to have more than one meaning is called
“overloading”. When we have an overloaded identifier it must always
be possible for the system to work out which of the possible alternative
meanings is required. As we have seen, with a procedure such as putit
knows by the type of the parameter in the procedure call.
72 Programming in Ada
Until you become experienced in the use of Ada you are advised to
restrict your use of overloading to the use of procedures which have
been defined for your use in some library package.
have to achieve.
is
loop calculate the “result so far”. This the loop invariant which we
PROGRAM 6.3
with student_io; use student_io;
procedure calculator is
number, result : real;
operator, continue : character;
begin
loop
put_line(““Type expression”);
get(result); --result contains the answer so far
while not end_of_line loop
--loop also contains error exits
--read next operator operand pair
get(operator); get(number);
--perform calculation
case operator is
when '+' =>
result := result + number;
when '—' =>
result := result — number;
when’) =>
if number = 0.0 then
put_line(““division by zero’);
exit;
else
result := result | number;
end if;
when '*' =>
result := result * number;
when others = >
put_line(‘“‘error in operator”);
exit;
end case;
--result contains the answer so far
end loop;
skip_line;
put(“result = 7); put(result);
new_line;
put(“If you wish to continue type y’);
get(continue);
exit when continue [= 'y’;
end loop;
end calculator;
If either “division by zero” or “error in operator” error is
a
detected, the program is able, after reporting the error, to continue
Input and output 75
and deal with the next expression. The use of skip_line is important
here as it causes any remaining information in the error line to be
skipped.
If an error is made in typing in one of the numbers in the expression
then a data_error exception will be raised. This will cause the program
to be terminated and so it will not then be possible to continue with the
next expression. We shall see later how errors like this can be handled.
Exercises
1. Write a program which will produce a table of the values #,
n**2 n**3. 1 / nand 1 | n**2 for n in the range 1 to 20. The
results should be in neat columns with each column having
been given a suitable heading.
2. Write a program which will read in ten lines of text and will
determine which line contains the largest number of the
punctuation characters comma, full stop, colon or semi-colon.
3. Extend Program 6.3 so that it can deal with expressions which
may contain spaces.
4. Write a series of statements to output the 95 printable charac-
ters in the ASCII character set in the order shown in appendix
3. No more than eight ASCII characters should be printed on
any one line.
7
Procedures and functions
PROGRAM 7.1
begin
--draw a two-line border surrounded by blank lines
new_line;
put_line(border);
put_line(border);
new_line;
end draw_border;
begin
--print a bordered triangle
draw _border;
put(spaces); put_line(” *>°);
put(spaces); put_line(““ ***’);
put(spaces); put_line(“*¥****>%);
draw_border;
end triangle;
In this progrem we have two procedures, one of which, procedure
triangle, acts as the “main program”. Within the declarative part of
procedure triangle we have the declaration of a “procedure body”
called draw_border. The instructions to draw the border are contained
in the sequence of statements within this procedure body.
To see how the program produces the bordered triangle, let us trace
its execution. After any initialisation of variables in the declarative
part of the main program, execution of an Ada program begins with
the first statement in the main program. In our example this
is a call of
the procedure draw_border and so this procedure is entered and the
statements in it are executed. This causes two lines of asterisks
surrounded by blank lines to be output.
Once this has been done we leave the procedure and return to the
statement following the original procedure call. This and the follow-
ing statements cause a triangle to be printed.
We then come to the statement
draw_border;
This is the second call of procedure draw_border and once again we
enter the procedure, obey the statements in the procedure body, and
78 Programming in Ada
then leave the procedure and return to the main program. As we are
now at the end of the program, execution stops.
Let us examine what we have done. Several statements, whose
combined effect is to draw a border, have been grouped into a
procedure and given a name. Now, if we wish to draw a border, all we
need do is call this procedure. While we were constructing the
procedure we concentrated on how we could draw a border. Once the
procedure has been written and tested so that we are sure it is working
correctly, we do not need to bother about the details of how it works,
just as we do not need to bother about how the predefined procedures
get and put manage to read and write information. What we are now
interested in is how we can use the procedure. We require the
specification of what it does rather than how it does it.
To look at it from another angle, we can consider that, when a
subprogram has been written to solve a subproblem, a new basic
operation has been added to our programming language.
7.3 Parameters
Procedure draw_border in Program 7.1 is not very flexible
because it can only draw a border of a certain size. What makes
procedures really useful and flexible is the use of parameters. Let us
see how procedure draw_border can be extended so that, instead of
always printing a border of 25 asterisks, it could produce a border of
any specified width.
PROGRAM 7.2
begin
--draw a two-line border surrounded by blank lines
--there are width asterisks in each border line
new_line;
for border_line in 1
.. 2 loop
--write width asterisks
for asterisks in 1 .. width loop
put("*');
end loop;
new_line;
end loop;
new_line;
end draw_border;
begin
--print a bordered triangle
draw_border(25);
put(spaces); put_line(* *°);
put(spaces); put_line(* ***7%);
put(spaces); put_line(“*X****>*);
draw_border(25);
end triangle;
80 Programming in Ada
Jk J Fk Kk
Kk kk kk kkk kkk kk kk kk
JF kK kk Kd kk kkk kkk kk kkk kkk
*
*%*
* kk kk
Because it has ten asterisks on each side of the triangle, the width of
the border is “the length of the triangle base + 20°.
Using this information, our next version of the outline algorithm
becomes
get triangle size
calculate length of triangle base
draw border of width “20 + triangle base”
draw triangle on ‘size’ lines
draw border of width “20 + triangle base”
As we already have a procedure which can draw a border of
any
given size, all we need do is solve the subproblem
draw triangle on “‘size” lines
Let us now consider this problem. On the first line of the triangle,
one asterisk is printed and on each subsequent line the number of
asterisks is increased by two. We have still to work out the initial
number of leading spaces, but we note that the number of leading
spaces on each line is one less than the line before. This leads to the
following outline algorithm:
82 Programming in Ada
end loop;
new_line;
space_counter := space_counter — 1;
num_asterisks := num_asterisks + 2;
end loop;
end draw_triangle;
begin
--print a bordered triangle on triangle_size lines
put(““‘How many lines in the triangle?”); get(triangle_size);
triangle_base := 2 * triangle_size — 1;
draw_border(20 + triangle_base);
draw_triangle(triangle_size);
draw_border(20 + triangle_base);
end triangle;
Note how short our main program has become and how closely
matches our outline algorithm. We shall find that it is often the case it
that statements in an outline algorithm are implemented as procedure
calls. Because we have chosen meaningful names for our procedures it
is possible, just by reading the statements in the main program, to gain
a very good idea about what the program does.
Now that we have examined several procedures let us look at the
general structure of a subprogram body. This is best shown by its
syntax diagram
declarative part
procedure
f=
parameter
aad
®
84 Programming in Ada
%% Y% %o %% %o Yo %o Yo %o Yo Yo %o Yo %o Yo Yo %o Vo Yo Vo To Yo Vo
| dentifier
list
type
identifier forma]
If we want our draw_border procedure normally to print asterisks, but
to have the ability to print other characters, it could be declared as
procedure draw_border(width : in integer;
ch : in character := '*') is
end draw_border;
We can call the procedure as
draw_border(25, '%,");
in which case
it will print a border of per cent characters, but, as the
formal parameter ck has been given a default value, a call of the
procedure with only one parameter
draw_border(25);
has the same effect as the call
draw_border(25, '*");
The fore, aft and exp formal parameters
inthe definition of pur all
have default values. The new_line procedure also has
a default par-
ameter to control the number of new lines to be taken. The default
value is one, but if we want more we can have a call such
as
new_line(3);
86 Programming in Ada
7.5 Functions
Function subprograms differ from procedures in that they
return a value. Hence a procedure call is a statement while a function
call is part of an expression. In chapter 6 we saw how to use predefined
functions such as col and end_of_line. Let us now look at how a
function body can be declared.
As an example, let us create a function called next_day which, given
a value of the enumeration type
type day is (sun, mon, tues, wed, thurs, fri, sat);
will calculate the following day. The declaration is
function next_day(this_day in day) return day is
:
begin
if this_day = day'last then
return day'first;
else
return day'succ(this_day);
end if;
end next_day;
Because functions return a value, the type of this value must be
given in the subprogram specification. The type is given after the
reserved word return and in this case is type day.
Execution of a function is terminated by executing a statement of
the form
(Ferarn)—[expressiont—=Q
been given the value thurs, the effect of executing the statement
tomorrow := next_day(today);
will be to give tomorrow the value fri.
Another function is shown in the following program, which reads
in a number of yards, feet and inches and prints. the corresponding
number of metres. The actual conversion is performed by a function
which accepts three integer parameters and returns a real value.
PROGRAM 7.4
with student_io, use student io;
procedure convert is
yards, feet, inches : integer;
metres real;:
type identifier
begin
--read and order two numbers
get(smaller); ger(larger);
if smaller > larger then
swap (smaller, larger);
end if;
put(“The ordered numbers are: Ys
put(smaller); put(larger);
new_line;
end order;
In procedure swap we have two real formal parameters of mode in
with two
out and one local variable. Execution of the program begins
Procedures and functions 89
numbers being read and assigned to smaller and larger. If the value of
smaller is greater than the value of larger, we enter procedure
swap.
The types of the corresponding actual and formal parameters must
match. During execution of the procedure, in out formal parameters
act as local variables which have been initialised to the value of the
corresponding actual parameter. Hence firsz is initially given the
value of the actual parameter smaller and second is given the value of
the actual parameter larger.
The statements in the procedure are then executed causing the
values of first and second to be swapped. We now leave the
procedure
and, because firsz and second are of mode in out, the current value of
first is assigned to the actual parameter smaller and the current value
of second is assigned to the actual parameter larger.
The effect of executing procedure swap is therefore to swap the
values of the variables smaller and larger.
With in out (and out) parameters the actual parameters must be
variables as they are updated by the procedure call. With in
param-
eters information is only passed into the procedure and so the actual
parameters can be expressions. If we wish a procedure to change the
value of an actual parameter then the formal parameters must either
be of mode in out or out.
Parameters of mode out are undefined when execution of the
procedure starts. They must be given a value during execution of the
procedure and, on leaving the procedure, this value is assigned to the
actual parameter. Even after they have been given a value, formal
parameters of mode out may not be used in an expression. Their only
appearance within a procedure is when they are being given a value.
We therefore use in mode parameters to pass information into
a
procedure, out mode parameters to receive information from a
cedure and in out mode parameters to pass in information whichpro-
can
be changed before being passed back out again. When the mode
of a
parameter is not specified it is taken to be in.
The following procedure uses an out mode parameter.
Fairly
frequently there are restrictions on the range of acceptable numbers
which may be read into a program. The following
procedure could be
used when only positive numbers were acceptable. If
a negative
number is read, an error message is printed and the
user is then
prompted to make a second attempt.
procedure read_pos(number : out real ) is
any_value : real;
begin
--read numbers until a positive number is obtained
90 Programming in Ada
loop
put(‘“‘positive number please:”);
get(any_value);
if any_value < 0.0 then
put_line(“‘error: number must be positive”);
else
number := any_value;
return;
end if;
end loop;
end read_pos;
This procedure shows another use of the return statement. A
procedure may be left either by executing a return statement or by
coming to the end of the sequence of statements in the procedure
body. As procedures do not return a value, there is no expression
following the reserved word return.
Functions must always be left by the execution of a return state-
ment.
write ‘“‘thousand”
end if
write the part under a thousand
This has reduced the problem to that of writing a number
range 1 to 999. A first attempt at this might be
in the
PROGRAM 7.6
with student_io; use student_io;
procedure write_number is
number : integer;
Once this part of the program has been thoroughly tested we can
=
Procedures and functions 93
PROGRAM 7.7
begin
--write number in the range 1 to 9
case digit is
when 1 = > pur(““one”);
when 2 > put(“‘two”);
when 3 = > pur(‘“‘three”);
when 4 II > pur(“four”);
when 5 = > pur(““five”);
when 6 = > pur(“‘six’);
when 7 = > pur(“‘seven”);
when 8 = > pur(‘“‘eight”);
when 9 = > put(“‘nine”);
when others = > null;
end case;
end write_digit
procedure write_tens(tens in : integer) is
begin
--deal with twenty to ninety
case tens is
when 2 => pur(‘“‘twenty”);
when 3 => pur(“‘thirty”);
when 4 = > pur(“forty’);
when 5 = > pur(“fifty”);
when 8 = > pur(“eighty”);
when others = > write_digit(tens);
pur(“ty”);
end case;
end write_tens;
94 Programming in Ada
begin
--write numbers in range 1 to 999
if num > 99 then
--write number of hundreds
write_digit(num | 100);
put(“ hundred’);
if under_100 [= 0 then
--and required if anything after hundred
put(* and”);
end if;
end if;
if under_100 in 10 .. 19 then
--deal with 10 to 19
case under_100 is
when 10 => pur(‘“‘ten”);
when 11 = > pur(“‘eleven”);
when 12 => put(‘“‘twelve’);
when 13 => pur(‘“‘thirteen”);
when 15 => put(‘“fifteen”);
when 18 => pur(‘“‘eighteen’);
when others = > write_digit(under_100 rem 10);
put(“‘teen’);
end case;
else
if under_100 > = 20 then
--write tens part
write_tens(under_100 | 10);
end if;
--write digits part
write_digit(under_100 rem 10);
end if;
end write_1_to_999;
begin
—-writenumber in the range —999999 to 999999
put(“number please”); get(number);
if number < 0 then
--deal with negative number
put(“minus ’);
number := — number;
end if;
if number > 999_999 then
put(“number too large”);
elsif number = 0 then
--zero is a special case
put(“‘zero”);
Procedures and functions 95
else
if number > 999 then
--deal with number of thousands
write_1_to_999 (number | 1000);
put(‘‘ thousand ”);
number := number rem 1000;
ifnumber in 1 .. 99 then
put(‘‘and **);
end if;
end if;
--deal with part under 1000
write_1_to_999 (number);
end if;
new_line;
end write_number;
Note that in this program the two procedures write_digit and write_
tens are declared locally withinprocedure write_1_ro_999. These two
procedures could have been declared in the declarative part of the
main program, but declaring them in the declarative part of wrire_1_
t0_999 means that the procedure write_1_to_999 remains self-
contained.
7.8 Recursion
Apart from the fact that a subprogram must be declared
before it can be called, there are no restrictions on the position of
subprogram calls. A subprogram can even call itself. This is known as
a recursive call and is useful when the solution to a problem can be
expressed in terms of a simpler version of the same problem.
The usual example of this is the calculation of factorials. The
definition of, for example, four factorial (written as 4!) is
4 x3 x2x1
while in general we have
nl=nxm-—1) x... x 2x1
Examination of the definition shows that 4! can be expressed as 4 x 31,
i.e. we can define 4! in terms of 3!. Similarly 3! can be defined in
terms of 2! and 2! in terms of 1!, whose value we already know, for
just 1. In general we can define 7! in terms of (n — DL.
it is
7.9 Summary
Now that we have seen how procedures can be used, let us
summarise the benefits. Our approach to the solution to large prob-
lems has been to divide them into a series of fairly independent
smaller problems. When procedures are used in the implementation
of the solution to these subproblems we produce, not one large
monolithic program, but a collection of related subprograms.
As the subprograms are relatively independent they can be written
and tested separately. Large programs can then be built up from
smaller ones which have already been written and tested.
Also, the solutions to many seemingly unrelated problems often
have common features. Subprograms written and tested as part of the
solution to one problem may be of direct use in the implementation of
solutions to other problems. This leads us to the notion of having
libraries of useful subprograms. This is an important feature of Ada,
which has facilities to support the creation and use of program
libraries. We shall return to this topic later.
Finally, procedures allow us to define new “higher-level” oper-
ations which can help us think about the solution to problems by
removing unwanted detail.
Exercises
1. Consider the following program:
with student_io; use student_io;
procedure factors is
large, small : integer;
function hcf(low, high : integer) return integer is
remainder : integer;
bigger : integer := high;
smaller : integer := low;
begin
--find the highest common factor
loop
remainder := bigger rem smaller;
exit when remainder = 0;
bigger := smaller;
smaller := remainder;
end loop;
return smaller;
end /icf;
98 Programming in Ada
begin
put_line(“‘input two positive integers, the smaller first”);
get(small); get(large);
if small < large and small > 0 then
put(‘““Their highest common factor is *’);
put(hcf(small, large), width = > 1); new_Uline;
else
put_line(‘““The data has the wrong format”);
end if;
end factors;
Identify each of the following in the program: a formal par-
ameter, an actual parameter, a function call, a procedure call
and a local declaration.
Rewrite the function call using the named form of passing
parameters.
Trace the execution of the program when the input data is
21 35
Add to the function comments to describe the loop invariant.
2. Consider the following function:
function increase(number : integer; by : integer := 1)
return integer is
begin
return number + by;
end increase;
What would be the effect of executing each of the following
function calls?
val : = increase(3);
val : = wncrease(3, 1);
val := increase(3, 2);
val := increase(3, by => 4);
val = increase(by = > 4, number = > 3);
1
the month starts, will write out a calendar for the month in the
form
SuM TuW ThF Sa
2 1
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Write a program which, when given the number of days in a
year and the day of the week of the first of January, will
produce a 12-month calendar.
. What would be the effect of executing the statement
put(hcf(21,35));
where Acf has been declared as
function Acf(low, high : integer) return integer is
remainder : integer := high rem low;
begin
if remainder = 0 then
return low;
else
return Acf(remainder, low);
end if;
end /icf;
Compare the execution of this function with the execution of
the function declared in question 1.
8
Declarations re-visited
fps
8.1 Subtype declarations
It is common in programming to have variables whose values
will, if the program works as expected, never go outside a certain
a
range. If we had, for example, variable today which was declared to
be of type day
type day is (sun, mon, tues, wed, thurs, fri, sat);
but which should only take values in the range mon to fri then, instead
it
of declaring to be of type day, it would be useful to be able to declare
it as a subrange or subtype of day and have its
values restricted to the
required range.
This can be done by replacing the declaration
today : day;
by the declaration
today : day range mon .. fri;
Alternatively, we can give a name to the subtype by means of a
subtype declaration
subtype weekday is day range mon .. fri;
and then declare the variable today to be of the subtype weekday
today : weekday;
A subtype
the
is
therefore created from an existing type or subtype by
set of allowed values.
restricting
The general form of a subtype declaration is
(is)—[subtype indicationt—()
el
where a subtype indication is defined by
— nand—
subtype name
Declarations re-visited 101
subtype positive is 1
.. integer'last;
Some of the programs in previous chapters can be modified to use
variables of subtype positive or natural instead of type integer. You are
recommended to go over these programs and decide where this
change can be made and where it cannot.
As an example of the use of subtypes, let us consider the following
function, which accepts two primary colours and returns the result of
mixing them together. We shall assume that the declarations
type colour is (red, yellow, blue, purple, green, orange);
subtype primary is colour range red .. blue;
have already been made.
function mix(prim_1, prim_2 : in primary) return colour is
begin
--mix two primary colours
if prim_1 = prim_2 then
return prim_1;
end if;
case prim_1 is
when red = >
if prim_2 = blue then
return purple;
else
return orange;
end if;
when blue = >
if prim_2 = red then
return purple;
else
return green;
end if;
when yellow =>
if prim_2 = red then
return orange;
else
return green;
end if;
end case;
end mix;
There is no return statement at the end of the function because
every possible path through the function has already been terminated
by a return statement.
Declarations re-visited 103
This operation can be carried out in the reverse direction using the
val attribute. Hence
day'val(3) = wed
day'val(4) = thurs
An attempt to evaluate day'val(7) would raise a constraint_error
exception.
The pos and val attributes have the same effect with a subtype as
they do with its base type. Hence, for example, weekday'val(6) is legal
and gives the value sat.
You should not make extensive use of the pos and val attributes, but
they can be useful on occasion. Consider the following statement, in
which it has been assumed that ck and num have been declared to be
character and integer variables respectively:
if chin ‘0’ .. '9’ then
num := character’ pos(ch) — character’ pos('0");
end if;
The pos attribute of a character value is its position in the ASCII
character set. Because the digit characters are in consecutive positions
in ASCII, if ch is equal to one of the digit characters ‘0’ to ‘9’ then
execution of the above statement puts its integer equivalent in num.
Another useful attribute is image. The value of day'image(sun) is
the string “SUN”. Hence the image attribute converts an enumer-
ation value into a sequence of upper case characters. This allows an
enumeration value to be written out as a string. The value of
integer'image(34) is the string “ 34”. The string image of an integer
contains either a leading space or a minus sign.
Conversely the attribute day'value(‘“‘ SUN’) has the value sun. This
allows us to convert a string into a value of a specified type. The value
of integer'value(‘‘34”) is the integer 34.
Associated with image is the width attribute. It gives the maximum
image width for a type or subtype. Hence day'width has the value as
5
the longest string image is that corresponding to the enumeration
value thurs.
Let us now see how we can use the image attribute to give a very
different solution to our earlier problem of printing the value of a
number in English. This time we shall restrict ourselves to numbers
in the range —99 to 99, but the solution can easily be extended.
PROGRAM 8.1
type zero_to_19 is (zero, one, two, three, four, five, six, seven,
eight, nine, ten, eleven, twelve, thirteen,
fourteen, fifteen, sixteen, seventeen, eighteen,
nineteen);
subtype zero_to_nine is zero_to_19 range zero .. nine;
low_val zero_to_19;
:
digit : zero_to_nine;
type tens is (place_0, place_l, twenty, thirty, forty, fifty, sixty,
seventy, eighty, ninety);
tens_part : tens;
number : integer;
begin
--write numbers in the range —99 to 99
put(“number please”); get(number);
if number < 0 then
--deal with negative number
put(“minus ’);
number := — number:
end if;
if number > 99 then
put(“number too large”);
elsif number < = 19 then
--convert number to enumeration value and write its image
low_val := zero_to_19'val(number);
put(zero_to_19'tmage(low_val));
else
--deal with numbers from 20 to 99
--find tens part and write its image
tens_part := tens'val(number | 10);
put(tens'image(tens_part));
--find digit part and write its image if nonzero
digit := zero_to_nine'val(number rem 10);
if digit |= zero then
put(zero_to_nine'image(digit));
end if;
end if;
new_line;
end write_number;
Let us trace the execution of this program so that we can see what
happening. We shall assume that 37 has been read in as data and has is
been assigned to number. Checks are made to find if it is less
than 0,
greater than 99 or less than or equal to 19. All these tests fail and so we
enter the else part, where the statement
tens_part := tens'val(number | 10);
106 Programming in Ada
THIRTYSEVEN
as the final result of the program.
Exercises
. Consider the following declarations:
type card is (two, three, four, five, six, seven, eight, nine, ten,
jack, queen, king, ace);
subtype face_card is card range jack .. king;
value : card;
high_value : face_card,
What values are assigned to the variables value and high_value
in the following statements and which statements, if
any,
would cause constraint_error exceptions to be raised?
value := card'succ(three);
value := face_card pred(five);
high_value := face_card'succ(face_card first);
high_value := card'pred(card'last);
value := face_card first;
value := face_card'val(0);
. Declare each of the following subtypes:
(a) A floating point subtype in which the values are held to an
accuracy of six decimal digits and are in the range — 1.0 to
+1.0
(b) A card subtype in which the values are lower than eight
(c) An integer subtype in which all the values are less than zero
. If we have the declaration
subtype is character range ‘a’ 7’;
lower_case
..
what would be the effect of executing the following
statements?
for ch in a’ .. 'z’ loop
put(ch);
end loop;
new_line;
forch in lower_case'first
.. lower_case'last loop
put(ch);
end loop;
new_line;
for ch in reverse lower_case loop
put(ch);
end loop;
. Using the information given in appendix 3, declare a subtype
called printable_character whose values are the
printable
ASCII characters.
Arrays
9.1 Introduction
Let us consider the following problem. We have a group of
100 people, each of whose ages is known. We want to know their
average age and how many of them are older than this.
We can find the average age easily enough by reading the 100 ages,
adding them together and dividing the result by 100. To find the
number of people of above average age requires us to look once again
at the 100 individual ages. For the ages to be available for re-
examination means that we have to store them somewhere.
This is a common problem in programming. We want to be able to
store a large amount of information and be able to deal with it in a
systematic manner. In such cases we use arrays.
An array is a composite object and consists of components each of
which must have the same type or subtype. To help solve our
problem, we could define the following array type
type ages is array (1 .. 100) of natural;
and then declare a variable of this type.
how_old :
ages;
This declares a composite object called how_old, which consists of 100
components with each component being of subtype natural.
Individual components of the array are referred to by what is called
an “index” or “‘subscript’’, which is written in brackets after the array
variable name. The ages of the first three people in our list are
therefore referred to as
how_old(1) how_old(2) how_old(3)
As you might expect, we must not use an index whose value is
outside |
17;
how_old(T) := how_old(1) + 5;
get(how_old(4));
put(how_old(4));
The value of the index determines which component is
being used.
After execution of these statements the first component of our array
will have the value 17 and the seventh component the value 22.
The big advantage of using arrays is that the index can be an
expression. This means that if we wish to perform the same calcu-
lations on each of the 100 ages, we can use a loop with the array index
changing each time round the loop.
To show how this can be done, a solution to our ages problem is
given in the following program. The average age is calculated to be a
whole number, as that seems reasonable for this particular problem.
PROGRAM 9.1
In the first loop the 100 ages are read into the array and their sum is
calculated. In the second loop the 100 ages stored in the array are
compared in turn with the average and the number above average are
counted. A constant identifier is used in the program instead of the
integer literal 100 so that the program can easily be modified to deal
with a group of a different size.
is
In the declaration of an array type there no restriction on the type
or subtype of the components. We can have arrays of real values,
integers or of any enumeration type although, of course, the normal
strict type-checking rules apply when they are used. The index is
restricted to any discrete type or subtype.
The following examples give some more information about array
a
declarations. If we wanted to hold the contents of line of text and we
knew that no line contained more than 80 characters, a suitable array
type declaration would be
type line .. 80) of character;
is array (1
.
type daily_number is array (day range sun sat) of natural,
Instead of declaring an array type and then declaring a variable of
it
that type, is possible to amalgamate the two declarations. We could,
for example, have had
ticket_number : array (sun .. sat) of natural;
In this case we have an anonymous array type. Such arrays cannot be
used as procedure parameters because they have no name.
end if;
end loop;
end loop;
We have seen how the numbers of tickets sold on each of seven days
can be held in an array. The following array could be used if
infor-
mation about a four-week period was to be stored:
type month is array (1 .. 4, day) of natural;
tickets : month;
The first index is of type integer and the second is of type day. The
following loop could be used to read 28 values into this array.
for week in 1 .. 4 loop
for days in day loop
get(tickets(week, days));
end loop;
end loop;
Let us now consider the problem of reading the daily number of
tickets sold in a four-week period and finding which of the seven days
in the week is, on average, the most popular. That day should be
printed out along with the numbers of tickets sold on it.
Our outline algorithm might be along the following lines:
Read and store the number of tickets sold for each of the 28
days
Calculate and store the total number of tickets sold on each
day of the week
Find the most popular day in the week
Write result
Write the four values making up the most popular choice
Before we develop the algorithm any further we must consider how
when we
we are going to store the required information. In general,
solve problems on a computer we need not only to design algorithms,
but also to decide on the appropriate “data structures’.
In this case, as well as holding all 28 daily ticket numbers we may
Arrays 113
wish to store the seven sub-totals so that we can easily find which day
is on average the most popular. This suggests that two arrays are
required, a two-dimensional array to hold the 28 ticket numbers and a
one-dimensional array to hold the seven sub-totals.
Once we have decided on the form of our data structures imple-
menting the solution is relatively straightforward. The sub-totals can
be calculated while the ticket numbers are being read in.
PROGRAM 9.2
day_total : sub_total;
best_day : day := sunday;
best_total : natural := 0;
begin
--Read the number of tickets sold in a 28-day period
--and calculate which day in the week is, on average,
--the most popular
for days in day loop
day_total(days) := 0;
end loop;
--Read data and calculate the sub-totals
for week in week_range loop
for days in day loop
get(tickets(week, days));
day_total(days) := day_total(days) + tickets(week, days);
end loop;
end loop;
--Find the most popular day in the week
for days in day loop
if day_total(days) > best_total then
best_day := days;
best_total := day_total(days);
end if;
end loop;
pur(“On average the most popular day is Ys
put_line(day'image(best_day));
--Write values making up the most popular day
114 Programming in Ada
—
new_line;
end popular;
Let us now trace the execution of the program. In the first loop the
seven components of day_total are initialised to zero. The nested for
loop is then entered. Each time round the inner loop, a number is read
in, stored in the two-dimensional array and then added to the appro-
priate sub-total for that day of the week.
We then go on to find which day is on average the most popular by
type
the usual technique of having a loop and each time round finding the
“most popular so far”. In the final loop the four components making
up the best day are written out. Possible output from the program
might be
—[array
On average the most popular day is SATURDAY
The four components are
1342 1287 1176 984
The array type definitions which we have been using have the form
index constraint—(of )—[subtype indication
il2
[range}— O—
—O7 7]
If we assume the subtype declaration
subtype letter is character range ‘a’
.. "z's
larger problem is sorting a list of objects into order. There are many
different sort algorithms. If we have a short list of objects a method
such as simple selection shown below is adequate, but for longer lists a
more efficient method should be used. The principle behind simple
selection sort is that it uses a loop which has the loop invariant that
after the kth cycle the first 2 components in the list are in their final
position. The value of is
in the range zero up
to
the length of the list.
Procedure selection_sort is passed a one-dimensional array of type
list and it sorts it into non-decreasing order before passing it back
to the calling routine. The mode of the list parameter must therefore
be in out.
In the procedure we assume that a declaration such as
type list is array (1 .. 15) of integer;
has already been made and that the components of the array have been
given values before the procedure is entered.
procedure selection_sort(item : in out list) is
small_pos : natural range list'range;
smallest : integer;
begin
--sort the components of the item list
for low in list'first .. list'last — 1 loop
--components in positions list'first to low — 1 are
--now in their final positions
--find the smallest remaining component
small_pos : = low; smallest : = item(low);
for pos in low + 1 .. list'last loop
if smallest > item( pos) then
small_pos := pos; smallest := item( pos);
end if;
end loop;
--swap smallest with the item in position low
item(small_pos) : = item(low);
item(low) : = smallest;
--components in position list'first to low are
--now in their final positions
end loop;
--components in positions list'first to list’last — 1
--are now in their final positions
--the last component must therefore be in
its final position
--all components are in their final sorted positions
end selection_sort;
118 Programming in Ada
Selection sort works by going through the list and selecting the
smallest element. This is swapped with the element at
the beginning
position and the
of the list. The smallest element is now in its final
remainder of the list is searched for the next smallest element which is
swapped with the element in
the second position in the list. The two
smallest components are now in their final position. This process of
cycling round the outer loop in the procedure and each time moving
one more component into its final sorted position is continued until all
the components have been moved to their correct positions.
Note that in the procedure array attributes are used to control the
number of times the loops are executed. The procedure would
therefore not need to be modified if the index bounds of type list were
changed. Also note that, although the example deals with a list of
the
if
integers, we wished instead to deal with a list of any other type only
declarations would have to be changed. The actual instructions do
not depend on the type of the array component.
9.5 Array aggregates
Scalar objects can be initialised when they are declared and so
it is reasonable to ask if this is possible with array objects. Because an
array is a composite type, the value of an array object consists of the
values of all its components. This can be represented by what is
known as an array aggregate.
The rules governing array aggregates are complicated and we shall
not consider all possible situations. The simplest case is known as
positional association, in which each component value is listed sep-
arately. In the example
type list is array (1 .. 7) of natural;
different : list := (5, 10, 15, 20, 25, 30, 35);
we have a list with seven components. The aggregate in the
initialisation of list different consists of seven expressions separated
by commas and surrounded by brackets. The value of the first
expression in the aggregate is assigned to the first component of
different, the value of the second expression to the second component
and so on. Hence different(1) becomes equal to
5, different(2) to 10,
etc.
If all components of
the array are
to be given the same value we use
what is known as named association. Within the brackets of the
fol-
aggregate the complete range of possible index values given,
is
lowed first by “= >’ and then by the value which is to be assigned to
the components. An example of this is
same list := (1..7 => 1);
Arrays 119
fives : constant list := (5, 10, 15, 20, 25, 30, 35);
A constant array must be given a value
when it is declared.
120 Programming in Ada
Exercises
Trace the execution of the following program using a data
value of your choice:
with student_io; use student_io;
procedure search is
type vals is array(0 .. 8) of integer;
value_list vals := (0, 7, 17, 5, 29, 13,
= 19, 3, 11);
position natural := 8;
:
number : integer;
begin
get(number);
value_list(0) : = number;
while value_list( position) |= number loop
position := position — 1;
end loop;
if position > 0 then
put(“‘position is”); put( position);
new_line;
else
put_line(“‘number not in list”);
end if;
end search;
. We have an array declared as
type list is array (1 .. 50) of positive;
A : list;
Write a sequence of statements which will read 50 positive
integers into this array and then find the position in the array
of the smallest.
_ Write a function which, when passed an array of type list, will
return the position in the array of the smallest component.
. Write a program to read in ten numbers and then write them
out in reverse order.
the
. (a) Declare an enumeration type month to represent
twelve months of the year.
(b) Declare a 12-component array constant
which will hold
the number of days in each month of the current year.
will return
(c) Design a function which, when given a date,
which day in the year itis. For example, given February 5
10.1 Introduction
Let us again consider the "procedure whereby a list of 15
numbers were sorted into non-decreasing order. A drawback of this
procedureis that it only works for an array with 15 components. Does
this mean that we need to write a separate procedure for each different
size of list?
The answer to thatdefined
question is no. The arrays we have looked at up
what is called a “constrained array
till now have been by
definition’’. Ada also allows arrays to be defined by an ‘“‘uncon-
strained array definition”, where the bounds are not specified when
the array type is declared. It is then possible for different arrays to
have the same base type although they have different index bounds.
The type of the index values, the type of the components and the
it
number of dimensions are the same for the different arrays; is only
the size of the array which may differ.
As an example of this let us look at how we can go about producing a
procedure which will be capable of sorting any one-dimensional array
which has integer components and index values which are natural
numbers. We must first declare an array type which has an
unconstrained array definition:
type any_list is array (natural range < >) of integer;
Here we have declared an array type called any_list which has one
dimension, has components of type integer and whose index values are
of subtype natural. The index range is undefined and is represented
by “<>, which we refer to as a box.
We can now declare array objects of this array type, but in the
declaration of an array object an index constraint must be given to
specify the index bounds, as in
numbers : any_list(1 .. 15);
more_numbers : any_list(0 .. 20);
Unconstrained arrays 123
Here two array objects have been declared. They are both of type
any_
list, but numbers has 15 components while more_numbers has 21
components. We have one array type, but two different array
subtypes.
We now want to be able to declare a procedure which,
assuming
that values have been assigned to both the arrays numbers and more_
numbers, will allow both the calls
selection_sort(numbers);
selection_sort(more_numbers);
We have, in fact, to make very few changes to
our existing selection
sort procedure because it does not explicitly mention the bounds of
the array being sorted, but instead uses array attributes. There
however, one major change. Our existing procedure uses the is,
attributes of the array type. Although this is allowed with a con-
strained array type, we cannot refer to the attributes of
an
unconstrained array type because attributes such as first, last, etc. are
not fixed. What we can do though is refer to the attributes of the
array
object because when the procedure is called they are fixed by the
subtype of the actual array parameter. Qur procedure therefore
becomes
procedure selection_sort(item : in out any_list) is
small_pos : natural range item'range;
smallest : integer;
begin
--sort the components of item
for low in item'first
.. item'last — 1
loop
--components in positions item'first to low — 1
are
--now in their final positions
--find the smallest remaining component
small_pos : = low; smallest : = item(low);
for pos in low + 1 .. item'last loop
if smallest > item( pos) then
small_pos := pos; smallest : = item( pos);
end if;
end loop;
--swap smallest with the item in position low
item(small_pos) : = item(low);
item(low) = smallest;
:
end loop;
--components in positions item'first to item’last—1
--are now in their final positions
--the last component must therefore be in its final position
—
--all components are in their final sorted positions
end selection_sort;
When we have the procedure call
EE
selection_sort(numbers);
the attributes item'range, item'first and item’last are the same as
numbers'range, numbers'first and numbers'last. The procedure will
therefore sort a list of 15 components. When the procedure call is
selection_sort(more_numbers);
the attributes are more_numbers' range, more_numbers'first and more_
numbers'last, and so a list of 21 components is sorted.
Now that we have seen how they can be used, let us look at the
"syntax diagram for an unconstrained array definition. It is
index subtype subtype
© definition Q (of) indication [>
0
where an index subtype definition is defined by
—
TREE
It follows from this that if we have a two-dimensional array then
both index ranges must be given, in which case we have a constrained
array definition, or both must be left unspecified.
held
Many numerical analysis algorithms require information to be
in a two-dimensional array called a matrix. A declaration such as
type matrix is array (positive range <>,
positive range < >) of real;
the
allows us to declare matrices which are of different sizes but have
matrix
same base type. This means that it is possible to write general
be
handling subprograms. Example of matrix objects would
table marrix(1 .. 6, 1 .. 4);
:
10.2 Strings
We have been using the built-in type string in most of our
programs. As we have seen, string literals are written as a series of zero
or more characters enclosed by quotation characters, as in
“This is a string literal”
A string is therefore a composite type whose components are
characters. It is, in fact, a one-dimensional unconstrained array type
which has been declared behind the scenes as
type string is array (positive range < >) of character;
This means that string variables and constants can be declared in the
same way as any other array objects. Hence
line : string(1 .. 80);
declares a one-dimensional array object of type string which has 80
components of type character.
We have already come across the declaration of string constants
such as
greeting : constant string : = “Hello”;
Note that an index constraint has not been given in this declaration;
instead the bounds are deduced from the string literal. This is allowed
only with array constants, not with array variables. A fuller definition
would have been
greeting : constant string(l .. 5) := “Hello;
Ordinary array aggregates can also be used in giving a string a value,
as in
greeting : constant string := ('H', '¢/, 1,1, '0");
Although in this case using the string literal is superior, aggregates are
useful when all the components are to be given the same value. The
declaration of border in Program 3.4 could, for example, be replaced
by
border : constant string := (1 ..25 => "*");
A string literal can be assigned to a string variable in an assignment
of charac-
statement, but the literal must contain the correct number
ters. Hence if we have the declaration
name : string(1 .. 10);
in
the name can only be assigned a ten character string literal, as
name = ‘James »5
begin
--copy old string to new string
if new_st'length > old'length then
--old’length characters copied from old string to new
--and new string padded with trailing blanks
new_st := (new_st'range => "");
new_st(new_st'first .. new_st'first + old'length — 1)
:= old,
else
--new_st'length characters copied from old string to new
new_st := old(old first .. old'first + new_st'length — 1);
end if;
end copy;
Strings may be compared using the relational operators C=
“l= cn cs => «<2 and ““< =" even when they are of differ-
ing length, as we saw in chapter 4, although of course for two strings to
be equal they must have the same length.
is
The type of an array component not restricted to the scalar types;
we can also have arrays of composite types such as arrays. A common
is
use of this arrays of strings. As an example consider a VDU screen
hold 24 lines with each line containing up to 80 characters.
which can
This information could be held in an array called screen, declared as
subtype line is string(1 .. 80);
screen : array (1 .. 24) of line;
The value of screen(1) is a string of 80 characters and as this is of course
itself an array we refer to a component such as the third character in
the first line as screen(1)(3).
10.3 String processing
In this book we are learning to design and write programs for
which have
ourselves, but many computer users only use programs
Unconstrained arrays 129
,
put_line(“‘Sorry,
&that is wrong.”’);
put_line(‘“Would you like to try again?”’);
end if;
elsif answer(1 .. answer_length) = “no” then
exit;
else
put_line(‘‘Please type either yes or no.”);
end if;
end loop;
put_line(“Bye, & name(1 .. name_length));
>”
end questions;
This program has made extensive use of slices and strings, but you
should have no trouble in following it. A possible dialogue might be
Hello, what is your name?
John
Hello, John
Would you like to answer a question?
OK
Please type either yes or no.
yes
What is the capital of the United States?
New York
Sorry, John, that is wrong.
Would you like to try again?
yes
What is the capital of the United States?
Washington
Well done, John, that is correct.
Bye, John
read lines of text and find out on which lines, if any, the following
words appear:
begin else end for if loop then while
The words may appear in either upper or lower case and we may
assume that no line contains more than 80 characters. The text is to be
terminated by a full stop typed alone at the beginning of a line.
A possible outline algorithm is
loop
read and store the next line of text
leave loop if the terminating line has been read
loop while still more characters in the line
take next word
if the word is in the list then
store details
end if
end loop
write information about the words on the current line
end loop
Our next step must be to think about the data structures which we
are to use. A string of 80 characters would seem to be the obvious
method of holding the line under consideration. The eight special
words whose occurrences are to be noted must be held somewhere so
that each word in the text can be compared with them. A possible
approach would be to hold the special words
each word in the text against this list.
in a list and then check
PROGRAM 10.2
pos : positive := 1;
begin
--get next word from curr_line starting at ch_pos +
1
Exercises
1. We have a type list declared as
type list is array(integer range < >) of positive;
Design a procedure which will be able to write out the
components of any array that has been declared to be of this
type. One of
the parameters of
the procedure should indicate
how many components are to be written on each line, with the
default being one component per line.
2. We have the declarations
name : string(1 .. 15) := “Pascal programs”;
new_name : string(1 .. 12);
What will be the value of new_name after the following slice
assignments have taken place?
new_name(l .. 3) := “Ada”;
new_name(4 .. 12) := name(7 .. 15);
3. Procedure get_line is described in section 10.3. Let us assume
Unconstrained arrays 135
Records
11.1 Introduction
Arrays are composite types that have two important features.
First, all components of an array are of the same type; secondly, array
be used in
components have a computable index. This means they can
situations such as a loop, with a different component being accessed
each time round the loop. There are, however, situations where arrays
are not the best way of holding a collection of values.
Quite often in a program we have several pieces of information
which are always used together. To represent a date for example, we
need the day, month and year. We could use three different variables
for this, but it would not then be clear that the three pieces” of
information are logically connected. Because they do not have the
the same
same type we cannot use an array, but even if they all had
be a particularly
type, an array with three components would not
meaningful way of representing the information.
What we need is a composite type where the components can have
different types and where individual components can be selected in a
record.
meaningful way. In Ada, such a composite type is called a
A date record type can be declared as
month : which_month;
year : positive range 1900 .. 1999;
end record;
Variables of type date, such as
birthday, day_after : date;
Records 137
if today.day = 30 then
next.day := 1;
next.month : which_month’succ(today.month);
else
next.day := today.day + 1;
end if;
when Dec = >
if today.day = 31 then
138 Programming in Ada
if roday.day = 31 then
next.day := 1;
next.month := which_month’succ(today.month);
else
next.day := today.day + 1;
end if;
end case;
return next;
end next_day;
11.2 Composite components
As you might expect, the components of a record can them-
selves be composite objects such as arrays or records, and it is also
possible to have arrays of records.
This is shown in the following example, where we wish to hold
information about book loans in a small library. Each book is ident-
ified by an eight-character code and each library member by a six-
digit number. We want to hold this information about each book on
loan together with the date when it is due to be returned and a flag to
indicate whether or not the book is overdue.
If we assume that no more than 500 books can be on loan at any one
time, the information could be held in an array of records declared as
type book is
record
code : string(1 .. 8);
reader_num positive range 100000 .. 999999;
:
due : date;
overdue : Boolean := false;
Records 139
end record;
loan :
array (1 .. 500) of book;
Record components can be given a default initial value. In this case
each overdue component is
initially set at false. Once the information
about borrowed books has been recorded in the array, the following
code could be used to write out the reader numbers and book codes for
each overdue book.
for i in loan'range loop
if loan(i).overdue then
put(loan(i).code);
put(loan(i).reader_num);
new_line;
end if;
end loop;
Each component of the array such as loan(5) is a record of type book.
The components of this record are therefore written as
loan(5).code loan(5).reader_num loan(5).due loan(5).o0verdue
The type of these components are string, integer, date and Boolean
respectively.
Because the due component of book is itself a record (of type date),
we refer to its components individually as
loan(5).due.day loan(5).due.month loan(5).due.year
Similarly, because a string is an array of characters, components such
as the fourth character of the book code are written as
loan(5).code(4)
The notation might seem strange at first, but it is straightforward and
you should quickly become used to it.
More complicated record types are possible, but are not dealt with
here. We have restricted ourselves to record type definitions which
have the form
~CDE
Sehrol—ED-Gd-
where a component declaration is defined as
Exercises
1. Function nexz_day in section 11.1 will raise a constraint_error
exception when asked to find the day following 31 Dec 1999.
Change the function so that when this situation arises, it
prints an error message and does not try to update the date.
2. Modify, and suitably
rename, function nexz_day so that it
returns the value of the preceding day.
3. Rational numbers can be
represented as two integers, a nu-
merator and a denominator. Construct a suitable
ation for rational numbers and then write a type declar-
function which
will take as parameters two rational
numbers and return their
sum as a result.
4. We have the following declarations
to represent a deck of
cards:
142 Programming in Ada
end record;
type hand is array(l .. 13) of cards;
type deck is array(l .. 52) of cards;
void : constant cards : = (value = > none, suit => empty);
In the games of whist and bridge the 52 cards in a deck are
dealt into four hands of 13 cards. Write code for each of the
following function and procedure skeletons:
procedure deal_cards(all_cards : deck;
north, east, south, west : out hand) is
begin
--take a deck of cards and deal
end deal_cards;
into four handsit
function aces_in_hand(dealt : hand) return natural is
begin
--return the number of aces in the hand
end aces_in_hand,
function number(dealt hand; : which_one : face_value)
return natural is
begin
--return the number of cards in the hand with the
--face value of which_one
end number;
procedure low_heart(dealt: in out hand; played out : cards)
is
begin
--the lowest heart in the hand is played and it is
--then removed from the hand
--i7 there are no hearts the value void is returned
--as the value played
end low_heart;
. Consider the following declarations:
type reply is
record
contents : string(1 .. 80);
Records 143
length :
natural,
end record;
procedure ger_response(current out reply) is
:
begin
get_line(current.contents, current.length);
end ger_response;
procedure pur_response(current : reply) is
begin
put(current.contents(l .. current.length));
end put_response;
Rewrite Program 10.1 so that the person’s name and the
answers to the questions are held in objects of type reply. The
reading and writing of responses should be done using the
procedures get_response and put_response defined above. A
function called equal should be written to check the replies. It
should have one parameter of type reply and one of type string.
12
Packages
the program does not need to know about the line, its length or the
current position reached in scanning it. But because these variables
are declared in the main procedure, they are visible throughout the
program. This has two disadvantages. The main part of the program
is cluttered with unwanted detail, and the variables are not protected
from being changed inadvertently.
What we need is to be able to make information available to one or
more subprograms and yet be hidden from the rest of the program.
This requires a means of packaging together a group of logically
related subprograms, types and objects. It must be possible to select
which information is to be visible from outside the package and which
information is to be hidden behind the package walls.
This extension to the subprogram concept gives us a powerful
mechanism by which we can split large problems into self-contained
units, each of which can be solved independently.
The package is the means by which this can be done in Ada. A
package can be in two parts, the package specification whose contents
may be seen by other parts of a program, and an optional package body
whose contents are hidden. In this chapter we shall look at package
specifications and how types and objects can be declared and made
visible to other parts of the program. In the next chapter we shall look
at the use of subprograms in packages and at package bodies.
package winning is
type list is array (positive range < >) of natural;
win_list constant [ist := (2546, 3479, 4321, 5173,
©
9716, 9837);
end winning;
par]
lation units through the context clause
with winning;
In this way a package or the main procedure can make use of
information declared in other packages.
Because package winning is independent of package student_io, it
does not need a context clause. The main procedure, on the ther
hand,
depends on both the packages student_io and winning and so both are
given in its context clause. The two with clauses and the two use
clauses can be amalgamated to give the context clause
with student_io, winning; use student_io, winning;
A with clause must precede its corresponding
use clause. Because
both the packages are listed in the use as well as the with clause,
we can
refer, in the main procedure, to put, get, put_line, list and win_list
instead of having to refer to student_to.put, student_io.get, student_
10.put_line, winning.list and winning.win_list.
We now see that an Ada program is composed of
separate units,
some of which may be existing library packages. In Program 12.1 the
units are package winning, the library package student_io and
pro-
cedure look_up, together with package standard which is automati-
|
cally part of all Ada programs. The built-in types such as integer and
character are declared in package standard.
Let us now look at the general form of a package specification:
declarative item
|
— [private
Ed [entific]
148 Programming in Ada
end main;
the Boolean
then, in the main procedure, the local declaration of
variable first takes precedence over the declaration in the package of
the integer variable first. Hence, to refer in the main procedure to the
integer variable we have to give it its full name of one first,
even
though one has been mentioned in a use clause. the
This is an important point. It means that we can introduce
subprogram without
contents of an external package into a package or
will interfere with any of our locally
any fear that its introduction
declared identifiers.
of two
The other point is more complicated. Consider the case
in
packages which contain the same identifier, as
package one is
first, second : integer;
end one;
Packages 149
package rwo is
Sirst, third : Boolean;
end rwo;
with one, two; use one, rwo;
procedure main is
begin
end main;
The full names one.first and two.first must be used in the main
procedure to determine which of the two possible identifiers is being
referred to. The system will not determine from the context whether
an integer or Boolean variable is needed. Hence we can refer to second
and third in the main procedure, but it would be an error to
try to refer
to first.
The exception to this rule is when the two conflicting identifiers
both refer to subprograms or to enumeration literals. In this
case the
context in which they are used will decide which of the possibilities
meant. This is the overloading feature which we met in chapter 6.
is
Once again you are advised not
to
introduce new examples of over-
loading into your programs until you are experienced in the
use of
Ada. Even then overloading should not be used lightly.
In large programs which make extensive use of packages, is often
preferable to give the full name for infrequently used it
types and
objects made available through a context clause, rather than to
use the
abbreviated form. This is to make clear to the reader where
the
declaration took place. Where this
is it
not done, is good practice to
give the information in a comment. The exception to this is when
the
identifiers are well known, such as those declared in
standard and
student_io.
Exercises
1. Assuming that the following package of astronomical data is
available, write a program which asks questions such
as
How far is the Earth from the Sun?
Your program should accept answers which
are within 109,
of the correct value and otherwise
package astronomical is
request the userto try again.
light_year : constant real : = 5.88E12 --miles
150 Programming in Ada
—
for pos in search_list'range loop
--note multiple exit from loop
if number = search_list(pos) then
return true;
end if;
end loop;
return false;
end n_list;
end winning;
Because
it appears in the visible part, function in_list can
from the main procedure in exactly the same way
be called
before. as
used a subprogram declaration. Itis
This is the first time we have
for all subprograms to be declared in two
possible, but not necessary,
all the information neces-
parts. The subprogram declaration gives
sary to call the subprogram, while the body also gives the information
about how the subprogram is implemented. The subprogram specifi-
cations used in the subprogram declaration and body must be equiv-
alent, and every subprogram declaration must have a corresponding
subprogram body.
A package body can include object, type, subtype, subprogram
and
unlike a package
package declarations, and use clauses, together with,
specification, the declarations of subprogram and package bodies. Its
syntax is
(package) (ody) {identifier —GO)
—~
—|declarative part
sequence of statements
mmm
The optional sequence of statements is used to initialise pack-
be
the
needed because most variables can
age. It is frequently not idea of infor-
initialised when they are declared, but, to support the
mation hiding, is important that objects declared within a package
it
are initialised in one of these two ways so
that users of the package can
Package bodies 153
correctly assume that the package is ready for use without taking any
special action.
When we design a package the general idea is to put the minimum
necessary information into the visible part of a package and to hide
away as much information as possible in the package body. Using this
principle, let us re-design package winning by considering the infor-
mation the main lottery procedure needs to know. The list of winning
numbers is accessed only by function in_/ist and so can be hidden in
the package body. This improves security significantly, for it would
seem desirable to keep information about the winning numbers
hidden from unauthorised access.
The revised version of the program is
PROGRAM 13.1
package winning is
function in_/list(number :
natural) return Boolean;
--search a list in the body for number
end winning;
package body winning is
type lst is array (positive range < >) of natural,
win_list : constant [ist := (2546, 3479, 4321, 5173,
9716, 9837);
function n_/list(number natural) return
: Boolean is
begin
--find if number is in the non-local win_list
for pos in win_list'range loop
--note multiple exit from loop
if number = win_list( pos) then
return true;
end if;
end loop;
return false;
end in_[ist;
end winning;
with student_io, winning; use student_io;
procedure look_up is
ticket_number : natural;
begin
--read ticket number and check if it wins a prize
put(“What is your number?”’);
get(ticket_number);
if winning.in_list(ticket_number) then
154 Programming in Ada
begin
--determine if the character is a letter
returnchin’a’ .. Zor chin 'A’ .. 'Z’;
end letter;
function lower_case(ch : character) return character is
begin
--convert upper case letters to lower case
if chin 'A’ .. 'Z’ then
return character'val(characer'pos(ch) +
character'ois('a’) — character'pos('A’));
else
return ch;
end if;
end Jower_case;
procedure next_line is
begin
--get the next line
get_line(line, line_length);
char_no := 0;
end next_line;
procedure ger_word(the_word out string) is
:
begin
--get next word from line starting at char_no + 1
parameters must be in mode, this means that a function call can then
interact with the rest of a program only through its returned value. If
we feel that itis necessary for a function to change a non-local variable
then we should rewrite the function as a procedure.
Because they are used only within the package, the two functions
letter and lower_case are declared in the package body but are not
given in the package specification. This means that they cannot be
called from outside the package. Package bodies can therefore contain
useful subprograms whose existence is hidden from the rest of the
program.
Let us now see how packages can be used in a re-worked version of
Program 10.2. When we design a package itis important to ensure that
all the objects, types and subprograms declared within it are logically
connected. Package lines has therefore dealt only with the structure of,
a
and the operations on, line of text. Program 10.2 also contained a list
of special words and a function to search the list. They have been put
into a second package because they have no logical connection with
the operations on a line of text. Package special_word is very similar to
package winning declared earlier in this chapter. This again shows that
when we produce solutions to problems we can often build on what we
have already rather than start from scratch.
PROGRAM 13.2
package special_word is
subtype word is string(1l .. 5);
function n_list(new_word word) return Boolean;
:
end special_word,
end record;
st : char_stack;
procedure push(x character) is :
begin
--add character to stack
if sz.zop = 100 then
put_line(“‘Stack is already full’);
else
st.top := st.top + 1;
st.item(st.top) := x;
end if;
end push;
procedure pop(x out character) is:
begin
--remove top character from stack
if sz.top = 0 then
put_line(““Stack is empty’);
else
:= st.atem(st.top);
x
st.top := st.top — 1;
end if;
end pop;
function stack_is_empty return Boolean is
160 Programming in Ada
begin
return st.zop = 0;
end stack_is_empty;
function stack_top return character is
begin
--return top character of stack
if sz.zop = 0 then
put_line(‘‘Stack is empty’);
return ' ’;
else
return st.item(st.top);
end if;
end szack_top;
procedure reser_stack is
begin
--reset stack to be empty
st.top := 0;
end reset_stack;
end stack;
The subprograms push, pop, etc. can be made visible to other units
by a context clause and characters can then be added or removed from
the stack by statements such as
push(‘a’); push('d"); pop(vall); pop(val2);
If vall and val2 are character variables this statement sequence will
give them the values 'b’ and ‘a’ respectively.
The actual stack, called sz, is hidden inside the package body.
Exactly how it has been implemented is therefore not known to units
which use the package.
end char_stack;
164 Programming in Ada
op : character;
and statements such as
push(’ +’, op_stack); push("*’, op_stack);
push('a’, temporary);
pop(op, op_stack);
After execution of these statements the op_stack and temporary stacks
will each contain one item, while the value held in the character
variable op will be an asterisk. If the package has been properly
defined and objects are manipulated only through the built-in oper-
ations, then our use of the type will always be at a fairly high, abstract
level.
A possible package body to fit the package specification is
with student_io; use student_io;
package body char_stack is
procedure push(x character; : st : in out stack) is
begin
--add character to stack
if sz.top = 100 then
put_line(“‘Stack is already full”);
else
st.top := st.top + 1;
st.item(st.top) := x;
end if;
end push;
procedure pop(x out character;
:
st : in out stack) is
begin
--remove top character from the stack
if stz.top
= 0 then
put_line(““Stack is empty”);
else
x := st.atem(st.top);
st.top := st.top — 1;
Package bodies 165
end if;
end pop;
end char_stack;
Alternative package bodies are possible of course.
A problem remains. Although we can urge the user to change a
stack only by the operations of push and pop, this advice may be
to
ignored. Itis, for example, still possible to add an element a stack by
statements such as
op_stack.top := op_stack.top + 1;
op_stack.item(op_stack.top) := "+;
instead of using
push(' +’, op_stack);
Although this should not be done it has not been made impossible.
If we change the internal components of a stack in this way there is no
guarantee that we are doing so in a way consistent with the defined
stack operations and, as we are at a more detailed level, it is much
easier to make mistakes. Most importantly, we are no longer consider-
ing a stack to be a new abstract type with its own operations, but are
becoming involved with the details of its implementation.
If the designer of a package feels that access to internal details
should be restricted, it should be possible to design a package in such a
way that this rule can be enforced.
Ada allows us to declare types to be private. The names of private
types can be used outside the package, but their internal structure can
be accessed only from within the package body. The specification of
type stack now becomes
package char_stack is
type stack is private;
procedure push(x character; st in out stack);
: :
private
type values is array (1 .. 100) of character;
type stack is
record
item : values;
top : natural := 0;
end record;
end char_stack;
The package body can be declared exactly as before.
166 Programming in Ada
—(privaté
gf declarative mL
Identifiers introduced in the private part of a stack specification
are, like identifiers declared in the package body, not visible outside
the package. Details of the structure of the type stack cannot therefore
be used outside the package. We can still declare stack objects such as
op_stack, temporary : stack;
but the only operations now available on these stacks are pusk and pop.
It is no longer possible to access the stack contents in any other way.
The type values is required in the definition of the stack and, as it does
not have to be seen from outside, it is declared in the private rather
than in the visible part of the package specification.
We have now achieved our aim of creating a new data type which
has the properties of a predefined data type. We can declare objects of
this type and can perform operations on these objects. We are,
however, unable to make use of any knowledge which we may have
concerning their internal structure.
13.5 Summary
Understanding a program is much easier when the declar-
ations of logically related objects, types and subprograms are kept
together instead of being scattered throughout a program. Because
packages enable this to be done, they form the basis for the logical
organisation of Ada programs.
The main problem with large programs is their size and compleXx-
ity. We therefore want to be able to divide them into self-contained
units which can interact with one another only through small, strictly
defined interfaces.
The Ada package allows us to do this. A large Ada program is
written as a series of compilation units, each of which usually contains
a main procedure, a package declaration or a package body. Each
package has a relatively small specification, which is its interface with
the other units. The implementation of the package is then hidden
from view in the package body. How the different compilation units
depend on one another is specified by their context clauses. In this
Package bodies 167
Exercises
1. Why, when we design a package, do we put the minimum
amount of information into the package specification and hide
as much information as possible in the package body?
2. Extend the stack abstract data type described in section 13.4
so that the operations stack_is_empty, stack_top and reset_
stack are also available.
3. Ifinstead of a stack of characters we needed a stack of integers,
how easy would it be to modify our stack package so that it
dealt with integers rather than characters?
4. In chapter 11 exercise 3 we constructed a type declaration for
rational numbers. Outline the design of a package to imple-
ment the abstract data type of rational numbers. The oper-
ations of addition, subtraction, multiplication and division
should be made available for objects of type rational_number.
Should rational_number be a private type?
5. In program 13.2 we read several lines of text and searched for
occurrences of certain words. If the problem was extended so
that we had to search for occurrences of any of the reserved
words in Ada (they are given in appendix 1), what changes, if
any, would have to be made to each of the following:
(a) the package specification and body of special_word,
(b) the package specification and body of /ines,
(c) the main program.
6. In a stack items are added or removed from the top of the
stack. In a queue items are added to the end of the queue and
are removed from the front. Outline the definition of a queue
abstract data type.
14
Visibility and existence
14.1 Visibility
The visibility of identifiers has been discussed informally in
earlier chapters; some more details are now given here. Ada’s visibil-
ity rules are quite complicated but as long as we do not try to be too
clever many details can be ignored until we wish to claim expert
status.
The most important rules are that all identifiers must be declared
before they can be used, and that an identifier can only be used where
it is visible. When identifiers are declared in the declarative part of a
subprogram or package they are visible from the end of their declara-
tion to the end of that subprogram or package. They are also visible
within any inner subprogram or package. Hence in the following
example identifiers declared within procedure outer are also visible
within procedure enclosed, but identifiers declared in procedure en-
closed cannot be seen from outside that procedure.
procedure outer is
val : constant positive := 1;
ch : character;
procedure enclosed(flag : Boolean) is
one : positive := val;
val : constant Boolean := true;
answer : Boolean := wal;
ein positive := outer.val;
:
begin
end enclosed,
Visibility and existence 169
begin
.
end outer;
We say that identifiers are local to the subprogram or package in
which they are declared and are global or non-local to any inner
subprogram or package. Hence val, ch and enclosed are local to
procedure outer and non-local to procedure enclosed, while flag, one,
answer, ein and the Boolean constant val are local to procedure
enclosed. We can always refer to both local and non-local identifiers,
it
but is good practice to declare identifiers to be as local as possible so
that subprograms and packages can be self-contained.
The identifier val has been declared as a positive constant within
procedure outer and as a Boolean constant within procedure enclosed.
Because an identifier becomes visible only after it has been declared,
the first declaration in procedure enclosed
one positive := val;
:
although itis not good practice, is legal and gives one an initial value of
1. Once the Boolean constant has been declared however, there is a
conflict as to which declaration any use of the identifier val belongs.
The more local declaration takes precedence and so the positive
constant identifier val is hidden by the Boolean constant identifier
val. If we still wish to use the positive constant in enclosed it has to be
referred to by its full name of outer.val.
Two instances of the same identifier may not be declared in the
same declarative part unless they are both subprograms with different
parameters or are enumeration literals. This is the overloading fea-
ture and once again it is recommended that you do not introduce
overloaded identifiers into your programs until you are experienced,
and even then only when there is a very good reason. Straightforward
programs are easier to read and understand and are much more likely
to be correct than complicated ones.
Finally, an identifier cannot be used within its own declaration.
This prohibits seemingly sensible declarations such as
border string(1 .. 40) := (border'range = > '*"); --illegal
:
end outer;
package body outer is
visible positive; :
end examine;
begin
visible = 1
1;
end outer;
Visibility and existence 171
with outer;
procedure main is
result : Boolean;
begin
outer.examine(result);
end main;
Here we have three compilation units: the declaration of package
outer, the body of package outer and procedure main.
A program consists of instructions which are to be executed and
objects which are to be manipulated by these instructions. Before a
program can be executed the instructions in all the compilation units
making up the program must be loaded into the main store of the
computer. Execution of the program may then start, but before the
main procedure can be executed all the packages on which it depends
must be initialised. In this example this involves the declaration and
the body of package outer. Storage is allocated to the objects declared
in the declarative part of package outer and these objects are assigned
any initial values. This act of reserving space for the objects and
giving them their initial values is called “elaborating” the declar-
ations. If a declarative part contains several declarations then they are
elaborated in the order in which they are given in the declarative part.
Elaboration of the declarative part of the package declaration of
outer results in storage being allocated to the variable object visible and
it being initialised to the value 0. Elaboration of the package body
consists of first elaborating its declarative part and then executing its
sequence of statements. Elaboration of the declarative part of the
body results in storage being allocated to the object invisible and its
value being left undefined. Execution of the sequence of statements in
the body then results in invisible being given the value 1.
We then start execution of the main procedure. This involves
elaborating its declarative part first, so space is reserved for the
Boolean variable result. Because it does not have an initial value, its
value is left undefined. Execution then continues with the first
statement in the main procedure.
During execution of procedure main we come across a call of
172 Programming in Ada
Exercises
1. Distinguish between the visibility and the life-time of an
object.
2. We say that variables exist at run-time when they have storage
space allocated. What important difference is there between
the life-time of a variable declared in the declarative part of a
package and one declared in the declarative part of a
subprogram?
15
Program structure
end outer;
with student_io; use student_io;
package body outer is
end outer;
with student_io, outer; use student_io, outer;
procedure start is
end start;
This program consists of the library packages student_io and useful_
things, the package outer and a main procedure, together, of course,
with the package standard, which is automatically part of all Ada
programs.
A compilation unit is a context clause followed either by a library
unit or by a secondary unit. A library unit can be a procedure body or a
package declaration, and a secondary unit can be a package body. A
Program structure 175
ue
outer
t
body of —
_~outer—
start
studentio
t —
useful things
body of student
_—
io
5
)
end outer;
but was not necessary because package useful_things is automatically
it
available to the body, having been given in the context clause of the
package specification. In this example the body of outer depends on
package student_io, but the specification of outer does not. Hence
student_io is given in the context clause of the body and not in the
context clause of the package specification.
have seen how packages can be used to divide large programs logically
into more manageable units. Compilation units are the means by
which we can divide large programs physically into manageable units.
The package therefore plays the central role in both the
physical and the logical separation of large programs into a series of
smaller, self-contained units which can be developed separately be-
fore being put together to form a complete program.
As the name implies, each compilation unit can be compiled
separately. This is not the same as compiling it independently, for
each compilation unit depends on the library units mentioned in its
context clause. These library units must already have been compiled
and be present in the program library before compilation of the new
unit takes place. If the compilation is successful, the new unit will be
added to the program library.
The program text presented to the compiler can consist of one or
more compilation units. The effect of presenting several compilation
units together is the same as presenting each one separately, but in the
same order. The order is important because a compilation unit can be
compiled only after all the library units on which it depends have been
compiled.
Separate compilation makes it possible to test a new package,
together with the units mentioned in its context clause, separately
from the rest of a program. To do this we write a small main program
to “drive” the package. This program might, for example, call the
subprograms in the package with suitably chosen values for the par-
ameters and then check that the returned values are correct. Once we
are sure that a package is working correctly it can be mentioned in the
context clause of other packages so that they can be tested in turn. Part
of this test will be to ensure that the interface between the packages
works correctly. In this way large tested programs can be systemati-
cally built up and we are never faced with the problem of a huge
monolithic untested program.
Another common situation where the ability to compile packages
separately gives significant advantages is when we have compiled and
linked together all the units making up a complete program and are
now testing the final program with sample data. During the tests we
find an error which we manage to trace to a mistake in one of the
compilation units. We correct the mistake and wish to continue the
test runs. The whole program does not have to be recompiled and
retested, only those parts which depend on the modified unit.
Let us consider the outline program from the previous section. The
178 Programming in Ada
_—-uscfulthings =i ?
7 __—
?
outer
¢
1 studentio
body of outer— t
body of student io
start
If the error was in the specification of package outer then only the
specification and body of package outer and procedure start would
have to be recompiled. As packages student_io and useful_things do not
depend on outer there is no need to recompile them.
If the error had been in the specification of useful_things then, after
the error had been corrected, the specification and body (if any) of
useful_things would have had to be recompiled followed by the specifi-
cation and body of package outer, which would in turn require the
main procedure to be recompiled.
It might seem from this that any change is going to require most of a
program to be recompiled. In large programs however, the library
units are likely to be clustered in logically related groups and any
modifications will require only other members of the group to be
recompiled.
The main advantage of separate compilation is realised when the
program modification has to be made to the body of a package rather
than to its specification. A package body can be compiled separately
from its specification, although the specification has to be compiled
before the body. Compilation units which depend on a package can
only see, and can therefore only depend on, the specification of a
package, not on the body.
If in our example the error was found in the body of package outer,
and this body was then modified in such a way that no change to the
package specification was required, only the body of package outer
would have to be recompiled. All the other compilation units could
remain as they were.
This means that the body of a package can be completely changed
and then recompiled and, as long as the changes do not affect the
package specification and hence how the package is seen and used
from outside, the package specification and the other units which
depend on the package do not need to be changed in any way. The
other units will not even be aware that the body has been modified.
Program structure 179
The specification of the package indicates the effect of the package. Its
text can be shown to potential customers. Exactly how the package is
implemented, i.e. the package body, will not be shown, although some
of the algorithms used in implementing the package might be
described.
4
Stack contents operator
When the operator + is encountered the top two values are 5 and 1, so
they are replaced by the value 6. When the operator / is encountered
the top two values are 6 and 2, so they are replaced by the result 3.
Finally the — operator acts on 7 and 3 to give the answer 4.
If we wish to produce a calculator program which can deal with the
precedence of operators and with brackets the easiest way is first to
convert the infix expression to reverse Polish and then evaluate the
reverse Polish expression as just described.
Let us now consider how the conversion can be done. To keep the
problem relatively simple we shall assume that the operands are all
single letters and that the infix expression is correctly formed. Our
task is to write a program to convert a series of characters such as
a—(b+c¢o)/d
into the equivalent reverse Polish form
abc+d| —
The first thing to note about the reverse Polish form is that the
order of the operands is unchanged from the infix expression. The
order of the operators, on the other hand, has been changed so that
they are now in the order in which they are to be used. The operands
are not going to cause any problem, while the precedence of the
operators is going to determine the order in which they are to appear
in the reverse Polish expression.
There are several solutions to this problem and we shall consider
one which involves a stack.
The input is read character by character. When an operand is
scanned it is immediately output, while the output of an operator is
delayed by keeping it on a stack. Exactly what happens when each
character is input is described by the following outline algorithm:
182 Programming in Ada
else
skip character
end if
end loop
loop while the stack is not empty
pop operator and write it out
end loop
To check that the outline algorithm works we must now trace it
with suitably chosen data which will test each of the possible con-
ditional paths. Among the cases to be considered are
Consecutive operators of equal precedence
Lower precedence operator followed by higher and vice versa
Bracketed expressions including nested brackets
Brackets at the beginning and at the end of expressions
Expressions containing no operators
These cases are tested by the following example expressions:
at b—c
(A+ B+ C)|D
a+b*c+d a+ (b+c)*d
a ((@)
You should trace how each of these expressions is processed to
improve your understanding of the action of the algorithm.
Once we are sure that our algorithm is working we can start
implementing it in Ada. The algorithm has made extensive use of a
stack of characters. Because we constructed a package to implement
such a stack in chapter 13, all we need to do is add this package to our
program library and make it available through a context clause. If it
has already been compiled for use as part of some other program, we
do not even have to recompile the package.
Because we have to compare the precedence of the different oper-
ators, we need to have some means of easily finding the precedence of
each character operator, If we restrict the problem to dealing only
with the multiplying and adding operators, the operators fall into the
following groups:
the multiplying operators * and / (highest priority)
the adding operators + and — (next priority)
open bracket (lowest priority)
We can convert a character representing an operator into its appro-
priate priority value by table look-up using a constant array which has
a character index and whose components give the relative priority.
Looking at the table of ASCII characters in appendix 3, we see that
the operator characters we are interested in are in the range '(" to I's
184 Programming in Ada
although this range does contain three characters we are not inter-
ested in. The look-up table can be put in the visible part of a package.
package convert is
subtype op_char is character range '(' ..'[’;
--characters in this range are () * + , — . /
precedence : constant array (op_char) of natural : =
((=>1,
+==>=>
end convert;
|)
YI => 0
3,
2,
PROGRAM 15.1
operator op_char;
:
|] =>
exit when szack_is_empty or else
precedence(next_char) > precedence(stack_top);
pop(operator); put(operator);
end loop;
push(next_char);
when '(' =>
Program structure 185
push('(");
when’) =>
--pop operators up to and including’(’
loop
pop(operator);
exit when operator = '(’;
put(operator);
end loop;
when others = >
null;
end case;
end loop;
--output the remaining contents of the stack
while not stack_is_empty loop
pop(operator); put(operator);
end loop;
new_line;
end rev_Polish;
Note that without looking at the package convert we cannot tell if
the conversion from characters to
priorities is performed by a func-
tion or by looking up a table. We could even change the method
without having to change the text of the main procedure, although it
would have to be recompiled.
This example shows us how previously written packages can be
utilised in the production of new programs. It also shows how hiding
the implementation of data structures such as the stack and the
conversion routine allows us to remain at a higher, more abstract and
problem-oriented level. In fact, the final implementation of the main
procedure is not too different from our outline algorithm.
Exercises
1. If a package body is modified and then recompiled, do the
packages and subprograms which depend on that package also
have to be recompiled?
2. How many library units and how many secondary units are in
Program 13.2? Outline the order in which the declarative
parts of the library and secondary units are elaborated.
3. Outline the order in which the declarative
parts of the library
and secondary units in Program 15.1 are elaborated.
4. Extend Program 15.1 so that the relational operators
>, <
and = can be dealt with. Use the information in appendix 3 to
obtain a suitable new definition for op_char.
16
Exceptions
16.1 Introduction
During the execution of a program an unusual or exceptional
circumstance may arise. This may be due to some logical error in our
algorithm, to unacceptable data being presented to the program, or
just some situation which, although it may occur during normal
execution, will occur only infrequently. If the ordinary program text
was modified so that it could pick up and deal with all exceptional
situations this could so complicate the text that the main flow of
control would be hidden. This goes against our aim of writing
programs which are easy to read and understand.
So that this does not happen, in Ada the handling of exceptional
situations is separated from the normal flow of control. When an
exceptional circumstance arises we say that “an exception is raised”.
Dealing with it is called “handling the exception”. Exceptions can be
raised in one of two ways, either automatically by the system when
some error is detected, or by the programmer by means of an explicit
raise statement such as
raise constraint_error;
Similarly we can either let the system deal with the exception or write
a special “exception handler” to deal with it ourselves. When we let
the system handle an exception the usual result is for the program to
be terminated.
Some of the exceptions which can be raised automatically by the
system have been described in earlier chapters. A fuller description is
now given before we go on to describe how we can raise and deal with
exceptions ourselves.
Let us now see how we can handle such an exception for ourselves.
which occur between the reserved words begin and exception in the
inner frame. No exception handlers are associated with the gez_small
procedure frame.
Let us now consider what happens when the body of procedure gez_
small is executed. The loop is entered and within the loop we enter the
inner frame. The statement
get(num);
is executed and if this is successful, i.e. if an integer in the range 1 to 15
is read, we continue on to the return statement which causes the
procedure to be left. The loop and the exception handler have had no
effect.
Let us now consider what happens if the statement
get(num);
is not executed correctly but, due to an attempt to read erroneous
data, a data_error exception is raised. When this happens normal
execution of the statements in the inner frame is abandoned and
control is transferred to the exception handler.
After the statements in the exception handler have been executed
we leave the frame. The statement following the frame is
put_line(““try again”);
This statement is executed and, because we are at the end of the loop,
control is transferred back to its beginning, where another attempt
made to read the data item.
is
To see how this will work in practice, let us assume that we have two
variables declared as
a, b : small_ pos;
and that we are to execute the statements
put_line(“‘two small positive numbers please”);
get_small(a); put_line(*“first number read successfully”);
get_small(b); put_line(“‘second number read successfully’);
In response to this, the data
7, 12
is typed in.
The get_small procedure is entered and the value 7 is read and
assigned to num. On leaving the procedure the variable a is given the
value 7. We then enter procedure get_small for a second time. This
time, when we attempt to execute
get(num);
190 Programming in Ada
begin
--pop top character from the stack
x := stack.item(stack.top);
stack.top := stack.top — 1;
end pop;
is
If this procedure called when szack.zop has the value zero, normal
execution of the procedure body will be abandoned when the attempt
is made to access the array component stack.item(stack.top). Because
the procedure does not contain a constraint_error exception handler,
the procedure body is left and the exception is again raised in the
frame containing the offending call of procedure pop. A check is then
made to see if there is an appropriate exception handler associated
with the call. If there is not then that frame is abandoned.
Frames are abandoned in this way until a suitable exception hand-
ler is encountered or we return to the main program or the statements
in a library package body. If no suitable handler is encountered the
system deals with the exception and this usually means termination of
the program.
Sometimes
it is useful partially to process an exception within a
frame before passing on information about it. This can be done by
raising another exception during execution of the exception handler.
A version of procedure pop could be written as
begin
--pop top character from the stack
x := stack.item(stack.top);
stack.top := stack.top — 1;
exception
when constraint_error = >
192 Programming in Ada
put_line(“‘Stack is empty’);
raise constraint_error;
end pop;
If a constraint_error exception is raised during execution of this
version of pop then normal execution of the procedure is abandoned
and the constraint_error exception handler entered. After the error
message
Stack is empty
has been printed another constraint_error exception is raised. The
frame is then abandoned and the new exception is raised in the frame
containing the procedure call.
If the exception had not contained a raise statement then this frame
would have fully dealt with the exception and the frame in which the
erroneous call had been made would not have been informed that an
error had occurred. This is important because when a subprogram is
abandoned the values of any out or in out parameters are not passed
back to the calling routine as normal. It is therefore vital, at the point
of call of procedure pop, to be informed whether the procedure has
that handler. As you would expect it caters for all exceptions which are
not named explicitly.
begin
--add character to stack
st.top := st.top + 1;
st.atem(st.top) := Xx;
exception
when constraint_error = >
put_line(“‘Stack is already full”);
raise stack_error;
end push;
Procedure pop and function stack_top can be modified in a similar
way. The other declarations in the package body do not have to be
changed from the version given in chapter 13.
returns the value zrue before any attempt has been made to read any
characters from the line.
Let us now write the program. As well as our new stack package,
will again use package convert.
it
The top-level algorithm is
loop while the line is not blank
convert infix expression to postfix and write the postfix
expression
get ready to deal with the next line
end loop
and the structure of the solution to the subproblem
convert infix expression to postfix
is basically unchanged from our earlier program.
PROGRAM 16.1
operator op_char;
:
if operand_next then
|] =>
raise order_error;
end if;
196 Programming in Ada
loop
exit when stack_is_empty or else
precedence(next_char) > precedence(stack_top);
pop(operator); put(operator);
end loop;
push(next_char);
operand_next := true;
when '(' =>
if not operand_next then
raise order_error;
end if;
push('();
when) =>
if operand_next then
raise order_error;
end if;
--pop operators up to and including (’
loop
pop(operator);
exit when operator = '(;
put(operator);
end loop;
when’ =>
null;
when others = >
raise spurious_error;
end case;
end loop;
--check that last character was an operand
if operand_next then
raise order_error;
end if;
--write remaining contents of stack
while not szack_is_empty loop
pop(operator);
if operator = '(' then
raise bracket_error;
end if;
put(operator);
end loop;
new_line;
exception
when stack_error bracket_error =>
|
put_line(“Unmatched brackets”);
reset_stack;
Exceptions 197
17.1 Introduction
We saw in chapter 1 that information could be held on backing
store, usually disks or magnetic tape, in what are called files. We shall
refer to these files as “external files”. If you have been running the
Ada programs in this book on a computer, you will have been making
extensive use of such files for, before we can translate and then run an
Ada program, it must have been put into a file on backing store. This
is done by typing the program into the computer as data to a special
program called the editor. This editor program reads what we type in
at a terminal and puts it into an external file.
Ifatalater date we wish to change an Ada program, we again use the
editor. This time the editor reads the contents of the external file
which contains the version of the program we wish to change, to-
gether with the proposed changes, which we type in at a terminal.
These two pieces of information are then used to create an updated
version of the program, which is put into a new external file.
Although we have used files to store Ada programs, we have
assumed that, when a program is executed, the data for it is typed in at
an interactive terminal and the results are displayed on a VD screen.
When we have a large amount of data it is inconvenient to have to type
in all the data each time the program is run. A better approach would
be to put some or all of the data into an external file on backing store.
Our program will then be able to read data from this external file
instead of from the terminal. The data will not have to be re-typed
each time the program is run.
Similarly, instead of displaying all our results on the VDU screen
we can write information to an external file. It is often the case that the
results produced by a program are not intended for humans to read,
but are to be used as data for another program. In such cases results
to
are output an external file so that they can be used as input data for
Files 199
the other program, or indeed be the input data for a later run of the
same program.
Consider, for example, a program which is used each week to
produce the pay cheques for a company’s employees. Data for this
program will come from two sources. One will be an external file
containing all the past information about each employee. The other
will be information about the work done by each employee in the
current week. This information may also be in an external file or may
be typed in at a terminal.
The result of running this program will be to print out the pay
cheques for the employees together with statements showing how the
pay has been calculated and how much has been deducted for tax, etc.
A new file of information on each employee composed of information
from the old file updated by the current week’s information will also
be produced. This new file, created during one run of the program,
will be used as input data for the run the following week.
numbers_file filestype;
:
value : integer;
sum integer := 0;
:
begin
--read and sum the first 50 integers in the file
open(numbers_file, in_file, external_name value);
for in 1
.. 50 loop
get(numbers_file, value);
sum := sum + value;
end loop;
close(numbers_ file);
total = sum;
end read_ fifty;
The first statement in procedure read_fifty
open(numbers_file, in_file, external_name);
causes the file_type object numbers_file to be associated with the
external file whose name isheld in the string external_name, and for
this file to be opened so that the information in it can be read.
Once we have opened a file we need a means of reading the
information which it contains. In this chapter we assume that the files
are what is known as “text files”, i.e. that they are files of characters
organised into lines. This isexactly the form in which the information
Files 201
exam results from the backing store. We can assume that no student
name has more than 30 characters.
This problem can be solved by the following procedure:
procedure produce_averages(old_file, new_file : string) is
exams, averages : file_type;
exam_result, exam_total : natural,
exam_average real; :
file_type object called exams. Then a new external file called student
averages is created and is associated with the file_type object called
averages.
The names and results in the external file associated with exams are
then read and the 500 names and averages are written to the external
file associated with averages by repeatedly executing the procedure
calls
put(averages, name(l .. name_length));
put(averages, exam_average); new_line(averages);
Once all the exam averages have been written to this new file, the
file associated with exams is
deleted, i.e. the external file class_results is
deleted from backing store. The newly-created file called student
averages is then closed.
As you might expect, exceptions are raised if any of these file
handling procedures are used incorrectly. If we try to open or create a
file which is already open,
been
or if we try to close or delete a file which has
not opened, then a szatus_error exception is raised. The current
status of a file can be checked by means of the Boolean function is_
open, as in the statement
if is_open(averages) then
close(averages);
end if;
If the string of characters used to identify the external name of a file
has the wrong form, or if we try to open an external file which does not
exist, then a name_error exception is raised. If we try to open, create or
delete a file for which we do not have permission to do these things,
then a use_error exception is raised.
A mode_error exception is raised if we try to read from a file which is
notin file mode n_ file or if we
tryto write to a file which is not in file
mode out_file. We can find the current mode of a file by calling the
predefined function mode. It has one parameter of type Sfile_type and
returns a value of type file_mode.
istrue when we have read to the end of the file and is false otherwise.
It is used in the following procedure, where the contents of one file
are copied to another with the line structure being preserved:
procedure copy(old_file, new_file : string) is
ch : character;
source, destination : file_type;
begin
--copy contents of source to destination
open(source, in_file, old_file);
create(destination, out_file, new_file);
loop
exit when end_of_file (source);
if end_of_line(source) then
skip_line(source);
new_line(destination);
else
get(source, ch);
put(destination, ch);
end if;
end loop;
close(source); close(destination);
end copy;
of charac-
As has already been said, text files are organised as lines
ters. In fact, groups of lines are organised into pages and there is a
procedure new_ page whose action is to terminate the current line if
this has not already been done and then to write a page terminator.
Similarly, there is a procedure skip_ page which will skip the rest of the
current page in the same way that skip_line will skip the rest of the
current line.
Each text file is terminated by a line terminator, followed by a page
terminator which is then followed by a file terminator. The function
end_of_file becomes true when this sequence of line, page and file
terminator is encountered or when a file terminator is encountered.
When we have been writing to a file and we call procedure close, it
ensures that the file is properly terminated with the appropriate line,
page and file terminators. If we try to read past the end of a file the
exception end_error is raised.
Files 205
Exercises
. Write a procedure which will take the contents of two files and
will create a third file consisting of the first file followed by the
contents of the second file.
. We have two files, each of which contains integer numbers in
non-decreasing order. Write a procedure to merge the infor-
mation in these two files into a third file. Hence if the first file
contained the numbers
3 19 467 467 543
and the second file contained the numbers
7 19 219
then the new file will contain
3719 19 219 467 467 543
. Modify your answer to question 2 so that your newly-created
merged file will contain no duplicates.
. Two societies each have names of their members on file in
alphabetic order. The two societies are to amalgamate and you
have been asked to design and write a procedure which will
read the membership lists from the two membership files and
create a new amalgamated membership file in which the
names of the members are still in alphabetic order. You may
assume that no person is a member of both societies.
. Temperatures have been collected from a remote weather
station over the last year and have been written to a file on
backing store. The file contains the minimum and maximum
temperature, and the temperature at noon for each day of the
year, starting with 1 January.
Determine which day of the year had
(iii) the lowest temperature,
(ii) the highest temperature,
(iii) the highest noon temperature.
Which month had, on average, the lowest minimum, and
which had the lowest maximum daily temperature?
APPENDIX 1
delay record
delta limited rem
digits loop renames
do return
mod reverse
206
APPENDIX 2
207
208 Appendix 2
kK’
rrS
rg
209
ANSWERS TO SELECTED EXERCISES
Chapter 2
. A program can be created by modifying Program 2.2. The
identifier sum could be changed to difference and the assign-
ment statement to
difference := second
first;
—
Chapter 3
. integer, real, character and string
. (a) number, size : integer;
(b) first_in_alphabet : constant character := ‘a’;
(c) type month is (Fan, Feb, March, April, May, June, July,
Aug, Sep, Oct, Nov, Dec);
(d) vacation : month;
(e) first_month : constant month := Jan;
(f) name : constant string := “Robert Clark”;
(g) zero : constant real := 0.0;
Chapter 4
. 535975 212
53.6 59.0 75.2 212.0
5. first > second
first rem second = 0
first >= 0 and second > = 0
first > 0 xor second > 0
firstinl .. 6
. The first set of statements will swap the values of my_appoint-
ment and your_appointment and the second will set them both
equal to wed.
210
Answers to selected exercises 211
Chapter 5
5@v)
with student_io; use student_io;
procedure adding is
sum : real := 0.0;
n : integer;
plus_or_minus_one : real := 1.0;
begin
ger(n);
for number in 1 .. n loop
sum := sum + plus_or_minus_one | real(number);
plus_or_minus_one := — plus_or_minus_one;
end loop;
put(“‘sum = *°); put(sum);
new_line;
end adding;
7. Remember to guard against a possible infinite loop when
income plus interest is greater than expenditure.
Chapter 6
3. The statement
get (operator);
must be expanded to
loop
get(operator);
exit when operator
end loop;
|=";
Any spaces before the numbers will be skipped automatically.
Chapter 7
6. procedure month(days_in_month positive; start_day day) is
: :
days_in_line positive := 1;
:
begin
put_line(" Su M Tu W Th F Sa”);
for blanks in mon .. start_day loop
pur“ 7);
days_in_line := days_in_line + 1;
212 Programming in Ada
end loop;
for days in .. days_in_month loop
1
Chapter 8
1. four, four, queen, king, jack, two.
None of the statements would cause constraint_error excep-
tions to be raised.
. subtype printable_character is character range '' .."
1~r
’;
Chapter 9
. type month is (fan, Feb, March, April, May, June, July,
Aug, Sep, Oct, Nov, Dec);
function day_in_year(this_month : month; this_day : positive)
return positive is
type days is array (month) of positive;
--assume a leap year
days_in_month : constant days : = (31, 29, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31);
day_count natural := 0;
:
Chapter 10
. subtype name is string(1 .. 20);
type name_list is array( positive range < >) of name;
names : name_list(1
.. 100);
The only changes required in procedure selection_sort are that
the type of the parameter is changed from any_list to name_list
and that smallest is declared to be of subtype name.
Chapter 11
. Example procedures are
procedure deal_cards(all_cards : deck;
north, east, south, west : out hand) is
card : positive := 1;
begin
for round in hand'range loop
north(round) := all_cards(card);
east(round) := all_cards(card + 1);
south(round) := all_cards(card + 2);
west(round) := all_cards(card + 3);
card := card + 4;
end loop;
end deal_cards;
function aces_in_hand(dealt hand) return natural is
:
count natural := 0;
:
begin
for round in hand'range loop
if dealt(round).value = ace then
count := count + 1;
end if;
end loop;
return count,
end aces_in_hand,
Chapter 13
. The definition of values and the type of the formal parameter x
must be changed, but no change is required in the code.
. The only change required is in the body of special_word where
the definition of word_list has to be changed.
214 Answers to selected exercises
Chapter 15
4. package convert is
subtype op_char is character range '(' .. ">";
precedence constant array(op_char) of natural :=
:
(C=>1
Pd r__1r I>! => 2,
rt
|
|
’
ro > 3,
Tx!
"y |
|
I
a |
—
rr
> 4,
|
0’ => 0);
end convert;
If we assume that only legal expressions have to be handled,
then the only other change required is to extend the choices of
operators in the case statement in procedure rev_Polish to
|
when "+ "=" |X"
Chapter16
|"
|'<"|'="|"'>" =>
Chapter 17
4. The following outline algorithm can be used:
read the first name from each of the society files
loop
if name from society A < name from society B then
output society A name
if no more names in society A file then
output remaining society B names and leave loop
end if
read next society A name
else
output society B name
if no more names in society B file then
output remaining society A names and leave loop
end if
read next society B name
end if
end loop
INDEX
215
216 Index
ISBN
the Ada language (ANSI/MIL-STD-1815A-1983) is used
throughout.
For beginning students of computer science in universities
and colleges, or for those with some programming experience
but who are not experts, this book provides a gentle introduc-
tion to this increasingly important programming language.
is
The figure on the front cover based on a portrait of Augusta Ada, Countess of
Lovelace, who collaborated with the nineteenth-century
computer pioneer
Charles Babbage. The Ada programming language is named after her.
GO 0185
0-521-27L75-1