Chapter 10
Preprocessor
The PL/I preprocessor lets you change a source program at compile time. You can mix preprocessor statements with nonpreprocessor statements in the source program, but preprocessor statements are executed only at compile time. Any resulting source program changes are then used for further compilation.
The preprocessor is embedded in the compiler, and so is also called the embedded preprocessor.
Note
The preprocessor statements are supplied for compatibility with Digital implementations of PL/I. By default, Endless PL/I
for POSIX Systems enables preprocessing for the Digital dialect (the -D dec command line option), and
disables preprocessing for the ANSI (-D ansi) and IBM (-D ibm) dialects. Use the
-P compiler option to override the default. See the Endless PL/I for POSIX Systems User's Manual for more
information on using the -P compiler option.
During compilation, the preprocessor performs two types of preprocessing:
It interprets preprocessor statements, including preprocessor expression evaluation.
It replaces the value of preprocessor variables and procedures.
Preprocessor statements allow you to include text from alternative sources (INCLUDE libraries, directories, and the VAX Common Data Dictionary), control the course of compilation (%DO, %GOTO, %IF, and %PROCEDURE), issue user-generated diagnostic messages, and selectively control listings and formats. The preprocessor statements are summarized in Table 10.1, “Summary of PL/I Preprocessor Statements”.
This chapter describes the preprocessor statements and functions.
10.1. Preprocessor Compilation Control
At compile time, preprocessor variables, procedures, and variable expressions are evaluated in the order that they appear in the source text, and the new values are substituted in the source program in the same order. Thus, the course of compilation becomes conditional, and the resulting executable program can have a variety of features. Note that preprocessor variables must be declared and activated before replacement occurs.
For example:
EXAMPLE: PROCEDURE OPTIONS(MAIN); %DECLARE HOUR FIXED; %HOUR = SUBSTR(TIME(),1,2); %IF HOUR > 7 & HOUR < 18 %THEN %FATAL 'Please compile this outside of prime time'; %DECLARE T CHARACTER; %ACTIVATE T NORESCAN; %T='''Compiled on '||DATE()||''''; DECLARE INIT_MESSAGE CHARACTER(60) VARYING INITIAL(T); %IF VARIANT() = '' | VARIANT() = 'NORMAL' %THEN %INFORM 'NORMAL'; %ELSE %DO; %IF VARIANT() = 'SPECIAL' %THEN %INFORM 'SPECIAL'; %ELSE %IF VARIANT() = 'NONE' %THEN %; %ELSE %DO; %T='''unknown variant '||variant()||'''' %WARN t; INIT_MESSAGE=INIT_MESSAGE||' with '||T; %END; %END; PUT SKIP LIST (INIT_MESSAGE); END EXAMPLE;
This example illustrates several aspects of the preprocessor. First, the programmer specified that this program must be
compiled outside of prime time. Second, the VARIANT preprocessor built-in function is used to determine which variant was
specified on the command line using the -T option plus the variant name. Third, user-generated preprocessor
messages remind the programmer which value was given to VARIANT.
Notice the number of apostrophes around the string constant assigned to T. Apostrophes are sufficient if the value of T is used only in a preprocessor user-generated diagnostic message; the value of T is concatenated with nonpreprocessor text and assigned to INIT_MESSAGE. During preprocessing, apostrophes are stripped off string constants. Use additional apostrophes to ensure that the run-time program also has apostrophes around the string.
10.2. Preprocessor Statements
All preprocessor statements are preceded by a percent sign (%) and terminated by a semicolon (;). All text that appears within these delimiters is considered part of the preprocessor statement and is executed at compile time. For example:
%DECLARE HOUR FIXED; /* declaration of a preprocessor single variable */ %DECLARE (A,B) CHARACTER; /* a factored preprocessor declaration */ %HOUR = SUBSTR(TIME(),1,2); /* preprocessor assignment statement using two built-in functions */ %STATE: PROCEDURE (X) RETURNS (BIT); /* preprocessor procedure */
Notice that a percent sign is required only at the beginning of the statement and must be the first text on the line (white space is ignored). Each preprocessor statement must be on a new line. The percent sign alerts the compiler that until the line is terminated with a semicolon, all subsequent text is preprocessor text. Therefore, no other percent signs are required on the line.
Labels (preceded by a percent sign) are permitted on preprocessor statements and required on preprocessor procedures. As with other labels, preprocessor labels are used as the target of program control statements.
A preprocessor label must be an unsubscripted label constant. The format for a preprocessor label is:
%label: preprocessor-statement;You can specify multiple labels on a preprocessor statement (note that this is not true of non-preprocessor statements).
Table 10.1, “Summary of PL/I Preprocessor Statements” summarizes the preprocessor statements. Each statement is then described in detail in an individual subsection.
| Statement | Use |
|---|---|
| %Assignment | Evaluates a preprocessor expression and gives its value to a preprocessor identifier |
| %; | Null statement, specifies no preprocessor operation |
| %ACTIVATE | Makes the value of declared preprocessor variables eligible for replacement |
| %DEACTIVATE | Makes the value of declared preprocessor variables ineligible for replacement |
| %DECLARE | Defines the preprocessor variable names and identifiers to be used in a PL/I program and specifies the data attributes associated with them |
| %DO | Denotes the beginning of a group of preprocessor statements to be executed as a unit |
| %END | Denotes the end of a block or group of statements that started with a %PROCEDURE or a %DO statement |
| %ERROR | Generates a user-defined error diagnostic message |
| %FATAL | Generates a user-defined fatal diagnostic message |
| %GOTO | Transfers control to a labeled preprocessor statement |
| %IF | Tests a preprocessor expression and establishes action to be performed based on the results |
| %INCLUDE | Copies the text of an external file into the source file at compile time |
| %INFORM | Generates a user-defined informational diagnostic message |
| %[NO]LIST[a] | Same as %[NO]LIST_ALL |
| %[NO]LIST_ALL[a] | Does or does not include INCLUDE files, machine code, and source statements in the listing from that point on |
| %[NO]LIST_INCLUDE ??? | Does or does not include INCLUDE files in the listing from that point on |
| %[NO]LIST_MACHINE ??? | Does or does not include machine code in the listing from that point on |
| %[NO]LIST_SOURCE ??? | Does or does not include source code in the listing from that point on |
| %PAGE | Provides listing pagination without form feeds in the source text |
| %PROCEDURE | Begins a preprocessor procedure |
| %REPLACE | Assigns a constant value to an identifier at compile time |
| %RETURN | Returns a value from execution of a preprocessor procedure to the point of invocation |
| %SBTTL ??? | Allows specification of a listing subtitle line |
| %TITLE ??? | Allows specification of a listing title line |
| %WARN | Generates a user-defined warning diagnostic message |
[a] Ignored by the Endless PL/I for POSIX Systems compiler. Included for compatibility with the Endless PL/I for OpenVMS compiler. | |
The preprocessor assignment statement gives a value to a specified preprocessor variable.
The format of the assignment statement is:
%target = expression;targetThe name of the preprocessor variable to be assigned a value. It must be an unsubscripted reference to a preprocessor variable.
expressionAny valid preprocessor expression.
For arithmetic operations, only decimal integer arithmetic of precision (10,0) is performed. Each operand and all results are converted, if necessary, to a fixed decimal value of precision (10,0). Fractional digits are truncated.
The % (null) statement performs no action.
The format of the % (null) statement is:
%;
The most common use for the % (null) statement is as the target statement of a %THEN or %ELSE clause in an %IF statement. For example:
%IF ERROR() > 0; %THEN %GOTO FIXIT; %ELSE %;
The %ACTIVATE statement makes preprocessor variable and procedure identifiers eligible for replacement. If the compiler encounters the named identifier after executing a %ACTIVATE statement, it initiates variable replacement.
The format of the %ACTIVATE statement is:
%{ ACTIVATE } element [ RESCAN ]... ;
{ ACT } [ NORESCAN ]elementThe name of a previously declared preprocessor identifier and/or a list of identifiers, where the identifiers are separated by commas and the list is enclosed in parentheses.
- RESCAN
orNORESCAN Specifies that the preprocessor is to continue or discontinue checking the text for secondary value replacement.
The RESCAN option specifies that preprocessor scanning continue until all possible identifier replacements are completed. RESCAN is the default option.
The NORESCAN option specifies that replacement be done only once; the resulting text is not rescanned for possible further replacement.
An identifier is activated by either a %ACTIVATE statement or a %DECLARE statement. When an activated identifier is encountered by the compiler, in unquoted nonpreprocessor statements, the variable name or procedure reference is replaced by its value. Replacement continues throughout the rest of the source program unless replacement is stopped with the %DEACTIVATE statement.
You can activate several variables with a single statement. For example:
%DECLARE (A,B,C) FIXED; %ACTIVATE (A,B), C NORESCAN;
Because RESCAN is the default action, this statement activates A and B with the RESCAN option. C is activated, but is not rescanned.
If an identifier that is not a preprocessor variable or procedure is the target of a %ACTIVATE statement, a warning message is issued and the identifier is implicitly declared as a preprocessor variable with the FIXED attribute. Thereafter, the identifier variable is eligible for replacement when activated.
For example:
DECLARE (A,B,C) FIXED; %DECLARE (A,B) FIXED; %ACTIVATE (A,B); %A = 1; %B = (A + A); C = A + B; PUT SKIP LIST (C); /* C = 3 */
In the preceding example, the activated preprocessor variables A and B are assigned values by the preprocessor. Notice that variables A and B are also declared as nonpreprocessor variables; this establishes them as variables within the nonpreprocessor program.
In the following example, the variable B is deactivated by the %DEACTIVATE statement.
⋮ %DEACTIVATE B; B = 900; C = A + B; PUT SKIP LIST (C); /* C = 901 */ END;
Because the preprocessor variable B is deactivated, the preprocessor assignment statement %B = (A + A) is not in effect and the value of B is taken from the run-time assignment of B = 900. However, the value of A remains 1.
The %DEACTIVATE statement makes preprocessor variable and procedure identifiers ineligible for replacement. After a variable or procedure has been deactivated, it will not be replaced during preprocessing. Replacement of a deactivated variable or procedure occurs again only after it is reactivated with the %ACTIVATE statement.
The format of the %DEACTIVATE statement is:
%{ DEACTIVATE } element, ... ;
{ DEACT }elementThe name of a preprocessor identifier, or a list of identifiers that is enclosed in parentheses. Deactivated elements must have been previously declared preprocessor variables.
For example:
TESTF:PROCEDURE OPTIONS (MAIN); DECLARE Y FIXED DECIMAL; Y = 10; /* initial value: Y = 10 */ %DECLARE Y FIXED; %Y = 3; /* replacement value: Y = 3 */ PUT SKIP LIST(Y); /* output: Y = 3 */ %DEACTIVATE Y; PUT SKIP LIST(Y); /* output: Y = 10 */ END;
In this example, %Y, when activated, replaces all the occurrences of the variable Y by the value assigned to %Y, until %Y is deactivated by the %DEACTIVATE statement. The identifier %Y is implicitly activated when it is declared as a preprocessor identifier.
It is possible to deactivate several variables with a single statement. For example:
%DEACTIVATE (A,B,C,D,E,F);
The %DECLARE statement establishes an identifier as a preprocessor variable, specifies the data type of the variable, and activates the identifier for replacement. %DECLARE can occur anywhere in a PL/I source program.
The format of the %DECLARE statement is:
%{ DECLARE } element [ FIXED ] ...;
{ DCL } [ CHARACTER ]
[ BIT ]elementThe name of a preprocessor identifier or a list of identifiers, which are separated by commas and enclosed in parentheses. You can give elements the attribute BIT, FIXED, or CHARACTER, but you cannot specify precision or length. The compiler supplies the variables with the implied attributes shown in Table 10.2, “Implied Attributes for Variables”.
Table 10.2. Implied Attributes for VariablesSpecified Attribute Implied Attributes BIT (31) INITIAL ((31)'0'B) FIXED DECIMAL (10,0) INITIAL (0) CHARACTER VARYING (32500) INITIAL (' ') If no data type is specified, FIXED is assumed.
When a variable is declared in a preprocessor statement, it is activated for replacement and rescanning. The scope of a preprocessor variable is all of the text following the declaration of the variable, unless the variable is declared inside a preprocessor procedure. Using %DECLARE inside of a preprocessor procedure has the effect of declaring a local variable. For example:
%DECLARE HOUR FIXED;
In this example, HOUR is declared as a preprocessor variable identifier with the FIXED attribute. The compiler supplies the default values that make this declaration the equivalent of the following:
DECLARE HOUR FIXED DECIMAL (10,0) INITIAL (0);
Note
In preprocessor declarations, the attribute FIXED implies FIXED DECIMAL. In nonpreprocessor declarations, FIXED implies FIXED BINARY.
Factored declarations are permitted and follow the same usage rules as nonpreprocessor declarations. For example:
%DECLARE (A,B) CHARACTER, C BIT;
Both A and B are declared with the CHARACTER attribute. The compiler supplies default values that make this declaration the equivalent of the following:
%DECLARE (A,B) CHARACTER VARYING(32500) INITIAL(''), C BIT(31)INITIAL((31)'0'B);
The %DO statement begins a preprocessor DO-group, a sequence of statements terminating with the %END statement. The preprocessor DO-group must be a non-iterative simple DO-group, but it can be usefully combined with an %IF statement.
The format of the %DO statement is:
DO; ⋮ %END;
You can include both preprocessor and nonpreprocessor text in a preprocessor DO-group. For example:
%DECLARE T CHARACTER; /* declare T */ %ACTIVATE T NORESCAN; /* activate T for replacement */ ⋮ %IF VARIANT() = 'NONE'; %THEN %DO; /* assign string to T */ %T = '''unknown variant'''; /* output unknown variant warning at compile time */ %WARN T; /* assign value of T to nonpreprocessor variable */ INIT_MESSAGE = INIT_MESSAGE||' with '||T; %END;
This preprocessor DO-group performs several steps. First, a string constant is assigned to T. Then the value of T is used in a preprocessor user-generated diagnostic message. This message is issued at compile time to warn the programmer that the program is compiled with an unknown variant. Finally, the value of T is concatenated with a nonpreprocessor string constant. INIT_MESSAGE, including the value of T, is part of the run-time image.
The %END statement terminates a preprocessor procedure or DO-group.
The format of the %END statement is:
%END;
Preprocessing then continues with the next executable preprocessor statement.
The %ERROR statement provides a diagnostic error message during program compilation.
The format of the %ERROR statement is:
%ERROR preprocessor-expression;preprocessor-expressionA maximum of 64 characters giving the text of the error message to be displayed. Messages of more than 64 characters are truncated.
Returned Message
The message displayed by %ERROR is:
%PLICOM-E-USERDIAG, preprocessor-expression
Compilation errors that result in the display of the %ERROR statement increment the informational diagnostic count displayed in the compilation summary, and inhibit production of an object file.
The %FATAL statement provides a diagnostic fatal message during program compilation.
The format of the %FATAL statement is:
%FATAL preprocessor-expression;preprocessor-expressionThe text of the fatal message you want displayed. The text is a character string with a maximum length of 64 characters. It is truncated if necessary.
Returned Message
The message displayed by %FATAL is:
%PLICOM-F-USERDIAG, preprocessor-expression
Compilation errors that result in a fatal error terminate compilation after the message is displayed.
The %GOTO statement causes the preprocessor to interrupt its sequential processing of source text and continue processing at the point specified in the %GOTO statement. A %GOTO is useful for avoiding large segments of text in the source program.
The format of the %GOTO statement is:
%GOTO label-reference;label-referenceA label of a preprocessor statement. The label reference determines the point to which the compiler processing will be transferred.
Nonlocal %GOTOs are not permitted. In other words, if a %GOTO is used within a preprocessor procedure, control must not be passed out of the containing procedure. Also, a %GOTO must not transfer control into a preprocessor procedure.
The following example illustrates transfers (forward and backward) and the use of %GOTO:
%TEXT:PROCEDURE RETURNS(CHARACTER); ⋮ %CHANGE_TEXT: DO; ⋮ %IF WARN() = 5 %THEN %GOTO CHANGE_TEXT; %ELSE %GOTO INSERT_TEXT; ⋮ %INSERT_TEXT: DO; ⋮ %END;
Depending on the status of the %IF statement in this example, program compilation takes one of two courses. Control is transferred either forward to the statement labeled INSERT_TEXT or backward to the statement labeled CHANGE_TEXT. The compiled program will then include one of the two blocks, but not both. Notice also in this example that the preprocessor built-in function WARN is used to determine preprocessor action, which makes the program self-diagnostic.
If program text is not compiled because the %GOTO statement transferred control over it, the compiler still checks the basic syntax of all statements. Therefore, comment delimiters and parentheses must balance, apostrophes must be paired correctly, and all statements must end with a semicolon.
The %IF statement controls the flow of program compilation according to the scalar bit value of a preprocessor expression. The %IF statement tests the preprocessor expression and performs the specified action if the result of the test is true.
The format of the %IF statement is:
%IFtest-expression%THENaction[%ELSEaction];
test-expressionAny valid preprocessor expression that yields a scalar bit value. If any bit of the value is 1, then the expression is true; otherwise, the expression is false.
actionA single, unlabeled preprocessor statement, %DO-group, %GOTO statement, or a preprocessor null statement. The specified action must not be an %END statement.
The %IF statement evaluates the preprocessor test expression. If the expression is true, the action specified following the keyword %THEN is compiled. Otherwise, the action, if any, following the %ELSE keyword is compiled. In either case, compilation resumes at the first executable statement following the termination of the %IF statement, unless a %GOTO in one of the action clauses causes compilation to resume elsewhere.
If an action is not compiled because the alternative action was compiled instead, the compiler still checks the basic syntax of all statements. Therefore, comment delimiters and parentheses must balance, apostrophes must be paired correctly, and all statements must end with a semicolon.
10.2.12. %INCLUDE
The %INCLUDE statement incorporates text from other files into the current source file during compilation. It can occur anywhere in a PL/I source file; it need not be part of a procedure.
The format of the %INCLUDE statement is:
%INCLUDE { 'file-spec' } ;
{ module-name }
{ 'directory-name(module-name)' }file-specA file specification enclosed in apostrophes interpreted as the complete file specification or path. The filename is tried as specified with no environment variable translations and no applications of the
-Iordir--suffixoptions. If the file is not found, an error will be generated.module-nameThe
module-nameis always converted to lower case. If the--suffixoption is given,.is appended, without conversion, tosuffixmodule-name. Next, each of the path names specified in the-Ioption is prepended todirmodule-nameto form a complete file name. If the file is not found in any of the given directories, the current directory (or `.') is tried.directory-nameSpecifies a path name which overrides the
-Ioption. The value of thedir--suffixoption is appended tomodule-namewithout conversion.
Notes
When you include a text file or a text module from a library, you can choose to include this INCLUDE file in the
program's listing by using the -s or --list option on the pl1 command
line.
This option provides compatibility with PL/I code previously compiled with VMS text libraries or IBM partition data
set names. The directory-name is treated as a UNIX directory, and the module-name as the last
qualifier in the path specification.
Included text files are listed in the data map in the listing file. The expanded include files are denoted by the file number (as listed in the data map) plus the line number within the include file. See the Endless PL/I for POSIX Systems User's Manual for more information.
The -M compiler option determines the case of include module names. When an include module name is
built, PL/I first applies the case rule specified by the -M option, then appends the suffix, if one was
specified by the -u compiler option.
When searching for a module, PL/I first checks in the current working directory, then each directory in the list
specified by the -I compiler option, and last in the standard include
directory, dir/usr/lib/pl1/include.
Examples
%INCLUDE '/usr/users/tim/project/header.in';
This statement includes the file /usr/users/tim/project/header.in without translation or regard
for the -I or dir--suffix options. If this file does not
exist, an error occurs.
%INCLUDE HEADER;This statement looks for a module named HEADER with a suffix given by the --suffix option (if any) in
the current directory then, if the module was not found, in the directory or directories specified by the
-I option.dir
Consider the following command line:
pl1 --suffix .in -Idir /usr/users/tim/project
Given this command line, the preprocessor looks for the file
/usr/users/tim/project/header.in.
%INCLUDE '/usr/users/tim/project(header)';
This statement looks in the directory /usr/users/tim/project for a file named header. If you
specified the --suffix .in option, the complete file specification is
/usr/users/tim/project/header.in.
The %INFORM statement specifies a user-written diagnostic informational message to be displayed during program compilation.
The format of the %INFORM statement is:
%INFORM preprocessor-expression;preprocessor-expressionThe text of the informational message displayed. The text is a character string of up to 64 characters. The string is truncated if necessary.
Returned Message
The format of the message to be displayed by %INFORM is:
%PLICOM-I-USERDIAG, preprocessor-expressionThe %INFORM statement increments the informational diagnostic count displayed in the compilation summary.
10.2.14. %PROCEDURE
A preprocessor procedure is a sequence of preprocessor statements headed by a %PROCEDURE statement and terminated by a %END statement. A preprocessor procedure executes only at compile time. Invocation is similar to a function reference and occurs in two ways:
Preprocessor statements can invoke preprocessor procedures. In addition, preprocessor statements from within preprocessor procedures can invoke other preprocessor procedures.
Statements from the source program can invoke preprocessor procedures.
The format of the %PROCEDURE statement is:
%label: { PROCEDURE } [(parameter-identifier, ...)] [STATEMENT]
{ PROC }
RETURNS( { CHARACTER } );
{ FIXED }
{ BIT }
preprocessor statements ...
[%] RETURN (preprocessor-expression); ...
[%] END;labelAn unsubscripted label constant. A preprocessor procedure is established by the appearance of the label name on the %PROCEDURE statement and terminated by the corresponding %END statement. The label name must be active if invoked from a nonpreprocessor statement.
Preprocessor label names can be activated and deactivated, but cannot be specified in a %DECLARE statement.
parameter-identifierThe name of a preprocessor identifier. Each identifier is a parameter of the procedure.
- STATEMENT
A preprocessor procedure option. The STATEMENT option permits the use of a keyword argument list followed by an optional positional argument list in the preprocessor procedure invocation. The STATEMENT option returns strings that can be used as PL/I statements at run time.
- RETURNS
A preprocessor procedure attribute. The RETURNS attribute defines the data type to be returned to the point of invocation in the source code. If you specify a data type that is inconsistent with the returned value, a conversion error may result.
preprocessor-expressionValue to be returned to the invoking source code. You must specify the preprocessor expression. The preprocessor expression is converted to the data type specified in the RETURNS option and is returned to the point of invocation. Therefore, the expression must be capable of being converted to CHARACTER(32500), FIXED(10), or BIT(31).
The %PROCEDURE statement defines the beginning of a preprocessor procedure block and specifies the parameters, if any, of the procedure. Because the preprocessor procedure is always invoked as a function, the %PROCEDURE statement must also specify (using the RETURNS option) the data type attributes of the value that is returned to the point of invocation.
For example:
%A_VAR = A_PROC();In this statement, the preprocessor procedure A_PROC is invoked and evaluated, and the result is returned and assigned to the preprocessor variable A_VAR.
As with other PL/I procedures, a parenthesized parameter list specifies the parameters that the preprocessor procedure expects when it is invoked. Each preprocessor parameter specifies the name of a variable declared in the preprocessor procedure. The preprocessor parameters must correspond one-to-one with arguments specified for the preprocessor procedure when it is invoked, except when the STATEMENT option is used.
The value to be returned to the invoking source code is converted to the data type specified in the RETURNS option. The return value replaces the preprocessor procedure reference in the invoking source code. Preprocessor procedures cannot return values through their parameter list. The return value must be capable of being converted to one of the data types BIT, CHARACTER, or FIXED. The maximum precision of the value returned by the %RETURNS statement is BIT(31), CHARACTER(32500), or FIXED(10).
Preprocessor procedures cannot be nested. The scope of a preprocessor procedure is the procedure itself; that is, variables, labels, and any %GOTO statements used inside of the procedure must be local.
A preprocessor procedure is invoked by the appearance of its
entry-nameand list of arguments. If the reference occurs in a nonpreprocessor statement, the entry name must be active before the preprocessor procedure is invoked. If the entry name is activated with the RESCAN option, the value of the preprocessor procedure is rescanned for further possible preprocessor variable replacement and procedure invocation. You can invoke preprocessor procedures recursively.When a preprocessor procedure (with or without the STATEMENT option) is invoked from a preprocessor statement, each argument is treated as an expression and the result of executing the preprocessor procedure is returned to the statement containing the invocation.
When a preprocessor procedure is invoked from nonpreprocessor source text, the arguments are interpreted as character strings and are delimited by the appearance of a comma or a right parenthesis occurring outside of balanced parentheses. For example, the positional argument list (Q(E,D), XYZ) has two arguments; the strings `Q(E,D)' and `XYZ'.
Examples
%A1: PROCEDURE RETURNS(FIXED); DECLARE (A,B,C) FIXED; A = 2; B = 10; C = A + B; RETURN(C); END;
This example declares the preprocessor procedure A1 and specifies that the procedure return a fixed decimal result after the preprocessor statements within the procedure have been executed.
The procedure returns the value 12 to the point of invocation. Note that the leading percent signs, normally associated with preprocessor statements, are not required within a preprocessor procedure.
PPFIB: PROCEDURE OPTIONS (MAIN); DECLARE Y CHAR(14) INITIAL('Fibonacci Test'); ❶ %DECLARE Y FIXED; ❷ %F: PROCEDURE(X) RETURNS (FIXED); ❸ DECLARE X FIXED; IF (X <= 1) THEN RETURN(1); ELSE RETURN(F(X-1)+F(X-2)); END; /* End preprocessor procedure */ %Y = F(10); ❹ PUT SKIP LIST(Y); ❺ %Y = F(11); ❻ PUT SKIP LIST(Y); %Y = F(12); PUT SKIP LIST(Y); %DEACTIVATE Y; ❼ PUT SKIP LIST(Y); ❽ END; /* End run-time procedure */
This example uses a preprocessor procedure to return a Fibonacci number. The recursive preprocessor procedure labeled %F is invoked to return a single value, a Fibonacci number, to the point of invocation. The following notes correspond to the example:
- ❶
The run-time variable Y is declared with the CHARACTER attribute and initialized to Fibonacci Test.
- ❷
The preprocessor variable Y is declared with the FIXED attribute, which implies FIXED DECIMAL (10,0). This declaration automatically activates the preprocessor variable Y.
- ❸
The preprocessor procedure F is defined. The percent sign for the END statement is optional in a preprocessor procedure.
Note that this procedure is recursive.
- ❹
The preprocessor procedure is called, passed the value 10, and the 10th number in the Fibonacci series is calculated. The resulting value is assigned to the preprocessor variable Y.
- ❺
Because the preprocessor variable Y is active by default, the compiler replaces the occurrence of Y in the PUT statement with the new preprocessor Y value.
- ❻
Step 4 is repeated for the value 11.
Step 4 is repeated for the value 12.
- ❼
The preprocessor variable Y is deactivated. No more scanning or replacement occurs. The preprocessor variable Y retains its final replacement value, 233.
- ❽
The run-time value of Y (Fibonacci Test) is output.
The output from this program is:
89 144 233 Fibonacci Test
Using the STATEMENT Option
All preprocessor procedures (with or without the STATEMENT option) return a value to the invoking source code; that is, they are function procedures. Through the use of the STATEMENT option, the argument list to a preprocessor procedure can be a keyword argument list. Keyword argument lists are unique to preprocessor procedures and provide a powerful tool for manipulating PL/I.
A keyword argument list ends with a semicolon rather than the right parenthesis. In this way, the STATEMENT option permits you to use a preprocessor procedure as if it were a statement. Consequently, preprocessor procedures using the STATEMENT option let you extend the PL/I language by simulating features that may not otherwise be available.
Preprocessor procedures can have one of two distinctly different types of argument lists: positional or keyword. Positional argument lists (ending with a right parenthesis) use parameters sequentially, as in a parenthesized list. You can use positional argument lists in any preprocessor procedure. Keyword argument lists (ending with a semicolon) use parameters in any order, as long as each keyword matches the name of a parameter. This permits the option of specifying the order in which parameters are passed. You can use keyword argument lists only when the preprocessor procedure contains the STATEMENT option and is invoked from a nonpreprocessor statement.
When a preprocessor procedure is invoked from a nonpreprocessor statement, the STATEMENT option permits the use of a keyword argument list that follows the optional positional argument list in a preprocessor procedure invocation.
When you use keyword arguments in nonpreprocessor statements, the keywords can be used in any order. The following reference examples would produce a variety of results with positional arguments, because values would be used sequentially. Keyword arguments produce consistent results because keyword parameters are matched with keyword arguments.
%B: PROCEDURE (ALPHA, BETA, GAMMA) STATEMENT ...; DECLARE (ALPHA, BETA, GAMMA) FIXED; ⋮ END; B(1,2,3); B ALPHA(1) GAMMA(3) BETA(2); B(1) GAMMA(3) BETA(2); B (,2,3) ALPHA(1);
The next example shows a more common use of the STATEMENT option; to generate PL/I source statements that define a unique run time feature. The preprocessor procedure APPEND returns a string, which is incorporated into the source program at compile time. At run time, the returned string is used as a PL/I function.
This preprocessor procedure permits a varying string to accumulate text up to its maximum size without danger of undetected truncation. Normally, strings that exceed their maximum size are truncated. The text returned by the preprocessor procedure provides the run-time program with a way to handle truncation. If the string would be truncated, a message is printed and the FINISH condition is signaled.
%APPEND: PROCEDURE (string,to) STATEMENT ❶ RETURNS(CHARACTER); %DECLARE (string,to) CHARACTER; ❷ %RETURN ( ❸ 'IF LENGTH('||string||')+LENGTH('||to||') > SIZE('||to||')-2'|| ' THEN DO;'|| 'PUT SKIP LIST (''Buffer overflowed appending to '||to||''');'|| 'SIGNAL FINISH;'|| ❹ 'END;'|| 'ELSE '||to||' = '||to||'||'||string||';'|| 'END;' ); %END;
The following notes are keyed to this example:
- ❶
The preprocessor procedure APPEND is defined with the parameters `string' and `to' and the STATEMENT option.
- ❷
`String' and `to' are declared as parameters within the preprocessor procedure.
- ❸
The %RETURN statement returns the value contained by the parentheses. This text then becomes part of the PL/I nonpreprocessor source program.
Notice the punctuation within the character string returned by %RETURN. At compile time, single quotes are stripped when the text is incorporated into the run-time PL/I program. In addition, the semicolon that delimits the invocation is not retained when the replacement takes place. All customary PL/I punctuation must be included in the character string.
- ❹
If the current varying string and the additional string together are greater than the maximum length of the varying string, an informational message is printed and the FINISH condition is signaled.
The following invocations of the preprocessor procedure APPEND are all equivalent:
APPEND STRING('New String') TO (My_string); APPEND TO(My_string) STRING('New String'); APPEND('New String') TO(My_string);
Notice that if you have a preprocessor procedure (A) with a label that is the same as the name of a keyword argument in another preprocessor procedure (B) with the STATEMENT option, then when B is invoked the keyword argument is treated as a call to procedure A, and not as a keyword parameter in B.
10.2.15. %REPLACE Statement
The preprocessor %REPLACE statement specifies that an identifier is a constant of a given value. It can be used anywhere within a procedure or anywhere in a PL/I source file.
Beginning at the point at which a %REPLACE statement is encountered, PL/I replaces all occurrences of the specified identifier with the specified constant value, until the end of compilation.
The format of the %REPLACE statement is:
%REPLACEidentifierBYconstant-value;
identifierAny valid PL/I identifier. PL/I keywords are not valid identifiers in a %REPLACE statement. The identifier must not be the name of a declared preprocessor or program variable. PL/I permits multiple %REPLACE statements and %REPLACE statements that redefine the %REPLACE identifier.
constant-valueAny valid character-string, bit-string, or arithmetic constant.
Integer constants that are given values by %REPLACE statements are valid in constant expressions. For example:
%REPLACE PREFIX BY 8; DECLARE BUFFER CHARACTER( 80 + PREFIX);
When the program containing these lines is compiled, the variable BUFFER is declared with a length of 88 characters.
The %RETURN statement terminates execution of the current preprocessor procedure.
The format of the %RETURN statement is:
[%]RETURN (preprocessor-expression);preprocessor-expressionValue to be returned to the invoking procedure. The preprocessor expression must be specified. The preprocessor expression is converted to the data type specified in the RETURNS option, and the value of the expression is returned to the point of invocation. Therefore, the expression must be capable of being converted to CHARACTER (32500) VARYING, FIXED (10), or BIT (31).
The value returned by a preprocessor procedure cannot contain preprocessor statements.
When the value of the evaluated preprocessor expression is passed back to the point of invocation, control returns to the evaluation of the statement that contained the reference to the preprocessor procedure.
Within a preprocessor procedure, the leading percent sign (%) is optional.
Multiple %RETURN statements are permitted in preprocessor procedures. See Section 10.2.14, “%PROCEDURE” for examples of %RETURN.
The %WARN statement provides a diagnostic warning message during program compilation.
The format of the %WARN statement is:
%WARN preprocessor-expression;preprocessor-expressionThe text of the warning message to be displayed. The text is a character string with a maximum length of 60 characters. It is truncated if necessary.
Returned Message
The message displayed by %WARN is:
%PLICOM-W-USERDIAG, preprocessor-expressionThe %WARN statement increments the warning diagnostic count displayed in the compilation summary.
10.3. User-Generated Diagnostic Messages
The PL/I embedded preprocessor provides four statements that permit user-generated diagnostic capability: %INFORM, %WARN, %ERROR, and %FATAL. Preprocessor diagnostic messages are compile-time messages, but you define the circumstances that invoke the message and the text displayed.
The action of each statement is to generate a diagnostic message of the appropriate severity level, with the preprocessor expression as the text of the message.
Examples
The first example shows how %INFORM can be used to return the value of VARIANT.
%IF VARIANT() = ''| VARIANT() = 'NORMAL' %THEN %INFORM 'NORMAL';
In this example, the %INFORM diagnostic message is used to let the programmer know that compilation is continuing according to a normal plan.
If the value of VARIANT is not specified at compile time or if the value is `NORMAL', then the following informational message is issued:
%PLICOM-I-USERDIAG, NORMAL
In the following example, an unknown variant is included at compile time. The %WARN statement issues a compile-time warning diagnostic message and saves the message so that when the program is run, the appropriate text is output by the program.
DECLARE INIT_MESSAGE CHAR(40) VARYING INITIAL (T); ⋮ %IF VARIANT() = 'NONE'; %THEN %; %ELSE %DO; %T = '''unknown variant'''; %WARN T; INIT_MESSAGE = 'Compiled with '||T; %END; PUT SKIP LIST (INIT_MESSAGE);
The preprocessor built-in functions INFORM, WARN, and ERROR return the number of user-generated diagnostics issued at any specified point during compilation. Therefore, you can use user-generated diagnostics to control the course of compilation. For example:
%IF WARN() > 5 %THEN %GOTO change_text; %ELSE;
This example specifies that compilation take a different course if there are more than five warning messages at that point in program compilation. If there are fewer than five warnings, then compilation proceeds along the current path.
%IF ERROR() >= 1 %THEN %FATAL 'Ending Compilation';
This example stops compilation if there is an error that would inhibit the production of an object file.
User-generated diagnostic messages increment the count displayed in the diagnostic summary.
10.4. Preprocessor Built-In Functions
A number of PL/I built-in functions are available for use at compile time. Since preprocessor built-in functions work the same way as run-time PL/I built-in functions, refer to Chapter 11, Built-In Functions, Subroutines, and Pseudovariables for detailed descriptions of the functions.
The built-in functions are summarized in Table 10.3, “Summary of PL/I Preprocessor Built-In Functions” according to the following functional categories:
Arithmetic built-in functions provide information about the properties of arithmetic values, or perform common arithmetic calculations.
String-handling built-in functions process character-string and bit-string values.
Conversion built-in functions convert data from one data type to another.
Timekeeping built-in functions return the system date and time of day.
Miscellaneous built-in functions are specifically preprocessor built-in functions.
Table 10.3. Summary of PL/I Preprocessor Built-In FunctionsCategory Function Reference Value Returned Arithmetic ABS(x) Absolute value of x MAX(x1,x2) Larger of the values x1 and x2 MIN(x1,x2) Smaller of the values x1 and x2 MOD(x,y) Value of x modulo y SIGN(x) -1, 0, or 1 to indicate the sign of x String-Handling COPY(s,c) c copies of specified string s INDEX(s,c[,p]) Position of the character string c within the string s, starting at position p LENGTH(s) Number of characters or bits in the string s REVERSE(s) Reverse of the source character string or bit string SEARCH(s,c[,p]) Position of the first character in s, starting at position p, that is found in c SUBSTR(s,i[,j]) Part of string s beginning at i for j characters TRANSLATE(s,c[,d]) String s with substitutions defined in c and d TRIM(s[,e,f]) String s with all characters in e removed from the left and all characters in f removed from the right. If you omit either e or f or both, blanks are trimmed from that side. VERIFY(s,c[,p]) Position of the first character in s, starting at position p, which is not found in c Conversion BYTE(x) ASCII character represented by the integer x DECODE(c,r) Fixed binary value of the character string c converted to a base r number ENCODE(i,r) Character string representing the base r number that is equivalent to the fixed binary expression i RANK(c) Integer representation of the ASCII character c Timekeeping DATE() System date of compilation in the form YYMMDD DATETIME() System date and time of compilation in the form CCYYMMDDHHMMSSXX TIME() System time of day of compilation in the form HHMMSSXX Miscellaneous ERROR() Count of user-generated diagnostic error message INFORM() Count of user-generated diagnostic informational message LINE() Line number in source program that contains the end of a specified preprocessor statement VARIANT() String result representing the value of the -Tcommand optionvariantWARN() Count of user-generated diagnostic warning message