Ada preconditions and post-conditions are implemented using
aspect clauses. While aspect clauses can include many other terms used to
specify program behavior, this posting will focus on preconditions and
post-conditions.
A thorough discussion of preconditions and post-conditions
can be found at http://www.ada-auth.org/standards/12rat/html/Rat12-2-3.html
Since its first official version in 1983 the Ada language
has always allowed the programmer to define data types and subtypes with
specific ranges. For example:
type Byte is
range -2**7..2**7 – 1; -- signed integer type
type Unsigned_Byte
is mod 2**8; -- modular type
type
Normalized is digits 5 range 0.0..1.0; -- floating point type
type Money
is digits 10 delta 0.01; -- decimal fixed point type
subtype
Uppers is Character range ‘A’..’Z’; -- character subtype
subtype Positive
is Integer range 1..Integer’Last;
type Days is
(Mon, Tue, Wed, Thu, Fri, Sat, Sun);
|
When a defined type or subtype is used as a function or
procedure parameter that type or subtype acts as a pre-condition on the call of
the function or procedure.
procedure
Swap(Left, Right : in out Positive);
|
The procedure example above defines a procedure named Swap. The procedure takes two parameters of
the subtype Positive. Even though subtype Positive is a subtype of Integer and
every instance of Positive is also an instance of Integer, not every instance of
Integer is an instance of Positive. The
Integer type can represent values less than 1, while the subtype Positive
cannot. The Ada compiler writes run-time checks to ensure that the values
passed to Swap are integers within the range of subtype Positive, creating a
precondition for the procedure that all values passed to it must be Integer
values greater than 0.
This use of strong typing in parameter calls provides some
very limited precondition capability. A more robust precondition capability
combined with a post-condition capability was introduced in the Ada 2012
standard.
Preconditions provide a guarantee to the writer of the
function or procedure that a condition will be true when the program is called.
Post-conditions provide a guarantee to the caller of the function or procedure
that a condition will be true when the function or procedure call returns.
The precondition becomes a contract between the caller and
the called subprogram when the subprogram is called. The post-condition becomes
a contract between the caller and the called subprogram when the called
subprogram returns. These contracts are direct implementations of the
requirements for the called subprogram.
Stack Example
The following example shows a stack implementation which
includes definition of some preconditions and some post-conditions.
-----------------------------------------------------------------------
-- Package
implementing a generic bounded stack
-----------------------------------------------------------------------
Generic
type Element_Type is private;
with function Image(E : Element_Type)
return String;
package
Bounded_Stack is
type Stack(Size : Positive) is tagged
private;
function Is_Full(S : in Stack) return
Boolean;
function Is_Empty(S : in Stack) return
Boolean;
procedure Push(S : in out Stack; Item : in
Element_Type) with
Pre => not S.Is_Full,
Post => not S.Is_Empty;
procedure Pop(S : in out Stack; Item : out
Element_Type) with
Pre => not S.Is_Empty,
Post => not S.Is_Full;
procedure Display(S : in Stack);
private
type Buf is array(Positive range <>)
of Element_Type;
type Stack(Size : Positive) is tagged
record
Stk
: Buf(1..Size);
Top
: Positive := 1;
Count : Natural := 0;
end record;
end
Bounded_Stack;
|
This package specification defines the public interface and
the private data definitions for a generic bounded stack ADT. A bounded stack is created with a fixed
maximum size.
The procedure Push pushes an item onto the stack. The precondition
for Push requires the Stack parameter S to not be full (not S.Is_Full). The post-condition requires that after
the successful Push operation the stack will not be empty (not S.Is_Empty). The Pop procedure has
inverse requirements. One can only Pop a value from the stack if the stack is
not empty before the procedure Pop is called (not
S.Is_Empty). After a successful Pop operation the stack will not be full
(not S.Is_Full).
The precondition and the post-condition seem nice enough,
but how do they help the programmer develop correct code? Let’s look first at
the implementation of the subprograms for the generic bounded stack and then at
the “main” procedure used to test this stack ADT.
with
Ada.Text_IO; use Ada.Text_IO;
package body
Bounded_Stack is
-------------
-- Is_Full --
-------------
function Is_Full (S : in Stack) return
Boolean is
begin
return S.Count = S.Size;
end Is_Full;
--------------
-- Is_Empty --
--------------
function Is_Empty (S : in Stack) return
Boolean is
begin
return S.Count = 0;
end Is_Empty;
----------
-- Push --
----------
procedure Push
(S : in out Stack; Item : in
Element_Type) is
begin
S.Stk(S.Top) := Item;
S.Top := S.Top + 1;
S.Count := S.Count + 1;
end Push;
---------
-- Pop --
---------
procedure Pop
(S : in out Stack; Item : out
Element_Type) is
begin
S.Top := S.Top - 1;
Item := S.Stk(S.Top);
S.Count := S.Count - 1;
end Pop;
-------------
-- Display --
-------------
procedure Display (S : in Stack) is
begin
if S.Is_Empty then
Put_Line("Stack is
empty.");
else
for index in reverse 1..S.Top - 1
loop
Put_Line(Image(S.Stk(Index)));
end loop;
end if;
New_Line;
end Display;
end
Bounded_Stack;
|
Now, let’s focus on the Push and Pop procedures, since their
specifications include preconditions and post-conditions.
----------
-- Push --
----------
procedure Push
(S : in out Stack; Item : in
Element_Type) is
begin
S.Stk(S.Top) := Item;
S.Top := S.Top + 1;
S.Count := S.Count + 1;
end Push;
---------
-- Pop --
---------
procedure Pop
(S : in out Stack; Item : out
Element_Type) is
begin
S.Top := S.Top - 1;
Item := S.Stk(S.Top);
S.Count := S.Count - 1;
end Pop;
|
Since the precondition for Pop guarantees that the stack is
not full when this procedure is called there is no need to check for a
stack-full condition within the procedure. Similarly there is no need for the
Pop procedure to check if the stack is empty. The precondition for Pop
guarantees that the stack is not empty when Pop is successfully called.
The programmer can simply assume the preconditions are
satisfied while writing the code for a subprogram with preconditions.
Now, let’s look at the “main” procedure used to test this
ADT:
with
Ada.Text_IO; use Ada.Text_IO;
with
Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with
bounded_Stack;
procedure
Main is
type Options is (Push, Pop, Display,
Quit);
package Int_Stack is new
bounded_Stack(Integer, Integer'Image);
use Int_Stack;
S : Stack(5);
function Menu return Options is
package Opts_Io is new Ada.Text_IO.Enumeration_IO(Options);
use Opts_Io;
Value : Options;
begin
Put_Line("-----------------------------------");
Put_Line(" Push");
Put_Line(" Pop");
Put_Line(" Display");
Put_Line(" Quit");
Put_Line("-----------------------------------");
Put_Line("Enter your
choice");
Get(Value);
return Value;
end Menu;
Choice : Options;
New_Value : Integer;
Popped_Value : Integer;
begin
loop
Choice := Menu;
case Choice is
when Push =>
Put("Enter the new value to
push on the stack: ");
Get(New_Value);
S.Push(New_Value);
when Pop =>
S.Pop(Popped_Value);
Put_Line("Popped "
& Popped_VAlue'Image);
when Display =>
Put_Line("Stack
contents:");
S.Display;
when Quit =>
exit;
end case;
end loop;
end Main;
|
This test makes an instance of the Bounded_Stack package
passing in the type Integer and the function Integer’Image. This creates a
stack package containing Integer elements. The variable S is defined to be an
instance of Stack from that package. This instance is set to contain a capacity
of 5 elements.
S : Stack(5);
A function is defined to display and manipulate a text menu
for interacting with the stack. The
function returns the value of type Options input by the user. The executable
part of the Main procedure simply loops through calling the Menu function and
handling the return value of that function until the Quit option is chosen.
The following output shows what happens when the first
option chosen is to Pop a value from the stack. In this case the stack is still
empty because no value has first been pushed onto the stack.
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
Pop
raised
SYSTEM.ASSERTIONS.ASSERT_FAILURE : failed precondition from
bounded_stack.ads:16 instantiated at main.adb:7
[2018-10-20
09:23:58] process exited with status 1, elapsed time: 06.38s
|
Notice that the program immediately terminated due to the
exception SYSTEM.ASSERTIONS.ASSERT_FAILURE.
Furthermore,
the exception was raised because the precondition stated in the file
bounded_stack.ads, line 16 was violated for the instance of Bounded_Stack
instantiated at line 7 of the file main.adb.
Line 16 of bounded_stack.ads is the line containing the
precondition for the Pop operation.
Now let’s look at the behavior of pushing too many items
onto the stack:
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
push
Enter the
new value to push on the stack: 1
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
push
Enter the
new value to push on the stack: 2
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
push
Enter the
new value to push on the stack: 3
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
push
Enter the
new value to push on the stack: 4
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
push
Enter the
new value to push on the stack: 5
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
display
Stack
contents:
5
4
3
2
1
-----------------------------------
Push
Pop
Display
Quit
-----------------------------------
Enter your
choice
push
Enter the
new value to push on the stack: 6
raised
SYSTEM.ASSERTIONS.ASSERT_FAILURE : failed precondition from
bounded_stack.ads:13 instantiated at main.adb:7
[2018-10-20
09:44:39] process exited with status 1, elapsed time: 38.56s
|
Again, the exception SYSTEM.ASSERTIONS.ASSERT_FAILURE was
raised, this time the precondition for the Push operation was violated.
In both cases the program was terminated because the precondition
for a procedure call was violated. The preconditions prevented buffer overflow
errors while ensuring the requirements for the Push and Pop procedures.