Begin by making an ada subdirectory in labs/09. Then copy the files Makefile, name_tester.adb, name_package.ads, and name_package.adb from the labs/09/ada/ class directory into your new directory. Use a text editor to open these files, and take a moment to study their contents. The file name_tester.adb should contain our work from last week, while the files name_package.ads and name_package.adb should be empty, except for documentation. Customize the opening documentation of name_tester.adb and save your changes. Then compile it using gnatmake and run it, to verify that it works correctly at the outset.
Ada Modules. Unlike C-family languages, Ada provides a distinct construct to represent a module. Ada calls its construct the package. An Ada package consists of two parts:
The general form of an Ada package specification is
PackageSpec ::= package identifier is
PublicSection
PrivateSection
end identifier;
PublicSection ::= DeclarationList
PrivateSection ::= private DeclarationList | Ø
DeclarationList ::= Declaration ; DeclarationList | Ø
Declaration ::= ConstantDec | VariableDec | TypeDec | SubprogDec
SubprogDec ::= FunctionDec | ProcedureDec
FunctionDec ::= function identifier (ParameterDecs) return Type
ProcedureDec ::= procedure identifier(ParameterDecs)
As in C/C++, an Ada package is stored in two separate files,
with declarations in the package specification and definitions in the
package body.
The syntax of the package body can be specified as follows:
PackageBody ::= package body identifier is
DefinitionList
end identifier;
DefinitionList ::= Definition ; DefinitionList | Ø
Definition ::= ConstantDec | VariableDec | TypeDec | SubprogramDef
Like a C-family "module", an Ada package is commonly used to store an
abstract data type, with type information (a type and operation declarations)
in the package specification, and implementation details
(operation definitions) in the package body.
A package thus permits the code stored within it to be used by multiple programs, accomplishing the reuseability goal. Using this BNF as a pattern, add a "stub" package specification named Name_Package to name_package.ads, and add a "stub" package body named Name_Package to name_package.adb.
To accomplish our information-hiding goal, the Ada package specification has two sections. Any identifiers declared in the PublicSection of a package specification can be accessed by a user of the package. Any identifiers declared in the PrivateSection of a package specification can only be accessed within the package itself. Any identifiers defined in a package body are only accessible within the package body.
Clearly we want to declare our subprograms Init(), PrintName(), First(), Middle(), Last(), and FullName() in the public section of name_package.ads, so take a few moments to copy-and-paste the documentation and headings of each subprogram into the public section of the Name_Package specification. (You will have get some of these from last week's project.) Then modify them as necessary to convert those headings into subprogram declarations. Don't forget the semicolon following each Declaration!
By contrast, the identifiers NAME_SIZE, and myFirst, myMiddle, and myLast are implementation details, and so should be kept private. To accomplish this, cut the declarations of NAME_SIZE and Name from name_tester.adb and paste them into the private section of the Name_Package specification.
However, this creates a problem. When we place the declaration of Name in the private section of the package, the identifier Name also becomes private, voiding our goal of making it reuseable! But if we declare Name in the public section of the package, then all of its internal (implementation) details become public too, voiding our goal of information hiding!
To resolve this "Catch-22" situation, Ada permits a type-identifier to be declared in one place, and its internal details specified in another. To illustrate, if we declare Name as follows:
type Name is private;as the first declaration in the public section of our package specification, then users of the package will be able to use the identifier Name. But because the internal structure of Name is specified in the private section, the compiler will hide its internals details from users of the package. The identifier Name is thus public, at the same time as its "data members" are kept private. Note that this declaration needs to appear before the subprogram declarations, because it declares the identifier Name which those subprogram declarations use, and Ada requires that an identifier must be declared before it can be used.
The final step is to cut the definitions of the Name operations and their documentation from their place in name_tester.adb and paste them into the DefinitionList of the package body in Name_Package.adb. When you have done this, your package is nearly complete.
The Put() method in our Name_Package outputs the string returned by Full_Name(). In order for this to build correctly, you will need to add
with Ada.Text_IO; use Ada.Text_IO;before the beginning of your Name_Package body.
Using a Package. Now that we have the type and its operations stored in a package, we are ready to use them in our program. In order to use a package, a program must supply the with directive:
Program ::= WithDirectives
UseDirectives
procedure identifier is
...
end identifier;
WithDirectives ::= WithDirective ; WithDirectives | Ø
WithDirective ::= with IdentifierList
UseDirectives ::= UseDirective ; UseDirectives | Ø
UseDirective ::= use IdentifierList
where each identifier is the name of a package to be used.
A with directive tells the compiler that our program will
be accessing a given package.
Using this information, add Name_Package to the
IdentifierList of the with directive
at the beginning of name_tester.adb.
However, the with directive by itself requires a program to qualify the identifiers from the package by preceding them with the name of the package and dot notation. For example:
aName : Name_Package.Name; ... Name_Package.Init(aName, "John ", "Paul ", "Jones "); ...This can be largely circumvented through the use directive. When a package name is given in the IdentifierList of a use directive, then the public identifiers from that package do not have to be qualified. Using this information, add Name_Package to the IdentifierList of the use directive at the beginning of name_tester.adb.
One Last Problem. Under normal circumstance, we would be ready to compile and execute our program. However, the declaration:
aName : Name;generates an unexpected compilation error. (To see the error, use make, as described below.) The problem is that the identifier Name is publicly declared in both of the packages Name_Package and Ada.Text_IO (it appears as the name of a subprogram in the latter). While Ada allows the same name to be overloaded for different subprograms, it does not allow a subprogram and a type to share the same name.
We could resolve this by renaming our Name identifier something different, such as Name_Type, in each of our three files. However it's a bit easier to use qualification to solve the problem. If we tell the compiler which Name we are using:
aName : Name_Package.Name;then we remove all ambiguity and the compiler accepts our declaration.
Note that it is not necessary to qualify the calls to the Name operations, because of our uses directive. We only have to qualify the identifier Name because it happens to also be declared in Ada.Text_IO.
Separating Compilation From Linking. As with the GNU C/C++ compiler, GNAT Ada source files can be separately compiled using the -c switch. When a GNAT Ada source file named F.adb is separately compiled, two files are created:
gcc -c name_tester.adbwill separately compile the program, creating name_tester.o and name_tester.ali. Likewise, the (simplified) command:
gcc -c name_package.adbwill separately compile the package, creating name_package.o and name_package.ali. Once the program and the package have both been compiled, the (simplified) commands:
gnatbind name_tester.ali gnatlink name_tester.aliread the symbol file for our source program, and bind and link the two object files to produce an executable named name_tester. (Study the Makefile, to see how it ensures these actions and dependencies.)
Testing. To test the correctness of your reorganized work, use the provided Makefile and the make utility, to separately compile and link your files. Then run your program to make certain it passes all the tests.
Turn In. When everything works correctly, create a script file in which you use cat to display the contents of each of your three source files, use make to translate them, and then run the program to show that it passes the tests.
That concludes the Ada portion of this lab.
Calvin > CS > 214 > Labs > 09 > Ada