Journal Articles

CVu Journal Vol 13, #1 - Feb 2001 + Programming Topics
Browse in : All > Journals > CVu > 131 (16)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Ada Overview - Part 1

Author: Administrator

Date: 03 February 2001 13:15:42 +00:00 or Sat, 03 February 2001 13:15:42 +00:00

Summary: 

Body: 

New computer languages appear all the time, but few achieve a sufficient following that projects are written in them, and so cause project managers to actively advertise for persons skilled in their use. One of the few to achieve this status in the past ten years is Ada.

And as knowledge of other tools helps you understand the capabilities and limitations of the tools you already have, I thought I would write this time about Ada. So that you can quickly see what a simple Ada program looks like, let me immediately show the usual example:

with ada.text_io; use ada.text_io;
procedure the_usual_example is
begin
  put_line ("Hello, world.");
end the_usual_example;

This is the way I have structured this piece. I start with a bit of the history of the language, and its purpose. I then move on to constants and variable names, which will lead me into the elementary types and aggregate types. From there, I discuss statements: firstly assignment statements and then control statements. I then talk about procedures, functions and packages. Finally, Ada specifies a number of run time library packages, some of which I will describe.

If all you know is C, please keep reading. I will point out the similarities and dissimilarities between C and Ada as I go. I do this because I find it easier to get my programs to work if I use features that are the same in the various languages, rather than those which are different. I do not want to get surprises when debugging my programs.

Ada's Origins

Ada is named after Augusta Ada Byron, Countess of Lovelace, who, through her assistance to Sir Charles Babbage on his differential engine, is regarded by many as the world's first programmer. To celebrate this, the United States Department of Defense (DoD) chose to name the computer language they started to design in 1974 after her.

The DoD design aim was to provide a modern algorithmic language for writing programs of a wide range of sizes running on everything from microcomputers to mainframes. Applications were seen as including all technical programming requirements from modelling and simulation to real-time process control. The language had to be simple, so as to encourage its use, but robust enough for safety critical applications. Although designed for military, aerospace and safety critical applications, its use in other areas was hoped for. That this has not happened is perhaps a reflection of programming standards generally.

Efficiency was a further important consideration, in terms of reliable compilation with the then current compiler technology, together with ease of code maintenance. Indeed, the importance of maintenance of a large project was placed above ease of writing as this is where the majority of software costs on a large programme can occur.

The ideas of a number of languages were drawn upon for Ada's design. Principal among these was Pascal together with a number of unnamed research languages, which probably included C. (Initial ideas were not drawn from C++ because Bjarne Stroustrup did not publish his book until 1986.) The result is a block structured language with strict typing which looks and feels more like Pascal or Modula than C, but with operator and procedure overloading in there too (based, I suspect, on Algol 68).

There have been two Ada standards published to date. The original release, in 1983, is usually called Ada 83 and the current standard, adopted in 1995, is Ada 95. There are some (on occasions, significant) differences between these, and I shall mention some of them as I proceed.

Ada Features

Let me describe some of the features of Ada to give you a better idea of it.

Perhaps the first things I should comment on are comments. Ada uses the double minus sign, --, to introduce a comment, which is terminated only by the end of the line. By being unable to terminate a comment early (by */ in C), code cannot hide in a forest of comments and comments cannot hide in a sea of code. (How many times have you discovered a bug was due to a line not being compiled because */ had been omitted from the comment on the preceding line?)

