1 PLIMAC
  PLIMAC is the PL/I preprocessor for the VAX.  
  To invoke PLIMAC you need to tell DCL that it is a foreign command.
  You should set up the following:

   $ ASSIGN (this directory) WES$PLIMAC
   $ PLIM*AC:==$WES$PLIMAC:PLIMAC.EXE

  The command syntax is:

  PLIMAC input_file_spec [options]

  The input file spec can be just a filename -- the default file type is 
  '.PLM'
  The options are preceded by a slash, and are parsed by WESLIB function
  TESTWORD, so you may abbreviate according to TESTWORD conventions.

2 INTRO  
  The PL/I preprocessor for the VAX is useful for setting 
  up 'shorthand' symbols for frequently used code and for extending the 
  standard PL/I language with new verbs.  Complex code can thus be 
  generated by inexperienced programmers.  In fact a whole language can 
  be created (or prototyped) using the PL/I compiler as a 'second pass'
  compiler, in much the same way that ancient IBM Fortran compilers 
  generated not machine code, but assembler code.  PLIMAC is a 
  generalized macro processor that can be used not only on PL/I code, 
  but on code for similar languages such as SAS, TPL, Fortran, etc.

  PLIMAC is invoked as a foreign command.  PLIMAC was 
  designed to be as compatible as possible with its IBM couterpart; 
  however there are some differences and enhancements.    A good 
  introduction to the preprocessor can be found in Barnes PL/I text.

2 Command Options
  /NOINSOURCE
  /OUT=...
  /LINESIZE=nnn
  /LIBRARY=...
  /DEBUG
  /MACHINE
  /FORTRAN
 
2 /NOINSOURCE
  Do not list the input source file.  /INSOURCE (do list input) is the 
  default.  PLIMAC does not list the output file -- it expects the PL/I 
  compiler to do this

2 /OUT
  /OUT=output_file_spec
  The default output filename is the same as the input filename.
  The default output filetype is '.PLI'.

2 /LINESIZE 
  /LINESIZE=nnn
  The maximum line length before PLIMAC forces a wrap on the output 
  record.  The default is 254 (which is also the system maximum).
  Following VAX PL/I conventions, PLIMAC will insert a carriage return
  in a string of blanks (deleteing succeeding blanks) or in string 
  constants.  Note that output text consists of variable length records, 
  unlike IBM.  In particular this means that Barnes's NEATEN function will 
  not work.  Use the builtin function NEWLINE.

2 /LIBRARY
  /LIBRARY=libfilespec
  or /LIBRARY=(libspec1,...)

  Additional text libraries for INCLUDE statements, as for the PL/I 
  compiler.
  
  Example:
     /LIB=(MYLIB.TLB,YOURLIB.TLB)

2 /DEBUG
  This does not invoke the VAX debugger -- rather it provides debugging 
  information for the PLIMAC system programmer.  If you think something 
  is goofy with PLIMAC, use this option and give the output to the 
  PLIMAC programmer.  In general it provides a trace of the symbol 
  scanner, dumps of the code generated for procedures, and dumps of the 
  symbol tables.

2 /MACHINE
  List the pseudo-machine code generated for procedures.  /DEBUG implies
  /MACHINE.
  
2 /FORTRAN
  This qualifier means that the output text should try to follow 
  FORTRAN text formatting conventions:
     o The linesize is set to 72. (No sequence numbers, please.)
     o The default output filetype is set to 'FOR' instead of 'PLI'.
     o When there is a forced wrap, the output text is preceded by 5 
       blanks and an asterisk, indicating a FORTRAN continuation line.
       (No check is made to ensure that there are no more than 19 such 
       continuation lines.)
  See Example4 for a FORTRAN example.

