Introduction

The world's most famous program is probably the Hello, world! program. It is often the first program programmers write when starting on a new language, so it is only natural that we have an entire page dedicated to greeting the world in Ada.

There are literally hundreds of ways to create a Hello, world! program in Ada. I'm going to show you a few of these, not because it's necessary to have more than one version, but because it allows me to demonstrate a few Ada basics.

But first, lets take a look at the simplest and most straightforward Ada version of the Hello, world! program.

The simple, no frills version

Put the following in a file named helloworld.adb

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

In the first line we notice the with Ada.Text_IO; use Ada.Text_IO clause. This is called a with clause and it gives the name of the library unit required by this program. We could have done without declaring the use Ada.Text_IO part, in which case the call to Put_Line at line 5 would've looked like this:

Ada.Text_IO.Put_Line ("Hello, world!");

Without the use Ada.Text_IO declaration Put_Line is no longer directly visible in the HelloWorld procedure, so we have to specify where the procedure is to be found.

On line 3 the main procedure of the program begins. This procedure is aptly named HelloWorld. This is comparable to C's main() function, except that in Ada you can name the main procedure as you wish.

Between the is and begin keywords we can declare objects, such as variables, constants and arrays, although we do not have any for this example. Between begin and end we write statements, in this case the lone call to the Put_Line procedure.

And that's all there's to it. Now, to compile the program, execute this command:

$ gnatmake helloworld.adb

You should now have 4 files in the directory where you placed the helloworld.adb file:

helloworld
helloworld.adb
helloworld.ali
helloworld.o

The first of these is the executable. The .ali and .o files are output generated by the compiler. Lets ignore those for now. Executing the helloworld program should result in this:

$ ./helloworld
Hello, world!

There. It does not get much simpler than that. Now feel free to marvel at the fact that you've created the famous Hello, world! program in Ada.

The somewhat more advanced and involved version

Before we start, we need to do a little preparation. First create the following directories and files somewhere on your computer:

$ mkdir HelloWorld
$ cd HelloWorld
$ mkdir exe objects
$ touch helloworld.adb helloworld.gpr

The above should leave you with the following:

exe/
objects/
helloworld.adb
hellowworld.gpr

This all seems a bit “much” for a simple Hello, world! program and it isn't absolutely necessary, but as this is Ada, we might as well do things properly from the beginning and that means keeping object files, executables, and source files separated.

Here's a quick overview of what we've got:

  • exe/ This is the directory where the helloworld executable is placed when compiling the program
  • objects/ When compiling a program, the compiler creates ALI files, object files and tree files. These files are placed in this directory.
  • helloworld.adb The main Ada source file for the Hello, world! program.
  • helloworld.gpr This is an Ada project file. This file controls various properties of the program, such as how to compile it, what sources to include, where to put things and so on.

The project file

The helloworld.gpr file is, as stated earlier, not strictly necessary for compiling an Ada program. Consider it instead as a means to simplify things when programming in Ada. When doing small programs it might seem a bit “over the top”, but you might as well get used to it, because it will become crucial as your programs increase in complexity.

The project file for our Hello, world! program, in spite of being well over twice the size of the program itself, is very simple. What it contains is this:

project HelloWorld is
   for Source_Dirs use (".");
   for Main use ("helloworld.adb");
   for Exec_Dir use "exe";
   for Object_Dir use "objects";
 
   package Ide is
      for Compiler_Command ("ada") use "/usr/gnat/bin/gnatmake";
   end Ide;
 
   package Compiler is
      Common_Options := ("-gnatwa",
                         "-gnaty3abcdefhiklmnoprstux",
                         "-Wall",
                         "-O2");
     for Default_Switches ("Ada") use Common_Options;
   end Compiler;
end HelloWorld;

As you can see, the syntax of a project file looks very much like a regular Ada program. In this project file we define where the compiler is to look for source files, in which Ada source file the main procedure is found, where to put the resulting executable, and where to put compiler objects.