Something Ada does not have in any form is a pre-processor (the thing that handles the #defines in C). Although a nice idea, the C pre-processor is generally regarded as causing a number of problems whilst providing a small number of facilities that might be better supplied by other means.

Names or Identifiers

As in C, identifiers (names) are sequences of alphanumeric and underscore characters. Unlike C, identifiers must not start with an underscore, and upper and lower case versions of the same name are considered to be the same. Ada 83 prohibited successive underscores in names, but Ada 95 has removed this restriction. Identifiers may be of any length.

Literals

Literals are the constants that appear in expressions along with variables. Decimal literals are generally the same as in C, and might look like 1. The underscore is also allowed in numbers, but ignored: its purpose is to act as a digit separator. So you might write the integer literal one million as 1_000_000 as well as 1000000 or 10_00000 (some prefer to collect digits into groups of five...).

Based literals, which means numbers to bases other than 10, up to a maximum base of 16, are provided. So 16#e# is e in base sixteen which is equivalent to fourteen in base ten.

Floating point constants are supported, together with, interestingly, based floating-point constants. C does not have the latter.

The other principal literals are characters and strings. These look the same as in C, with a character literal being a single character delimited by single quote marks, and a string being a possibly zero length sequence of characters delimited by double quote marks.

Elementary Types

From the above, you will correctly guess that integer and floating-point types are available, and a couple of simple declarations might be:

i       : integer;
f, d    : float;

Ada tackles the problem of providing various sizes of integer and floating-point variables differently from C. C says you get three sorts of integers: short, int and long and three floating-point types: float, double and long double.

Ada says that there is an integer type and a floating-point type, and if the compiler wishes to offer various lengths of these it may do so by prefixing long_ or short_ to the elementary type name as often as necessary to name the additional types. So you find types like short_integer, or long_long_float depending on whose compiler you use. So:

li      : long_integer;
sf      : short_float;
lf      : long_float;

What these actually are you can find out by inspecting the attributes of the type. I discuss attributes later on.

Ada also offers fixed-point numbers. These are a cross between floating point and integer types. Like an integer, the difference between two successive numbers is always the same, but, like a floating-point number, that difference does not have to be exactly one. Unlike a floating-point number, the difference does not depend on the magnitude of the number (which is the effect the exponent has on floating point types).

The advantage of fixed-point numbers is speed of calculation, for whilst fixed-point numbers look like floating point numbers to the user, the compiler will implement them as integers, and hide all the messy business of scaling factors from their user.

Fixed-point numbers would typically be used in real time calculations for measurements like temperature, or engine speed. Both of these range between well defined limits and would be measured to known, fixed, resolutions.

Fixed-point constants look exactly like floating point constants: it is only how the compiler generates code for them that is different.

Characters and Strings

Both character and string types are present, with the string type being predeclared, rather than the "do-it-yourself" job of C. Ada 83 specified the character type as being the 7-bit ASCII character set. However, the subsequent general move towards an 8-bit character set resulted in the Ada Joint Program Office "interpreting" the original standard as allowing this as well. Ada 95 was formally specified from the start as using an 8-bit character set, and bowed to the inevitable and introduced the 16-bit wide_character type at the same time.

Both the character and the wide_character types are actually enumerated types in Ada, but this does not impose any restrictions on their use.

Other Types

Enumerated types are present in Ada and the boolean type is supplied as a predefined enumerated type:

type boolean is (false, true);

What C calls pointers are catered for by the provision of an access type so you can build dynamically allocated structures. You can copy an access type, assign to it the value obtained from the new reserved word which I discuss later, assign to it the value null to indicate that its value is otherwise invalid and (in Ada 95) assign to it the access attribute of an existing object.

This list may seem a little brief to the C programmer used to incrementing pointers to select the next element of an array, or subtracting them to find the number of elements in an array. But Ada would say that as such uses are unsafe and their use should be prohibited.

Aggregates and Arrays

An aggregate is merely a collection of objects of which the array is perhaps the simplest example. An array is simply a collection of objects which are all the same and exactly which object in the collection you get is specified by the array index.

Arrays in Ada may look a little confusing at first to the C programmer. Firstly, parentheses, rather than square brackets surround array indices. Then you can have not only multidimensional arrays of objects, but also arrays of arrays of objects. This means that you write things like a(1, 2) for an element in a multidimensional array and b(1)(2) for an element in an array of arrays. C supports only singly dimensioned arrays of arrays, though often generates code as though the use is of a multidimensional array.

Another feature of arrays is that, unlike C, array indices can start at any value, rather then the value of 0 always assumed by C, or 1 if you believe in Fortran 66. Further, an index may be a type, in which case an array element is associated with each possible value of the indexing type. Of course, you can only realistically do this for types that are not very big, such as boolean, or character. But an array c, defined as below, would be useful for accumulating the number of times each character appeared in a piece of text. Possible declarations for a, b and c are:

a       : array (0 .. 12, 0 .. 12) of boolean;
type sfb_t is array (-27 .. 10) of short_float;
b       : array (6 .. 100) of sfb_t;
c       : array (character) of long_integer;

And here you see the reason for arrays of arrays. If you gave a reference like b(7), the object selected would be an array of type sfb_t that contains 38 short_floats.

Aggregates: Records

The other aggregate type in Ada is the record, which is a collection of objects that are usually of different types. C would call these structures.

There is little difference between these in Ada and C and an example of a record intended to build a binary tree is:

type node_type;
type node_ptr_type is access node_type;
type node_type is record
        value           : integer;
        left, right     : node_ptr_type;
end record;
node    : node_type;
this_node,
root    : node_ptr_type;

This introduces the new type node_type with an incomplete type declaration so that you can define an access type for node_type. (Do not worry if that sounds confused, this is Ada avoiding a circular argument by ensuring that everything is sufficiently defined before it is used.) Once you have the types of all the variables in the record, you can complete the declaration of the type. For completeness, I create a variable node of the type node_type, also the variable root, which might access the top of a binary tree of these nodes and this_node which could be used to walk the tree (visit each node in the tree in turn).

Accessing the components of a record is similar to the approach of C by using the "." notation.

node.value := 7;

will set the value field of node to 7.

But how are the components referenced using this_node, which is of the access type for node_type? C ploughs in with the &, * and -> operators. Ada says that the compiler knows whether the type of the prefix, i.e. the name before the ".", is that of a record, or the access type of a record, so can insert the appropriate de-reference instruction when needed. And so

this_node := this_node.left;

will move to the left sub-tree of the current node.

This does, however, fall down if you want to use an access type as an actual parameter to a procedure: do you want to pass the access type, or the object it points to? To sort this out, Ada introduces the reserved word all, as in this_node.all, which means pass the object. Then this_node as a parameter means pass the pointer to the object. This also means that a record cannot have a component called all.

Exceptions

Ada has the concept of exceptions, which are in C++ but not C. These provide a means for the programmer to control what happens in the presence of hardware detected problems such as division by zero or run time problems such as array subscript errors. Alternatively perhaps the programmer has used a conditional statement to decide that something has gone wrong with his code, raises an exception himself and uses the exception mechanism to handle the problem. C++ throws and catches exceptions, Ada raises and handles them.

In C, a programmer making heavy use of the trigonometric functions should frequently check the value of the errno macro to see if anything has detected an error. This is somewhat tedious. In Ada, you would merely place an exception handler at the end of the block to be protected. Then, should one of the functions in the block detect an error, it must raise an exception and control would transfer directly to the handler dealing with that particular exception.

As a short example, suppose it is suspected that the result of the division of y by z will occasionally be out of range. For y, z and r all well defined, one might write:

begin
    r := y / z;
exception
when constraint_error =>
    put_line ("Failed to calculate 'r'.");
    raise;
end;

This code forms the ratio of y and z and places it in r. Ada says that should the result not fit in r, a constraint_error exception must be raised. (How it does that is a problem for the compiler and the run-time library.) If this exception is raised, the handler is taken, which in this example prints a diagnostic message and re-raises the exception (by the raise; statement) so that an attempt may be made to repair the problem at a higher level in the program.

Attributes

Another feature of Ada without direct parallel in C is that of attributes. An attribute provides information about the type, or object, of which it is requested. It looks like the name of the type, or object, of interest, a single quote, and the name of the attribute required. So integer'size is replaced by the number of bits required by an integer variable, which is roughly what the C sizeof operator does. Or integer'last gives the largest possible value an integer can hold. That piece of information C will tell you as INT_MAX in <limits.h>.

The attributes succ and pred allow you to step to the succeeding and preceding values in an enumerated type.

Arrays always carry all their dimensions with them in Ada, even when handed to a procedure or function as an actual argument. The attributes first and last can be used to find the limits of each dimension of an array, so there is no excuse in Ada for using the wrong array dimension. And as looping over all values of an array dimension is a common operation in any language, Ada also provides an attribute range for use with the for loop which selects each value of the dimension in turn.

An attribute of use to embedded systems programmers is address which yields the address of the object to which it is applied. Its purpose is to allow hardware registers, such as in Direct Memory Access (DMA) controllers, to be loaded with objects' addresses. But be warned, it is not meant as a substitute for the access type when you want a pointer to something.

Overloading

Overloading" is where an identifier, or an operator, is given several meanings, and the compiler has to sort out from the context which of the possible meanings is the one to use. For example in the expression a + b the compiler will generally look at the types of a and b to decide whether to emit an integer or floating point add instruction, and even whether one of the operands should first be converted from an integer to a floating point type.

This is built in to many languages, but it is only when the programmer can write his own definition of what + does that the idea becomes known as "overloading". The concept is usually extended to allow several different procedures to have the same name. Again, the compiler has to look at the context to decide which meaning to use. In the case of an overloaded procedure, this would entail looking at the number and type of all the parameters.

Ada has this concept, as does C++.

Wisely used, the concept can be a great aid to clarity. You do not have to think up a new procedure name to do the same thing on a new type: just use the old procedure name again. Misused, for instance by helpfully making * do addition and + do multiplication, causes chaos and provides further evidence for the Project Managers who prefer to ban it in their coding standards.

Assignments and Expressions

Let me move on to statements, and firstly the assignment statement.

Ada has only the simple assignment operator which is :=, so the compound assignment operators of C, like +=, are not allowed. But the usual arithmetic, relational and boolean operations are supplied.

**, the exponentiation operator, comes from Fortran and raises its left operand to the power given by the right operand. As in Fortran, the right hand operand must be an integer (the left hand operand may be an integer or a floating point type) so you cannot raise a number to a fractional power. However, you can overload the ** operator, and the interface to a suitable declaration to do this is included in the standard library package ada.numerics.generic_elementary_functions.

Ada provides two operators to return the remainder of a division (% in C): mod and rem. Which one you want depends on whether you think the remainder after dividing -11 by 5 should be +4 (use mod) or -1 (use rem). (C says this is implementation defined.)

Ada uses & to signify array concatenation. Remembering that strings are character arrays, one frequent use is to join two strings together: "hello" & ", world.". C would use the standard library functions strcat or memcpy to do this.

The inequality relational operators are as you would expect: <, <=, >= and >. To test for equality, Ada uses = (in C, when did you last find an if statement control expression was always true because you had typed = instead of ==?) The inequality operator is /= rather than the != of C.

The boolean operators are spelt out as and, or and xor. not is used for boolean negation (not !).

There are no bit operators (~, & and | in C), but using the equivalent logical operator on a packed array of booleans will achieve the same result.

Unlike C, Ada does not automatically convert between types before evaluating operators. So in the expression 1 + 2.3, whilst C would convert 1 to the double precision value 1.0, Ada would generate an error during compilation. This is to increase code reliability by forcing the programmer to consider why he is adding an integer to a float.

To do the necessary type conversion (casting in C), Ada provides a function for every type, with the same name as that type, which will convert its argument to that type. So the programmer should re-write the above example as 1 + integer (2.3) to get the integer result 3. A floating point type is always rounded when converted to an integer, whereas C would truncate towards zero.

Of course, some conversions are not possible, such as the conversion between a character and an integer, but the compiler will tell you about that quickly enough. Because the character type is actually an enumerated type, you need the attribute functions character'val to convert an integer to the equivalent character and character'pos to convert a character to an integer. So character'pos ('A') will give the integer value 65.

Statements

Most of the statement types available in C are present in Ada. The most significant difference is that whilst the control statements in C control only a simple statement, resulting in a proliferation of braces as you struggle to make an if statement control several statements at once, every control statement in Ada operates on a sequence of statements. The consequence is that Ada needs things like end if to signify the end of the statements controlled by an earlier if. But this also makes it easier for the compiler to detect where you have omitted an end.

If you want a block statement, begin .. end are available and, like C, you can commence with a list of variable declarations which will exist only within that block. The block may be optionally named, and that name repeated after the end, to assist the compiler in producing meaningful error messages should the number of begins not match the number of ends. This feature may also be used on the loop statement.

As I indicated above, an if statement is present, looking like:

if control_expression then
    statement_1;
    statement_2;
else
    statement_3;
    etc;
end if;

Note that a then is needed after the control_expression so that the compiler knows control_expression has ended. And, of course, the else and following statements are optional. If you want the else as a place marker for further development, you can always use the null statement of null;.

At first sight, the syntax of Ada appears to allow only one type of loop statement, and closer scrutiny is needed to find that this may be either of the more familiar for or while statements. There is no equivalent of the C do ... while construct.

The for statement in Ada is a little strange in that the control variable exists only within the scope of the for statement. So using a for statement to sequentially search an array for a value requires explicit thought as to how the index of the wanted value is extracted. One ends up with something like

l       : integer;
x       : sfb_t;
test    : short_float;
l := x'last + 1;
for i in x'range loop
    if x(i) = test then
        l := i;
        exit;
    end if;
end loop;

If test is the value to be found, then the if statement checks the current element of array x and, when found, places the index of the wanted value in l and exits the loop. (exit does what break does in C, and there is no equivalent in Ada of C's continue.) The for statement uses the range attribute on x to determine the loop limits, and also the type of i. And immediately before the for statement I assign an illegal value to l (of one more than the highest legal index into x) so that I can subsequently determine if the test value was ever found in the array,

The multiple selection switch statement in C is the case statement in Ada. When the sequence of statements selected by the control variable is completed, the Ada case statement is also completed. Unlike C, Ada does not then start executing the statements for the next value of the control variable, so there is no need to worry about a missing break. Ada also requires that statements appear which match every possible value of the control variable. This means in practise that the default choice of others becomes a compulsory, rather than an optional, feature of the case statement.

To move across a chess board using commands like north or south by updating the (x, y) coordinates of the selected cell you might end up with:

type direction_type is (north, south, east, west, left, right);
d       : direction_type;
x, y    : integer;
case d is
when north => y := y - 1;
when south => y := y + 1;
when east  => x := x + 1;
when others => null;
end case;

which as well as using the others choice to ensure that actions for all possible values of d are provided for, introduces the feature that you cannot move west. A computer language can make it difficult to write a bug by prohibiting known dubious practises, but it can never prevent a determined programmer achieving a dubious goal.

And finally, Ada allows every statement to be named by preceding it with the name in double angle brackets:

<<label_1>> a := b;

so that, when all else fails, a goto statement may be used:

goto label_1;

(There are several reasons why a modern, block structured, language must have a goto statement, but few occasions when it may be used.)

Notes: 

More fields may be available via dynamicdata ..