Begin by copying the program skeleton name_tester.adb from the course directory into your new directory. If you have not already done so, copy the Makefile from the course directory and take a moment to view its contents. Note that it includes ada as a target, the ada target depends on name_tester, name_tester depends on name_tester.adb, and the command following that tells make how to create name_tester from name_tester.adb. Note also that it has a clean target so that the command:
make cleanwill clean out all the extraneous files you create during this project, leaving on the source files and the Makefile, which can be used to rebuild the binary program files any time they are needed.
Next, use a text editor to open the Ada file, and take a moment to study it.
From a command-terminal in your project directory, enter:
make adaand you should see name_tester.adb build. Then run the program and verify that it runs correctly.
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.
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. Rebuild and rerun your program to make certain this much is correct before continuing.
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 data members (aka "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. Again, rebuild your program and make certain this much is correct before continuing.
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.
Uncomment the call to Init() in the name_tester procedure. Rebuild the program. What happens? To fix the problem, we need to define an Init() subprogram.
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 name_tester.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.identifierwhere 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's string variables are (strongly typed) arrays.
Check what you have written, and continue when it builds and runs without any errors.
Accessors. To check that our Init() procedure is working correctly, we need to be able to access the fields of a Name aggregate. Uncomment the first pragma Assert directive in procedure name_tester; then rebuild your program. What happens?
That first pragma Assert tries to access the first-name field within a Name aggregate using a procedure called getFirst(). To "pass this test", we can write a simple function getFirst() 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 getFirst(TheName : in Name) return String is
begin
return TheName.MyFirst;
end getFirst;
Add this definition at the appropriate place in the program;
then rebuild and rerun your program to test its correctness.
Then uncomment the next two pragma Assert statements and add similar definitions for getMiddle() and getLast().
Note that Ada's Assert() requires two arguments:
gnatmake name_tester.adb -gnata(which our Makefile provides). Continue when your program compiles and runs correctly.
String Conversion. Uncomment the final pragma Assert statement, and rebuild your program. What happens?
To pass this test, we need to define a getFullName() subprogram that, 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 getFullName().
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 & StringExpruse this information to define getFullName() to return the string equivalent of the full name of its Name parameter. Then rebuild and rerun your program. Continue when it passes this test.
Output. For debugging (and other) purposes, an output subprogram is useful. In Ada, output subprograms are usually named Put(), so uncomment the Put() statement and rebuild the program. What happens?
As indicated in this test, a Put() subprogram for our Name type must receive the Name object to be displayed from its caller, and then display each field using the Put() command for string values:
OutputStatement ::= Put( String );Using this information, define a Put() procedure that, given a Name object, displays each of the fields of that 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.
When your program passes all of the tests and Put() correctly displays a name, use script to create an script.ada file in which you cat your source file, compile it, and run it.
That concludes the Ada portion of this lab.
Calvin > CS > 214 > Labs > 08 > Ada