Loose Ends and Pitfalls
This last section covers a few topics that were omitted earlier for simplicity.
We also mention some common errors made by Ada programmers. Beginners aren't
expected to understand every paragraph, so we won't ask questions here.
Some terminals and printers don't support the entire ASCII character set. In
an Ada program, the vertical bar | may be replaced with the exclamation mark !,
as in when 3 ! 5 =>. Also, a pair of sharp signs # may be replaced with a pair
of colons :, as in 16:7C03:. The quotation marks around a string constant may
be replaced with percent signs if the string doesn't contain any quotation
marks. In that case, any percent signs within the string must be doubled. For
example, Put(%a 10%% increase%);. These character replacements shouldn't be
used in programs if the equipment will support the standard characters.
Section 4.9(2) of the Ada 95 RM gives a detailed definition of a static
expression, but briefly, an expression is called static if it can be evaluated
at compile time and isn't too complicated. In almost every case where a
constant normally appears, a static expression may also be used. For example,
an address representation clause normally takes a constant of type
System.Address. A static expression of this type is also acceptable, as in
for Clock_Interrupt'Address use 16*16;.
The unary minus is always an operator and never part of a constant. Thus -5
is actually a static expression and not a constant. Normally, this doesn't
concern the programmer, because, as we just said, static expressions can
usually appear where a constant normally appears. However, in a few special
situations we can get into trouble. For example, in Ada 83 we can write
for I in 10 .. 20 loop and A : array(10 .. 20) of Float; but we can't omit the
words Integer range in for I in Integer range -10 .. 10 loop and
A : array(Integer range -10 .. 10) of Float;! (Ada 95 lets us write these
without Integer range, however.)
Also, if a package P declares type Count is new Integer; then the unary minus
operator for that type is part of the package. If our program withs but
doesn't use P, we can write A : P.Count := 1; but not B : P.Count := -1;.
We either have to use the package, rename P."-", or write
B : P.Count := P."-"(1);. Because we sometimes don't want to use the package
except to avoid writing P."-"(1), Ada 95 lets us write
with P; use type P.Count;
This automatically uses only the infix operators belonging to the type
P.Count. Other operators belonging to P.Count, and other identifiers in the
package P still require dot notation with the above use type clause.
The operators have precedence, so that 1 + 2 * 3 means 1 + (2 * 3). The
precedence of all the operators is given in section 4.5 of the Ada 95 RM. A
programmer should never have to look these up, because parentheses should be
used for any cases that aren't obvious. Unary minus has a low precedence, so
-A mod B means -(A mod B).
If we write A, B : array(1 .. 5) of Float; then A and B have different
anonymous types, and we can't write A := B;. To fix this, write
type Vector5 is array(1 .. 5) of Float; and then A, B : Vector5;, or write
type Vector is array(Integer range <>) of Float; and A, B : Vector(1 .. 5);.
Ada 83 and Ada 95 will automatically convert from a universal type to a named
type, but only Ada 95 will convert the other way. For example,
C1 : constant Integer := 1; -- legal
C2 : constant Integer := 2; -- legal
C3 : constant := C1 + C2; -- legal only in Ada 95
When arrays are assigned, the subscripts don't have to match; only the lengths
and types need match. But in Ada 83 (not Ada 95), if a formal parameter
("dummy argument") of a subprogram is a constrained array, the subscripts in
the call to the subprogram must match. For example, the last line here will
raise Constraint_Error in Ada 83:
subtype Name is String(1 .. 30);
John : Name;
Line : String(1 .. 80);
procedure Display(Person : in Name);
...
John := Line(51 .. 80); -- legal
Display(Line( 1 .. 30)); -- legal
Display(Line(51 .. 80)); -- Raises Constraint_Error in Ada 83
When a subprogram formal parameter is an unconstrained array, beginners often
wrongly assume that the subscripts will start with one. For example,
Line : String(1 .. 80);
procedure Display(S : in String) is
begin
for I in 1 .. S'Length loop
... S(I) ...
This will raise Constraint_Error if we call
Display(Line(51 .. 80));. The for
statement should be changed to say
for I in S'Range loop.
Remember that elaboration occurs at run time. The following raises
Program_Error by trying to activate a task before elaborating its body:
task type T is ... end T;
type P is access T;
T1 : P := new T;
task body T is ... end T;
The third line should be changed to
T1 : P; and the statement
T1 := new T; should be placed in the executable region.
Similarly, this procedure tries to activate a function before elaborating its
body. The initialization of J should be moved to the executable region:
procedure Test is
function X return Integer;
J : Integer := X; -- Raises Program_Error.
function X return Integer is
begin
return 5;
end X;
begin
null;
end Test;
A return statement in a function is used with an object: return
Answer;. However, return may also appear without an object in
a procedure; we simply write return;. Normally, a procedure
returns after executing its last statement, but an early return is possible by
this method. In the author's opinion, such early returns should be used
rarely, if at all.
Many implementations of Ada allow us to insert machine code into a program.
Ada 95 compilers that allow machine code insertions provide a package
System.Machine_Code which usually contains a rather complex record definition
representing the format of a machine instruction. We can write a procedure or
function that withs and uses that package. In place of the usual Ada
statements in the executable region, we write record aggregates, each one
representing a machine code instruction. Since the package System.Machine_Code
varies greatly from one implementation to the next, you'll have to consult the
compiler documentation.
Ada 83 compilers that allow machine code insertions sometimes provide a package
Machine_Code and sometimes provide a pragma, such as pragma Native, which can
be inserted in the middle of a procedure, function, etc. Again, the details
vary widely from one implementation to the next, and you'll have to consult the
compiler documentation.
In the unusual case of a for loop index hiding an explicitly declared object of
the same name, the explicitly declared object can be referenced inside the
loop. Simply use dot notation with the name of the compilation unit (function,
procedure, etc.) For example, the following is legal:
procedure Main is
Ix : Float;
J : Integer;
begin
Ix := 3.2;
for Ix in 1 .. 10 loop
Main.Ix := 6.0;
J := Ix;
end loop;
end Main;
Inside the loop,
Ix refers to the loop index, and the explicitly declared
object can be referenced by writing
Main.Ix.
Outside the loop,
Ix refers to
the explicitly declared object, and the loop index doesn't exist.
In the rare case of an aggregate containing just one element, we must use named
notation rather than positional notation. For example, the last line is
illegal in the following program segment, because the right hand side is a
Float rather than an array of one Float.
type Vector is array(Integer range <>) of Float;
A : Vector(1 .. 1);
...
A := (1 => 2.3); -- legal
A := (2.3); -- illegal
Of course, it's OK to use positional notation in calls to subprograms with only
one parameter, for example, Put_Line("Hello");.
Annexes C through H of the Ada 95 RM describe optional features of Ada 95. An
Ada 95 compiler may implement any, all, or none of these. Consult your
compiler documentation for details. The optional Ada 95 features are as
follows:
- ANNEX C. Systems Programming: Access to machine
operations, interrupt support, shared variable
control, task identification, etc.
- ANNEX D. Real-Time Systems: Dynamic task priorities,
scheduling, dispatching, queueing, monotonic
time, etc. An implementation that provides this
must provide Systems Programming as well.
- ANNEX E. Distributed Systems: Multiple partitions of an
Ada program executing concurrently.
- ANNEX F. Information Systems: Decimal types for handling
monetary values. "Picture" strings to simplify
output of monetary amounts.
- ANNEX G. Numerics: Complex numbers, improved accuracy
requirements for floating-point arithmetic,
random number generation, etc.
- ANNEX H. Safety and Security: Adds requirements on
compilers for safety-critical systems.
Well, we haven't covered all there is to know about Ada, but this has been a
very thorough course. If you've come this far and completed the six Outside
Assignments, you should be an excellent Ada programmer. To continue learning,
start doing all your casual programming in Ada. If you need a simple program
to balance your checkbook, write it in Ada! At this point, switching to Ada
for all your programming will do you much more good than further instruction
from a tutorial program.
The best way to answer any remaining questions about Ada is to "ask the
compiler" by writing a brief test program, especially if your compiler is
validated. You can also look in the
Ada 95 RM, which, by definition, does
cover all of the Ada language. However, the RM isn't easy reading!
The best way to debug a short program is often to execute it by hand, with
pencil and paper. You can also add extra statements to the program to display
intermediate results, and remove them later.
We wish you success with Ada, and welcome your comments and suggestions!
< prev