Ada 2012 Aspect Clauses => Static Predicates

John English wrote a wonderful book titled Ada 95: The Craft of Object-Oriented Programming, which is available on line at http://archive.adaic.com/docs/craft/html/contents.htm. In chapter 3 of that book he explores many Ada programming fundamentals using a simple calculator program example. The code for that example is:
    with Ada.Text_IO, Ada.Integer_Text_IO;
    use  Ada.Text_IO, Ada.Integer_Text_IO;
    procedure Calculator is
        Result   : Integer;
        Operator : Character;
        Operand  : Integer;
    begin
        Put ("Enter an expression: ");
        Get (Result);                                   --  1
        loop                                            --  2
            loop                                        --  3
                Get (Operator);
                exit when Operator /= ' ';
            end loop;
            if Operator = '.' then                      --  4
                Put (Result, Width => 1);               --  5
                exit;                                   --  6
            else
                Get (Operand);                          --  7
                case Operator is
                    when '+' =>
                        Result := Result + Operand;     --  8
                    when '-' =>
                        Result := Result - Operand;
                    when '*' =>
                        Result := Result * Operand;     --  9
                    when '/' =>
                        Result := Result / Operand;
                    when others =>
                        Put ("Invalid operator '");     -- 10
                        Put (Operator);
                        Put ("'");
                        exit;                           -- 11
                end case;
            end if;
        end loop;
        New_Line;
    end Calculator;
This program implements a simple left-to-right calculator that does not understand parentheses. Each calculation string is terminated with a period (full stop for those speaking the King’s English).
1 + 2 * 3.
The calculation string above is evaluated left to right, yielding the value 9. The calculator does not impose any operator precedence.
The internal loop beginning with the line labeled 3 in an end-of-line comment deals with the fact that the Get procedure for type Character simply gets the next character in the input stream. It does not skip space characters to input the next non-space character. The loop implemented by John English simply reads characters until it encounters something that is not a space character. The hope implicit in this program is that the person creating the input strings only uses integral numbers, spaces, or one of the characters ‘+’, ‘-‘, ‘*’, ‘-‘, or ‘.’. If it encounters any other character it declares that an invalid operator has been input and terminates the program.
As a software safety engineer I am always uncomfortable with a program that cannot clearly and concisely specify simple input values. What happens, for instance, in the John English version of this program if the person running the program inputs horizontal tabs instead of spaces? They may look similar on an editor screen, but they are distinctly different to program.
I know that John English was well aware of these issues, but was trying to introduce students to the fundamentals of Ada programming rather than the complications of error handling. In fact, until the 2012 version of Ada there was no simple way to do better.
Ada 2012 introduced aspect clauses. The form of the aspect clause of interest to this article is the static predicate. A static predicate is a qualification of a type or subtype definition defining a set of properties about the type. In Ada 95 the best you could do was to specify the range of values for a subtype or type. That range had to be contiguous, with no gaps in the values. The Ada 2012 static predicate definition introduces more flexibility. It allows any expression that can be evaluated as a membership test.
How does this relate to the calculator program shown above? The operators in that program are five distinct characters, but not a contiguous range of characters. Let’s see how we could express that set of characters using an Ada 2012 static predicate.

Subtype Acceptable is Character
   with Static_Predicate => Acceptable in ‘+’|’-‘|’*’|’/’|’.’;

The result is a subtype of Character with five valid values.
Once we have established a subtype with only the few values we want to use, we have managed to more clearly express the intent of our program. A comparable program to the Calculator program above, but using this static predicate expression is:
------------------------------------------------------------------
-- Simple Calculator Program                                    --
------------------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_Io; use Ada.Integer_Text_IO;

procedure Simple_Calculator is
   subtype Acceptable is Character
   with Static_Predicate => Acceptable in '+'|'-'|'*'|'/'|'.';

   function Get_Operator return Acceptable is
      Temp : Character;
   begin
      loop
         get(Temp);
         exit when Temp in Acceptable;
      end loop;
      return Temp;
   end Get_Operator;

   Operator : Acceptable;
   Operand  : Integer;
   Result   : Integer;
begin
   Get(Item => Result);
   Outer:
   loop
      Operator := Get_Operator;

      if Operator = '.' then
         Put(Item => Result, Width => 1);
         New_Line;
         exit Outer when End_Of_File;
         Get(Item => Result);
      else
         Get(Item => Operand);
         case Operator is
         when '+' => Result := Result + Operand;
         when '-' => Result := Result - Operand;
         when '*' => Result := Result * Operand;
         when '/' => Result := Result / Operand;
         when '.' => null;
         end case;
      end if;
   end loop Outer;
end Simple_Calculator;

I have encapsulated the operator input loop into a function to clearly specify my intentions. Note that the function Get_Operator now loops until it reads a character that is a member of the subtype Acceptable. This logic will read past spaces, tabs, or any other characters that are not members of the subtype Acceptable. The case statement now does not need a “when Others” clause because it explicitly handles all possible values of subtype Acceptable. If another operator were to be added to the set of values for Acceptable the compiler would flag the need to handle the new value in the case statement.
The use of the Static Predicate to create a discontinuous set of valid values can be very useful. It also greatly simplifies programming over creating a more complex set of conditional statements on an input loop. Finally, it allows a more robust use of the case clause.

Comments

Popular posts from this blog

Comparing Ada and High Integrity C++

An Ada Publish-Subscribe Producer-Consumer Exercise

Ada vs C++ Bit-fields