PL/I is a language that combines features of numerous other languages and adds to them all. As such, it is an unwieldy language full of bizarre syntax and complicated operations. To provide a short set of notes would not do much to understand the language. Yet, its beyond tehs cope of this course to provide anything other than an introduction. So, here are some of the highlights of the language's features and some examples of the syntax. Variables: like FORTRAN, variable declarations can largely be omitted for floating point and integer values (I..N mean integer, all others, including those that start with @, #, $, are floats). You can override the implicit declaration by using the DECLARATION (or DCL) statement as follows: DECLARE var_name type, var_name type, ... var_name type; Legal types are FIXED DECIMAL and FIXED BINARY (numeric values stored in base 10 or base 2 respectively with a fixed number of fractional digits/bits), FLOAT DECIMAL (floating point values stored in base 10), PICTURE (BCD values), CHARACTER, BIT (boolean stored in 1 byte), COMPLEX (complex numbers, that is, numbers like 5 + 3i where i is the square root of -1). When declaring a variable, you can specify the maximum field widths similar to COBOL, such as DECLARE X FIXED DECIMAL(5, 2); in this case, X has 5 total digits (not including the decimal point) and 2 to the right of the decimal point. The value 381.56789 is rounded to 381.57 whereas 3812.56789 leads to an error since we can't store 3812 in 3 digits. For floats, you only specify one value, the number of digits to the right of the decimal point. So DECLARE X FLOAT DECIMAL(2) makes X a float that stores only 2 digits to the right of the decimal point, rounding as needed, but any number of digits to the left of the decimal point up to the limit of the precision. For CHARACTER, the specifier denotes the total number of bytes (characters) stored in the string, as in DECLARE NAME CHARACTER(20); Adding VARYING allows the representation to store only as many characters as needed, with (20), the string MUST BE 20 characters long. I/O: The input statement is GET and the output statement is PUT. PUT can be followed by LIST to return the cursor at the end of the output (LIST will also be used after a GET statement when inputting from file). We can also use a blank PUT SKIP LIST statement to start a new line, or add (n) after SKIP to skip n lines, such as PUT SKIP (2) LIST; PUT and GET can also be followed by FILE(filename) to perform file I/O. The EDIT version of PUT and GET allow you to succeed your list of variables with a format. GET EDIT (X, Y) (A(10), X(10), A(20)); X and Y are strings, we input X to 10 characters, skip 10 characters, and Y to 20 characters. Selection Statements: PL/I's if-then/if-then-else requires that a clause with more than one statement be placed inside a block. Blocks are denoted as DO; ... END; For instance: IF A = B THEN IF C = D THEN DO; A = 1; B = 2; C = 3; END; ELSE A = 0; As with C, a mismatched else is always paired with the most recent condition, so the indentation above is appropriate (but of course not necessary). If you want to pair the ELSE with the first condition, then you would need to include a NULL ELSE clause, that is, ELSE; which would appear after the END; statement, followed by ELSE A = 0; NOTE that in PL/I, = is used for both assignment and equality. This is unique, no other language combines the two different uses with one symbol. Consider the statement A = B = C What does this mean? It sets A to either TRUE or FALSE depending on whether B = C or not. That is, the first = is assignment and the second = is equality. In C, the code would be a dual assignment of A and B to C. PL/I also has a switch statement called SELECT which works in two ways, one like a true switch that tests a variable against a value: SELECT (X); WHEN (1) CALL DO_1; WHEN (2) CALL DO_2; OTHERWISE CALL DO_ELSE; END; Or it can omit the variable and have logical tests SELECT; WHEN (X = 1) CALL DO_1; WHEN (X = 2) CALL DO_2; OTHERWISE CALL DO_ELSE; END; If the action after the WHEN is more than one statement, they must be placed into a DO...END block. Conditions: In PL/I, the word condition means an exception to be tested for. You can define your own conditions and you can define your own condition handlers. There are also built-in conditions and condition handlers. You can enable or disable the built-in ones, or you can disable (and later re-enable) your own defined ones. PL/I's use of condition handlers can be very complex, so we just look at a few highlights here. We will also explore this in chapter 14. Among the built-in conditions are ENDFILE, SIZE (size error), CONVERSION (conversion error), OVERFLOW, ZERODIVIDE. Condition (exception) handling code is written using the ON statement: ON condition code to handle the condition here As usual, if the code is more than one instruction, it must be placed inside DO...END. The condition handler is placed amid the code that might raise the condition (unlike Java where the exception handler goes after the block of code that might raise the exception). The handler is called upon whenever the given condition arises. To define a new condition handler, you just define a new one later on. Built-in conditions will always trigger an condition handler unless you specify otherwise by starting the procedure with (NOcondition): as in (NOZERODIVIDE): You can define your own conditions which default to being disabled unless you activate them by starting the procedure with (condition): You can also throw to a condition handler by using SIGNAL condition; A condition handler's code can terminate the program or perform some operation, but typically they contain a CALL or GO TO statement to transfer control elsewhere. Because there is no consistent treatment of conditions (could be disabled, could terminate the program, could transfer control elsewhere, could attempt to resolve the condition right there), it makes PL/I programs extremely hard to read and even though condition handling should improve reliability, it can also damage reliability due to their complex nature! Loops: There are two forms of DO loops, the counting DO loop and the logical DO WHILE loop. The DO loop has the following general form: DO INDEX = 1 TO N BY X; SUM = SUM + INDEX; PUT LIST (INDEX, SUM); END; The BY statement is optional (if omitted, it defaults to a step size of 1) and the TO statement is optional as well (if omitted, it creates an infinite loop). However, the DO loop is more like ALGOL than FORTRAN in that you can specify a list rather than the TO/BY values, or use a WHILE or UNTIL statement. Here are some examples: DO I = 1 TO 8 UNTIL (A = B); DO I = 1 to 10, 11 BY 0 WHILE (A = B); DO I = 1, 3, 4, 8, 11; The DO WHILE loop as this form: INDEX = 1; DO WHILE (INDEX <= N); SUM = SUM + INDEX; PUT LIST (INDEX, SUM); INDEX = INDEX + X; END; Notice in both cases, the end of the loop is denoted by an END; statement. Arrays are much the same as in FORTRAN. You use ( ) (instead of [ ]), and you declare them with their initial bounds. You can specify your own lower bound if desired, otherwise it defaults to 1. Arrays can be as many dimensions as desired. Examples: DECLARE TEMPS(365) FIXED DECIMAL(4, 1); TABLE(6, 4) FLOAT(4); You can access slices of arrays using *, as in TABLE(*, 2) which references the second column of all rows. Structures: In PL/I, structures are much like COBOL's data definitions. The form is DECLARE number name, number subname type, number subname type, ...; There can be as many levels as desired, but only the innermost level contains a type. The numbers should increase by level, although unlike COBOL, they are typically going to be numbers like 1, 2, 3 (instead of COBOL's 1, 5, 10, 20). Here's a brief example: DECLARE 1 TEACHER, 2 NAME, 3 FIRST CHARACTER(10), 3 MIDDLE CHARACTER, 3 LAST CHARACTER(20), 2 DEGREE CHARACTER (4), 2 DISCIPLINE CHARACTER (20); DECLARE 1 STUDENT 2 NAME, 3 FIRST CHARACTER(10), 3 MIDDLE CHARACTER, 3 LAST CHARACTER(20), 2 HOURS FIXED DECIMAL(3, 0), 2 GPA FIXED DECIMAL(4,3); You can be more precise in terms of storage and formatting by using PICTURE followed by '999V99' or 'ZZZ9V99', etc. The V stands for a decimal point and Z and 9 mean to suppress or include leading zeroes respectively. This allows a record to be input from disk file easily. Subroutines: there are two forms, procedures and functions. They are identical in their definition except for the RETURN statement, as described below. To call a procedure, use a call statement, as in CALL SUB1(X, Y); Functions are invoked by name as in X = FUN1(Y); The procedure has the form: NAME: PROCEDURE(param list); DECLARE parameters and local variables ... RETURN; END NAME; The only difference in appearance for a function is the RETURN statement, which must return a value. The value being returned is placed in ( ) as in RETURN (X); or RETURN (Y + Z * 3); Parameters are passed by reference unless the item being passed is a constant or literal. Variables can be passed by copy as well if you enclose them in ( ) as in FOO(A, B, (C), D); C is passed by value, A, B and D are passed by reference. When passing an array, you do not have to specify its size in the declare statement, instead you can use *, as in DECLARE ARRAY(*, *) FIXED DECIMAL(10, 0); if you are passing a 2-D array but do not know or want to specify the size. Built-in Types: PL/I has a great number of built-in data structures available, along with the necessary operations to manipulate them. These include linear list, circular list, bidirectional list (doubly linked), binary tree, stack, pointer, static storage. You can find sample PL/I code at http://home.roadrunner.com/~pflass/PLI/plisrc.html and a PL/I manual at http://publibfi.boulder.ibm.com/epubs/pdf/ibm3lr40.pdf.