2 STATEMENTS

  Statements outside procedures are preceded by a percent sign ('%') and 
  are terminated by a semicolon.  They are interpreted on the fly (which 
  is not very efficient.)  Conversions from character to numeric and 
  vice-versa are done freely.  Attempts to convert invalid character 
  strings to numeric yield a zero.  Conditional expressions have a numeric 
  value of 1 (true) or 0 (false).  Thus statements like "A = B = C|D;" are
  perfectly legal.
  
  Statements within procedures are compiled into a kind of bastard 
  Polish and saved.  When invoked they are executed on the VAX hardware 
  stack (which is much more efficient).  The leading % sign is optional
  execpt on the PROC statement itself.

  The follwing statements can be used outside procedures:
    DO              END           assignment      REPLACE
    IF THEN ELSE    ON ENDTEXT    INCLUDE         DECLARE
    ACTIVATE        DEACTIVATE    NOTE            PAGE
    PRINT           NOPRINT       DEBUGON         DEBUGOFF
    TRACEON         TRACEOFF
  There is no %GOTO statement.  Use %IF THEN ELSE  or  %DO WHILE.

  The following statements can be used within procedures:
    PROCEDURE       RETURN        DO              END
    assignment      DECLARE       NOTE            GOTO
    IF THEN ELSE    INCLUDE       PRINT           NOPRINT
    DEBUGON         DEBUGOFF      TRACEON         TRACEOFF
    TRAP

  Procedure definitions cannot be nested.  (The PROC statement cannot 
  be used inside the body of another procedure.)  Of course, procedure 
  calls can be nested.

2 Assignment
  The assignmet statement assigns a value to a preprocessor variable.
  The variable should first be DECLAREd.  If not, it will be given
  the default attribute CHAR.  This statement may be used both inside 
  and outside of procedures.  If an undeclared variable is used
  within a procedure, it will be defaulted globally, as if a %DCL ...
  CHAR; statement had appeared outside of the procedure.

  The general form is:

    [label:] variable = expression;

  where 'expression' is a valid combination of variable, procedure
  or builtin invocations, constants, arithmetic operators (+,-,*,/),
  string operators (||), logical operators(|,&,^), relational 
  operators (=,^=,<,<=,>,>=), and parentheses.  Contrast the
  assignment statement with the much simpler %REPLACE statement

  Example:

  %DCL (MAXLINES,MAXCOLS,BUFSIZE) FIXED;
  %MAXLINES = 26;
  %MAXCOLS = 80;
  %BUFSIZE = MAXLINES*(MAXCOLS + 1);
  DCL LINEBUF CHAR(MAXCOLS);
  DCL SCREENBUF CHAR(BUFSIZE);

2 DO
  The DO ... END statement provides for iteration, or for inserting
  several statements where the syntax allows only one (as in IF THEN).
  The DO statement may be used both inside and outside of procedures.

  The general form is:

  [label:] DO [varible = expr1 TO expr2 [BY expr3]] [WHILE expr4];
  ...
  END;

  Example:

  %DCL (NELT,I) FIXED, X CHAR;
  %X = 'ARRAY A ';
  %DO I = 1 TO NELT;
    %X = X||' ELT'||LTRIM(I);
  %END;
  %X = X||';';
  X

2 IF
  The IF statement provides for altering the flow of control.  It may
  be used both inside and outside of procedures.
  The general form is:

    IF expression THEN statement1; [ELSE statement2;]

  When used outside of procedures, the keywords IF, THEN, and ELSE
  should all be preceded by a % sign.
  'expression' will be converted to FIXED if it is CHAR.  The
  expression is false if it equals zero, and true otherwise.

  Example:
  
  %DCL (DEBUG_FLAG, CODE) CHAR;
  % IF DEBUG_FLAG = 'Y' THEN CODE = 'PUT LIST (A,B,C);';
  %ELSE CODE = '';
  CODE

2 REPLACE
  
  This is included for compatibility with the VAX PL/I compiler.
  The general form is:
    
  REPLACE identifier BY replacement_string;

  where 'replacement_string' is either an identifier or a constant.
  In the case of string constants, the surrounding quotes will not be 
  stripped.  The REPLACE statement cannot be used inside procedures;
  use the DECLARE and assignment statements.  Contrast REPLACE with
  "assignment", whee the replacement string can be any expression.

  Example:

  %REPLACE TRUE BY '1'B;

