The Basics

The Basics

Before you can start programming in Ada, you must make certain that you have an Ada compiler installed on your computer. If you’re using Linux or any other Unix flavored operating system, it’s almost certain that you have such a compiler installed already. You can check if this is the case with the following command:

$ gcc -v

GCC is a compiler made by GNU. It’s installed on more or less all Linux/Unix systems. On my system, the result of the above command is this:

Reading specs from /usr/lib64/gcc/x86_64-slackware-linux/4.5.2/specs
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-slackware-linux/4.5.2/lto-wrapper
Target: x86_64-slackware-linux
Configured with: ../gcc-4.5.2/configure --prefix=/usr --libdir=/usr/lib64 --mandir=/usr/man --infodir=/usr/info --enable-shared --enable-bootstrap --enable-languages=c,c++,ada,fortran,java,objc,lto --enable-threads=posix --enable-checking=release --with-system-zlib --with-python-dir=/lib64/python2.6/site-packages --disable-libunwind-exceptions --enable-__cxa_atexit --enable-libssp --enable-lto --with-gnu-ld --verbose --enable-multilib --target=x86_64-slackware-linux --build=x86_64-slackware-linux --host=x86_64-slackware-linux
Thread model: posix
gcc version 4.5.2 (GCC)

Note the part that says –enable-languages=c,c++,ada,fortran,java,objc,lto. Obviously you must to have ada in there in order to be able to compile Ada programs.

Next you can check your gnatmake:

$ gnatmake --version

You should see something like this:

GNATMAKE 4.5.2
Copyright (C) 1995-2010, Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

If you have similar output, then you can be sure you have an Ada ready environment.

If you don’t have an Ada compiler on your computer, or the one you have is very old, you can download the excellent GNAT GPL Package, which contains both an Ada compiler and an excellent Ada IDE. The scope of using the GNAT Programming Studio (GPS) IDE is far too large for this short article. I can say, though, that the documentation is excellent and both the GPS IDE and the GNAT GPL compiler are very easy to get up and running.

The classic “Hello, world!” program

With the basic compiler stuff out of the way, lets get on with some actual Ada programming. The first snippet of code is the ever famous “Hello, world!” example. Create a file named hello.adb and enter this:

with Ada.Text_IO;
 
procedure Hello is
begin
   Ada.Text_IO.Put_Line ("Hello, world!");
end Hello;

To compile the code, do this:

$ gnatmake hello.adb

Executing the hello program, should result in this output:

Hello, world!

Simple and straightforward. But how does it work?

In line 1 the Ada.Text_IO library unit is added to the program by using a with clause. This makes all the types, functions and procedures of the Ada.Text_IO package available to the main procedure Hello.

The statements of the program are written between the begin and end keywords.

The actual output is done by calling one of the Ada.Text_IO procedures: Put_Line.

Another version of the “Hello, world!” program is shown here:

with Ada.Text_IO;
 
procedure Hello is
   use Ada.Text_IO;
 
   HW : String := "Hello, world!";
begin
   PutLine (HW);
end Hello;

There are some important changes to note in this second edition of our “Hello, world!” program. We see that a use clause has been added. With this in place, all the types, procedures and functions in the Ada.Text_IO library can be used without having to prefix them with Ada.Text_IO. This can be very handy, but don’t overuse it, as too many use clauses can make your program very unreadable.

The next change is the addition of a variable declaration. In Ada, declarations are written between is and begin, and statements are written between begin and end:

procedure Some_Program is
-- Declarations here
begin
-- Statements here
end Some_Program;

Declarations, in general, are things like variables, constants, functions and procedures, whereas statements are the actions the program perform.

The variable HW is of type String with a preset value of Hello, world!. The Ada String type is actually just an array of characters, so it can be manipulated as any other array.

We output the value of HW using the Put_Line procedure, and with the use Ada.Text_IO clause in place, we no longer need to prefix Put_Line with Ada.Text_IO.

Lets up the complexity of the program a little, this time touching on the fact that the Ada String type is nothing but a simple array of characters:

with Ada.Text_IO;
 
