CSE 505 Lecture Notes:
Ada
October 17, 1994
Syntax
fully bracketed syntax
examples:
if x=0 then y:=1;
end if;
if x=0 then y:=1;
else y:=3;
end if;
if x=0 then y:=1;
elseif x=3 then y:=3;
elseif x=10 then y:=8;
else y:=88;
end if;
loop - end loop;
case - end case;
Semicolon is statement terminator, not statement separator (less
error-prone than Algol's syntax)
Ada control structures
conventional structures: conditional, case, loop;
also exceptions, rendezvous
loop
...
end loop;
will loop forever
exit statement -- exit from loop
for, while statements
for I in K+1 .. N loop
...
end loop;
I is automatically declared, and is read-only within the loop body
K+1, N evaluated once at start of loop
(this was a problem in the specification of Algol-60
can give names to loops; can exit from named loop
exit statement is more restricted than general goto statement
Ada procedure and function declarations
parameters are declared as in, out, or in out (in is the default)
for scalar values, these are passed by value, result, or value-result
respectively
for composite types (e.g. arrays and records) the compiler can choose the
above, or pass by reference
Can get different behavior depending on which is chosen, either because of
aliasing or because an exception is raised. The Ada language defines such
programs as erroneous (i.e. the compiler doesn't need to indicate
an error, but the programmer shouldn't write a program that depends on this
behavior.)
in parameters are local constants
all parameters to a function must be "in"
parameters can be defaulted; parameters can be passed using keywords
example:
procedure ACTIVATE( PROCESS : in PROCESS_NAME;
AFTER : in PROCESS_NAME := NO_PROCESS;
WAIT : in DURATION := 0.0;
PRIOR : in BOOLEAN := FALSE);
example calls:
ACTIVATE(X);
ACTIVATE(X, AFTER=>Y);
ACTIVATE(X, PRIOR=>TRUE, WAIT=>60);
ACTIVATE(X,Y,10.0,FALSE);
Common Lisp also allows mixtures of positional and keyword parameters
Types
- builtin types in Ada: integer, real, boolean, arrays, etc.
- user-definable types: records, abstract data types, subtypes, etc.
Some issues in the design of a language's type facility:
- type equivalence
- static versus dynamic properties
- parameterization
Two principal schemes for type equivalence
- name equivalence
- structural equivalence
pure name equivalence says two types are the same only if they have the
same name
structural equivalence says two types are the same if they have the same
textual definition
Example:
type my_array is array(1..10) of integer;
type another_array is array(1..10) of integer;
a: my_array;
b: my_array;
c: another_array;
Here, both a and b are variables of the same type, but c is of a different type
structural equivalence would say a,b,c are all of the same type
Problems with structural equivalence: insecurities, inefficiency
Example:
type SackOfFlour is record pounds: real; end record;
type EnglishBankAccount is record pounds: real; end record;
s: SackOfFlour;
e: EnglishBankAccount;
s := 3; -- this assignment is allowed using structural equivalence
however, there are also problems with pure name equivalence
type index is integer range 1..10;
i: index;
j: integer;
-- under pure name equivalence, these statements are illegal:
j := i;
i := i+1;
Ada's solution: modified name equivalence
Subtypes
subtypes are types with additional constraints. In the above example,
index is a subtype of integer
In Ada, types can always be determined statically; subtype constraints
may be determined either statically or dynamically
Example:
type index is integer range 1..10;
i: index;
i := 8; -- the legality of this assignment can be checked statically
i := random_integer; -- this one must be checed at run time
Variant Records
records - similar to Pascal
variant record type loophole fixed
- when assigning to the tag field, must assign entire record
- tag must be an element of an enumerated type, statically known
Example of record type with variant:
type DEVICE is (PRINTER,DISK,DRUM);
type STATE is (OPEN,CLOSED);
type PERIPHERAL(UNIT : DEVICE := DISK) is
record
STATUS : STATE;
case UNIT is
when PRINTER =>
LINE_COUNT : INTEGER range 1 .. PAGE_SIZE:
when others =>
CYLINDER : CYLINDER_INDEX;
TRACK : TRACK_NUMBER;
end case;
end record;
-- example record subtypes:
subtype DRUM_UNIT is PERIPHERAL(DRUM);
subtype DISK_UNIT is PERIPHERAL(DISK);
-- example assignment:
P: PERIPHERAL;
P := (STATUS=>OPEN, UNIT=>PRINTER, LINE_COUNT=>60);
Overloading
discrete types - as in Pascal
type DAY is (MON,TUE,WED,THU,FRI,SAT,SUN);
subtype WEEKDAY is DAY range MON .. FRI;
type ASTRONOMICAL is (SUN,MOON,MARS);
-- note overloading of SUN
-- if necessary we can write DAY(SUN) or ASTRONOMICAL(SUN) to disambiguate
Complications of overloading
procedure PROC(X: DAY) is ... end PROC;
procedure PROC(Y: ASTRONOMICAL) is ... end PROC;
procedure FUNC(X: DAY) return INTEGER is ... end PROC;
procedure FUNC(Y: ASTRONOMICAL) return BOOLEAN is ... end PROC;
PROC(SUN) is ambiguous -- must say PROC(DAY(SUN))
FUNC(SUN)+1 is not ambigous
can also overload user-defined procedure, function names
In contrast, in Pascal there is no overloading of user-defined procedures,
functions, or enumerated types. However builtin functions such as +, - ,
etc. are overloaded
Generics
can have generic functions, procedures, packages
Examples:
- generic stack, parameterized by the element type
- generic sort procedure, parameterized by the array element type and the
comparison function
Ada generics are compile-time-only constructs -- like macros
you can't manipulate the generic sort procedure at run time, only
instantiations of it
This is in contrast to polymorphic functions in e.g. Miranda
Exception Handling
handle exceptional situations that might arise, such as divide-by-zero,
array out-of-bounds, etc.
Key terms:
- RAISE an exception
- HANDLE an exception
- RERAISING an exception
dynamic search (up the calling stack) for an exception handler
(note this is not like other scoping rules in Ada, which are lexical)
in Ada, can't return and resume computation -- it is terminated prematurely
other languages: can resume computation
Example:
with TEXT_IO; use TEXT_IO;
procedure exception_factorial is
package int_io is new integer_io (integer); use int_io;
function factorial(n: integer) return integer is
result: integer := 1;
begin
for i in 1 .. n loop
result := result*n;
end loop;
return result;
exception
when constraint_error =>
put("overflow in factorial -- n = ");
put(n); new_line;
return integer'last;
end factorial;
begin
-- main program here
put(factorial(5));
new_line;
put(factorial(1000));
end exception_factorial;
can propagate an exception
for example, in factorial we might replace the handler with
raise FACTORIAL_ERROR;
Concurrency
can have multiple tasks
task communication is normally via a rendesvous
Example:
with TEXT_IO; use TEXT_IO;
procedure BUFFER_TASKS is
task BUFFER is
entry IS_EMPTY (B: out BOOLEAN);
entry READ (C: out CHARACTER);
entry WRITE (C: in CHARACTER);
end BUFFER;
task body BUFFER is
POOL_SIZE: constant INTEGER := 5;
POOL: array(1..POOL_SIZE) of CHARACTER;
COUNT: INTEGER range 0..POOL_SIZE := 0;
IN_INDEX, OUT_INDEX: INTEGER range 1..POOL_SIZE :=1;
begin
loop
select
accept IS_EMPTY(B: out BOOLEAN) do
B := COUNT=0;
end IS_EMPTY;
or when COUNT < POOL_SIZE =>
accept WRITE(C: in CHARACTER) do
POOL(IN_INDEX) := c;
end WRITE;
IN_INDEX := IN_INDEX mod POOL_SIZE + 1;
COUNT := COUNT + 1;
or when COUNT > 0 =>
accept READ(C: out CHARACTER) do
c := POOL(OUT_INDEX);
end READ;
OUT_INDEX := OUT_INDEX mod POOL_SIZE + 1;
COUNT := COUNT - 1;
or terminate;
end select;
end loop;
end BUFFER;
task PRODUCE_LETTERS is
end PRODUCE_LETTERS;
task body PRODUCE_LETTERS is
begin
for C in 'A' .. 'Z' loop
BUFFER.WRITE(C);
end loop;
end PRODUCE_LETTERS;
task PRODUCE_DIGITS is
end PRODUCE_DIGITS;
task body PRODUCE_DIGITS is
begin
for C in '0' .. '9' loop
BUFFER.WRITE(C);
end loop;
end PRODUCE_DIGITS;
task CONSUME is
end CONSUME;
task body CONSUME is
P: CHARACTER;
EMPTY: BOOLEAN;
begin
loop
BUFFER.IS_EMPTY(EMPTY);
exit when PRODUCE_LETTERS'TERMINATED and PRODUCE_DIGITS'TERMINATED
and EMPTY;
BUFFER.READ(P);
PUT("consuming a "); PUT(P); NEW_LINE;
end loop;
end CONSUME;
begin
PUT("main program here ..."); NEW_LINE;
end BUFFER_TASKS;
Sample output:
main program here ...
consuming a A
consuming a B
consuming a 0
consuming a C
consuming a 1
consuming a D
consuming a 2
consuming a E
consuming a 3
consuming a F
consuming a 4
consuming a G
consuming a 5
consuming a H
consuming a 6
consuming a I
consuming a 7
consuming a J
consuming a 8
consuming a K
consuming a 9
consuming a L
consuming a M
consuming a N
consuming a O
consuming a P
consuming a Q
consuming a R
consuming a S
consuming a T
consuming a U
consuming a V
consuming a W
consuming a X
consuming a Y
consuming a Z
concurrency mechanism has lots of other features:
generic tasks, delay statements, timed entry calls, priorities, ...
task termination is messy