2 INCLUDE

  As in the VAX PL/I compiler, read in text from an external file or
  library and insert it at the current point in the original input
  text.  Thus outside procedures the text is scanned for replacement,
  or, if it contains '%' signs, it is executed.  Inside procedures the
  text is compiled.  INCLUDE statements in existing Subset G PL/I will
  work correctly.

  The general form is:

     INCLUDE 'filespec';
  or INCLUDE library_member;
  
  Example:

    DCL DATA_BASE FILE RECORD INPUT;
    %INCLUDE DATA_LAYOUT;
  
  INCLUDE statements are also typically used to load commonly used
  preprocessor funtions from a library.  See the PRINT and NOPRINT
  statements for a way to control the listing of INCLUDEd text.

2 ON
  See ENDTEXT

2 ENDTEXT

  The ENDTEXT condition is the only  valid condition that can be used
  with the ON statement.  It provides a branch to a cleanup routine
  when the preprocessor senses the end of the input text, thus
  eliminating the need for a "this statement must be the last statement"
  rule.  The ON ENDTEXT statement cannot be used inside a procedure.
  
  The general form is:
  
  [label:] ON ENDTEXT procedure_name;

  where 'procedure_name has been previously DECLAREd with the ENTRY
  attribute.  No arguments are passed, although the called routine may
  be defined with arguments.
  
  Example:
  
  %DCL (HEADER, CLEANUP) ENTRY, HDR_COUNT FIXED, RTRIM BUILTIN;
  % HDR_COUNT = 0;
  %HEADER: PROC (X) RETURNS (CHAR);
    DCL X CHAR;
    HDR_COUNT = HDR_COUNT + 1;
    ...
  %END HEADER;
  %CLEANUP: PROC RETURNS (CHAR);
    RETURN('    DCL HDR_ARRAY(' || RTRIM(HDR_COUNT) ||
           ') CHAR(132), HEAD_COUNT FIXED BIN(31) INIT (' ||
           RTRIM(HDR_COUNT) || '); END;');
  %END CLEANUP;
  %ON ENDTEXT CLEANUP;
  ...

2 DECLARE
  
  This statement assigns type information, etc., to preprocessor
  variables.  It may be abbreviated DCL.  The DECLARE statement should
  be executed before any reference to the variable, otherwise it will 
  be assumed to be of type CHARACTER.  When a variable is DECLAREd,
  it is ACTIVATEd;

  The general form is:

  [label:] DECLARE identifier attribute [,...];

  where 'attribute' is one of FIXED
                              CHARACTER or CHAR
                              ENTRY
                              BUILTIN.
  Attribute factoring is allowed:
    DCL (identifier [,...]) attribute [,...];

  Example:
  
  % DCL (A,B,C) CHAR, (D,E) FIXED, LENGTH BUILTIN;
  
  2 ACTIVATE
  
  Activate the replacement of a preprocessor variable by its value.
  This statement is used in conjunction with the DEACTIVATE statement.
  Activate cannot be used inside a procedure.  It may be abbreviated
  ACT.
  
  The general form is:

  [label:] ACTIVATE variable [RESCAN | NORESCAN] [,...];

  RESCAN is the default.  If the NORESCAN option is used then the
  preprocessor does not scan the replaced text for further replacement.

  Example:  To use  % signs in source text other than PL/I

  %DCL PCT_SIGN CHAR;
  %PCT_SIGN = '%';
  %ACTIVATE PCT_SIGN NORESCAN;
  
2 DEACTIVATE
  
  Deactivate the replacement of a preprocessor variable by its value; 
  i.e., the preprocessor treats the variable justlike any other trash
  in the input stream and outputs it without further ado.  This
  statement cannot be used inside a procedure.  It may be abbreviated
  DEACT.
  
  The general form is:
  
  [label:] DEACTIVATE varible [,...];
  
  Example:  The ACTIVATE and DECATIVATE statements are useful in
  maintaining a temporary distinction between preprocessor variables
  and ordinary text, as in the case of subscripts.
  
  %DCL I FIXED;
  %DO I = 1 TO 10;
      ...
  %END;
  %DEACTIVATE I;
  
  The identifier 'I' may be used elsewhere in the program with no side
  effects (i.e., the value '11' will not be substituted).
  
