CS 214 Lab 7: Ada-95


Begin by copying the program skeleton namer.adb from the course directory into your new directory. Then use emacs to open the file, and take a moment to study it.

In keeping with the (imperative) Algol/Pascal family languages, Ada's aggregate type is called the record. Unlike a class, an Ada record can store data, but not operations. Our Name type will thus be a simple "wrapper" that encapsulates three string members.

Declaring a Name Type

Recall that the Ada String type is really an array of characters. As a result, its size must be specified at some point. To specify the size of each part of a name, we will declare a constant. Recalling that an Ada-95 constant can be declared using
   ConstantDec      ::=   identifier : constant Type := Expression ;
begin by declaring a named constant NAME_SIZE equal to 8 as the size of a string to store a name. Then use the following BNF to declare a record type named Name (following the declaration of NAME_SIZE). The general pattern for an Ada record-type declaration is
   RecordDec        ::=   type identifier is
                               record
                                  FieldList
                               end record;
   FieldList        ::=   VariableDeclaration
                          MoreFields
   MoreFields       ::=   Ø | Declaration
                              MoreFields
where the VariableDeclaration can be any Ada variable declaration. To supply the "fields" of Name, we need to declare three "string" fields:
   StringDec        ::=   IdList : String( Range );
   IdList           ::=   identifier MoreIds
   MoreIds          ::=   , identifier MoreIds | Ø
use this information to declare three string components MyFirst, MyMiddle and MyLast within Name, each a String indexed using the range 1..NAME_SIZE.

Defining Name Operations

Unlike a class, an Ada record can only store data -- not operations. Because of this, we must implement each Name operation as an "external" subprogram that operates on a Name it receives via its parameters. We now consider each operation in turn.

Initialization. When declared within a program (as opposed to a library), Ada provides no constructor mechanism to automatically initialize the fields of a record. Instead, a subprogram can be defined, and then called to explicitly perform the initialization. Such a subprogram must receive the record-object to be initialized, as well as the initialization values, and then assign the initialization values to the appropriate fields of the object. Since such a subprogram changes the value of its argument (as opposed to returning a value), it should be defined as a procedure and not a function.

As we have seen before, Ada subprograms can be declared in a procedure's declaration section, along with constants, types, variables, and so on:

   AdaProgram   ::=       procedure identifier is
                             DeclarationSection
                          begin
                             StatementSection
                          end identifier
We can thus begin by defining a stub procedure prior to the begin in namer.adb:
   procedure Init (TheName : out Name; First, Middle, Last : in String)

   end Init;
Note that information flows out of the procedure through parameter TheName, and into the procedure through parameters First, Middle, and Last. The modes of these parameters are thus declared accordingly.

To fill in the body of our stub, we must be able to access the fields of an aggregate type object. Like most other languages, Ada uses the dot (.) operator for this operation:

   Expression        ::=   identifier.identifier
where the left identifier is the name of the aggregate object, and the right identifier is the field within the aggregate. Using these observations, we can complete our Init() procedure:
  procedure Init(TheName: out Name;
                 First, Middle, Last : in String) is

  begin
    TheName.MyFirst := First;
    TheName.MyMiddle := Middle;
    TheName.MyLast := Last;
  end Init;
Given such a procedure, our program can now execute
   Init(aName, "John    ", "Paul    ", "Jones   ")
and the fields withing aName will be initialized to the given arguments. Note that the size of the string literals passed as arguments must match the size of the fields to which they are assigned, or a compilation error will result, because Ada string variables are (strongly typed) arrays. Test the correctness of this code, and continue when it works properly.

Output. To verify that our initialization succeeded, an output subprogram is useful. Such a subprogram must receive the Name object to be displayed from its caller, and then display each field using the Put() command:

   OutputStatement ::=   Put( String );
Using this information, define a Put() procedure that displays each of the fields of a Name on the same line, with a space separating each field. Note that since information flows into our procedure via the Name parameter, but not out (i.e., back to the caller) the procedure's parameter should be defined with mode in. Test your procedure using the supplied program (you will have to "uncomment" the line that calls Put()), and continue when it is correct.

Accessors. To access the first-name field within a Name aggregate, we can write a simple function First() that, given a Name object, returns its MyFirst field. Recalling that an Ada function returns a value via a return statement, such a function can be defined by:

   function First(TheName : in Name) return String is
   begin
      return TheName.MyFirst;
   end First;
Add this definition at the appropriate place in the program; then test its correctness. Then add similar definitions for Middle() and Last(). Test these new definitions and continue when they work correctly.

String Conversion. Our final operation is our FullName() subprogram which, given a Name object, returns a corresponding string. Because of this, it should be written as a function whose return-type is a String. Using this information, create a stub for a function named FullName().

To fill in the body of the stub, we must concatenate together the fields of the Name parameter of the function, with intervening spaces. Recalling that Ada uses the & symbol as a concatenation operator:

   Expression          ::= StringExpr & StringExpr
use this information to define FullName() to return the string equivalent of the full name of its Name parameter.

When you have finished, use the supplied program to test your work. When it works correctly, use script to create a file in which you cat your file, compile it, and show that it works correctly. Then print a hard copy of the resulting script file.


That concludes this part of the lab. To go to a different part, choose one of the links below:


Last week's exercise

Next week's exercise


This page maintained by Joel Adams.