procedure Hello is
   use Ada.Text_IO;
   HW : String := "Hello, world!";
begin
   for i in 1 .. HW'Length loop
      Put (HW (i));
   end loop;
end Hello;

The output of the above program is, as expected:

Hello, world!

Here we encounter one of the Ada loop structures: The for loop. The condition i in 1 .. HW’Length is evaluated on each pass of the loop, and as long it evaluates to True, the loop continues. It ends as soon as the condition evaluates to False.

The condition in this loop introduces a new syntax: n .. n. In this case the first n is set at 1, and the second n is set to whatever the length is of the HW string, in this case 13. So the loop goes on as long as i is in the range 1 .. 13.

We’ve switched from using Put_Line to Put. The difference between these two should be obvious: Put_Line outputs what you tell it to and then adds a newline. Put just outputs what you tell it to – it does not add a newline.

We’ve made this change, because HW (i) return the i position, or index, of the HW character array, instead of the entire HW String, and obviously we don’t want newlines after each character in “Hello, world!”.

The 1 .. HW’Length construct can be shortened using the ‘Range attribute. The next program will show this, and while we’re at it, we’ll also show how to reverse the for loop from ascending to descending order:

with Ada.Text_IO;
 
procedure Hello is
   use Ada.Text_IO;
   HW : String := "Hello, world!";
begin
   for i in reverse HW'Range loop
      Put (HW (i));
   end loop;
end Hello;

The output of this program is:

!dlrow ,olleH

As you can see, the ‘Range attribute completely replace the n .. n construct, and the reverse keyword is all it takes to change the loop from ascending to descending order, making the code even easier to read. This is one of the great strengths of Ada: It is a very readable language.

So far we’ve touched upon two of the many Ada attributes that is available, ‘Length and ‘Range. Attributes are a sort of functions that can be applied to types and variables. There are many attributes, far too many to show here, but I’ll show a few of them in the following example:

with Ada.Text_IO;
with Ada.Integer_Text_IO;
 
procedure Hello is
   use Ada.Text_IO;
   use Ada.Integer_Text_IO;
 
   Truth : Integer := 42;
   HW : String := "Hello, world!";
   Foo : array (range 5 .. 7) of Character := (5 => 'B',
                                               6 => 'a',
                                               7 => 'r');