2 NOTE
  
  NOTE provides a way of issuing user-defined error messages.  Unlike
  IBM, the error severity code is not passed on to the operating system.
  INstead, the code is treated as an ordinary error code.  To 
  distinguish these errors from PLIMAC-generated errors, 200 is added
  to the code when it is output.  The Note statement is also useful in
  debugging preprocessor code, since its output is deirected to the 
  logical file SYSPRINT rather than to the output text file.
  
  The general form is:

  [label:] NOTE (message [,code]);
  
  where 'message' is a CHARACTER expression and 'code' is a FIXED
  expression.
  
  Example:
  
  %DCL P ENTRY;
  %P: PROC (X) RETURNS (CHAR) STATEMENT;
    DCL X CHAR;
    IF ^PARMSET(X) THEN NOTE ('Null argument');
    DO WHILE (LENGTH(X) > 0);
      I = INDEX(X,'?');
      NOTE ('DEBUG X:'||X,I);
      ...
    END;
  %END P;
  
2 PAGE

  Inserts a formfeed in the output text.  This statement cannot be
  used inside a procedure.
  
  THe general form is:
  
  %PAGE;
  
2 NOPRINT
  
  Turn off the lis-input-source flag.  Unlike IBM, this only affects
  future INCLUDE statements.  For compatibility, This statement should
  be the only statement on the line.

  The general form is:
  
  %NOPRINT;

  Example:

  %NOPRINT;
  %INCLUDE FILEDEFS;
  %PRINT;
  
2 PRINT
  
  Turn on the list-input-source flag.
  The general form is:
  
  %PRINT;
  
  Example: See NOPRINT
  
2 DEBUGON
  
  Turn on the DEBUG flag.  (See the /DEBUG comand options and the 
  DEBUGOFF statement.)  In large programs the DEBUG option can produce
  vast quantities of output; this statement can be used to target 
  suspect code.
  
  The general form is:
  
  DEBUGON;
  
  Example:  Suppose one of many processes appears to be blowing up:
  
  %DCL (A,B,C,D,E,...) ENTRY;
  ...
  %DEBUGON;
  %E: PROC;
    TRACEON;
    ...
    TRACEOFF;
    RETURN(...);
  %END E;
  %DEBUGOFF;
  
  The debug option provides information about the compilation and
  interpretation of preprocessor code.  The TRACE option provides 
  information about the execution of procedures.  It prints each
  pseudo-polish instruction before it is executed and dumps the
  contents of the execution stack.
  
2 DEBUGOFF
  
  Turn off the DEBUG flag.
  The general form is:

  DEBUGOFF;
  
  Example:  See DEBUGON.
  
2 TRACEON
  
  Turn on the TRACE flag.
  The general form is:
  
  TRACEON;
  
  Example:  See DEBUGON.
  
2 TRACEOFF
  
  Turn off the TRACE flag.
  The general form is:
  
  TRACEOFF;
  
  Example:  See DEBUGON.
  
2 TRAP
  
  This statement is for use by systems programmers.  It can be used 
  only inside procedures.  It provides an address to break on 
  (PLM_EVALEXPR\trapop) for the VAX debugger.
  
  The general form is:
  
  TRAP;
  
