Friday, September 28, 2012

AdaTutor - Advanced Topics (9)

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.IxOutside 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

No comments: