When a largish program is to be written it is often advantageous to divide the program up into chuncks (modules) and compile the parts seperatly. This option is particularly desirable when several programmers are jointly developping a large program.
One method whereby different parts of an Ada program can be compiled seperatly is to make use of library units. Here we first create a desired library unit, for example:
------------------------------------------------------------ -- TOTAL PROCEDURE procedure TOTAL(N1, N2 : INTEGER) is sperate begin PUT(N1+N2); end TOTAL; ----------------------------------------------------------
and compile it. We now write a program that will use this code as follows:
-- SUBUNIT EXAMPLE -- 19 December 1997 -- Frans Coenen -- Dept Computer Science, University of Liverpool with TEXT_IO, TOTAL; use TEXT_IO; procedure SUBUNIT_EXAMPLE is package INTEGER_INOUT is new INTEGER_IO(INTEGER); use INTEGER_INOUT; NUM_1, NUM_2 : INTEGER ; -- TOP LEVEL begin PUT_LINE("Input two integers"); GET(NUM_1); GET(NUM_2); TOTAL(NUM_1,NUM_2); end SUBUNIT_EXAMPLE;
Note the use of the with clause to include the library unit. A use caluse is not included as this is intended for use only with predefine packages. The above program can now be run in the normal manner. Generally speaking library units are collected together into packages and not alone as in the above example.
A package (also known as a module or task) is a collection of a number of related declarations of types, variables and routines. Ada packages comprise two parts:
Note that the body is not visible to the program (at least conceptually). The template for writing an Ada package is as follows:
-- Specification package PACKAGE_NAME is <DECLARATIONS> end PACKAGE_NAME; -- Body -- With and use clauses package body PACKAGE_NAME is <PROGRAM_DETAILS> end PACKAGE_NAME;
If we consider the random number generator example presented earlier we can wrap the generator part of this program up into a package as follows:
-- RANDOM NUMBER GENERATOR PACKAGE -- 9 October 1997 -- Frans Coenen -- Dept Computer Science, University of Liverpool -- Specification package RANDOM is -- Set seed procedure START_VALUE; -- Generate number function RANDOM_NUMBER return NATURAL; end RANDOM; -- Body with TEXT_IO; use TEXT_IO; package body PACKAGE_NAME is package INTEGER_INOUT is new INTEGER_IO(INTEGER); use INTEGER_INOUT; package FLOAT_INOUT is new FLOAT_IO(FLOAT); use FLOAT_INOUT; M_CONSTANT : constant := 8192; -- 2**13 K_CONSTANT : constant := 3125; -- 5**5 TERM : NATURAL; ------------------------------------------------------------ -- START VALUE PROCEDURE procedure START_VALUE is SEED: POSITIVE range 1..M_CONSTANT-1; begin loop -- Output request to user and get SEED PUT("Enter seed (odd number within the range 1 to "); PUT(M_CONSTANT-1, WIDTH=>5); PUT_LINE("): "); GET(SEED); -- Check seed (deed must be an odd number if SEED rem 2 = 1 then TERM := SEED; exit; else PUT("Invalid seed: "); PUT(SEED); PUT_LINE(", try again"); NEW_LINE; end if; end loop; NEW_LINE; end START_VALUE; ---------------------------------------------------------- ---------------------------------------------------------- -- RANDOM NUMBER function RANDOM_NUMBER return NATURAL is begin TERM := TERM * K_CONSTANT mod M_CONSTANT; RETURN(TERM); end RANDOM_NUMBER; ---------------------------------------------------------- end RANDOM;
Having created the package (and compiled it) we can now use it. The most common way of doing this is as follows:
-- RANDOM NUMBER GENERATOR -- 16 September 1997 -- Frans Coenen -- Dept Computer Science, University of Liverpool with TEXT_IO, RANDOM; use TEXT_IO; procedure RANDOM_GENERATOR is package INTEGER_INOUT is new INTEGER_IO(INTEGER); use INTEGER_INOUT; RAND_NUM : NATURAL := 0; begin RANDOM.START_VALUE; while RAND_NUM /= 50 loop RAND_NUM := RANDOM.RANDOM_NUMBER*100/8191; PUT(RAND_NUM, WIDTH=>4); end loop; NEW_LINE; end RANDOM_GENERATOR;
Here we have used a dot operator to specifically link a procedure/function to a package (e.g. RANDOM.START_VALUE and RANDOM.RANDOM_NUMBER). We could have included a use instead, however this is normally re served for Ada's standard packages (TEXT_IO etc.)
Design and implement (in Ada) a "spot the ball" computer game. The rules for the game are as follows.
Note: make use of the package RANDOM defined above.
A top-down analysis of the proposed problem is given below.
Where required we will define coordinates as a subtype (called COORDINATE) of the pre-defined subtype POSITIVE with a range of 1 to 256 inclusive. We will address the identified operations using the following procedures/functions:
NAME | USAGE | TYPE | RANGE |
---|---|---|---|
X_COORD | Global input variable | COORDINATE | 1..256 |
Y_COORD | Global input variable | COORDINATE | 1..256 |
X_POSN | Global variable | COORDINATE | 1..256 |
Y_POSN | Global variable | COORDINATE | 1..256 |
MAX_NUM_GUESSES | Global constant | INTEGER | 8 |
NUM_GUESSES | Global variable | INTEGER | Default |
NAME | USAGE | TYPE | RANGE |
---|---|---|---|
X_POS | Formal (out) parameter | COORDINATE | 1..256 |
Y_POS | Formal (out) parameter | COORDINATE | 1..256 |
NAME | USAGE | TYPE | RANGE |
---|---|---|---|
X_CRD | Formal parameter | COORDINATE | 1..256 |
X_POS | Formal parameter | COORDINATE | 1..256 |
Y_CRD | Formal parameter | COORDINATE | 1..256 |
Y_POS | Formal parameter | COORDINATE | 1..256 |
NAME | USAGE | TYPE | RANGE |
---|---|---|---|
Y_CRD | Formal parameter | COORDINATE | 1..256 |
Y_POS | Formal parameter | COORDINATE | 1..256 |
NAME | USAGE | TYPE | RANGE |
---|---|---|---|
Y_CRD | Formal parameter | COORDINATE | 1..256 |
Y_POS | Formal parameter | COORDINATE | 1..256 |
NAME | USAGE | TYPE | RANGE |
---|---|---|---|
Y_CRD | Formal parameter | COORDINATE | 1..256 |
Y_POS | Formal parameter | COORDINATE | 1..256 |
Complete Nassi-Shneiderman charts for the above are given below:
A Control Flow Chart/Diagram indicating the broad flow of control through the above is given below.
-- SPOT THE BALL -- 12 October 1997 -- Frans Coenen -- Dept Computer Science, University of Liverpool with TEXT_IO, RANDOM; use TEXT_IO; procedure SPOT_THE_BALL is package INTEGER_INOUT is new INTEGER_IO(INTEGER); use INTEGER_INOUT; subtype COORDINATE is POSITIVE range 1..256; X_COORD, Y_COORD : COORDINATE; X_POSN, Y_POSN : COORDINATE; MAX_NUM_GUESSES : constant := 8 ; NUM_GUESSES : INTEGER := 1; ---------------------------------------------------------------- -- Random location of ball within space procedure LOCATE_BALL(X_POS, Y_POS : out COORDINATE) is begin RANDOM.START_VALUE; X_POS := RANDOM.RANDOM_NUMBER*256/8191; Y_POS := RANDOM.RANDOM_NUMBER*256/8191; end LOCATE_BALL; ---------------------------------------------------------------- ---------------------------------------------------------------- -- Check guess function CHECK_GUESS(X_CRD, Y_CRD, X_POS, Y_POS : COORDINATE) return BOOLEAN is -------------------------------------------------------- -- Correct X function CORRECT_X_COORD(Y_CRD, Y_POS : COORDINATE) return BOOLEAN is begin if Y_POS = Y_CRD then PUT_LINE("bull's-eye!"); RETURN(TRUE); else if Y_POS > Y_CRD then PUT_LINE("Ball is to the North"); else PUT_LINE("Ball is to the South"); end if; RETURN(FALSE); end if; end CORRECT_X_COORD; -------------------------------------------------------- -- Ball to west procedure BALL_TO_WEST(Y_CRD, Y_POS : COORDINATE) is begin if Y_POS = Y_CRD then PUT_LINE("Ball is to the West"); else if Y_POS > Y_CRD then PUT_LINE("Ball is to the North-west"); else PUT_LINE("Ball is to the South-west"); end if; end if; end BALL_TO_WEST; -------------------------------------------------------- -- Ball to east procedure BALL_TO_EAST(Y_CRD, Y_POS : COORDINATE) is begin if Y_POS = Y_CRD then PUT_LINE("Ball is to the East"); else if Y_POS > Y_CRD then PUT_LINE("Ball is to the North-east"); else PUT_LINE("Ball is to the South-east"); end if; end if; end BALL_TO_EAST; -------------------------------------------------------- begin if X_POS = X_CRD then RETURN(CORRECT_X_COORD(Y_CRD,Y_POS)); else if X_POS < X_CRD then BALL_TO_WEST(Y_CRD,Y_POS); else BALL_TO_EAST(Y_CRD,Y_POS); end if; RETURN(FALSE); end if; end CHECK_GUESS; ---------------------------------------------------------------- begin -- Locater ball LOCATE_BALL(X_POSN,Y_POSN); -- Loop through maximum of 8 guesses while NUM_GUESSES <= MAX_NUM_GUESSES loop PUT_LINE("Input your guess (X and Y coordinates between 1 and 256): "); GET(X_COORD); GET(Y_COORD); exit when (CHECK_GUESS(X_COORD,Y_COORD,X_POSN,Y_POSN)); NUM_GUESSES := NUM_GUESSES+1; end loop; -- Final output if NUM_GUESSES = 9 then PUT_LINE("Program has won"); else PUT_LINE("You have won"); end if; end SPOT_THE_BALL;
TEST CASE | RESULT | |
---|---|---|
X_COORD | Y_COORD | |
0 | * | CONSTRAINT_ERROR |
1 | 0 | CONSTRAINT_ERROR |
2 | 1 | OK |
2 | 2 | OK |
255 | 255 | OK |
255 | 256 | OK |
256 | 257 | CONSTRAINT_ERROR |
257 | * | CONSTRAINT_ERROR |
First note that we do not need to test the package - it is assumed that this has been designed, implemented and tested separately by the developer and that consequently it is fully operational.
BVA and limit Testing: The input coordinates should be tested as shown in the table to the right using BVA and limit testing techniques.
Path Testing: The DFD given above identifies the paths through the system. Test cases should be generated to ensure that every path is exercised. A seed of 123 wile produce X_POS = 235 and Y_POS = 109. Using this seed the following sequence of "guesses" will test all the paths in code which may influence the "advice". Note that after 8 iterations the loop is complete (program has won). The final path, where a correct answer is guessed is exercised bo the loop test cases considered next.
"GUESSES" | RESULT | |
---|---|---|
X_COORD | Y_COORD | |
250 | 250 | Ball is to the South-west |
250 | 109 | Ball is to the West |
250 | 10 | Ball is to the North-west |
235 | 250 | Ball is to the South |
235 | 10 | Ball is to the North |
10 | 250 | Ball is to the South-east |
10 | 109 | Ball is to the East |
10 | 10 | Ball is to the North-east Program has won |
Loop Testing: we should also test that the loop is working appropriately. In this case we should test the loop at 1, 2 4 7 and 8 iterations (the last has already been taken into account as part of the proposed path test cases). Using the same seed and the inputs given above, and by imposing a correct guess where appropriate, we cause the loop to repeat 1, 2, 4 and 7. Thus:
"GUESSES" | RESULT | |
---|---|---|
X_COORD | Y_COORD | |
235 | 109 | bull's-eye! You have won |
"GUESSES" | RESULT | |
---|---|---|
X_COORD | Y_COORD | |
250 | 250 | Ball is to the South-west |
235 | 109 | bull's-eye! You have won |
"GUESSES" | RESULT | |
---|---|---|
X_COORD | Y_COORD | |
250 | 250 | Ball is to the South-west |
250 | 109 | Ball is to the West |
250 | 10 | Ball is to the North-west |
235 | 109 | bull's-eye! You have won |
"GUESSES" | RESULT | |
---|---|---|
X_COORD | Y_COORD | |
250 | 250 | Ball is to the South-west |
250 | 109 | Ball is to the West |
250 | 10 | Ball is to the North-west |
235 | 250 | Ball is to the South |
235 | 10 | Ball is to the North |
10 | 250 | Ball is to the South-east |
10 | 109 | Ball is to the East |
235 | 109 | bull's-eye! You have won |
Example Problem Spot The Ball.
Use of packages is one way in which different parts of an Ada program can be compiled seperatly. An alternative is to use subunits. Here the programmer must "tell" the Ada compiler that the body of some procedure/function will be written and compiled separately by including the keywod separate after the subprogram specification:
procedure <NAME(PARAMETERS)> is separate;
or:
function <NAME (PARAMETERS)> return <RETURN TYPE> is separate;
When the body of the subunit is later written we must indicate the program in which it is to be used. For example if the subunit is to be used in a program A, then the following must be included in the subunit code:
seperaste (A) <SUBPROGRAM BODY>
For example:
-- SUBUNIT EXAMPLE -- 19 December 1997 -- Frans Coenen -- Dept Computer Science, University of Liverpool with TEXT_IO; use TEXT_IO; procedure SUBUNIT_EXAMPLE is package INTEGER_INOUT is new INTEGER_IO(INTEGER); use INTEGER_INOUT; NUM_1, NUM_2 : INTEGER ; ------------------------------------------------------------ -- TOTAL PROCEDURE procedure TOTAL(N1, N2 : INTEGER) is separate; ---------------------------------------------------------- -- TOP LEVEL begin PUT_LINE("Input two integers"); GET(NUM_1); GET(NUM_2); TOTAL(NUM_1,NUM_2); end SUBUNIT_EXAMPLE;
Note the use of the key word separate. The declaration of TOTAL, in the above example, is known as a stub. The program can now be compiled. However, before it can be run we must write and compile the subunit TOTAL:
-- TOTAL -- 19 December 1997 -- Frans Coenen -- Dept Computer Science, University of Liverpool with TEXT_IO; use TEXT_IO; separate (SUBUNIT_EXAMPLE) ------------------------------------------------------------ -- TOTAL PROCEDURE procedure TOTAL(N1, N2 : INTEGER) is saparate; begin PUT(N1+N2); end TOTAL; ----------------------------------------------------------
Since TOTAL is to be inserted into the procedure SUBUNIT_EXAMPLE, this name is written in brackets after the word separate. The initial program can now be executed.
From the above it can be seen that the technique of working with subunits can be used naturally in connection with top down design. The disadvantages of subunits are that they tend to be very specialised and are only applicable to a specific program. Generally speeking this author would recomend the use of user defined packages whereever separate compilation of parts of a programme would seem to be either desirable or appropriate.
Created and maintained by Frans Coenen. Last updated 11 October 1999