Ada was designed in the late 1970s, first released in 1983, and was the first object-oriented language to be internationally standardized (in 1995). The language was designed to facilitate the design of large, long-lived, efficient, reliable software systems. It is particularly useful in concurrent, distributed, real-time, and embedded systems. It is an extremely safe language and is thus primarily used today in avionics, air traffic control, rail systems, embedded systems, medical devices, and communication.
As a general purpose language, you can use it in virtually every interesting area of Computer Science.
Some resources you’ll want to know about are:
The language was designed for complex tasks. Readability was an important design goal, so many people find it verbose. But others consider this is a good thing. Getting work done in Ada is a very pleasant experience because many aspects of good software engineering are enforced by the language.
The language has evolved over time, with the following major versions:
Version | Wikibook | Reference Manual | Rationale or Overview | Standard |
---|---|---|---|---|
Ada 83 | Info | RM '83 | ISO/IEC 8652:1987 | |
Ada 95 | Info | ARM 95 AARM 95 |
Rat 95 | ISO/IEC 8652:1995(E) with COR.1:2000 |
Ada 2005 | Info | ARM 2005 AARM 2005 |
Rat 2005 | ISO/IEC 8652:1995(E) with COR.1:2001 and AMD.1:2007 |
Ada 2012 | Info | ARM 2012 AARM 2012 |
Rat 2012 | ISO/IEC 8652:2012(E) |
Ada 2022 | Info | ARM 2022 AARM 2022 |
Overview | ISO/IEC 8652:2023 |
The notes on this page are designed to only get you started and cover only the most basic elements of the language. I have notes on Ada concurrency elsewhere, but even these cover only a small subset of the language.
For a more modern and much better tutorial, see the Wikibook or learn.adacore.com.
The simplest kind of program is a simple procedure with no parameters:
with Ada.Text_IO;
procedure Hello is
begin
Ada.Text_IO.Put_Line ("Hello, world");
end Hello;
The modern way to execute Ada programs is to use the package manager Alire. If you are familiar with Rust, Alire is equivalent to Cargo. If you set Alire up correctly, and ask it to build a binary crate for your hello program, you should be able to run it with:
$ alr run ⓘ Building ada=0.0.1/ada.gpr... Compile [Ada] hello.adb Bind [gprbind] hello.bexch [Ada] hello.ali Link [link] hello.adb ✓ Build finished successfully in 0.13 seconds. Hello, world
Since the run command produces a lot of output, you can just do alr build
, which puts the executable in the bin folder. Then just run:
$ bin/hello Hello, world
Here’s a program that prints some lucky numbers:
-------------------------------------------------------------------------------
-- This program writes out a table of all Pythagorean triples whose elements
-- are in the range 1..100. Each of the triples are written on a single line
-- of text with each value right-justified in a text field six characters
-- wide.
-------------------------------------------------------------------------------
with Ada.Text_IO, Ada.Integer_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO;
procedure Triple is
begin
Put_Line (" A B C");
Put_Line ("------------------");
for C in 1 .. 100 loop
for B in 1 .. C loop
for A in 1 .. B loop
if A ** 2 + B ** 2 = C ** 2 then
Put (A, Width => 6);
Put (B, Width => 6);
Put (C, Width => 6);
New_Line;
end if;
end loop;
end loop;
end loop;
end Triple;
And more numbers you know:
-------------------------------------------------------------------------------
-- This program prints out Fibonacci numbers in octal, decimal and hexadecimal
-- in reverse order. The number of Fibonacci numbers that the user wants
-- to see is input on the command line, but if no arguments are given on the
-- command line then the program will prompt the user to input that value.
--
-- This script is meant only to demonstrate a few Ada features, such as user-
-- defined exceptions, enumeration types, the 'Pos attribute, attributes, the
-- reverse keyword, the case statement, and assignment to array slices.
-------------------------------------------------------------------------------
with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Command_Line;
use Ada.Text_IO, Ada.Integer_Text_IO, Ada.Command_Line;
procedure Fib is
Number_Of_Values_Requested : Integer;
-- In Ada there are a number of predefined exceptions, but we can declare
-- our own. Too_Few will be raised if the user does not ask for at least
-- two Fibonacci numbers, and Bad_Argument will be raised if the program
-- can not make sense out of the command line argument:
Too_Few, Bad_Argument : exception;
type Number_List is array (Integer range <>) of Integer;
type Number_System is (Octal, Decimal, Hex);
-- The function Get_User_Input returns the user's "input" as follows:
-- First it checks to see if any argument was entered on the command
-- line and if so attempts to convert that value to a natural number.
-- If the command line has no parameters the user is prompted for an
-- input. In either case, the value input must be greater than or
-- equal to 2; the function raises Too_Few if it is not.
function Get_User_Input return Integer is
Size : Integer;
begin
if Argument_Count > 0 then
Size := Natural'Value (Argument (1));
else
Put ("How many numbers do you want (minimum = 2)? ");
Get (Size);
end if;
if Size < 2 then
raise Too_Few;
end if;
return Size;
exception
-- Natural'Value raises a Constraint_Error if the string cannot be
-- converted to a natural. Let's repackage this to a nicer exception
when Constraint_Error => raise Bad_Argument;
end Get_User_Input;
-- Compute (Size) finds and displays the first Size Fibonacci numbers.
-- It first allocates an array of just the right size to store the
-- numbers, then loads the array by performing the straightforward
-- calculations. Then the array is printed backwards three times
-- with different bases, using the helper function Display.
procedure Compute (Size : Integer) is
Fibs : Number_List (1 .. Size);
procedure Display (B : Number_System) is
begin
Put ("Fibonaccis in " & Number_System'Image (B) & " backwards: ");
New_Line (2);
for I in reverse Fibs'Range loop
case B is
-- The order of arguments should be value, width, base. But
-- you can use argument association to change things up a bit.
when Decimal => Put (Fibs (I), 15, 10);
when Octal => Put (Fibs (I), Base => 8, Width => 15);
when Hex => Put (Base => 16, Item => Fibs (I), Width => 15);
end case;
New_Line;
end loop;
New_Line (2);
end Display;
begin
Fibs (1 .. 2) := [1, 1];
for I in 3 .. Size loop
Fibs (I) := Fibs (I - 1) + Fibs (I - 2);
end loop;
Display (Octal);
Display (Decimal);
Display (Hex);
end Compute;
begin
Number_Of_Values_Requested := Get_User_Input;
Compute (Size => Number_Of_Values_Requested);
Put_Line ("Program successfully completed");
exception
when Too_Few => Put_Line ("Not enough values requested");
when Bad_Argument => Put_Line ("Illegal command line argument");
when Constraint_Error => Put_Line ("I can't find that many");
when Data_Error => Put_Line ("That's not a decent response");
end Fib;
And prime numbers, because you may have expected to see them:
------------------------------------------------------------------------------
-- This program computes and prints all the prime numbers up to and including
-- a number which is provided as the sole argument on the command line, or,
-- if no command line arguments are given, input by the user in response to
-- a prompt, and then reports how many primes it found.
------------------------------------------------------------------------------
with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Command_Line;
use Ada.Text_IO, Ada.Integer_Text_IO, Ada;
procedure Prime_Sieve is
Limit : Natural; -- will find primes up to this
How_Many : Natural; -- how many primes were found
procedure Compute_Primes (Up_To : Natural; Count : out Natural) is
Sieve : array (2 .. Up_To) of Boolean := (others => True);
begin
Count := 0;
for I in Sieve'Range loop
if Sieve (I) then
Put (Item => I, Width => 12, Base => 10);
Count := Count + 1;
declare -- start a block
K : Natural := I; -- variable local to the block
begin
while K <= Sieve'Last - I loop
K := K + I; -- go to next multiple
Sieve (K) := False; -- check off this multiple
end loop;
end; -- after the block K does not exist
if Count mod 6 = 0 then -- write six primes per row
New_Line;
end if;
end if;
end loop;
New_Line (2);
end Compute_Primes;
begin
if Command_Line.Argument_Count = 0 then
Put ("Find primes up to what number? ");
Get (Limit);
else
Limit := Natural'Value (Command_Line.Argument (1));
end if;
Compute_Primes (Up_To => Limit, Count => How_Many);
Put_Line ("I found" & Natural'Image (How_Many) & " primes.");
exception
when Constraint_Error => Put_Line ("Illegal command line argument");
when Data_Error => Put_Line ("I don't understand that input");
end Prime_Sieve;
The Ada language is comprised of two parts:
Required of all implementations.
ARM Clauses 1-13, and Annexes A, B, and J.
Not required of an implementation.
ARM Annexes C, D, E, F, G, and H.
You should browse the reference manual table of contents now to see what these clauses and annexes refer to.
Ada is clearly not a small language.
abort
abs
abstract
accept
access
aliased
all
and
array
at
begin
body
case
constant
declare
delay
delta
digits
do
else
elsif
end
entry
exception
exit
for
function
generic
goto
if
in
interface
is
limited
loop
mod
new
not
null
of
or
others
out
overriding
package
parallel
pragma
private
procedure
protected
raise
range
record
rem
renames
requeue
return
reverse
select
separate
some
subtype
synchronized
tagged
task
terminate
then
type
until
use
when
while
with
xor
An Ada program is made up of a collection of program units, which can be either subprograms or packages. Ada units are designed to have well specified interfaces so they are highly reusable.
Here is a trivial example of a program made up of a package exporting a type Float_Array
and two functions, Mean
and Variance
:
package Stats is
type Float_Array is array (Integer range <>) of Float;
function Mean (A : Float_Array) return Float;
function Variance (A : Float_Array) return Float;
end Stats;
To show off information hiding features, the package contains a local, non-exported utility function called Sum
:
package body Stats is
function Sum (A : Float_Array) return Float is (A'Reduce("+", 0.0));
function Mean (A : Float_Array) return Float is
(Sum (A) / Float (A'Length));
function Variance (A : Float_Array) return Float is
M : constant Float := Mean (A);
Square_Diffs : Float_Array (A'Range);
begin
Square_Diffs := [for I in A'Range => (A (I) - M) ** 2];
return Mean (Square_Diffs);
end Variance;
end Stats;
Here’s a command line application that shows off the functions:
with Ada.Text_IO, Ada.Float_Text_IO, Stats;
use Ada.Text_IO, Ada.Float_Text_IO, Stats;
procedure Show_Stats is
A : constant Float_Array := [3.0, 5.0, 8.0, 2.0];
begin
Put (Mean (A)); New_Line;
Put (Variance (A)); New_Line;
end Show_Stats;
There was a lot going on there!
See Section 3 of the Reference Manual, particularly the note at the bottom of Section 3.2 which gives a really nice hierarchy of the kinds of types in the type system, and a few of the built-in (standard) types. Here’s a summary of that information:
All Types │ ┌─────────────────┴─────────────────┐ │ │ Elementary Composite │ │ ┌───┴──────┐ ┌─────┴─────────┐ │ │ │ │ Scalar Access Untagged Tagged │ ├─ To Object ├─ Array ├─ Nonlimited Tagged Record │ └─ To Subprogram │ └─ String └─ Limited Tagged │ ├─ Record ├─ Limited Tagged Record ├─ Discrete ├─ Task └─ Synchronized Tagged │ ├─ Enumeration └─ Protected ├─ Tagged Task │ │ ├─ Character └─ Tagged Protected │ │ └─ Boolean │ └─ Integer │ ├─ Signed │ └─ Modular │ └─ Real ├─ Floating Point └─ Fixed Point ├─ Ordinary └─ Decimal
Here’s a program that illustrates a few of the simplest types in use:
TODO
TODO
We’ve seen a lot of the statements above, so for completeness, here’s a list of every kind of Ada statement:
Here’s another classic pedagogical exercise: creating an abstract data types for stacks, from first principles, using linked lists. The stack element type will be generic. First, the package specification:
generic
type Element is private;
package Unbounded_Stacks is
type Stack is limited private;
Overflow, Underflow, No_Top : exception;
function Is_Empty (S : Stack) return Boolean;
function Size (S : Stack) return Natural;
procedure Push (S : in out Stack; E : Element);
function Top (S : Stack) return Element;
procedure Pop (S : in out Stack);
procedure Pop (S : in out Stack; E : out Element);
private
type Node;
type Stack is access Node;
type Node is record
Data : Element;
Link : Stack;
end record;
end Unbounded_Stacks;
Next, the package body, in a separate file:
package body Unbounded_Stacks is
function Is_Empty (S : Stack) return Boolean is (S = null);
function Size (S : Stack) return Natural is
(if S = null then 0 else 1 + Size (S.Link));
procedure Push (S : in out Stack; E : Element) is
begin
S := new Node'(E, S);
exception
when Storage_Error => raise Overflow;
end Push;
function Top (S : Stack) return Element is
begin
if Is_Empty (S) then
raise No_Top;
end if;
return S.Data;
end Top;
procedure Pop (S : in out Stack) is
begin
if Is_Empty (S) then
raise Underflow;
end if;
S := S.Link;
end Pop;
procedure Pop (S : in out Stack; E : out Element) is
begin
if Is_Empty (S) then
raise Underflow;
end if;
E := S.Data;
S := S.Link;
end Pop;
end Unbounded_Stacks;
Now, if we want to add new functionality to our ADT, we can use a child package. This is a pretty decent illustration of when to use child packages, since it’s probably best not to pollute the basic stack package with I/O, which not everyone may want:
------------------------------------------------------------------------------
-- unbounded_stacks-io.ads
--
-- A generic child package which enables output for unbounded stacks.
--
-- Generic Parameters:
--
-- Put (F, E) procedure to display element E to file F.
--
-- Operations:
--
-- Put (F, S) write S to file F in the form <x1 x2 ... xn> from top
-- to bottom.
-- Put (S) Same as Put (Current_Output, S)
------------------------------------------------------------------------------
with Ada.Text_IO;
use Ada.Text_IO;
generic
with procedure Put (F : File_Type; E : Element);
package Unbounded_Stacks.IO is
procedure Put (F : File_Type; S : Stack);
procedure Put (S : Stack);
end Unbounded_Stacks.IO;
Here’s the body of the child package:
package body Unbounded_Stacks.IO is
procedure Put (F : File_Type; S : Stack) is
T : Stack := S;
begin
Put (F, "< ");
while T /= null loop
Put (F, T.Data);
Put (' ');
T := T.Link;
end loop;
Put (F, '>');
end Put;
procedure Put (S : Stack) is
begin
Put (Current_Output, S);
end Put;
end Unbounded_Stacks.IO;
To exercise the stack package, we write a little driver:
with Ada.Text_IO, Unbounded_Stacks, Unbounded_Stacks.IO;
use Ada.Text_IO;
procedure Test_Unbounded_Stacks is
package My_Stacks is new Unbounded_Stacks (Character);
use My_Stacks;
package My_Stacks_IO is new My_Stacks.IO (Put);
use My_Stacks_IO;
S : Stack;
procedure Test is
Option : Character;
Data : Character;
begin
loop
Put (S);
Put_Line (" Length =" & Integer'Image (Size (S)));
Put ("Empty pusH Pop Quit: ");
Get (Option);
New_Line;
case Option is
when 'h' =>
begin
Put ("push what? "); Get (Data); Push (S, Data);
exception
when Overflow => Put_Line ("Cannot push onto full stack");
end;
when 'p' =>
begin
Pop (S, Data); Put (Data); Put_Line (" popped");
exception
when Underflow => Put_Line ("Cannot pop from empty stack");
end;
when 'e' =>
Put (if Is_Empty (S) then "Empty" else "Not Empty");
when 'q' => exit;
when others => null;
end case;
New_Line;
end loop;
end Test;
begin
Test;
end Test_Unbounded_Stacks;
TODO
TODO
TODO
Concurrency features have been built into Ada since its inception, and is one of the language's key strengths. The topic is so large that full-length books have been written about Ada concurrency.
My notes are on a separate page.
TODO
Ada features a really cool and comprehensive standard library. The full list of units is summarized at the beginning of Annex A of the Reference Manual. The library is organized into a hierarchy, which can be used to organize code and provide a namespace for identifiers.
There are three root library units: Ada, System, and Interfaces. Here’s a listing for scanning. See the Reference Manual index to dig deeper.
Ada — A.2 Assertions — 11.4.2 Asynchronous_Task_Control — D.11 Calendar — 9.6 Arithmetic — 9.6.1 Formatting — 9.6.1 Time_Zones — 9.6.1 Characters — A.3.1 Conversions — A.3.4 Handling — A.3.2 Latin_1 — A.3.3 Command_Line — A.15 Complex_Text_IO — G.1.3 Containers — A.18.1 Bounded_Doubly_Linked_Lists — A.18.20 Bounded_Hashed_Maps — A.18.21 Bounded_Hashed_Sets — A.18.23 Bounded_Indefinite_Holders — A.18.32 Bounded_Multiway_Trees — A.18.25 Bounded_Ordered_Maps — A.18.22 Bounded_Ordered_Sets — A.18.24 Bounded_Priority_Queues — A.18.31 Bounded_Synchronized_Queues — A.18.29 Bounded_Vectors — A.18.19 Doubly_Linked_Lists — A.18.3 Generic_Array_Sort — A.18.26 Generic_Constrained_Array_Sort — A.18.26 Generic_Sort — A.18.26 Hashed_Maps — A.18.5 Hashed_Sets — A.18.8 Indefinite_Doubly_Linked_Lists — A.18.12 Indefinite_Hashed_Maps — A.18.13 Indefinite_Hashed_Sets — A.18.15 Indefinite_Holders — A.18.18 Indefinite_Multiway_Trees — A.18.17 Indefinite_Ordered_Maps — A.18.14 Indefinite_Ordered_Sets — A.18.16 Indefinite_Vectors — A.18.11 Multiway_Trees — A.18.10 Ordered_Maps — A.18.6 Ordered_Sets — A.18.9 Synchronized_Queue_Interfaces — A.18.27 Unbounded_Priority_Queues — A.18.30 Unbounded_Synchronized_Queues — A.18.28 Vectors — A.18.2 Decimal — F.2 Direct_IO — A.8.4 Directories — A.16 Hierarchical_File_Names — A.16.1 Information — A.16 Dispatching — D.2.1 EDF — D.2.6 Non_Preemptive — D.2.4 Round_Robin — D.2.5 Dynamic_Priorities — D.5.1 Environment_Variables — A.17 Exceptions — 11.4.1 Execution_Time — D.14 Group_Budgets — D.14.2 Interrupts — D.14.3 Timers — D.14.1 Finalization — 7.6 Float_Text_IO — A.10.9 Float_Wide_Text_IO — A.11 Float_Wide_Wide_Text_IO — A.11 Integer_Text_IO — A.10.8 Integer_Wide_Text_IO — A.11 Integer_Wide_Wide_Text_IO — A.11 Interrupts — C.3.2 Names — C.3.2 IO_Exceptions — A.13 Iterator_Interfaces — 5.5.1 Locales — A.19 Numerics — A.5 Big_Numbers — A.5.5 Big_Integers — A.5.6 Big_Reals — A.5.7 Complex_Arrays — G.3.2 Complex_Elementary_Functions — G.1.2 Complex_Types — G.1.1 Discrete_Random — A.5.2 Elementary_Functions — A.5.1 Float_Random — A.5.2 Generic_Complex_Arrays — G.3.2 Generic_Complex_Elementary_Functions — G.1.2 Generic_Complex_Types — G.1.1 Generic_Elementary_Functions — A.5.1 Generic_Real_Arrays — G.3.1 Real_Arrays — G.3.1 Real_Time — D.8 Timing_Events — D.15 Sequential_IO — A.8.1 Storage_IO — A.9 Streams — 13.13.1 Storage_Streams — 13.13.1 Bounded_FIFO_Streams — 13.13.1 FIFO_Streams — 13.13.1 Stream_IO — A.12.1 Strings — A.4.1 Bounded — A.4.4 Equal_Case_Insensitive — A.4.10 Hash — A.4.9 Hash_Case_Insensitive — A.4.9 Less_Case_Insensitive — A.4.10 Equal_Case_Insensitive — A.4.10 Fixed — A.4.3 Equal_Case_Insensitive — A.4.10 Hash — A.4.9 Hash_Case_Insensitive — A.4.9 Less_Case_Insensitive — A.4.10 Hash — A.4.9 Hash_Case_Insensitive — A.4.9 Less_Case_Insensitive — A.4.10 Maps — A.4.2 Constants — A.4.6 Text_Buffers — A.4.12 Bounded — A.4.12 Unbounded — A.4.12 Unbounded — A.4.5 Equal_Case_Insensitive — A.4.10 Hash — A.4.9 Hash_Case_Insensitive — A.4.9 Less_Case_Insensitive — A.4.10 UTF_Encoding — A.4.11 Conversions — A.4.11 Strings — A.4.11 Wide_Strings — A.4.11 Wide_Wide_Strings — A.4.11 Wide_Bounded — A.4.7 Wide_Equal_Case_Insensitive — A.4.7 Wide_Hash — A.4.7 Wide_Hash_Case_Insensitive — A.4.7 Wide_Equal_Case_Insensitive — A.4.7 Wide_Fixed — A.4.7 Wide_Equal_Case_Insensitive — A.4.7 Wide_Hash — A.4.7 Wide_Hash_Case_Insensitive — A.4.7 Wide_Hash — A.4.7 Wide_Hash_Case_Insensitive — A.4.7 Wide_Maps — A.4.7 Wide_Constants — A.4.7 Wide_Unbounded — A.4.7 Wide_Equal_Case_Insensitive — A.4.7 Wide_Hash — A.4.7 Wide_Hash_Case_Insensitive — A.4.7 Wide_Wide_Bounded — A.4.8 Wide_Wide_Equal_Case_Insensitive — A.4.8 Wide_Wide_Hash — A.4.8 Wide_Wide_Hash_Case_Insensitive — A.4.8 Wide_Wide_Equal_Case_Insensitive — A.4.8 Wide_Wide_Fixed — A.4.8 Wide_Wide_Equal_Case_Insensitive — A.4.8 Wide_Wide_Hash — A.4.8 Wide_Wide_Hash_Case_Insensitive — A.4.8 Wide_Wide_Hash — A.4.8 Wide_Wide_Hash_Case_Insensitive — A.4.8 Wide_Wide_Maps — A.4.8 Wide_Wide_Constants — A.4.8 Wide_Wide_Unbounded — A.4.8 Wide_Wide_Equal_Case_Insensitive — A.4.8 Wide_Wide_Hash — A.4.8 Wide_Wide_Hash_Case_Insensitive — A.4.8 Synchronous_Barriers — D.10.1 Synchronous_Task_Control — D.10 EDF — D.10 Tags — 3.9 Generic_Dispatching_Constructor — 3.9 Task_Attributes — C.7.2 Task_Identification — C.7.1 Task_Termination — C.7.3 Text_IO — A.10.1 Bounded_IO — A.10.11 Complex_IO — G.1.3 Editing — F.3.3 Text_Streams — A.12.2 Unbounded_IO — A.10.12 Unchecked_Conversion — 13.9 Unchecked_Deallocate_Subpool — 13.11.5 Unchecked_Deallocation — 13.11.2 Wide_Characters — A.3.1 Handling — A.3.5 Wide_Command_Line — A.15.1 Wide_Directories — A.16.2 Wide_Environment_Variables — A.17.1 Wide_Text_IO — A.11 Complex_IO — G.1.4 Editing — F.3.4 Text_Streams — A.12.3 Wide_Bounded_IO — A.11 Wide_Unbounded_IO — A.11 Wide_Wide_Characters — A.3.1 Handling — A.3.6 Wide_Wide_Command_Line — A.15.1 Wide_Wide_Directories — A.16.2 Wide_Wide_Environment_Variables — A.17.1 Wide_Wide_Text_IO — A.11 Complex_IO — G.1.5 Editing — F.3.5 Text_Streams — A.12.4 Wide_Wide_Bounded_IO — A.11 Wide_Wide_Unbounded_IO — A.11 Interfaces — B.2 C — B.3 Pointers — B.3.2 Strings — B.3.1 COBOL — B.4 Fortran — B.5 System — 13.7 Address_To_Access_Conversions — 13.7.2 Atomic_Operations — C.6.1 Exchange — C.6.2 Integer_Arithmetic — C.6.4 Modular_Arithmetic — C.6.5 Test_And_Set — C.6.3 Machine_Code — 13.8 Multiprocessors — D.16 Dispatching_Domains — D.16.1 RPC — E.5 Storage_Elements — 13.7.1 Storage_Pools — 13.11 Subpools — 13.11.4
Please, read the Wikibook. There wasn’t much covered here.