Structure#
Control Flow#
If Statements and Expressions#
if I = 10 then
-- ...
elsif I in 11 .. 20 then -- Note the elsif spelling
-- ...
else
-- ...
end if;
-- If expressions must be enclosed in parentheses
If_Expr1: Boolean := (if I > 10 then 1 else 0);
If_Expr2: Boolean := (if I > 1000 then 3 elsif I > 100 then 2 else 0);
-- Special rule for boolean expressions
-- These two are equivalent:
(if C1 then C2 else True)
(if C1 then C2)
-- Mostly useful for preconditions
Pre => (if C1 > 0 then C2 > 0)
-- These two expressions have the same value
(if C1 then C2)
(not C1 or C2)
Case Statements and Expressions#
TODO 7.2
Hours: Integer := (case D is
when Mon .. Thurs => 8,
when Fri => 6,
when sat | Sun => 0);
Month_Days: Integer := (case Month is
when Sep | Apr | Jun | Nov => 30,
when Feb =>
(if Year mod 100 = 0 or else
Year mod 400 = 0 or else
Year mod 4 = 0
then 29
else 28),
when others => 31);
Inline Expressions#
Quantified Expressions#
Quantified expressions are a kind of for loop expression that always return a boolean. They were introduced primarily for preconditions and postconditions.
-- If An_Array'Range is null then it evaluates to True
Are_All_Zero: Boolean := (for all I in An_Array'Range => An_Array(I) = 0);
Are_All_Zero_2D: Boolean :=
(for all J in A_2D_Array'Range(1) =>
(for all I in A_2D_Array'Range(2) => A_2D_Array(J, I) = 0));
Are_All_Zero_2D_Iter: Boolean := (for all E of A_2D_Array => E = 0); -- iterator form
-- If An_Array'Range is null then it evaluates to False
Are_Any_Zero: Boolean := (for some I in An_Array'Range => An_Array(I) = 0);
-- Two possible ways of checking if N is a prime
RN: Integer := Integer(Sqrt(Float(N)));
if (for some K in 2 .. RN => N mod K = 0) then -- ...
if (for all K in 2 .. RN => N mod K / = 0) then -- ...
Declaration Expressions#
For declaration expressions, the values must be constant and not limited
, aliased
, Access
, or
Unchecked_Access
.
Val1: Float := (declare X: constant Float := A + B;
begin X**3 + X**2 + X);
Val2: Float := (declare X renames Float'(A + B); -- Specifying the subtype is not needed in Ada2022
begin X**3 + X**2 + X);
Reduction Expressions#
Reduction expressions may be written using the 'Reduce
attribute which takes two arguments: the operation and the
starting value for the accumulation. The loop may not contain reverse
and an array literal must use brackets.
Range_Sum: Integer := ([for I in 1 .. 10 => I]'Reduce("+", 0)); -- 55
Squares_Sum: Integer := ([for I in 1 .. 10 => I**2]'Reduce("+", 0)); -- 385
function Factorial(N: Natural) return Natural is
([for I in 1 .. N => I]'Reduce("*", 1));
-- Can be written via iterator syntax:
Total_1: Float := [for E of Float_2D_Array => E]'Reduce("+", 0.0);
-- Which can be abbreviated to:
Total_2: Float := Float_2D_Array'Reduce("+", 0.0);
Maximum: Float := Float_2D_Array'Reduce(Float'Max, 0.0);
Scope and Visibility#
Scope is typical in that each block introduces a new scope and creating symbols with a name existing in a parent scope hides the parent’s symbol. In order to access the parent’s symbol after it is hidden, the name of the outer scope can be used. Scopes that do not have names may be given names in order to refer to them for this purpose.
Outer: -- Naming this declare block
declare
X: Integer := 77;
begin
-- ...
for X in 1 .. 10 loop
Put(Integer'Image(X));
Put(Integer'Image(Outer.X));
end loop; -- 1 77 2 77 3 77 ...
end Outer;
In addition, return
can be used to terminate the execution of a subprogram, exit
can be used to
terminate the execution of the current loop (or a named exit Outer
to exit an outer loop) and goto
may
be used to jump to a label within the current subprogram.
Outer_Loop:
for I in 1 .. Integer'Last loop
for I in 1 .. 2 loop
Put(Integer'Image(I));
Put_Line(Integer'Image(Outer_Loop.I));
end loop;
if I = 2 then
goto label;
end if;
end loop Outer_Loop;
-- ...
<<label>>
-- 1 1
-- 2 1
-- 1 2
-- 2 2
Subprograms#
Subprograms in Ada are distinguished by functions and procedures. Functions return a value while procedures do not.
Functions
Should a function run into the final end
, a Program_Error
exception is raised. For procedures,
running into the final end
is the same as returning.
Functions#
function Factorial(N: Positive) return Positive is
-- This is the declaration section
begin
-- This is the statement section where the body of the function goes.
-- A value must be returned because this is a function.
if N = 1 then
return 1;
else
return N * Factorial(N - 1);
end if;
end Factorial;
- If an invalid value (such as -1) is passed in, it will result in a
Constraint_Error
exception. - If a moderate value (such as 100) is passed in, it will result in a
Constraint_Error
exception for integer overflow. - If a large value (such as 1_000_000) is passed in, it may result in a
Storage_Error
exception for a stack overflow.
Abbreviated Form#
An abbreviated form of a function can be just an expression:
function Dot(V, U: Vector2) return Float is
(V.X*U.X + V.Y*U.Y); -- Expression must be in parentheses
Procedures#
-- Parameters with the same type may be grouped
procedure My_Procedure(Param1, Param2: Integer; Param3: Float) is
-- Local subprograms may also be declared here.
function Calc_Something(X: Float) return Float is
begin
return X + X**2;
end Calc_Something;
Val: Float;
begin
-- Procedure body goes here and does not return a value. It may not be empty.
Val := Calc_Something(Param3);
-- ...
end My_Procedure;
Null Procedures#
One special kind of procedure is a null procedure.
procedure Option(X: in T) is null;
-- Null procedures may not have bodies and act as follows:
-- procedure Option(X: in T) is
-- begin
-- null;
-- end Option;
Overriding#
Derived type may overload inherited procedures. There are two optional qualifiers that may be given to subprograms:
overriding
and not overriding
. Although they are optional, using them can prevent certain errors as they
require/do not require the subprogram to be currently available.
overriding procedure Option(X: in S);
not overriding procedure Maybe(X: in S);
Parameters#
Parameters may have a combination of two different qualifications: in
and out
. in
parameters are
the default.
Function Parameters
Before Ada2012, functions could only take in
parameters.
(Param: Integer) -- Defaults to in
(Param: in Integer) -- Equivalent to the previous
(Param: out Integer)
(Param: in out Integer)
in
parameters can be passed in as any valid expression and they act as local constants within the subprogram.
out
and in out
parameters can be modified and a variable to be modified must be passed into the
subprogram. Parameters marked as out
may be copied (depending on the implementation for performance or
simplicity reasons), but access types, discriminated record types and records with components that have default
initial values are always copied.
For in
parameters, scalar types are passed by value, whereas parameters of:
- arrays
- task and protected types
- tagged records
- explicitly limited types
are always passed by reference (including records containing any of these types).
Type conversions are allowed for arguments and follow the process of converting, processing with the subprogram and then converting back.
-- procedure Increment(I: in out Integer)
A: Float := 1.1;
B: Float := 1.5;
Increment(Integer(A)); -- A is now 2.0; 1.1 -> [1 -> 2] -> 2.0
Increment(Integer(B)); -- B is now 3.0; 1.5 -> [2 -> 3] -> 3.0
Named arguments are given in the same way as named values for records (and similar to that of arrays). The rules for positional arguments are like those of other languages:
- Positional arguments must come first
- Named arguments may be in any order
- Fields with default arguments may still be excluded
-- procedure Put_Line(File: File_Type; Item: String)
Put_Line(File => Standard_Error, Item => "Output line to stderr");
-- procedure Replace_Element(Container: in out Vector; Index: Index_Type; New_Item: Element_Type);
Replace_Element(My_Container, New_Item => My_Item, Index => 10);
Default values may be given for any parameters, but placing them after parameters without default values is not useful unless used with named arguments.
-- procedure My_Procedure(X: Float; Y: Float := 2.0; Z: Float)
My_Procedure(2.0, 4.0, 8.0);
My_Procedure(2.0, 4.0); -- error: missing argument for parameter "Z"
My_Procedure(X => 2.0, Z => 4.0); -- Okay
Mutable Parameters
For understanding semantics, it is best to think of out
parameters as being copied into their destination
at the end of the subprogram rather than as implicit pointers. This makes the aliasing rules easier to remember.
Out Parameters
Out
parameters that are not assigned to may have undefined values.
procedure Calculate(A: in Float; B: out Float) is
begin
if A < 0.0 then
return;
else
B := 10.0;
end if;
end Calculate;
Result: Float;
Calculate(-1.0, Result); -- Result is not given a value and so is still undefined
procedure Calculate(A: in Float; B: in out Float)
in this case would
lead to the possiblity that Result
via B
is referenced before assignment inside of the subprogram.
Both cases (although they may compile) are incorrect and your compiler should emit a warning about this.
A solution in this case (apart from changing the subprogram’s design) is to use a type with a default value:
type Real is new Float with Default_Value => 0.0;
. Ada’s access
types are like this because they default
to null
to avoid the potential of garbage in a pointer.
Overloading#
Subprograms may be overloaded as in other languages. One difference is that functions and procedures may be overloaded by the return value.
procedure My_SR(X: Float) is
begin
-- ...
end My_SR;
function My_SR(X: Float) return Integer is
begin
-- ...
return 1;
end My_SR;
My_SR(2.47); -- Calls the procedure
X: Integer := My_SR(3.69); -- Calls the function
Extended Return Statement#
type Vector is array(Integer range <>) of Float;
function Squared(Input: Vector) return Vector is
begin
return Result: Vector(Input'Range) do -- Can also provide a default value, if needed
for I in Input'Range loop
Result(I) := Input(I) * Input(I);
end loop;
end return;
end Squared;
Operators#
The name of a function may be an operator provided in quotation marks. The list of operators that may be overloaded is:
abs | and | mod | not | or | rem | xor |
---|---|---|---|---|---|---|
= |
/= |
< |
<= |
>= |
> |
|
+ |
- |
* |
/ |
** |
& |
Operator Overloading
Overloaded operators may not have parameters other than in
parameters regardless of Ada version
function "+"(V, U: Vector2) return Vector2 is
(X => V.X + U.X,
Y => V.Y + U.Y);
-- Unary sum operator for a vector
-- A_Sum := +A_Vector;
function "+"(V: Vector) return Float is
Result: Float := 0.0;
begin
for I in V'Range loop
Result := Result + V(I);
end loop;
return Result;
end "+";
The only operator with special rules is /=
. An overload of =
need not return a boolean value, but if it does then
an overload of /=
is implicitly created. If =
does not return a boolean value, the /=
function may be overloaded
as normal.
Membership tests via in
and not in
and the short-circuit forms of and then
and or else
may not be given new meanings.
Packages#
Packages are the global namespacing abstraction in Ada. For the GNAT toolchain, the specifications and bodies for packages are normally separated into “Package_Name.ads” specification files and “Package_Name.adb” body files. The only bodies allowed inside of a specification are short abbreviated form functions and any declared as such do not need to appear in the body.
-- Specification
-- For GNAT, this should be in "My_Package.ads".
package My_Package is
-- Visible values declared here are:
-- 1. Types
-- 2. Subprograms
-- 3. Variables
-- No bodies are allowed for subprograms, except for functions of the
-- abbreviated (single expression) form.
private -- Optional section
-- Symbols declared as "private" are defined here
end My_Package;
-- Implementation
-- For GNAT, this should be in "My_Package.adb".
package body My_Package is
-- Subprograms declared in the specification must match and have their bodies here.
-- All symbols in the specification are visible here, but anything declared here
-- and not in the specification will only be available in this package.
begin -- Optional section
-- Initialisation statements can go here.
end My_Package;
with
is
used and to add symbols from the packages into the current scope, use
is used. So, for the package
Ada.Text_IO
:
with Ada.Text_IO; -- package "Ada.Text_IO" is now available in the current scope.
Ada.Text_IO.Put_Line("Had to access this via dot syntax.");
use Ada.Text_IO; -- The package's symbols are now in the current scope.
Put_Line("Don't have to use dot syntax now unless there are ambiguities.");
Ada.Text_IO.Put_Line("This still works.");
-- Types and their primitive operators can be made available without "using" the whole package.
use type A_Package.A_Type;
-- To make all operators (including user-defined ones) and not just the primitive ones available:
use all type A_Package.A_Type;
with
statement may only appear in a context clause, but use
statements may appear in a
declaration clause.
Child Packages#
Child packages may be created by using the dot notation.
-- For GNAT, this should be in "parent_package-child_package.ads"
package Parent_Package.Child_Package is
-- ...
end Parent_Package.Child_Package;
with
clause or use
clause for its parent. Sibling packages are like unrelated packages except that they
may refer to each other through the implicit knowledge of their parent package. The parent’s body may depend on a child,
but it must be explicitly included.
Child packages may also be labelled as private (private Parent_Package.Child_Package is -- ...
) in which case
they are only visible to their parent and siblings and you can use private with Child_Package;
in order to only
make the package available to the private section of the package. In this case a use
statement must be inside
the private section as well.
Subunits#
Bodies of subprograms may be removed from their package by making them separate
.
package body My_Package is
procedure SR(X: Integer) is separate;
end My_Package;
separate
followed by the name of the parent package in parentheses.
separate(My_Package)
procedure SR(X: Integer) is
begin
-- ...
end My_Package;
Mutually Dependent Packages#
When you need packages that depend on each others types, limited with Some_Package;
can be used to include the
types as though they were declared as incomplete. This allows the typical use of incomplete types such as having access
types to them. Using a limited clause does not create a dependency like a standard with clause which means that there
is no requirement that Some_Package
be compiled before the dependent package.
limited with
clauses may only appear in specifications and a use
clause cannot apply to packages with a
limited view.
Exceptions#
Note
Exceptions are not like normal values. They cannot be in arrays, components of records or parameters of a
subprogram. They behave like literals of the global type Ada.Exceptions.Exception_Id
and are not created
dynamically.
There are four predefined exceptions (there is also the obsolescent Numeric_Error
which is a renamed
Constraint_Error
).
Constraint_Error
— Something is out of range, division by zero, etc.Program_Error
— The control structure is violated. For example, running into theend
of a function, breaking accessibility rules or calling a subprogram whose body has not been elaborated.Storage_Error
— Memory has run out such as with a stack overflow.Tasking_Error
— Threading errors such as failure to activate a task or being aborted mid rendezvous.
Exceptions are handled with exception handlers (the when
clauses) at the end of the block and are dynamically
propagated. The syntax matches that of case
statements and exceptions that are propagated outside of their scope
may only be caught in the others
clause.
begin
-- Something that may raise an exception
exception
when Constraint_Error =>
-- Handle a constraint error, if it occurs
when others =>
-- Handle any other exception, if one occurs
end;
Custom exceptions may be declared and raised and it is generally poor practice to raise the built-in exceptions.
My_Exception: exception;
raise My_Exception;
raise My_Exception with "Information about the exception";
-- Get the message with Ada.Exceptions.Exception_Message.
-- Without the message, its value is implementation-defined.
raise;
in a handler will do so.
Note
Once an exception has been raised, control cannot be returned directly to the unit where the exception was raised.
The statements following the =>
must terminate the unit which means that for a function there must be a
return
.
In addition, any goto
statements should not jump into or between handlers, but they can jump to a label in
a block outside of the one handling the exception or exit a loop via exit
.
out
and in out
Parameters
If a subprogram is terminated by an exception then the value of a parameter passed in by value may not have been updated since the updating normally happens on a normal return.
Exception Information#
The package Ada.Exceptions
contains the limited type Exception_Occurrence
. This with the subprograms
Exception_Name
, Exception_Message
and Exception_Information
can be used to get further
information about the exception that was raised. These subprograms also allows you to get information about an exception
that is propagated out of its scope.
with Ada.Exceptions; use Ada.Exceptions;
begin
-- ...
exception
when Exn: others =>
Put_Line(Exception_Name(Exn));
Put_Line(Exception_Message(Exn));
Put_Line(Exception_Information(Exn));
end;