begin
   Put (HW'First, 0); -- First index in the HW string
   New_Line;
 
   Put (HW'Last, 0); -- Last index in the HW string
   New_Line;
 
   Put (HW'Length, 0); -- Length of the HW string
   New_Line;
 
   Put (Foo'First, 0); -- First index in the Foo array
   New_Line;
 
   Put (Foo'Last, 0); -- Last index in the Foo array
   New_Line;
 
   Put (Foo'Length, 0); -- Length of the Foo array
   New_Line;
 
   for i in Foo'Range loop -- Range of the Foo array
      Put (Foo (i));
   end loop;
   New_Line;
 
   Put_Line ("The truth is " & Truth'Img); -- Image of the Truth variable
   Put ("The truth is ");
   Put (Truth, 0);
   New_Line;
 
   Put (HW'Size, 0); -- The HW string size in bits
   New_Line;
 
   Put (Foo'Size, 0); -- The Foo array size in bits
   New_Line;
 
   Put (Truth'Size, 0); -- The Truth integer size in bits
end Hello;

Output from this program is:

1
13
13
5
7
3
Bar
The truth is 42
The truth is 42
104
24
32

This short example barely scratches the surface, but it should be enough to give you a general idea of the attribute concept. There are some ~100 attributes to master in Ada, so the subject of attributes almost warrants a book on its own – it is far beyond the scope of this page.

The Party program

Ada is a statically and strongly typed language. This feature of Ada protects the programmer from many classic programming errors, as you’re forced to really think about the data your program is manipulating. We’ve already touched a bit on the Integer, String and Character types, and with the Party program, we will add some more types to the mix, a control structure, a new string type, renaming and a block.

So without further ado, here’s the Party program:

with Ada.Text_IO;
with Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;
 
procedure Party is
   use Ada.Text_IO;
   use Ada.Strings.Unbounded;
   use Ada.Strings.Unbounded.Text_IO;
 
   type Gender is (Female, Male);
   type Colors is (Black, Brown, Blond, Grey);
   subtype Years is Natural range 0 .. 130;
   type Characteristics is (Brains, Looks, Humor);
   type Scores is array (Characteristics) of Natural range 0 .. 100;
   type Person is record
      Name      : Unbounded_String;
      Sex       : Gender;
      Hair      : Colors;
      Age       : Years;
      Abilities : Scores := (others => 0);
   end record;
   type Persons is array (1 .. 4) of Person;
   Party_Goers : Persons;
   Total_Score : Natural range 0 .. 300;
begin
   Party_Goers (1) := (Name      => To_Unbounded_String ("Eliza"),
	 	       Sex       => Female,
		       Hair      => Black,
		       Age       => 27,
		       Abilities => (Brains => 50, Looks => 70, Humor => 65));
   Party_Goers (2) := (Name      => To_Unbounded_String ("Murray"),
		       Sex       => Male,
		       Hair      => Blond,
		       Age       => 29,
		       Abilities => (Brains => 72, Looks => 29, Humor => 88));
   Party_Goers (3) := (Name      => To_Unbounded_String ("Sarah"),
	 	       Sex       => Female,
		       Hair      => Brown,
		       Age       => 31,
		       Abilities => (Brains => 97, Looks => 86, Humor => 100));
   Party_Goers (4) := (Name      => To_Unbounded_String ("Billy"),
		       Sex       => Male,
		       Hair      => Grey,
		       Age       => 45,
		       Abilities => (Brains => 93, Looks => 82, Humor => 97));
 
   Put_Line ("Interesting party goers:");
 
   for i in Party_Goers'Range loop
      declare
	 P : Person renames Party_Goers (i);
      begin
	 Put (P.Name);
	 Total_Score := 
           P.Abilities (Brains) + P.Abilities (Looks) + P.Abilities (Humor);
	 if Total_Score >= 270 then
	    Put (" is perfect.");
	 else
	    Put (" is average.");
	 end if;
	 New_Line;
      end;
   end loop;
end Party;

More information about Elizas lacking mental abilities can be found here, or you can try having a conversation with her here.

With Eliza out of the way, we once again turn our attention to the party. Lets go over the entire program, from the top and down, starting with the with and use clauses:

with Ada.Text_IO;
with Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;

Two new Ada libraries are introduced here: Ada.Strings.Unbounded and Ada.Strings.Unbounded.TextIO. They do exactly what their names imply – they allow for handling of strings with an arbitrary length. This is handy in situations where we have no clue as to how long an actual string might be, such as the names in the Party program. One person might be named “Bo”, and another one “Archibald Hubbenfleichsen, Sr.”

So instead of using a sufficiently long fixed length string, and then padding the empty part with white space, Ada gives us the excellent concept of unbounded strings. They can be any length, which makes it a lot easier to handle string data of unknown length. Actually, it is worth noting that there are a bundle of different string types in Ada, each with different strengths and weaknesses.

Lets move on to the next part:

procedure Party is
   use Ada.Text_IO;
   use Ada.Strings.Unbounded;
   use Ada.Strings.Unbounded.Text_IO;
 
   type Gender is (Female, Male);
   type Colors is (Black, Brown, Blond, Grey);
   subtype Years is Natural range 0 .. 130;
   type Characteristics is (Brains, Looks, Humor);
   type Scores is array (Characteristics) of Natural range 0 .. 100;
   type Person is record
      Name      : Unbounded_String;
      Sex       : Gender;
      Hair      : Colors;
      Age       : Years;
      Abilities : Scores := (others => 0);
   end record;
   type Persons is array (1 .. 4) of Person;
   Party_Goers : Persons;
   Total_Score : Natural range 0 .. 300;

There’s a lot of new stuff happening in this declaration, but in actuality much of it is variations over the same theme.

The first type we declare is an enumeration type, which is just a fancy way of saying a list of identifiers.

type Gender is (Female, Male);
type Colors is (Black, Brown, Blond, Grey);

Variables declared to be of type Gender can only take one of the two values Female or Male. The Sex component of the Person record is one such variable. When we declare it as type Gender, we limit the values we can assign to it to the two identifiers declared in the type. If we try to assign anything but Female or Male to Sex, the compiler will complain.

The Colors type is also an enumeration type, only here we define the allowed hair colors. I’ll leave it up to the reader to add Red as an allowed color.

Next we have a subtype declaration:

subtype Years is Natural range 0 .. 130;

Years is declared as a subtype. A subtype is a subset of the allowed values of an existing type. In this case Years is a subtype of Natural, which in turn is a subtype of Integer. A Natural number in Ada is a number that range from 0 to whatever value the Integer’Last attribute returns on a given system. With the subtype Years we further constrain the allowed values to the range 0 .. 130, in effect limiting the values that can be assigned to the Age component of the Person record to an integer ranging between 0 and 130.

Moving on we reach the Characteristics and Scores types:

type Characteristics is (Brains, Looks, Humor);
type Scores is array (Characteristics) of Natural range 0 .. 100;

Characteristics is just like the previous enumeration types, but the Scores type is new. The Scores type is an array of Natural in the range 0 .. 100, with the Characteristics type as index keys. This means that we can address a position in a Scores array with Brains, Looks and Humor. Nothing else is allowed in a Scores array as index keys, meaning that the Abilities component of the Person record is limited to those three index keys. The values of the array have been constrained to the integer range 0 .. 100.

We now come to the Person type, which is a record:

type Person is record
   Name      : Unbounded_String;
   Sex       : Gender;
   Hair      : Colors;
   Age       : Years;
   Abilities : Scores := (others => 0);
end record;

A record is a collection of related information. The components of a record can be of any type, even another record. In the case of the Person record, we have 5 components, each declared as a specific type, eg. the Hair component is of type Colors and the Abilities component is of type Scores.

What this means, is that when an object is declared as a Person, it effectively contains all the components of the Person type.

Note the Abilities component: The usage of the Scores := (others => 0) assignment tells us that the initial values of the Scores array are set to 0.

There are many different kinds of records in Ada. The one used here is the most basic one.

The final three declarations are quite simple and easy to understand:

type Persons is array (1 .. 4) of Person;
Party_Goers : Persons;
Total_Score : Natural range 0 .. 300;

Type Persons is an array of Person records with 4 indices.

Party_Goers is declared to be a Persons array and finally Total_Score is a Natural with the range 0 .. 300.

And with that, the declarations of the Party program are done. With these in place, we’ve tightly defined what kind of data the program accepts. No matter what you do, it’s impossible to break the types, ie. you can’t add a new characteristic to the Abilities array without first having added it to the Characteristics type, and you can’t assign a score of more than 100 to any one of the characteristics. We’ve even defined the max amount of Party_Goers to 4.

With the types out of the way, lets move on to the statements:

begin
   Party_Goers (1) := (Name      => To_Unbounded_String ("Eliza"),
                       Sex       => Female,
                       Hair      => Black,
                       Age       => 27,
                       Abilities => (Brains => 50, Looks => 70, Humor => 65));
   Party_Goers (2) := (Name      => To_Unbounded_String ("Murray"),
                       Sex       => Male,
                       Hair      => Blond,
                       Age       => 29,
                       Abilities => (Brains => 72, Looks => 29, Humor => 88));
   Party_Goers (3) := (Name      => To_Unbounded_String ("Sarah"),
                       Sex       => Female,
                       Hair      => Brown,
                       Age       => 31,
                       Abilities => (Brains => 97, Looks => 86, Humor => 100));
   Party_Goers (4) := (Name      => To_Unbounded_String ("Billy"),
                       Sex       => Male,
                       Hair      => Grey,
                       Age       => 45,
                       Abilities => (Brains => 93, Looks => 82, Humor => 97));

Here we create 4 distinct party goers: Eliza, Murray, Sarah and Billy.

Adding our party goers to the Party_Goers array is a simple matter of telling what numerical position in the array we wish to work on, and then assign the data. The natural thing is to start at position 1, and that’s what we’ve done. Party_Goers (1) indicates just that: Grab the first position in the array, and put some data in it. The first position contains a Person record, so we’ll put some data into each of the components of that specific record, in this case a Name, Sex, Hair, Age and Abilities.

In this specific program we’ve assigned our values using a named aggregate. We could also have used a positional aggregate or even dot notation, as seen here:

-- Named aggregate
Party_Goers (1) := (Name      => To_Unbounded_String ("Eliza"),
                    Sex       => Female,
                    Hair      => Black,
                    Age       => 27,
                    Abilities => (Brains => 50, Looks => 70, Humor => 65));
 
-- Dot notation
Party_Goers (2).Name := To_Unbounded_String("Murray");
Party_Goers (2).Sex := Male;
Party_Goers (2).Hair := Blond;
Party_Goers (2).Age := 29;
Party_Goers (2).Abilities := (72, 29, 88);
 
-- Positional aggregate
PartyGoers (3) := (To_Unbounded_String ("Sarah"),
                   Female,
                   Brown,
                   31,
                   (Brains => 97, Looks => 86, Humor => 100));

The biggest advantage to the named aggregate, is one of consistency. If the Person record is changed at a later state, the compiler will complain if all the named aggregates aren’t changed accordingly. This is not the case with both dot notation and a positional aggregate.

After having created our 4 party goers, we output the headline “Interesting party goers:”, and finally we enter the main loop, where the actual computation is done:

Put_Line ("Interesting party goers:");
 
for i in Party_Goers'Range loop
   declare
      P : Person renames Party_Goers (i);
   begin
      Put (P.Name);
      Total_Score := 
        P.Abilities (Brains) + P.Abilities (Looks) + P.Abilities (Humor);
      if Total_Score >= 270 then
         Put (" is perfect.");
      else
         Put (" is average.");
      end if;
      New_Line;
   end;
end loop;

The for loop is already known to us. There’s nothing new going on here. We simply loop the entire Party_Goers range, in this case 1 .. 4.

But just inside the loop, we stumble on something new: declare.

Ada is a block structured language. Anywhere in the body of a block (between begin and end), you can add new blocks. This can theoretically go on indefinitely. Each block is self-contained, complete with it’s own declarative part and it’s own body of statements.

In the case of the Party program, the block inside the loop does all the actual work. We declare P as what at first appears to be a normal variable, but what in reality is a renamed object. In Ada most things can be renamed, and in this specific example we rename the contents of Party_Goers (n). What this basically means, is that from then on, P equals an actual Person record.

In the body of the block, we encounter a regular if else control structure. We check the “stats” of each person and if those stats are sky-high (Sarah and Billy), we output that the person is perfect. If the person doesn’t live up to our somewhat unhealthy high standards, then he or she is average. It all ends with a New_Line statement, which does exactly what its name implies: It outputs a newline.

If gender matters, then adding a check for either Female or Male is really simple:

Put_Line ("Interesting Male party goers:");
for i in Party_Goers'Range loop
   declare
      P : Person renames Party_Goers (i);
   begin
      if P.Sex = Male then
         Put (P.Name);
         Total_Score := 
           P.Abilities (Brains) + P.Abilities (Looks) + P.Abilities (Humor);
         if Total_Score >= 270 then
            Put (" is perfect.");
         else
            Put (" is average.");
         end if;
         New_Line;
      end if;
   end;
end loop;

Or

Put_Line ("Interesting Female party goers:");
for i in Party_Goers'Range loop
   declare
      P : Person renames Party_Goers (i);
   begin
      if P.Sex = Female then
         Put (P.Name);
         Total_Score := 
           P.Abilities (Brains) + P.Abilities (Looks) + P.Abilities (Humor);
         if Total_Score >= 270 then
            Put (" is perfect.");
         else
            Put (" is average.");
         end if;
         New_Line;
      end if;
   end;
end loop;

And with that, all likely use-cases should be covered.

I hope you’ve enjoyed this basic and very simple introduction to the Ada language. Please visit our wiki for more comprehensive examples on Ada usage.

Leave a Reply