-*- Mode: Text; Fonts: Ct18b -*- 2.2 Overview A complete Ada program is written as a series of units. The outermost layer of units will include a main program and possible additional library units which can be thought of as providing services to the main program. The main program takes the form of a procedure of an appropriate name. The service library units can be other subprograms (procedures or functions) but they are more likely to be packages. A package is a group of related items which may be other entities as well as subprograms. Suppose we wish to write a program to print out the square root of some number such as 2.5. We can expect various library units to be available to provide us with a means of computing square roots and producing output. Our job is merely to write a main program to use these services as we wish. For the sake of argument we will suppose that the square root can be obtained by calling a function in our library whose name is SQRT. In addition we will suppose that our library includes a package called SIMPLE_IO containing various simple input-output facilities. These facilities might include procedures for reading numbers, printing numbers, printing strings of characters and so on. Our program might look like with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPLE_IO; begin PUT(SQRT(2.5)); end PRINT_ROOT; The program is written as a procedure called PRINT_ROOT preceded by a with clause giving the names of the library units which it wishes to use. The body of the procedure contains the single statement PUT(SQRT(2.5)); which calls the procedure PUT in the package SIMPLE_IO with a parameter which in turn is the result of calling the function SQRT with the parameter 2.5. Writing use SIMPLE_IO; gives us immediate access to the facilities in the package SIMPLE_IO. If we had omitted this use clause we would have had to write SIMPLE_IO.PUT(SQRT(2.5)); in order to indicate where PUT was to be found. We can make our program more useful by making it read in the number whose square root we require. It might then become with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPLE_IO; X: FLOAT; begin GET(X); PUT(SQRT(X)); end PRINT_ROOT; The overall structure of the procedure is now clearer. Between is and begin we can write declarations and between begin and end we write statements. Broadly speaking declarations introduce the entities we wish to manipulate and statements indicate the sequential actions to be performed. We have now introduced a variable X of type FLOAT which is a predefined language type. Values of this type are a set of certain floating point numbers and the declaration of X indicates that X can have values only from this set. In our example a value is assigned to X by calling the procedure GET which is also in our package SIMPLE_IO. Some small scale details should be noted. The various statements and declarations all terminate with a semicolon; this is unlike some other languages such as Algol and Pascal where semicolons are separators rather than terminators. The program contains various identifiers such as procedure, PUT and X. These fall into two categories. A few (62 in fact) such as procedure and is are used to indicate the structure of the program; they are reserved and can be used for no other purpose. All others, such as PUT and X, can be used for whatever purpose we desire. Some of these, notably FLOAT in our example, have a predefined meaning but we can nevertheless reuse them if we so wish although it might be confusing to do so. For clarity in this book we use lower case bold letters for the reserved identifiers and upper case letters for the others. This is purely a notational convenience; the language rules do not distinguish the two cases except when we consider the manipulation of characters themselves. Note also how the underscore character is used to break up long identifiers into meaningful parts. Finally observe that the name of the procedure, PRINT_ROOT, is repeated between the final end and the terminating semicolon. This is optional but is recommended in order to clarify the overall structure although is obvious in a small example such as this. Our program is still very simple; it might be more useful to enable it to cater for a whole series of numbers and print out each answer on a separate line. We could stop the program somewhat arbitrarily by giving it a value of zero. with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPLE_IO; X: FLOAT; begin PUT("Roots of various numbers"); NEW_LINE(2); loop GET(X); exit when X = 0.0; PUT("Root of"); PUT(X); PUT("is); if X < 0.0 then PUT("not calculable"); else if; NEW_LINE; end loop; NEW_LINE; PUT("Program finished"); NEW_LINE; end PRINT_ROOTS; The output has been enhanced by the calls of further procedures NEW_LINE and PUT in the package SIMPLE_IO. A call of NEW_LINE will output the number of new lines specified by the parameter; the procedure NEW_LINE has been written in such a way that if no paramter is supplied then a default value of one is assumed. There are also calls of PUT with a string as argument. This is in fact a different procedure from the one which prints the number X. The compiler knows which is which because of the different types of parameters. Having more than one procedure with the same name is known as overloading. Note also the form of the string; this is a situation where the case of the letters does matter. Various new control structures are also introduced. The statements between loop and end loop are repeated until the condition X = 0.0 in the exit statement is found to be true; when this is so the loop is finished and we immediately carry on after end loop. We also check that X is not negative; if it is we output the message 'not calculable' rather than attempting to call SQRT. This is done by the if statement; if the condition between if and then is true, then the statements between then and else are executed, otherwise those between else and end if are executed. The general bracketing structure should be observed; loop is matched by end loop and if by end if. All the control structures of Ada have this closed form rather than the open form of Pascal which can lead to poorly structured and incorrect programs. We might well ask what would have happened if we had not tested for a negative value of X and consequently called SQRT with a negative argument. Assuming that SQRT has itself been written in an appropriate manner then it clearly cannot deliver a value to be used as the parameter of PUT. Instead an exception will be raised. The raising of an exception indicates that something unusual has happened and the normal sequence of execution is broken. In our case the exception might be NUMERIC_ERROR. If we did nothing to cope with this possibility then our program would be terminated and no doubt the Ada Programming Support Environment (APSE) will give us a rude message saying that our program has failed and why. We can, however, look out for an exception and take remedial action if it occurs. In fact we could replace our conditional statement by begin PUT(SQRT(X)); exception when NUMERIC_ERROR => PUT("not calculable"); end; We will now consider in outline the possible general form of the function SQRT and the package SIMPLE_IO that we have been using. The function SQRT will have a structure similar to that of our main program; the major difference will be the existence of parameters function SQRT(F:FLOAT) return FLOAT is R: FLOAT; begin --compute value of SQRT(F) in R return R; end SQRT; We see here the description of the formal parameters (in this case only one) and the type of the result. The details of the calculation are represented by the comment which starts with a double hyphen. The return statement is the means by which the result of the function is indicated. Note the distinction between a function which returns a result and is called as part of an expression and a procedure which does not have a result and is called as a single statement. The package SIMPLE_IO will be in two parts, the specification which describes its interface to the outside world and the body which contains the details of how it is implemented. If it just contained the procedures that we have used, its specification might be package SIMPLE_IO is procedure GET(F:out FLOAT); procedure PUT(F:in FLOAT); procedure PUT(S:in STRING); procedure NEW_LINE(N:in INTEGER:=1); end SIMPLE_IO; The parameter of GET is an out parameter because the effect of calling GET as in GET(X); is to transmit a value out from the procedure to the actual parameter X. The other parameters are all in parameters because the value goes in to the procedures. Only part of the procedures occurs in the package specification; this part is known as the procedure specificatin and just gives enough information to enable the procedures to be called. We see also the two overloaded specifications of PUT, one with a parameter of type FLOAT and the other with a parameter of type STRING. Finally, note how the default value of 1 for the parameter of NEW_LINE is indicated. The package body for SIMPLE_IO will contain the full procedure bodies plus any other supporting material needed for their implementation and which is naturally hidden from the outside user. In vague outline it might look like with INPUT_OUTPUT; package body SIMPLE_IO is ... procedure GET(F: out FLOAT) is ... begin ... end GET; -- other procedures similarly end SIMPLE_IO; The with clause shows that the implementation of the procedures in SIMPLE_IO uses the more general package INPUT_OUTPUT. It should also be noticed how the full body of GET repeats the procedure specification which was given in the corresponding package specification. (The procedure specification is the bit up to but not including is.) The example in this section has briefly revealed some of the overall structure and control statements of Ada. Details of data types will be discussed in due course. One purpose of this section has been to stress that the idea of packages is one of the most important concepts in Ada. A program should be conceived as a number of components which provide services to and receive services from each other. In the next few chapters we will of necessity be dealing with the small scale features of Ada but in doing so we should not lose sight of the overall structure which we will return to in Chapter 8. Perhaps this is an appropriate point to mention the special package STANDARD. This is a package which exists in every implementation and contains the declarations of all the predefined identifiers such as FLOAT and NUMERIC_ ERROR. We can assume access to STANDARD automatically and do not have to give its name in a with clause. It is discussed in detail in Appendix 2. Exercise 2.2 1 In practice it is likely that the function SQRT will not be in the library on its own but in a package along with other mathematical functions. Suppose this package has the identifier SIMPLE_MATHS and other functions LOG, EXP, SIN and COS. By analogy with the specification of SIMPLE_IO, write the specification of such a package. How would our PRINT_ROOTS need to be changed?