ADA PROGRAM ORGANIZATION --- ------- ------------ Since Ada is designed for writing large software systems, typically a program consists of many separately-written pieces called "program units." There are 4 kinds of program units: 1. Subprograms -- procedures designed to perform specific subtasks 2. Packages -- "bundles" consisting of entities which logically belong together and which are usually shared among several programs 3. Tasks -- programs designed to execute concurrently 4. Generics -- parameterized program units..ie., "templates" The program units are compiled and kept in a "program library." Certain packages are "predefined" and are available to all Ada programs...these 0include STANDARD and TEXT_IO. Note that in Ada, no distinction is made between upper and lower case letters, hence the words Hello heLLo HELLO are viewed as the same name. INTRODUCTION TO ADA NUMERIC EXPRESSIONS ------------ -- --- ------- ----------- Ada numeric expressions are either of real type or of integer type. The binary operators include +, -, * (times), / (divide), ** (exponent), rem (remainder), mod (modulo). The meanings and restrictions of these are given below: MEANINGS: E1 + E2 -- sum of E1 and E2. E1 and E2 both integer or both real E1 * E2 -- product of E1 and E1. E1 and E2 both integer or real E1 / E2 -- quotient of E1 and E2. If E1 and E2 are real, the result -- is a of real type...if E1 and E2 integer, then the -- result is an integer quotient (decimal part truncated) E1 ** E2 -- E1 raised to the power of E2. E2 must be of integer type -- and if E1 is integer, then E2 must be nonnegative E1 rem E2 -- the remainder obtained after dividing integer E1 by -- integer E2. The result always has the same sign as -- E1 E1 mod E2 -- E1 modulo E2, E1 and E2 are integers. This is the integer -- M having the same sign as E2, and of smaller absolute -- value than |E2|, and such that E1 = (N * E2) + M -- for some integer N The unary operators include +, - , and abs (for absolute value). + E1 -- same as E1. E1 is of integer or real type. - E1 -- negative of E1. E1 integer or real. abs E1 -- | E1 | where E1 is integer or real. RESTRICTIONS AND EXCEPTIONS: 1. In the absense of parentheses, abs and ** take precedence over *, /, mod and rem, which take precedence over + and -. 2. In the absense of parentheses, the operations *, / , mod and rem are done in left-to-right order, as are + and -. 3. The binary operators +, - , *, /, rem and mod must have the same type for both arguments (this rule is relaxed when declaring so called "universal constants"...e.g., HALF_PI : constant := 3.14159 / 2; is allowed, but HALF_PI : constant FLOAT := 3.14159 / 2; is not -- strange.) If you're using an Integer and Float together, put a function called `Float()' around the Integer variables to cast them as floating-point values. Also, whenever you set a Float to a constant, the constant must be written with a period in it, or the compiler will complain. 4. Consecutive abs and ** operations must be parenthesised, e.g., A ** B ** C and abs abs X are invalid, but A ** (B ** C) and abs( abs X ) are OK. INTRODUCTION TO ADA DATA TYPES ------------ -- --- ---- ------ The Ada type INTEGER is used to store integer values if you don't care what its minimum and maximum range is -- Ada will then select whatever range is ``most natural'' for your machine. An Integer type must use at least 16 bits, but the actual number of bits used will depend on the compiler and machine. Like Integer, Ada also has a predefined type called FLOAT. Float can store fractional values and `large' values. Float is used when you don't care about the minimum or maximum range of numbers, nor about the minimum accuracy of the value - the Ada compiler will choose whatever is most `natural' for the machine. A new Float type may be defined in one of two ways: type FloatingPoint1 is new Float; type FloatingPoint2 is digits 5; The second line asks the compiler to create a new type, which is a floating point type "of some kind" with a minimum of 5 digits of precision type Fixed is delta 0.1 range -1.0 .. 1.0; The declaration above defines a a FIXED POINT type which ranges from -1.0 to 1.0 with an accuracy of 0.1. Each element, accuracy, low-bound and high-bound must be defined as a real number. Ada also predefines a simple type called BOOLEAN, which can only have two values, True and False. 1. All of the comparison operations (=, >=, /=, etc.) have result values of type Boolean. 2. All conditions (such as what goes after an if and while) must be of type Boolean. 3. There are a few special infix operations that take two Booleans and result in a Boolean: "and", "or", and "xor" (exclusive-or), with their usual meanings. `Exclusive or' is true if either of two conditions, but not both, is true. There is also the prefix operation ``not''. If a boolean variable A has the value True, `not A' has the value False. Ada has a predefined STRING type (defined in Standard). There is a good set of Ada packages for string handling, much better defined than the set provided by C, and Ada has a & operator for string concatenation. Ada lets you create your own TYPES, and has a very rich set of capabilities for creating types with exactly the operations you want. To create a new type, use a type declaration. A type declaration begins with the keyword type, followed by the name of the new type, the keyword is, and then a definition of the new type. type Column is range 1 .. 72; type Row is range 1 .. 24; Ada provides a set of 4 attributes for RANGE types: First This provides the value of the first item in a range. Considering the range 0 .. 100 then 'First is 0. Last This provides the value of the last item in a range, and so considering above, 'Last is 100. Length This provides the number of items in a range, so 'Length is actually 101. Range This funnily enough returns in this case the value we gave it, but you will see when we come onto arrays how useful this feature is. A SUBTYPE is simply another name for an existing type that may have some additional constraints on it. type Count is range 0 .. 99_999; subtype Widget_Count is Count range 0 .. 99; subtype Egg_Count is Count; Often a variable can have only one of a small set of values. An ENUMERATION type can be created for such variables, making it easier to understand and permitting error detection. type Day is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday); subtype Weekday is Day range Monday .. Friday; subtype Weekend is Day range Saturday .. Sunday; Today : Day; -- ... Here's an example of setting Today to a value. Today := Tuesday; Ada provides four useful attributes for enumeration type handling. Succ This attribute supplies the 'successor' to the current value, so the 'Succ value of an object containing Monday is Tuesday. Note: If the value of the object is Sunday then an exception is raised, you cannot Succ past the end of the enumeration. Pred This attribute provides the 'predecessor' of a given value, so the 'Pred value of an object containing Tuesday is Monday. Note: the rule above still applies 'Pred of Monday is an error. Val This gives you the value (as a member of the enumeration) of element n in the enumeration. Thus Val(2) is Wednesday. Note: the rule above still applies, and note also that 'Val(0) is the same as 'First. Pos This gives you the position in the enumeration of the given element name. Thus 'Pos(Wednesday) is 2. Note: the range rules still apply, also that 'Last will work, and return Sunday. Day'Succ(Monday) = Tuesday Day'Pred(Tuesday) = Monday Day'Val(0) = Monday Day'First = Monday Day'Val(2) = Wednesday Day'Last = Sunday An ARRAY type in Ada can contain many components with the same subtype. An Ada array is quite similar to arrays in many other languages, but here are some important things to know about them: 1. Ada array indices are not required to start at zero or one. Array indices can begin (and end) with any discrete value. This means that you can start arrays at -5 (if that makes sense), and you can use enumerated values as indices as well. 2. Like many other things in Ada, array accesses (both read and write) are normally checked at run-time. Thus, if the array index is out-of-bounds, instead of quietly doing the wrong thing, an exception will be raised. 3. Multi-dimensional arrays are handled in an intuitive way. 4. A directive can be used to pack arrays, which requests the compiler to store the array in a memory-efficient way. This is particularly handy for arrays of Boolean and Character values. 5. You can define array types without completely fixing their minimum and maximum size. These are called `unconstrained' arrays. Here are a few examples: -- Sample array type definitions: type Table is array(1 .. 100) of Integer; -- 100 Integers. type Schedule is array(Day) of Boolean; -- Seven Booleans, one per Day type Grid is array(-100 .. 100, -100 .. 100) of Float; -- 40401 Floats. -- Sample variable declarations: Products_On_Hand : Table; -- This variable has 100 Integers. Work_Schedule : Schedule; Temperature : Grid; -- And sample uses: Products_On_Hand(1) := 20; -- We have 20 of Product number 1 Work_Schedule(Sunday) := False; -- We don't work on Sunday. Temperature(0,0) := 100.0; -- Set temperature to 100.0 at grid point (0,0). Put(Products_On_Hand(1)); -- Print out the number 20. Types can be a complex collection of other types; the primary method for collecting these is through the RECORD (which is basically identical to the Pascal record and C struct). For example, here's an example of a record useful for recording dates: type Date is record Day : Integer range 1 .. 31; Month : Integer range 1 .. 12; Year : Integer range 1 .. 4000 := 1995; end record; When declaring a type in a package declaration, you can declare the type as PRIVATE. By declaring the type as "private" you disable the normal operations on that type (for example, "+" on Integers). After declaring a type as private, you then list the subprograms that you want to permit as publically-accessible operations on that type. If you declare a type as private, you must complete the type's definition in a section of the package declaration called the private part. If a type is declared as private, other packages can only use the operations that you provide and the assignment (:=) and equality (=) operations. What if we don't want the default assignment (:=) and equals-to (=) operations? The answer: we declare the type as a LIMITED PRIVATE type instead of a private type. A limited private type is a private type that doesn't even define the default assignment and equals-to operations. For an example, see the class notes from last meeting. INTRODUCTION TO ADA EXECUTABLE STATEMENTS ------------ -- --- ---------- ---------- Once memory is set aside for the variables and constants of a program, the CPU begins executing the statments between the begin and end. procedure MyProg is -->begin end MyProg; Ada statements are always terminated with a semicolon. I/O STATEMENTS: Most input and output you will do is "sequential." This means that all output statements append values to the output, and input statements read values from where the last input statement left off reading. Statements which perform input and output are actually calls to procedures which are defined in the Text_IO package. In particular, the Text_IO package defines PUT and GET operations for variables and values of types INTEGER, FLOAT and STRING. In general, if E is a numeric expression or string, and V is a numeric variable, then PUT( E ); -- displays the value of E on the current output line GET( V ); -- reads the next value from the input and changes the -- contents of V to that value PUT_LINE( E ); -- prints E followed by a "newline" to the output SKIP_LINE; -- goes to the next line of input NEW_LINE; -- starts a new line EXAMPLE: with Text_IO; use Text_IO; procedure ADD_2 is -- This procedure reads 2 integers from the input and computes -- their sum, then writes it to the output NUM1, NUM2: INTEGER; -- The two numbers to input SUM : INTEGER; -- The sum of the numbers begin PUT("Enter two numbers to add: "); GET( NUM1 ); GET( NUM2 ); PUT(" The sum is "); SUM := NUM1 + NUM2; -- or, we could have done simply PUT_LINE( SUM ); -- PUT(NUM1 + NUM2) followed by \n end ADD_2; FORMATED OUTPUT In Ada, there are PUT procedures which output an item to the output using a specified number of spaces. FOR INTEGERS: PUT( num, width, base ); where base is optional This procedure writes the integer num to the output using a minimum of width columns (spaces). The value of num is right justified. If width is smaller than the number of digits in num, then `more spaces will be allocated as needed. (Note that both num and width may be expressions.) Examples: PUT( 12345, 10 ); -- appends the 10 characters " 12345" to the output PUT( 12345, 4 ); -- appends the 5 characters "12345" to the output PUT (20 - (4*6), 2 * (8 - 6) ); -- outputs " -4" FOR FLOATS: PUT( num, before, after, exponent ); This procedure writes the floating point number num to the output, using "before" spaces before the decimal point, and "after" spaces to the right of the decimal point (rounded to the last decimal place). If the "before" value, if too small, will be expanded to accommodate the number. If the fourth parameter is nonzero, then the output will be appended with an exponent of 10, as in scientific notation. Examples: PUT( 123.456, 5, 2, 0 ); -- appends the 8 characters " 123.46" to the -- output PUT( 123.456, 2,4, 0 ); -- appends "123.4560" to the output GENERAL "IF" STATEMENT ------- ---- --------- SYNTAX: If_Statement ::= if Boolean_Exp then Statement_List {elsif Boolean_Exp then Statement_List } [else Statement_List ] end if; If the "else slist" is omitted, then "else null;" is assumed (i.e., no action). Note that "elsif" parts do not require an "end if," but nested "if" statements following an "else" do...i.e., if cond1 then stat1 if cond1 then stat1 else (is equivalent to) elsif cond2 then stat2 if cond2 then stat2 end if; end if; -- inner if end if; -- outer if EXAMPLE: with Text_IO; use Text_IO; procedure TRIANGLE is -- This procedure reads 3 integer lengths of sides of a triangle and -- prints whether the triangle is obtuse, right, or acute S1,S2,S3: INTEGER; -- the sides of the triangle procedure SWAP(X,Y: in out INTEGER) is -- This procedure swaps the contents of 2 integer variables X and Y Temp : INTEGER := X; begin X := Y; Y := Temp; end SWAP; begin -- TRIANGLE -- get the sides PUT("Enter 3 integer sides:"); GET(S1); GET(S2); GET(S3); -- place the largest side in S1 if S1 < S2 then if S2 < S3 then SWAP(S1,S3); else SWAP(S1,S2); end if; elsif S1 < S3 then SWAP(S1,S3); end if; -- print out the appropriate message if S1 > S2 + S3 then PUT("Not a triangle!"); elsif S1**2 > S2**2 + S3**2 then PUT("Obtuse"); elsif S1**2 = S2**2 + S3**2 then PUT("Right"); else PUT("Acute"); end if; NEW_LINE; end TRIANGLE; THE "CASE" STATEMENT --- ------ --------- SYNTAX: Case_Statement ::= case Exp is Alternative {Alternative} end case; Alternative ::= when Choice {| Choice} => Statement_List Choice ::= Simple_Exp | Simple_Exp .. Simple_Exp | others The "Simple_Exp" in the choice must be a constant value of the same type as "Exp." No pair of choices may be overlapping in the same "case" statement (e.g., 5 and 3..6 are not both allowed). The "other" choice, if it appears, must appear only once, and as the last choice by itself. If the "other" choice does not appear, then the choices must cover all possible values of the "Exp." In addition, the "Exp" must be of discrete type (i.e., not FLOAT, STRING, etc.) EXAMPLE: SCORE: INTEGER range 0..100; -- test scores, restricted to the -- range 0 thru 100. ... -- print the appropriate letter grade for SCORE case SCORE / 10 is when 9 | 10 => PUT('A'); when 8 => PUT('B'); when 7 => PUT('C'); when 6 => PUT('D'); when others => PUT('F'); end case; THE (TOP-TESTED) "LOOP" STATEMENT --- ------------ ------ --------- SYNTAX: Loop_Statement ::= loop exit when Boolean_Expression ; Statement_Sequence end loop; Statement_Sequence ::= Statement { Statement } Boolean_Expression ::= Exp Relop Exp RelOp ::= = | < | > | <= | >= | /= EXAMPLE: with Text_IO; use Text_IO; procedure SUM_NUMS is -- This procedure reads an integer N > 0 and then reads N integers -- from the input and prints the sum of the N integers N: INTEGER; -- the number of integers to read Num: INTEGER; -- the last integer read Sum: INTEGER := 0; -- the sum of the integers begin PUT("Enter N, the number of integers to sum: "); NEW_LINE; GET( N ); -- read and sum the N integers loop exit when N <= 0; GET( Num ); Sum := Sum + Num; N := N - 1; end loop; -- print out the sum PUT(" The sum is "); PUT( Sum, 1); NEW_LINE; end SUM_NUMS; ALTERNATE LOOK STRUCTURES: loop while Exp loop for i in low .. high loop S1; S1; S1; S2; ... ... ... Sn; Sn; exit when Exp; end loop; end loop; ... Sn; end loop; PROCEDURE BODY DECLARATIONS --------- ---- ------------ Procedures declarations can be nested. Procedure bodies may be declared after variable and constant declarations: with Text_IO; use Text_IO; procedure MAIN is begin -- MAIN .... end MAIN; SYNTAX: Procedure_Body ::= procedure Identifier [ ParameterList ] is Declarations begin Statement_Sequence end [Identifier] ; ParameterList ::= ( FormalParameter {; FormalParameter} ) FormalParameter ::= Identifier {, Identifier} : Mode TypeIdentifier Mode ::= IN | OUT | IN OUT | Statement_Sequence ::= Statement { Statement } Note that the Identifier on the "end", if present, must match the Identifier after the "function" keyword. (parameterless procedures) A declarations of the form procedure MyProc is D1; D2; ... ; Dn; begin Statlist end MyProc; is invoked by the call MyProc ; (procedures with parameters) A declaration of the form procedure MyProc(fp1: Mode1 Type1; ...; fpN: ModeN TypeN) is D1; D2; ... ; Dn; begin Statlist end MyProc; is invoked by the call MyProc( E1, E2, ..., EN ); The Types of the expressions Ei must be compatible with the corresponding types Typei of the formal parameters. For example, one cannot pass an INTEGER type value as a parameter whose declared type is FLOAT. The optional Mode (IN, OUT, IN OUT) describes the use of the parameter it modifies. In particular, IN -- says only the parameter's value is needed (i.e., it will not be changed by the procedure) OUT -- says only the parameter's address (i.e., location in memory) is needed, and the procedure will not use the value of the parameter. This implies that a variable must be passed as an actual parameter for an OUT formal parameter IN OUT -- says both the value and address of the parameter are needed by the procedure. This implies that a variable must be passed as an actual parameter for IN OUT mode formal parameters If the Mode is omitted, a mode of IN is assumed. Note that consecutive formal parameters having the same mode and type may be combined as fp1,fp2,...,fpN : Mode Type. For example, procedure P(X,Y : INTEGER; Z: in out FLOAT) EXAMPLE: with Text_IO; use Text_IO; procedure AVE3 is -- This procedure reads 3 integers and prints out their average. N1,N2,N3: INTEGER; -- the 3 numbers to read and average AVE: FLOAT; -- the average of the integers procedure COMPUTE_AVE(X,Y,Z: in INTEGER; ANS: out FLOAT) is -- This procedure computes the average of X, Y and Z, and stores -- the answer in the variable ANS begin ANS := FLOAT(X + Y + Z) / 3.0; end COMPUTE_AVE; procedure GET_THREE(X,Y,Z: out INTEGER) is -- This procedure prompts the user for 3 integers and reads them -- into the variables X, Y and Z begin PUT("Enter 3 integers: "); NEW_LINE; GET(X); GET(Y); GET(Z); end GET_THREE; begin -- AVE3 GET_THREE(N1, N2, N3); -- get the 3 integers to average ... COMPUTE_AVE(N1,N2,N3, AVE); -- average them -- print out the average PUT(" The average is "); PUT( AVE, 10, 3, 0); NEW_LINE; end AVE3; ADA FUNCTION SUBPROGRAMS --- -------- ----------- Ada Functions are subprograms which return a single value based on 0 or more 'IN' mode parameters. SYNTAX: Function body declarations appear after the variable and constant declarations, as do procedure body declarations. Function_Body ::= function Identifier [FParameterList] return Identifier is Declarations begin Statement_Sequence end [Identifier] ; ParameterList ::= ( FormalFParameter {; FormalFParameter} ) FormalParameter ::= Identifier {, Identifier} : [ in ] TypeIdentifier Statement_Sequence ::= Statement { Statement } Note that the Identifier on the "end", if present, must match the Identifier after the "function" keyword. Also, the Identifier after the 'return' must be the name of a type (e.g., INTEGER, CHARACTER,..). A declaration of the form function MyProc(fp1: Mode1 Type1; ...; fpN: ModeN TypeN) return RType is D1; D2; ... ; Dn; begin Statlist end MyProc; is invoked by the call MyProc( E1, E2, ..., EN ); The Types of the expressions Ei must be compatible with the corresponding types of the formal parameters. For example, one cannot pass an INTEGER type value as a parameter whose declared type is FLOAT. All parameters are of 'in' mode, which is the default mode. Note that consecutive formal parameters having the same type may be combined as fp1,fp2,...,fpN : Type. For example, function F(X,Y : INTEGER; Z: FLOAT) is equivalent to function F(X: in INTEGER; Y: in INTEGER; Z: in FLOAT) In a Function_Body, the statement sequence must include a "return" statement, which is of the form return Expression ; where Expression must be of the type RType. Execution of this statement causes Expression to be evaluated and that value to be passed back as the result of the function call. EXAMPLE: with Text_IO; use Text_IO; with MATH_LIB; use MATH_LIB; procedure PERIMETER is -- This procedure reads 3 X-Y coordinate pairs representing the corners -- of a triangle, and prints the perimeter of the triangle. X1,Y1, X2,Y2, X3,Y3 : INTEGER; -- stores the 3 points read from the input function DIST(X1,Y1, X2,Y2: INTEGER) return FLOAT is -- This function returns the distance between the two points -- (X1,Y1) and (X2,Y2) Xdiff, Ydiff: FLOAT; -- holds the differences between the -- X and Y coordinates begin Xdiff := FLOAT(X1 - X2); Ydiff := FLOAT(Y1 - Y2); return SQRT(Xdiff * Xdiff + Ydiff * Ydiff); end Dist; procedure GET_POINT(X,Y: out INTEGER) is -- This procedure prompts the user for an integer pair and reads them -- into the variables X and Y begin PUT("Enter a point (2 integer coordinates): "); NEW_LINE; GET(X); GET(Y); end GET_POINT; begin -- PERMITER GET_POINT(X1,Y1); -- get the 3 points ... GET_POINT(X2,Y2); GET_POINT(X3,Y3); -- print out the perimeter PUT(" The perimeter is "); PUT(DIST(X1,Y1,X2,Y2) + DIST(X2,Y2,X3,Y3) + DIST(X3,Y3,X1,Y1), 10, 3, 0); NEW_LINE; end PERIMETER; In Ada, the user may overload his own procedures and functions. For example, function MAX(X,Y: in INTEGER) return INTEGER is begin if X > Y then return X; else return Y; end if; end MAX; function MAX(X,Y: in FLOAT) return FLOAT is begin if X > Y then return X; else return Y; end if; end MAX; function MAX(X,Y,Z: INTEGER) return INTEGER is begin return MAX(X, MAX(Y,Z)); end MAX; Note that, given the number and types of the actual parameters to MAX, it is always clear which MAX applies. SCOPE OF A DECLARATION ----- -- - ----------- The "scope" of a declaration which defines an entity named Name is the region of the program where the Name may be used to refer to that entity. Declarations include procedures, objects, packages and formal parameters. The declaration of a procedure/function body constitutes a "declarative region" which includes the formal parameters of the procedure and extends to the "end" of that subprogram. If we drew a box around the region, it would look like this: --------------------------- procedure R | ( x1,x2,x3: INTEGER ) is | -------------- | | Decl1; | | Decl2; | | .... | | begin --R | | ... | | end R; | ------------------------------------------ The formal parameter declarations and the others (Decl1, ...) are limited to the inside of the box. The declarations themselves may by subprograms, which define another region of scope. Below is a more general picture. procedure P(fp1: ..., fpN: ..) is -- may refer to any formal parameter fpi in the region -- from here to "end P;" LV1: ... .. -- Similarly, can refer to Local variables/constants LVm: ... -- LVi in declarations following, and up to "end P" procedure Q1(....) is .... -- This means we can refer to -- LVi and fpj inside the Q subprograms -- too. -- Conversely, the scope of the Q -- subprograms extends till "end P" function Qr(....) return ... is .... begin -- P S1; -- In the executable statements for P, we can refer ... -- to any fpi, LVi, Qi (i.e., "local declarations") Sk; -- as well as declarations in any declarative region -- enclosing P (such as, in the main program) end P; Note that each procedure has its own declarative region (from the formal parameters to the "begin" of that procedure), hence, nested procedures imply nested declarative regions. RESOLVING REFERENCES: Suppose "X" is referenced (used, not declared) at some point in your program...e.g., Y := X + 5; or Z : constant INTEGER := X * X; or X(5,6,"Hello"); The Ada compiler must find an appropriate definition for X which includes that point of reference in its scope. The basic strategy is to search the innermost declarations enclosing the point of reference first, that is, search local before nonlocal declarations. EXAMPLE: For each reference, draw an arrow from the reference to the declaration to which it refers. (Don't worry about the behavior of this program...it does nothing useful!) procedure MAIN is W,X,Y,Z: INTEGER; procedure P( Y,Z: INTEGER ) is W: INTEGER; begin W := X + Y + Z; PUT( W ); end P; procedure Q( W, X: INTEGER ) is Y: INTEGER; procedure R is begin Y := 5; Z := 4; end R; begin --Q R; P(5,6); PUT (W + X + Y + Z); end Q; begin --MAIN W := 1; X := 2; Y := 3; Z := 4; Q(7,8); end MAIN; PACKAGES -------- A package groups together various kinds of logically-related entities. They are units which may be shared and reused among many programs. Packages normally consist of 2 parts: a specification part and a body. The body is not needed when the package only contains type and object declarations. If the package contains procedures, then their bodies are placed in the body part of the package. The package specification and body are compiled separately. SYNTAX: Package_Spec ::= package Identifier is {Basic_Declaration} end [Identifier]; Package_Body ::= package body Identifier is {Declaration} [begin Statement_Sequence [exception Exception_Handler {Exception_Handler} ] ] end [Identifier]; where "Basic_Declaration" excludes procedure and function bodies, but allows an inclomplete subprogram declaration: Procedure_Declaration ::= procedure Identifier [FParameter_List]; Function_Declaration ::= function Identifier [FParameter_List] return Identifier; SEMANTICS: A package specification makes declarations available for use in other program units. To use the entities declared in package P (compiled some time previously), attach a "with P;" clause before the declaration of the program unit that will use P, and also attach a "use P;" to allow the entities in P to be referred to by simple name. A package body is necessary if there are subprograms in the package specification. The body and the corresponding specification must bear the same Identifier as the name. All subprogram declarations appearing in the specification must be completely defined in the corresponding body. The "begin Statement_Sequence ..." part of the body is optional, and when present serves to initialize variables declared in the package. EXAMPLE: package POINTS is -- Package specification containing 2-dimensional point routines -- Written: Jane M. Doe, 3/15/92 -- type QUADRANT is (NorthEast, NorthWest, SouthWest, SouthEast); function DISTANCE(X1,Y1,X2,Y2: FLOAT) return FLOAT; procedure PUT_POINT(X,Y: FLOAT); procedure ROTATE90(X,Y: in out FLOAT); function IN_QUADRANT(X,Y: FLOAT; Q: QUADRANT) return BOOLEAN; -- Is (X,Y) in quadrant Q? end POINTS; with Text_IO; use Text_IO; with Math_Lib; use Math_Lib; package body POINTS is -- Package body for 2-dimensional points -- Written: Jane M. Doe, 3/15/92 function DISTANCE(X1,Y1,X2,Y2: FLOAT) return FLOAT is begin return SQRT((X1-X2)**2 + (Y1-Y2)**2); end DISTANCE; procedure PUT_POINT(X,Y: FLOAT) is begin PUT('('); PUT(X); PUT(', '); PUT(Y); PUT(')'); end PUT_POINT; procedure ROTATE90(X,Y: in out FLOAT) is NewX, NewY : FLOAT; begin NewY := X; NewX := -Y; X := NewX; Y := NewY; end ROTATE90; function IN_QUADRANT(X,Y: FLOAT; Q: QUADRANT) return BOOLEAN is begin case Q of when NorthEast => return X>0 and Y>0; when SouthEast => return X>0 and Y<0; when NorthWest => return X<0 and Y>0; when SouthWest => return X<0 and Y<0; end case; end IN_QUADRANT; end POINTS; In order to use the example above, compile it, and use the "with" and "use" clauses before your main program...e.g., with POINTS; use POINTS; with Text_IO; use Text_IO; procedure MAIN is ... PUT_POINT(0,0); ... EXCEPTIONS ---------- An exception is a condition arising when the CPU cannot continue the action specified by the statement currently being executed. When an exception is raised, some special actions are taken in a procedure called an "exception handler." In Ada, exception handlers can be written by the user. Ada has several predefined exceptions. Some are described below. 1. CONSTRAINT_ERROR -- raised when a value exceeds a range constraint. e.g., POS_NUM := -1; raises this exception, assuming POS_NUM is of type INTEGER range 1..INTEGER'LAST 2. NUMERIC_ERROR -- raised when a numeric operation produces a result which is not representable by the machine. e.g., divide by 0 3. PROGRAM_ERROR -- raised, in general, when program execution becomes erroneous or undefined. e.g., if no "return" statement is executed in the execution of a function body. 4. STORAGE_ERROR -- usually raised if memory is exhausted. This may happen in a very long sequence of nested subprogram calls. In addition to these, there are predefined I/O exceptions (in the package IO_EXCEPTION). Some are described below. 5. END_ERROR -- raised when an attempt is made to GET a value and the end of the input file is reached. 6. DATA_ERROR -- raised when an attempt is made to read a value in the input of the wrong type...e.g., if NUM:INTEGER; then GET(NUM) will raise DATA_ERROR if it tries to read, say, a character 'A' SYNTAX: The programmer may also define new exceptions (with object declarations) using the syntax Exception_Declaration ::= Identifier {, Identifier} : exception; The executable statement "raise Identifier;" has the effect of raising the exception named by Identifier (either predefined or user-defined). Exceptions handling may be specified by the programmer in the last portion of the executable statement region of a subprogram (between the "begin" and "end"), and at the end of a "block." A block is an enclosed sequence of statements with an optional local declaration sequence and an optional exception handling section: Block ::= [ declare Declaration_List ] begin Statement_List [ exception when Exception_Choice => Statement_List {when Exception_Choice => Statement_List} ] end Exception_Choice ::= Identifier {| Identifier} | others A restriction is that, if the Exception_Choice "others" is used, it must appear last. SEMANTICS: When a block of the form declare Declist -- local declarations for the block begin Statlist0 exception when ExList1 => Statlist1 -- exception handler for ExList1 exceptions ... when Exlistk => Statlistk -- exception handler for ExListk exceptions end is executed, the following actions occur: 1. The declarations in Declist are processed ("elaborated"), causing storage to be allocated for variables and constants. The scope of these entities declared are local to the statements in the block. 2. Execution of Statlist0 begins. If no exceptions are raised during this execution, then the block terminates after the last statement in Statlist0 is executed. (This means the objects declared in Declist are deallocated and no longer accessable, as in a procedure return.) 3. If an exception is raised during the execution of Statlist0, then the lists of exceptions ExList1,...,ExListk are searched. If the raised exception is found, then the corresponding Statlist is executed, and the block terminates. If the raised exception is not found among the ExLists, the block terminates, and the same exception is raised in the enclosing block. If the enclosing region is a subprogram body, then the subprogram performs a "return" and the exception is re-raised at the point of the call. 4. If an exception is raised during the execution of an exception handler, then the block terminates as a result. Note that in an exception handler Statlist, the statement "raise;" is allowed. This has the effect of terminating the block and re-raising the exception which was caught by the handler. EXAMPLE: procedure GET_NUM(Num: out INTEGER) is -- Procedure to prompt user until an integer is entered. NUM_READ: INTEGER; begin loop begin -- block to handle IO exceptions NEW_LINE; PUT(" Please Enter an integer: "); GET(NUM_READ); exception when DATA_ERROR => PUT("You did not enter an integer!"); end; -- block end loop; Num := NUM_READ; exception when END_ERROR => NEW_LINE; PUT("End of file encountered. GET_NUM returning 0."); Num := 0; NEW_LINE; end GET_NUM; EXAMPLE PROGRAM ------- ------- -- Program Name: HangMan -- -- Program to play hangman, a word guessing game -- Input: A file containing words to guess, 1 per line. -- Terminal I/O: Dialogue to allow the user to guess letters -- in a mystery word read from the word file -- ..the number of guesses is specified by the user. -- Written: Jane M. Doe, 4/22/91 -- with TEXT_IO; use TEXT_IO; procedure HangMan is package INT_IO is new INTEGER_IO ( INTEGER ); use INT_IO; MaxLen: CONSTANT INTEGER:= 20; -- Max word length subtype LowerCase is CHARACTER range ('a' .. 'z'); type CharArray is ARRAY (1..MaxLen) OF CHARACTER; type BoolArray is ARRAY (1..MaxLen) OF BOOLEAN; -- place type declaration here -- Main Program Variables Letters: CharArray; -- stores the "mystery word" Guessed: BoolArray; -- Guessed[i]=TRUE means Letters[i] has -- been guessed by the user Length: INTEGER; -- The length of the mystery word GuessesLeft : INTEGER; -- how many guesses left in the game? LineNumber: INTEGER; -- Which line does the mystery word -- come from? NoLetter, NoInput: EXCEPTION; function CAP(ch: CHARACTER) return CHARACTER is i,j: INTEGER; begin -- CAP if(ch in LowerCase) then i:= CHARACTER'pos('A') - CHARACTER'pos('a'); j:= CHARACTER'pos(ch) + i; return(CHARACTER'val(j)); else return ch; end if; end CAP; procedure GetWord(Letters: in out CharArray; Guessed: in out BoolArray; Length: in out INTEGER; WhichWord: INTEGER) is -- Reads the word on line WhichWord into Letters, sets Guessed -- so that the first letter is turned over, and sets Length -- to length of the word read i:INTEGER; C: CHARACTER; Skiped_Word: STRING (1 .. 15); Input_File: FILE_TYPE; WordLen,NameLen: INTEGER; FileName: STRING (1.. 15):=" "; begin -- GetWord put("Please type in the input file name: "); get_line(FileName, NameLen); open(Input_File, In_File, FileName); -- skip over the proper number of words for i in 1 .. WhichWord-1 -- skip a line in the input loop get_line(Input_File, Skiped_Word, WordLen); end loop; -- extract the word, converting to upper case i := 0; loop i:= i + 1; get(Input_File, Letters(i)); Letters(i) := CAP(Letters(i)); exit when (end_of_file(Input_File)) or (Letters(i)=' ') or (end_of_line(Input_File)); end loop; -- set the length if end_of_line(Input_File) then Length := i; else Length := i-1; end if; -- initialize the Guessed array for i in 1 .. MaxLen loop Guessed(i) := FALSE; -- haven't guessed any letters yet end loop; -- turn over the first letter .. for i in 1 .. Length loop if Letters(i) = Letters(1) then Guessed(i) := TRUE; end if; end loop; close(Input_File); exception when end_error => raise NoInput; end GetWord; procedure ShowWord(Letters: CharArray; Guessed: BoolArray; Length: INTEGER ) is -- Displays the word on a line, showing only those letters which -- have been guessed begin -- ShowWord for i in 1 .. Length loop put(" "); if Guessed(i) then put(Letters(i)); else put('_'); end if; end loop; new_line; end ShowWord; procedure GetGuess( Letters:CharArray; Guessed: in out BoolArray; Length: INTEGER ) is -- Prompts and reads the next non-white space character as the -- user's guessed letter...marks occurances of that letter in -- the word as "guessed" L: CHARACTER; begin -- GetGuess -- prompt for a letter put_line("Enter a letter:"); -- read the letter... L:= ' '; while (L = ' ') loop -- skip white space if end_of_line then skip_line; else get(L); end if; end loop; if (CAP(L) not in ('A'..'Z')) then raise NoLetter; end if; -- count that letter "Guessed" for i in 1 .. Length loop if Letters(i) = CAP(L) then Guessed(i) := TRUE; end if; end loop; end GetGuess; function IsWordGuessed(Letters:CharArray; Guessed: BoolArray; Length: INTEGER) return BOOLEAN is -- returns TRUE only if all letters in the word were guessed begin -- IsWordGuessed -- See if all letters have been guessed .. for i in 1 .. Length loop if not Guessed(i) then return FALSE; end if; end loop; return TRUE; end IsWordGuessed; begin -- Main Program -- Ask user which word he wants to try to guess put_line("Enter a line number from the word file (1..Maxlines):"); get(LineNumber); skip_line; -- Now get a word from the word file ... GetWord(Letters,Guessed,Length,LineNumber); -- Get number of guesses from the user ... ShowWord(Letters,Guessed,Length); put_line("Enter Number Of Guesses You Want To Take : "); get(GuessesLeft); skip_line; -- play the guessing game ... loop begin ShowWord(Letters,Guessed,Length); GetGuess(Letters,Guessed,Length); GuessesLeft:= GuessesLeft -1; exit when IsWordGuessed(Letters,Guessed,Length) or (GuessesLeft=0); exception when NoLetter => put_line("Please enter a letter."); end; end loop; -- print out result of game ... if IsWordGuessed(Letters,Guessed,Length) then ShowWord(Letters,Guessed,Length); put_line("Congratulations! You're too good for me!"); else put_line("Time up...The word was '"); for i in 1 .. Length loop put(Letters(i)); end loop; put_line("'"); end if; exception when NoInput => put_line("Can not read a word."); end HangMan;