Skip to content

Instantly share code, notes, and snippets.

@atweiden
Created April 10, 2016 00:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atweiden/abacb9632d7cdbddf9a3999aec33073e to your computer and use it in GitHub Desktop.
Save atweiden/abacb9632d7cdbddf9a3999aec33073e to your computer and use it in GitHub Desktop.
/* vim: ft=journal */
Ada Tutorial
============
Variables and Types
-------------------
- Variables must be declared between a procedure/function's `is` and `begin`
statements
- To create a read-only variable you may either:
1. use the `constant` reserve word in variable declaration
- `Input : constant Months := Months'Value (Ada.Command_Line.Argument (1));`
2. implement a function that takes no parameters
- “Notice that Ada does not require (or even allow) you to use
an empty parameter list on functions that take no parameters. This
means that function `End_Of_File` looks like a variable when it
is used. This is considered a feature; it means that read-only
variables can be replaced by parameterless functions without
requiring any modification of the source code that uses that
variable.”
- Ada is strongly typed. There is no type inference.
- The `Natural` type:
- `subtype Natural is Integer range 0 .. Integer'Last;`
- The `Positive` type:
- `subtype Positive is Integer range 1 .. Integer'Last;`
Case Statements
---------------
- must be exhaustive
- must not contain clauses that overlap
Package Imports
---------------
- standard:
```ada
with Compress.Algo.LZW;
procedure Hello is
Compressor : Compress.Algo.LZW.LZW_Engine;
begin
Compress.Algo.LZW.Process(Compressor, "Compress Me!");
end Hello;
```
- aliasing with `renames`:
```ada
with Compress.Algo.LZW;
procedure Hello is
package Squeeze renames Compress.Algo;
Compressor : Squeeze.LZW.LZW_Engine;
begin
Squeeze.LZW.Process(Compressor "Compress Me!");
end Hello;
```
- "radical" aliasing example with `subtype` and `renames`:
```ada
with Compress.Algo.LZW;
procedure Hello is
subtype LZW_Type is Compress.Algo.LZW.LZW_Engine;
procedure Comp(Engine : LZW_Type; Data : String)
renames Compress.Algo.LZW.Process;
Compressor : LZW_Type;
begin
Comp(Compressor "Compress Me!");
end Hello;
```
- `use` statement:
```ada
with Compress.Algo.LZW; use Compress.Algo.LZW;
procedure Hello is
Compressor : LZW_Engine;
begin
Process(Compressor, "Compress Me!");
end Hello;
```
- `use type` statement:
```ada
procedure Example is
use type Big_Number.Number_Type;
X, Y, Z : Big_Number.Number_Type;
begin
-- Fill Y and Z with interesting values .
X : = Y / Z;
exception
when Big_Number.Divide_By_Zero =>
Put_Line("Big Number division by zero!");
when others =>
Put_Line("Unexpected exception!");
end Example;
```
- “The previous example also shows an interesting detail related
to operator overloading. The example assumes that there is
a context clause of `with Big_Number` (not shown) but no
`use` statement. Thus the division operator is properly named
`Big_Number."/"`. Unfortunately it can’t be called using the
infix operator notation with that name. There are several ways
to get around this. One could include a `use Big_Number` in the
context clause or in the procedure’s declarative part. However
that would also make all the other names in package `Big_Number`
directly visible and that might not be desired. An alternative is
to introduce a local function named `/` that is just a renaming
(essentially an alias) of the function in the other package. As we
have seen Ada allows such renaming declarations in many situations,
but in this case, every operator function would need a corresponding
renaming and that could be tedious. Instead the example shows a more
elegant approach. The `use type` declaration tells the compiler that
the primitive operations of the named type should be made directly
visible. I will discuss primitive operations in more detail in the
section on object oriented programming. In this case, the operator
overloads that are declared in package `Big_Number` are primitive
operations. This method allows all the operator overloads to be
made directly visible with one easy statement, and yet does not make
every name in the package directly visible.”
Error Types
-----------
- `Constraint_Error`
- Raised whenever a constraint is violated. This includes going outside
the bounds of a subtype (or equivalently the allowed range of an
array index) as well as various other situations.
- `Program_Error`
- Raised when certain ill-formed programs that can’t be detected
by the compiler are executed. For example, if a function ends without
executing a return statement, `Program_Error` is raised.
- `Storage_Error`
- Raised when the program is out of memory. This can occur during
dynamic memory allocation, or be due to a lack of stack space when
invoking a subprogram. This can also occur during the elaboration
of a declarative part (for example if the dynamic bounds on an array
are too large).
- `Tasking_Error`
- Raised in connection with certain tasking problems.
Access Types
------------
- Access types can be named or anonymous. I will only consider named
access types; anonymous access types have special properties that are
outside the scope of this tutorial. For example the declaration:
`type Integer_Access is access Integer;` declares `Integer_Access`
as a type suitable for accessing, or pointing at, integers. Once the
access type has been declared, variables of that type can then be
declared in the usual way.
- Ada automatically initializes access variables to the special value
`null` if no other initialization is given. Thus access variables are
either `null` or they point at some real object. Indeed, the rules of
Ada are such that, under appropriate circumstances, dangling pointers
are not possible. Access variables can be copied and compared like any
other variable. For example if `P1` and `P2` are access variables than
`P1 = P2` is true if they both point at the same object. To refer to
the object pointed at by an access variable you must use the special
`.all` operation: `P.all := 1;`.
- The `.all` syntax plays the same role in Ada as the indirection operator
(the `*`) plays in C and C++. The use of `.all` may seem a little odd
in this context, but understand that most of the time access types
are pointers to aggregate entities such as arrays or records. In
that case, the components of the array or record pointed at can be
accessed directly by using the component selection operation on the
access variable itself. This is shown as follows:
```ada
type Date is
record
Day, Month, Year : Integer;
end record;
type Date_Access is access Date;
D1, D2 : Date_Access;
D1.Day := 1; -- Accesses the Day member of referenced Date.
D1 := D2; -- Causes D1 to point at same object as D2.
D1.all := D2.all; -- Copies Date objects.
```
- In this case the `.all` syntax is very natural. It shows that you are
accessing all the members of the referenced object at the same
time. It is important to notice that Ada normally “forwards”
operations applied to the access variable to the referenced object.
Thus `D1.Day` in the example means the `Day` component of the object
pointed at by `D1`. Only operations that are meaningful for the
access type itself are not forwarded. Thus `D1 := D2` copies the
access values. To copy the objects pointed at by the access variables
one must use the `.all` syntax. I should also note that syntax such as
`D1.all.Day`, while verbose, is also legal. The `.all` dereferences
`D1`. The result is a date record so the selection of the `Day`
component is meaningful.
- Access variables that refer to arrays also allow the normal array
operations to be forwarded to the referenced object as the following
example illustrates:
```ada
type Buffer_Type is array (0..1023) of Character;
type Buffer_Access is access Buffer_Type;
B1, B2 : Buffer_Access;
B1 := B2; -- Copies only the access values.
B1. all := B2.all; -- Copies arrays.
B1(0) := 'X'; -- Forwards index operation.
for I in B1'Range loop -- Forwards 'Range attribute.
end loop;
```
- Because of forwarding, using access types is generally quite convenient
in Ada. However, you must keep in mind that operations that are
meaningful for the access types themselves will be applied directly
to the access variable and are not forwarded.
- So far we’ve seen how to declare and use access types. How does one
get an access variable to point at another object in the first place? In
Ada 83 there was only one way: using the `new` operation. For example:
`P := new Integer'(0);`
dynamically allocates an integer, initializes that integer to zero,
and then assigns the resulting access value to `P`. Here I assume `P`
has an access to integer type. Notice that the argument to `new` has
the form of a qualified expression (the apostrophe is required). Also
as with dynamically allocated objects in other languages, the lifetime
of the object created in this way extends beyond the lifetime of the
access variable used to point at it.
Garbage Collection
------------------
- Ada allows, but does not require garbage collection. Thus for maximum
portability one must assume that garbage collection is not done and
take steps accordingly.
- The Ada library provides a generic procedure named
`Unchecked_Deallocation` that can be used to manually deallocate a
dynamically allocated object. Unfortunately the use of
`Unchecked_Deallocation` can violate important safety properties the
language otherwise provides. In particular, if you deallocate an object
while an access variable still points at it, any further use of that
access variable will result in erroneous behavior. As a service
`Unchecked_Deallocation` will set the access variable you give it to
`null` causing future use of that access variable to result in a well
defined exception. However, there might be other access variables
that point at the same object and `Unchecked_Deallocation` cannot,
in general, know about all of them.
- If your program never uses `Unchecked_Deallocation` then all access
variables are either `null` or point at a real object; dangling pointers
are impossible. However, your program might also leak memory if the Ada
implementation does not provide garbage collection. Thus in most real
programs `Unchecked_Deallocation` is used.
- This is an example of where Ada compromises safety for the sake of
practical reality. In fact, there are several other “Unchecked”
operations in Ada that are used to address certain practical concerns
and yet introduce the possibility of unsafe programs. Since every such
operation starts with the word “Unchecked” it is an simple matter
to search an Ada program for occurrences of them. This feature makes
reviewing the unchecked operations easier.
************************************************************************
- Credit: https://github.com/pchapin/tutorialada
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment