Ada provides rather general mechanisms for executing code at elaboration time, that is to say before the main program starts executing. Such code arises in three contexts:
Sqrt_Half : Float := Sqrt (0.5); |
BEGIN-END
section at the outer level of a package body is
executed as part of the package body elaboration code.
Subprogram calls are possible in any of these contexts, which means that any arbitrary part of the program may be executed as part of the elaboration code. It is even possible to write a program which does all its work at elaboration time, with a null main program, although stylistically this would usually be considered an inappropriate way to structure a program.
An important concern arises in the context of elaboration code:
we have to be sure that it is executed in an appropriate order. What we
have is a series of elaboration code sections, potentially one section
for each unit in the program. It is important that these execute
in the correct order. Correctness here means that, taking the above
example of the declaration of Sqrt_Half
,
if some other piece of
elaboration code references Sqrt_Half
,
then it must run after the
section of elaboration code that contains the declaration of
Sqrt_Half
.
There would never be any order of elaboration problem if we made a rule
that whenever you with
a unit, you must elaborate both the spec and body
of that unit before elaborating the unit doing the with
'ing:
with Unit_1; package Unit_2 is ... |
would require that both the body and spec of Unit_1
be elaborated
before the spec of Unit_2
. However, a rule like that would be far too
restrictive. In particular, it would make it impossible to have routines
in separate packages that were mutually recursive.
You might think that a clever enough compiler could look at the actual elaboration code and determine an appropriate correct order of elaboration, but in the general case, this is not possible. Consider the following example.
In the body of Unit_1
, we have a procedure Func_1
that references
the variable Sqrt_1
, which is declared in the elaboration code
of the body of Unit_1
:
Sqrt_1 : Float := Sqrt (0.1); |
The elaboration code of the body of Unit_1
also contains:
if expression_1 = 1 then Q := Unit_2.Func_2; end if; |
Unit_2
is exactly parallel,
it has a procedure Func_2
that references
the variable Sqrt_2
, which is declared in the elaboration code of
the body Unit_2
:
Sqrt_2 : Float := Sqrt (0.1); |
The elaboration code of the body of Unit_2
also contains:
if expression_2 = 2 then Q := Unit_1.Func_1; end if; |
Now the question is, which of the following orders of elaboration is acceptable:
Spec of Unit_1 Spec of Unit_2 Body of Unit_1 Body of Unit_2
or
Spec of Unit_2 Spec of Unit_1 Body of Unit_2 Body of Unit_1
If you carefully analyze the flow here, you will see that you cannot tell
at compile time the answer to this question.
If expression_1
is not equal to 1,
and expression_2
is not equal to 2,
then either order is acceptable, because neither of the function calls is
executed. If both tests evaluate to true, then neither order is acceptable
and in fact there is no correct order.
If one of the two expressions is true, and the other is false, then one
of the above orders is correct, and the other is incorrect. For example,
if expression_1
/= 1 and expression_2
= 2,
then the call to Func_1
will occur, but not the call to Func_2.
This means that it is essential
to elaborate the body of Unit_1
before
the body of Unit_2
, so the first
order of elaboration is correct and the second is wrong.
By making expression_1
and expression_2
depend on input data, or perhaps
the time of day, we can make it impossible for the compiler or binder
to figure out which of these expressions will be true, and hence it
is impossible to guarantee a safe order of elaboration at run time.