Blocks. PL/I is a block-structured language. The outermost block, the External Procedure, provides the shell for all code and data. Other blocks, Procedures, which are executed only as subroutines or functions, and Begin-blocks, which are executed inline can be nested to any depth. Data declared within a block has a "scope" consisting of the block within which it is declared and all nested blocks, unless overridden by another declaration. This provides an extremely flexible and convenient referencing scheme. Newer PL/I's have introduced the concept of the "package" which is an additional level of structuring which allows a number of External Procedures to share common data while hiding it from outside.
Keywords. An interesting feature of PL/I is the absence of reserved words. PL/I has "keywords" which are recognized only in context, and may otherwise be used freely by the programmer. It is legitimate to use a variable named FIXED (roughly equivalent to C's INT); the compiler differentiates the keyword FIXED from the variable FIXED through their usage.
Data. PL/I offers the programmer nearly complete control over data attributes. It is possible to specify a variable down to the number of digits or bits it is to occupy, and the compiler will provide this if possible. The compiler can also, if requested, check assignments to this variable to insure that the declared size is not exceeded. Strings can have constant lengths or vary from zero to a maximum number of bits or bytes, with the compiler keeping track of the current length.
PL/I provides a wide variety of data types, including the usual arithmetic types FIXED, FLOAT, REAL or COMPLEX. It offers true strings (CHARACTER or BIT) with enforcement of declared maximum lengths, unlike C which does no checking on assignments to strings. Other data types are POINTER and OFFSET, and GRAPHIC (double-byte characters). PICTURE data, provides automatic editing and de-editing on assignments according to a predefined mask. More esoteric data types are LABEL and ENTRY variables, AREA, EVENT, FILE, and so on.
Defaults. To simplify the burden on the programmer, PL/I provides an extensive system of defaults. In the minimal case, data does not need to be declared at all, but will be provided with a set of default attributes depending on the context in which it is used. The programmer has to declare only enough information as necessary and the compiler will fill in the rest. For example, declaring a variable FIXED with no other attributes will cause the compiler to add the default attributes DECIMAL and a precision (number of digits).
Storage. PL/I provides a number of storage attributes. The default is AUTOMATIC, which is allocated on entry to a block, possibly with initialization, and freed at exit. STATIC storage exists throughout the life of the program, CONTROLLED storage must be explicitly allocated by the program, analogous to a pushdown stack in which freeing the current generation pops up the previous allocation if any. BASED storage provides a mapping for storage otherwise allocated or referenced, or may also be explicitly allocated and freed. EXTERNAL storage resembles STATIC, but may be referenced by other separately-compiled programs.
I-O. Input-output is an integral part of PL/I, not an add-on via library functions. Files can be processed as streams of bytes or as individual records which can be fixed-length or variable. Files can be processed sequentially or randomly by record or key. Programs can start an I/O operation and later be interrupted on completion.
Exceptions. PL/I has a detailed system for handling exceptions, which can be asynchronous resulting from I/O errors, hardware-generated such as overflow, or program-generated. Handlers for each condition can be specified via the ON statement, and handlers can be stacked and unstacked. Many error conditions can be "enabled" or "disabled" by the program on the level of a block or a single statement.
One of the simplest PL/I programs is:
World: Procedure options(main); Put List( 'Hello world' ); End World;