2 PROCEDURE
  
  This statement introduces the definition of a preprocessor function.
  It may be abbreviated PROC.  Procedures using the STATEMENT option
  cannot be invoked from within other procedures.  Unlike IBM, the
  RETURNS clause is optional;  if it is omitted the default is
  RETURNS(CHAR).
  
  The general form is:
  
  label: PROCEDURE [(param [,...])] [STATEMENT] [RETURNS(CHAR|FIXED)];

  'label' should have been previously DECLAREd ENTRY.  It is the name
  by which the procedure is invoked.
  'param' should be DECLAREd within the procedure, before it is 
  referred to in any other statement, using either of the attributes
  FIXED or CHAR.  If the STATEMENT option is used, arguments may be
  passed either by position or by keyword, and the scan for arguments
  is terminated by a semicolon.  Thus new PL/I-like statements are born.
  
  Example:
  
  %DCL MYPROC ENTRY;
  %MYPROC: PROC(X) RETURNS (CHAR);
    DCL X CHAR;
    RETURN (X||X);
  %END MYPROC;

  For a more substantive example (including the STATEMENT option)
  see the HELP entry for 'Example3'.
  
2 RETURN
  
  This statement (used only inside procedures) defines the value of
  the function.

  The general form is:
  
  RETURN (expression);
  
  where 'expression' is of the type specified in the RETURNS clause of
  the PROC statement (or can be converted to it).
  
  Example:
  
  RETURN (LINE1 || LINE2 || LINE3);
  
  See also PROCEDURE and Example3.

2 Builtins

  All builtins must be declared as such.  The following are available:
    LENGTH
    SUBSTR  -- with either 2 or 3 args
    INDEX  -- second arg can be a string, so it really is INDEXC
    NEWLINE -- also called CARRET, returns a carriage return
    COUNTER
    COMPILETIME -- returns 23 chars instead of IBM's 18
    LTRIM
    RTRIM
    LZTRIM -- trim left zeros (eg from COUNTER)
  
  Currently all builtins have their own Polish instruction codes.
  
  The language element PARMSET has the syntax of a builtin function, but
  it is not.  Hence it should not be declared.  (It is a generic builtin
  that operates on the attributes of its argument, not the value.)

2 Differences
  
  The character '@' is part of the symbol constituent set.  It can be 
  included in the spelling of an identifier, unlike VAX PL/I.  Thus it
  should be used to flag preprocessor variables, instead of the '$'.

  Since expressions and procedures are first 'compiled' and then executed,
  execution time error messages (conversion error, uninitialized variable)
  give only an approximate location of the error.

  Variable length text records (both input and output) are the standard, 
  rather than fixed 80-character card images.  Use the NEWLINE builtin
  to neaten the output text.

  DO WHILE and ON ENDTEXT are new.  
  
  GOTO cannot be used outside procedures.

  Multiple closure of DO groups is not allowed; however an attempt to 
  close a procedure early will generate an error message.

  The '%' is not required on the END statement for a procedure.

  The INCLUDE statement may be used within procedures.

  In general, '%'s and thus preprocessor statements may be returned by
  procedures.  However, statements that depend directly on their location
  in the input text may work strangely.  Iterative DO groups will almost
  certainly not work, even if the loop is only executed once.  Text loaded
  by an INCLUDE statement will be scanned after all the returned text is 
  scanned, rather than direcly after the INCLUDE statement is scanned.
  BE CAREFUL!

  For now, all builtins must be declared.

  ASCII formfeeds in the source text will generate a page eject on both
  insource listing and in the output text.  PAGE statements generate
  page ejects only in the output text.

  Procedures must be defined (& thus compiled) before they are invoked.

  Statements following an ELSE statement cannot be labeled.  
  (Maybe so later.)

2 Examples

  1 Declaring common structures (DCL ... LIKE ...)
  2 Generating test data
  3 Defining new statements: set membership
  4 Example3, but in FORTRAN
 