If you use the GPS IDE (you really should - it's very good) or another IDE that supports project files, then the ”package Ide is” block tells the IDE which compiler to use. You should of course change the path /usr/gnat/bin/gnatmake to where you've installed the GNAT compiler. And finally, in the ”package Compiler is” block, we define the options used when compiling the program. There's a whole slew of options available. Just do:

$ gnatmake -h

And you should see them all.

Compiling with a project file

Either use your IDE, or this command:

$ gnatmake -P helloworld.gpr

You should get some output, similar to this:

gcc -c -gnatwa -gnaty3abcdefhiklmnoprstux -Wall -O2 -I- -gnatA /home/thomas/HelloWorld/helloworld.adb
gnatbind -I- -x /home/thomas/HelloWorld/objects/helloworld.ali
gnatlink /home/thomas/HelloWorld/objects/helloworld.ali -o /home/thomas/HelloWorld/exe/helloworld

And after that you should have an executable in the exe directory and a few object files in the objects directory. This is the method you should use to compile the following Hello, world! examples.

Some alternative Hello, world! examples

In the first example we included the Ada.Text_IO package using a with clause. But we also declared that we wanted to use Ada.Text_IO on the same line. The use Ada.Text_IO declaration could have been moved into the declarative part of the HelloWorld procedure:

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

In this version, the procedures, functions, and types of Ada.Text_IO are directly available inside the HelloWorld procedure. Outside the block in which use Ada.Text_IO is declared, we would have to use the dotted notation to invoke, for example:

with Ada.Text_IO;
 
procedure HelloWorld is
begin
   Ada.Text_IO.Put ("Hello, "); --  The Put function is not directly visible here
   declare
      use Ada.Text_IO;
   begin
      Put_Line ("world!"); --  But here Put_Line is, so no Ada.Text_IO. is needed
   end;
end HelloWorld;

This enables us to isolate the use … declarations to where they are necessary. But use … is not the only method to lessen the pain of having to write long package names. How about this one:

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

Here we've renamed the entire Ada.Text_IO package to the much shorter IO. We then proceed using the normal dotted notation to use the Put_Line procedure. How does this fit in with the use … declaration?

with Ada.Text_IO;
 
procedure HelloWorld is
   package IO renames Ada.Text_IO;
begin
   IO.Put ("Hello, ");
   declare
      use IO;
   begin
      Put_Line ("world!");
   end;
end HelloWorld;

Cool eh'?

Of course the above example is completely crazy, but it does prove a point: Ada is very flexible.

Hello, world! using variables and constants

So far we've called Put_Line and Put with the Hello, world! string literal as the parameter, but it's of course also possible to instead use a constant:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : constant String (1 .. 13) := "Hello, world!";
begin
   Put_Line (Hello);
end HelloWorld;

On line 4 we declare the constant Hello, which is immediately assigned the value Hello, world!. Constants should obviously always be initialized when declared, unless the constant is deferred.

Using a regular variable, the program looks like this:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : String (1 .. 13) := "Hello, world!";
begin
   Put_Line (Hello);
end HelloWorld;

or

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : String (1 .. 13);
begin
   Hello := "Hello, world!";
   Put_Line (Hello);
end HelloWorld;

On line 4 we declare the variable Hello to be of the type String (1 .. 13). That one line there tells us something important about the String type: It is really just an array of characters, in this case an array with room for 13 characters. The String type is declared like this:

type String is array(Positive range <>) of Character;

This means that all the tools Ada makes available for handling arrays, can also be used to manage String objects.

On line 6 we assign the string literal Hello, world! to the Hello variable, and then we output the contents of Hello using Put_Line, as usual.

In the above examples we have specifically declared the Hello object to be of the type String (1 .. 13). This is not strictly necessary, as the length of the array is implied when the Hello, world! string literal is assigned:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : constant String := "Hello, world!";
begin
   Put_Line (Hello & " I come in peace.");
end HelloWorld;

This will of course only work if the Hello object is initialized when it is declared. You cannot do this:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : String; --  Illegal
begin
   Hello := "Hello, world!";
   Put_Line (Hello & " I come in peace.");
end HelloWorld;

If you try to compile the above code, you will get an error like this:

helloworld.adb:4:12: unconstrained subtype not allowed (need initialization)
helloworld.adb:4:12: provide initial value or explicit array bounds

So as the error states, you have to provide either an initial value or explicit array bounds for your string.

Hello, world! is really just an array

Now that we know that the String type is really just an array of characters, it's easy to do various “fun” things with the Hello, world! string literal. How about reversing it?

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

The output from this program is:

!dlrow ,olleH

Let's go vertical:

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

Now we have this:

H
e
l
l
o
,
 
w
o
r
l
d
!

Neat!

Let's take a closer look at the two programs. In both of them we have a for statement. This is a loop which allows for a specific number of iterations, here given by the Hello'Range discrete range. Hello'Range is just a short/convenient way of saying Hello'First .. Hello'Last, which would've worked just as well.

We know that the String type is an array of characters, with an index value of the type Positive

subtype Positive is Integer range 1 .. Integer'Last;

According to the above, a Positive is an integer ranging from 1 to the highest available integer on the platform. Because of this, the first index of a String defaults to 1. On the first iteration of the loop i equals 1, so the line Put (Hello (i)); outputs the first character in the Hello, world! string, and on the final iteration of the loop it outputs the 13th. index of the string, the character !.

Since we know that the String type is merely an array of characters, we can do weird stuff like this:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : constant String := "Hello, world!";
   C : Character;
begin
   for i in Hello'Range loop
      C := Hello (i);
      Put (Character'Succ (C));
   end loop;
end HelloWorld;

And the output we get is:

Ifmmp-!xpsme"

What just happened there? Remember the 'Range attribute we used in the for .. loop? Well, there are lots of such attributes in Ada, and 'Succ is one such:

function S'Succ(Arg : S'Base) return S'Base

Looks a bit odd eh'? Here's the formal explanation:

For an enumeration type, the function returns the value whose position number is one more than that of the value of Arg; Constraint_Error is raised if there is no such value of the type. For an integer type, the function returns >the result of adding one to the value of Arg. For a fixed point type, the function returns the result of adding small to the value of Arg. For a floating point type, the function returns the machine number (as defined in 3.5.7) >immediately above the value of Arg; Constraint_Error is raised if there is no such machine number.

In Ada the Character type is an enumeration type. The 'Succ attribute give the successor of an enumeration value, so the value H becomes an I instead. The 'Pre attribute gives the predecessor.

Going up or down

Adding the package Ada.Characters.Handling gives us a few tools to handle characters and strings, for example the ever useful ability to either change a string to uppercase or lowercase:

with Ada.Text_IO;             use Ada.Text_IO;
with Ada.Characters.Handling; use Ada.Characters.Handling;
 
procedure HelloWorld is
   Hello : constant String := "Hello, world!";
begin
   Put_Line (Hello);
   Put_Line (To_Upper (Hello));
   Put_Line (To_Lower (Hello));
end HelloWorld;

The output is as expected:

Hello, world!
HELLO, WORLD!
hello, world!

In this package there are also useful functions like Is_Letter (Item : Character) return Boolean, function Is_Digit (Item : Character) return Boolean, function Is_Alphanumeric (Item : Character) return Boolean and so on.

Concatenate

One-dimensional arrays can be concatenated using the & operator:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : constant String (1 .. 13) := "Hello, world!";
begin
   Put_Line (Hello & " I come in peace.");
end HelloWorld;

or

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello : constant String := "Hello";
   World : constant String := "world";
begin
   Put_Line (Hello & ", " & World & "!");
end HelloWorld;

Both of which yield the expected output. This next one perhaps demonstrates the use of the & operator even better:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   Hello       : constant String := "Hello,";
   World       : constant String := " world!";
   HelloWorld  : String (1 .. 13) := (others => ' ');
begin
   Put_Line (Hello);
   Put_Line (World);
   Put_Line (HelloWorld);
 
   HelloWorld := Hello & World;
   Put_Line (HelloWorld);
end HelloWorld;

Note how at line 6 initialize the value of the HelloWorld string to whitespace using the others aggregate. What others enables us to do is fill the remaining part of an array with the given value. In our case, the remaining part of the HelloWorld array is the entire array, as we have not assigned a single character to the string.

Running this we get:

Hello,
world!
              
Hello, world!

We can even avoid the String (1 .. 13) declaration:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure HelloWorld is
   H  : constant String := "Hello,";
   W  : constant String := " world!";
   HW : String (1 .. (H'Length + W'Length)) := (others => ' ');
begin
   Put_Line (H);
   Put_Line (W);
   Put_Line (HW);
 
   HW := H & W;
   Put_Line (HW);
end HelloWorld;

Using the 'Length attribute, we can initialize the H and W arrays whatever we feel like, and the HW array will adjust itself accordingly. The output is the same as the previous example.

Goodbye, world!

These few examples are obviously just scratching the surface of Ada, yet I hope you got a little taste of what Ada is. At least you should be capable of writing your own Hello, world! program in Ada.


Navigation