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

Some issues in the design of a language's type facility:

Two principal schemes for type 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

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:

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:

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