2 Example1

  IBM PL/I implements the LIKE clause in a DECLARE statement; Vax PL/I 
  does not.  You can use the preprocessor to get around this.  Suppose
  you want to declare two files with the same record layout, but with
  different 01 level names, so you can't use %INCLUDE:

  %DCL NEWLINE BUILTIN;
  %DCL LAYOUT CHAR;
  %LAYOUT = NEWLINE ||
  '        02  ID FIXED BIN(31),' || NEWLINE ||
  '        02  NAME CHAR(20),' || NEWLINE ||
  '        02  OTHER_JUNK CHAR(40) INIT('' '')';

  ...

  DCL  01 INREC, LAYOUT;
  DCL  01 OUTREC, LAYOUT;


  You could be really fancy by setting up a function that would insert
  suitable prefixes:

  %DCL NEWLINE BUILTIN;
  %DCL LAYOUT ENTRY;
  %LAYOUT: PROC (PFX) RETURNS(CHAR);
    DCL PFX CHAR;
    DCL XX CHAR;
    XX = NEWLINE ||
    '        02  ' || PFX  || '_ID FIXED BIN(31),' || NEWLINE ||
    '        02  ' || PFX  || '_NAME CHAR(20),' || NEWLINE ||
    '        02  ' || PFX  || '_OTHER_JUNK CHAR(40) INIT('' '')';
    RETURN(XX);
  %END LAYOUT;
  ...
  DCL 01 INREC, LAYOUT(IN);
  DCL 01 OUTREC, LAYOUT(OUT);
  
  This would result in the following output text:
  ...
  DCL 01 INREC, 
        02  IN_ID FIXED BIN(31),
        02  IN_NAME CHAR(20),
        02  IN_OTHER_JUNK CHAR(40) INIT(' ');
  DCL 01 OUTREC, 
        02  OUT_ID FIXED BIN(31),
        02  OUT_NAME CHAR(20),
        02  OUT_OTHER_JUNK CHAR(40) INIT(' ');

  A third possibility is to put the whole layout in an include file, but
  activate the 01 level name for replacement:
  Include file 'RECLAYOUT.TXT':
        DCL 01 LAYOUT,
               02 ID FIXED BIN(31),
               02 NAME CHAR(20),
               02 OTHER_JUNK CHAR(40) INIT(' ');

  Source code:
  %DCL LAYOUT CHAR;
  %LAYOUT = 'INFILE';
  %INCLUDE 'RECLAYOUT.TXT';
  %LAYOUT = 'OUTFILE';
  %INCLUDE 'RECLAYOUT.TXT';

2 Example2

  The following is a short segment of a frame program to be used for
  testing.  We would like at least 20 copies of it, but we want to generate
  sequential labels ( the "L FRM n" statement) and change the position in 
  the row (the second number in the pair in the "F 1,1" statement, and in
  the "%%1,1" clause).  Builtins are not callable directly from non-
  preprocessor text, so we write the little procs NUM & LAB to invoke
  COUNTER and LTRIM.  Note that we use a little trick to cope with the
  frame program syntax element "%%", which PLIMAC would otherwise cough 
  on as an unrecognizable statement.
  
    L FRM 2
    C
    D 10/10/ENTER DATA 3#9
    F 1,1
    X
    L FRM 3
    C
    D 10/10/DISPLAY LAST FRAME DATA (%%1,1)
    * CONTINUE

  The input text to accomplish this is as follows:
    %DCL IJ FIXED;
    %DCL (NUM,LAB) ENTRY;
    %DCL (LTRIM,LZTRIM,COUNTER) BUILTIN;
    %LAB: PROC(Z) RETURNS(CHAR);
        DCL Z CHAR;
        RETURN(Z||LZTRIM(COUNTER));
    %END LAB;
    %DCL PP1 CHAR; %PP1 = '%%1'; %ACTIVATE PP1 NORESCAN;
    %NUM: PROC(I) RETURNS(CHAR); DCL I FIXED; RETURN(LTRIM(I)); END;
    %IJ = COUNTER /*START COUNTER AT 2*/;
    % DO IJ = 1 TO 20;
    L LAB(FRM )
    C
    D 10/10/ENTER DATA 3#9
    F 1,NUM(IJ)
    X
    L LAB(FRM )
    C
    D 10/10/DISPLAY LAST FRAME DATA (PP1,NUM(IJ))
    * CONTINUE
    %END;

