|
Laboratory 1
Structured Strongly Typed Programming with Ada95
Ada is a general purpose programming language intended for a wide spectrum of applications. The language was designed to address problems inherent in the writing of large long-lived systems built by teams. Since most of the cost associated with a long-lived program is incurred maintaining the program after it has been written and delivered, Ada facilitates the writing of programs that are easy to understand and modify without surprising side effects. To expert C programmers like yourselves (at least those who completed COMP2100 or COMP2300) an Ada program may seem unduly verbose. To non-expert C programmers (those who did not complete COMP2100 or COMP2300) this verbosity greatly aids readability and comprehension of the code!
Ada distinguishes itself from other languages in its concern with reliability. Consistency checks are performed when a program is being compiled so that errors can be caught as early as possible, even before a program is tested. In addition, checks are performed during program execution, so that unexpected conditions will be noticed soon after they arise.
The Ada standard consists of the core language and additional specialized annexes, eg for system programming, real-time systems, distributed systems etc. For the moment we will restrict our use of Ada to the core language.
Subprograms, Functions, Packages
The two most important kinds of building blocks for Ada programs are subprograms and packages. Subprograms are similar to those in C, while packages are collections of related subprograms and other entities.
Like C, Ada provides two kinds of subprograms, called procedures and functions. Procedures are like C functions with a void return value. Execution of an Ada program entails the invocation of a main subprogram. A typical procedure body has the following form:
procedure identify (formal-parameter-list) is
declarative-part
begin
sequence-of-statements
end identifier;
The form of a typical function body is similar, except for the first line:
function identify (formal-parameter-list) return result-type is
declarative-part
begin
sequence-of-statements
end identifier;
(Note: all parameters to a function subprogram must be of mode in. This means that data passed as an argument to a function cannot be modified.) The declarative part is a sequence of zero or more declarations defining names that will be used in the subprogram.
A Simple Queue Specification
We will now construct a slightly more complex Ada program that implements a simple queue, that is a first in first out (FIFO) data structure. We will do this by implementing the queue as a package.
For larger packages it is normal to split the package into two parts, the specifications and the body. For gnat you should put the specifications in a file with a suffix of .ads (Ada specifications), while the body of the package should be in a file with suffix .adb (Ada body). These files bear a similar relationship to the .h and .c files in C programs.
The specifications for our simple queue is as follows:
package Queue_Pack_Simple is
QueueSize : constant Positive := 10;
type Element is new Positive range 1_000..40_000;
type Marker is mod QueueSize;
type List is array (Marker'Range) of Element;
type Queue_Type is record
Top, Free : Marker := Marker'First;
Elements : List;
end record;
procedure Enqueue (Item: in Element; Queue: in out Queue_Type);
-- procedure Dequeue (Item: out Element; Queue: in out Queue_Type);
end Queue_Pack_Simple;
ACTIONS
From the above consider
- What is the maximum number of elements we can have in the queue?
- The values of the elements on the queue are restricted to a certain range - what is this range?
- What is meant by 1_000 or 40_000?
- Ada has various kinds of integer types. Variable Marker is an variable of modular type - what does this mean?
- Types (and other entities) in Ada have attributes. This is denoted by an apostrophe and a special identifier called an attribute designator immediately following the variable name. For example if T is the name of a discrete type, then the attribute T'First names the smallest value in T and the attribute T'Last names the largest value in T. Attribute T'Pred names T's predecessor function (the function that takes an argument in type T and returns the next lower value), while T'Succ names T's successor function. Can you guess what the 'Range attribute does?
- What do you expect the procedures enqueue and dequeue to do?
We now need to implement the body of Queue_Pack_Simple. The following code should be saved to a file called queue_pack_simple.adb:
package body Queue_Pack_Simple is
procedure Enqueue (Item: in Element; Queue: in out Queue_Type) is
begin
Queue.Elements (Queue.Free) := Item;
Queue.Free := Queue.Free - 1;
end Enqueue;
end Queue_Pack_Simple;
And finally we need to write some code to test this package. Save the following code to a file called queue_test_simple.adb
with Queue_Pack_Simple; use Queue_Pack_Simple;
procedure Queue_Test_Simple is
Queue : Queue_Type;
Item : Element;
begin
Enqueue (2000, Queue);
-- Dequeue (Item, Queue);
end Queue_Test_Simple;
ACTIONS
You should now have three files, queue_test_simple.adb, queue_pack_simple.ads, queue_pack_simple.adb.
- Build the executable by typing gnatmake queue_test_simple and run it. (did you notice that we never wrote a 'make'-file? - wasn't an oversight: there is no such thing as a makefile required in Ada)
- Uncomment the call to dequeue from queue_test_simple and write the implementation of dequeue. Insert suitable I/O to convince yourself that enqueue and dequeue are working.
Exception Handling
Consider the following queue_test_simple program.
with Queue_Pack_Simple; use Queue_Pack_Simple;
procedure Queue_Test_Simple is
Queue : Queue_Type;
Item : Element;
begin
Enqueue (2000, Queue);
Dequeue (Item, Queue);
Dequeue (Item, Queue);
end Queue_Test_Simple;
This program adds one element to the queue, then makes two calls to dequeue. The effect of the second call will be unpredictable. In this instance it is easy to see that the code contains an error, but if the second request to dequeue was contained in a different routine it would be much less obvious. To deal with such problems Ada includes a range of exception handling capabilities that we can use to write a better implementation of the queue package:
- file queue_pack_exceptions.ads
package Queue_Pack_Exceptions is
QueueSize : constant Positive := 10;
type Element is (Up, Down, Spin, Turn);
type Marker is mod QueueSize;
type List is array (Marker'Range) of Element;
type Queue_State is (Empty, Filled);
type Queue_Type is record
Top, Free : Marker := Marker'First;
State : Queue_State := Empty;
Elements : List;
end record;
procedure Enqueue (Item: in Element; Queue: in out Queue_Type);
-- procedure Dequeue (Item: out Element; Queue: in out Queue_Type);
Queueoverflow, Queueunderflow : exception;
end Queue_Pack_Exceptions;
- file queue_pack_exceptions.adb
package body Queue_Pack_exceptions is
procedure Enqueue (Item: in Element; Queue: in out Queue_Type) is
begin
if Queue.State = Filled and Queue.Top = Queue.Free then
raise Queueoverflow;
end if;
Queue.Elements (Queue.Free) := Item;
Queue.Free := Marker'Pred (Queue.Free);
Queue.State := Filled;
end Enqueue;
end Queue_Pack_Exceptions;
- file queue_test_exceptions.adb
with Queue_Pack_Exceptions; use Queue_Pack_Exceptions;
with Ada.Text_IO; use Ada.Text_IO;
procedure Queue_Test_Exceptions is
Queue : Queue_Type;
Item : Element;
begin
Enqueue (Turn, Queue);
-- Dequeue (Item, Queue);
-- Dequeue (Item, Queue);
exception
when Queueunderflow => Put("Queue underflow");
when Queueoverflow => Put("Queue overflow");
end Queue_Test_Exceptions;
ACTIONS
- In this example we have used an enumerated type definition - where?
- Compile the above test program and verify that it does indeed give rise to an exception if too many items are added to the queue.
- Insert your code for function dequeue, but include modifications with underflow exception handling. Verify that it works.
- The above code terminates after the exception has been raised. Can you name at least one circumstance where termination may be an in appropriate course of action?
Information Hiding
Finally, to protect data Ada provides the private and limited private statements. While private data can be assigned and copied, limited private data cannot be assigned or compared. Thus to protect the content of the queue we re-write our code as:
- File queue_pack_private.ads
package Queue_Pack_Private is
QueueSize : constant Positive := 10;
type Element is new Positive range 1..1000;
type Queue_Type is limited private;
procedure Enqueue (Item: in Element; Queue: in out Queue_Type);
-- procedure Dequeue (Item: out Element; Queue: in out Queue_Type);
Queueoverflow, Queueunderflow : exception;
private
type Marker is mod QueueSize;
type List is array (Marker'Range) of Element;
type Queue_State is (Empty, Filled);
type Queue_Type is record
Top, Free : Marker := Marker'First;
State : Queue_State := Empty;
Elements : List;
end record;
end Queue_Pack_Private;
- File queue_pack_private.adb
package body Queue_Pack_private is
procedure Enqueue (Item: in Element; Queue: in out Queue_Type) is
begin
if Queue.State = Filled and Queue.Top = Queue.Free then
raise Queueoverflow;
end if;
Queue.Elements (Queue.Free) := Item;
Queue.Free := Marker'Pred (Queue.Free);
Queue.State := Filled;
end Enqueue;
end Queue_Pack_private;
- File queue_test_private.adb
with Queue_Pack_Private; use Queue_Pack_Private;
with Ada.Text_IO; use Ada.Text_IO;
procedure Queue_Test_Private is
Queue, Queue_Copy : Queue_Type;
Item : Element;
begin
Enqueue (Item => 1, Queue => Queue);
Queue_Copy := Queue;
Enqueue (Item => 2, Queue => Queue_Copy);
-- Dequeue (Item, Queue);
-- Dequeue (Item, Queue_Copy);
exception
when Queueunderflow => Put("Queue underflow");
when Queueoverflow => Put("Queue overflow");
end Queue_Test_Private;
ACTIONS
- Attempt to compile the above program. You will find that it gives a build error - why?
- Fix the above code so that it does compile and run.
- Limited private does not permit private data to be copied or compared. Can you envisage a scenario where copying of private data would be a bad idea?
- Augment the above code with the dequeue procedure.
Next Week
Next week we will look at reusable programming and begin to study tasks. |