2 Example3
  
  We define 2 procedures: one, DEFINESET, is used to generate a 
  preprocessor variable containing the set members, deleimet by
  commas.  The second, INSET, generates code to test whether an 
  ordinary variable is in the set (equals any of its members).
  Note that unlike IBM, DEFINESET returns preprocessor code.
  The source text is:
  
  %DCL (DEFINESET, INSET) ENTRY;
  %DCL (LENGTH,SUBSTR, INDEX) BUILTIN;
  %DEFINESET: PROC(NAME,ELEMENTS) STATEMENT RETURNS(CHAR);
    DCL(NAME,ELEMENTS) CHAR;
    RETURN('  %DCL '||NAME||' CHAR;  %'||NAME||' = '''|| ELEMENTS ||
           ','';');
  %END DEFINESET;
  %INSET: PROC (Y,X) RETURNS(CHAR);
    DCL(Y,X) CHAR;
    DCL VAL CHAR;
    DCL (I,J) FIXED;
    VAL = '';
    I = 1;
    DO WHILE(I < LENGTH(X));
      IF I ^= 1 THEN VAL = VAL || '|';
      J = INDEX(SUBSTR(X,I),',');
      VAL = VAL || ' ' || Y || '=' || SUBSTR(X,I,J-1);
      I = I + J;
    END;
    RETURN ('('||VAL||')');
  %END INSET;

  DEFINESET (VALID_PROGS) ELEMENTS(2,10,14,16,30);
  IF ^ INSET(PROG,VALID_PROGS) THEN STOP;
  
  The output text is:

  IF ^ ( PROG=2| PROG=10| PROG=14| PROG=16| PROG=30) THEN STOP;

2 Example4

  This is the same as example3, but in FORTRAN.  For '|' we substitute
  '.OR.' and for '=' we substitute '.EQ.'.  You must use the /FORTRAN
  command qualifier, as otherwise the text will go beyond col 72 and
  wouldn't be recognized by the compiler.
  
  %DCL (DEFINESET, INSET) ENTRY;
  %DCL (LENGTH, NEWLINE, SUBSTR, INDEX) BUILTIN;
  %DEFINESET: PROC(NAME,ELEMENTS) STATEMENT RETURNS(CHAR);
    DCL(NAME,ELEMENTS) CHAR;
    RETURN('  %DCL '||NAME||' CHAR;  %'||NAME||' = '''|| ELEMENTS ||
           ','';');
  %END DEFINESET;
  %INSET: PROC (Y,X) RETURNS(CHAR);
    DCL(Y,X) CHAR;
    DCL VAL CHAR;
    DCL (I,J,K) FIXED;
    VAL = '';
    I = 1;
    DO WHILE(I < LENGTH(X));
      IF I ^= 1 THEN VAL = VAL || ' .OR. ';
      J = INDEX(SUBSTR(X,I),',');
      VAL = VAL || Y || '.EQ.' || SUBSTR(X,I,J-1);
      I = I + J;
    END;
    RETURN ('('||VAL||')');
  %END INSET;
        INTEGER*4 PROG
        DEFINESET (VALID_PROGS) ELEMENTS(2,10,14,16,30);
        IF  INSET(PROG,VALID_PROGS) THEN 
              WRITE(6,100) PROG
              END IF !A FORTRAN 77 COMMENT
  C     A REGULAR FORTRAN COMMENT
  100   FORMAT(' INVALID PROGRAM ACTIVITY: ',I5)
  200   FORMAT(' TESTING SCANNER:',2F10.4)
      END
  
  The output text is:
  
        INTEGER*4 PROG
        IF  (PROG.EQ.2 .OR. PROG.EQ.10 .OR. PROG.EQ.14 .OR. PROG.EQ.16 .OR
       *. PROG.EQ.30) THEN
              WRITE(6,100) PROG
              END IF !A FORTRAN 77 COMMENT
  C     A REGULAR FORTRAN COMMENT
  100   FORMAT(' INVALID PROGRAM ACTIVITY: ',I5)
  200   FORMAT(' TESTING SCANNER:',2F10.4)
        END
