Software Engineers Handbook/Language Dictionary/PLI/procedures

Note: This article requires basic knowledge of PL/I which can be found in Software Engineers Handbook/Language Dictionary/PLI.

Procedures as Subroutines
A PL/I program may contain user defined subroutines: example: proc options ( main ); call proc_1 ( 0 );  /* output: 'zero'                */ call proc_1 ( 7 );  /* output: 'the number is     7' */ proc_1: proc ( parm ); dcl  parm   bin fixed (15); if parm = 0 then do; put skip list ( 'zero' ); return; end; put skip list ( 'the number is' || parm ); end proc_1; end example;
 * The definition of a (internal) subroutine starts with
 * The name of the subroutine followed by ':'
 * The keyword PROCEDURE (abbreviation: PROC)
 * Optionally a parameter-descriptor list in brackets
 * The subroutine will be invoked by CALL statements
 * The subroutine may be left by optionally return statements

Procedures as Functions
A PL/I programm may contain user defined functions: example: proc options ( main ); dcl  number   bin fixed (31); put skip list ( hundred ( 0 ) ) ;  /* output:          0 */ put skip list ( hundred ( 3 ) ) ;  /* output:        300 */ number = hundred ( 5 ) + 55; put skip list ( number );          /* output:        555 */ hundred: proc ( parm ) returns ( bin fixed (31) ); dcl  parm   bin fixed (15); if parm = 0 then return ( 0 ); return ( parm * 100 ); end hundred; end example;
 * The definition of a (internal) function starts with
 * The name of the function followed by ':'
 * The keyword PROCEDURE (abbreviation: PROC)
 * Optionally a parameter-descriptor list in brackets
 * The keyword RETURNS followed by the type of the return value in brackets
 * The result of the function may be used in expressions
 * The subroutine must have at least one return statements which returns the result

Changes of Parameter Variables
When a procedure is invoked with variables as parameter this parameters may be passed in one of the following ways:
 * "By reference" (also known as "by name") which means changes inside the procedure alters the original variable.
 * "By value" (or "as dummy") which means changes inside the procedure alters only a copy of the original variable.

In PL/I a variable which is is passed by reference if and only if the variable matches exactly the argument definition inside the procedure. example: proc options ( main ); dcl  bifi_15   bin fixed (15); dcl  bifi_31   bin fixed (31); bifi_15 = 15; call change ( bifi_15 ); put skip list ( bifi_15 );  /* output: 99 */ bifi_31 = 31; call change ( bifi_31 ); put skip list ( bifi_31 );  /* output: 31 */ change: proc ( parm_15 ); dcl  parm_15   bin fixed (15); parm_15 = 99; end change; end example;
 * of simple type, e.g. char, number, ...
 * an array of simple type variables
 * a structure of simple type variables [1]

Asterisk Notation
example: proc options ( main ); dcl  a_ch (2)   char (10); dcl  b_ch (5)   char (20); dcl  v_ch (5)   char (20) varying; a_ch (1) = 'BEFORE' call change ( a_ch ); put skip list ( a_ch (1) );  /* output: 'AFTER     ' */ b_ch (1) = 'BEFORE' call change ( b_ch ); put skip list ( b_ch (1) );  /* output: 'AFTER               ' */ v_ch (1) = 'BEFORE' call change ( v_ch ); put skip list ( v_ch (1) );  /* output: 'BEFORE' because v_ch-strings are varying */ change: proc ( ch_array ); dcl  ch_array (*)   char (*); ch_array (1) = 'AFTER'; end change; end example;
 * If the size attribute of the parameter definition is an asterisk every matching variable with any size is passed by reference.
 * If the dimension of the parameter definition is an asterisk every matching variable with any dimension is passed by reference.

Scope
The scope of PL/I variables is confined to procedures in which they are declared. outer: proc options ( main ); dcl  a   char (07)   init ( 'outer A' ); dcl  b   char (07)   init ( 'outer B' ); call inner; put skip list ( a ); put skip list ( b ); inner: proc; dcl  b   char (07)   init ( 'inner B' ); dcl  c   char (07)   init ( 'inner C' ); put skip list ( b ); put skip list ( c ); end inner; end outer; output: 'inner B'   'inner C'    'outer A'    'outer B'

outer: proc options ( main ); call hello;   /* output: 'hello from outer' */ call inner;   /* output: 'hello from inner' */ hello: proc; put skip list ( 'hello from outer' ); end hello; inner: proc; call hello; hello: proc; put skip list ( 'hello from inner' ); end hello; end inner; end outer;

Storage Class
There are two storage class locale variables may have.[2]

Automatic storage:
 * Its value is lost between successive invocations of the procedure
 * If the variable has the INIT attribute initialisation is done every time the procedure is invoiced
 * The keyword AUTOMATIC may be abbreviated as AUTO
 * Locale variables are automatic by default[3]

Static storage:
 * Its value is saved between successive invocations of the procedure
 * If the variable has the INIT attribute initialisation is done only once when program starts

example: proc options ( main ); call static_memory ( 7 ); call static_memory ( 0 );  /* output: 7 */ call  auto_memory ( 7 ); call  auto_memory ( 0 );   /* output: 1 */ static_memory: proc ( parm ); dcl  parm     bin fixed (15); dcl  memory   bin fixed (15)   init ( 1 )   STATIC; if parm = 0 then put skip list ( memory ); else memory = parm; end static_memory; auto_memory: proc ( parm ); dcl  parm     bin fixed (15); dcl  memory   bin fixed (15)   init ( 1 ); if parm = 0 then put skip list ( memory ); else memory = parm; end auto_memory; end example;

Multiple Entries
Procedures may have more than one entry.

example: proc options ( main ); dcl  number   bin fixed (15); call value_set ( 3 ); call value_get ( number ); put skip list ( number );   /* output: 3 */ call value_calc ( 2, 1 ); call value_write;           /* output: 7 */ value_set: PROC ( parm_1 ); dcl  parm_1   bin fixed (15); dcl  memory   bin fixed (15)   static; memory = parm_1; return; value_get: ENTRY ( parm_1 ); /* do NOT declare parm_1 and memory once again ! */   parm_1 = memory; return; value_calc: ENTRY ( parm_1, parm_2 ); dcl  parm_2   bin fixed (15); memory = memory * parm_1 + parm_2; return; value_write: ENTRY; put skip list ( memory ); end value_set; end example;

Multiple Parm Lists
Using the attribute GENERIC procedures may have more than one parm list. Syntax: DCL  procname   GENERIC ( procname_1   WHEN ( parm_descriptor_1 ),                               procname_2   WHEN ( parm_descriptor_2 )   ); Meaning at compile time: WHENEVER  a call of procname is inside the source IF     the calling parameters are like parm_descriptor_1 THEN replace procname with procname_1 ELSE IF the calling parameters are like parm_descriptor_2 THEN replace procname with procname_2 ELSE   throw compile error

Parm descriptor may contain the attributes of the calling parameters: example: proc options ( main ); dcl  twice   generic ( twice_C   when ( char  ),                         twice_F   when ( fixed )   ); put skip list ( twice ( 'hello' ) );  /* output: 'hello hello' */ put skip list ( twice ( 1234321 ) );  /* output: 2468642       */ twice_C: proc ( parm ) returns ( char (11) ); dcl  parm   char (05); return ( parm || ' ' || parm ); end twice_C; twice_F: proc ( parm ) returns ( bin fixed (31) ); dcl  parm   bin fixed (31); return ( parm + parm ); end twice_F; end example;

Parm descriptor may contain the number of the calling parameters: dcl  calc   generic ( calc_with_3_parameters   when ( *, * , * ) ,                        calc_with_2_parameters   when      ,                        calc_with_1_parameter    when ( * )           ); calc_with_3_parameters: proc ( a, b , c ); ... calc_with_2_parameters: proc ( a, b ); ... calc_with_1_parameter: proc ( a  ); ...

Parm descriptor may contain the number of dimensions of the calling parameters: dcl  calc   generic ( calc_value    when ( ( * )         ),                        calc_vector   when ( ( * , * )     ) ,                        calc_matrix   when ( ( * , * , * ) )   ); calc_value: proc ( v ); dcl  v         char (12); ... calc_vector: proc ( v ): dcl  v (5)     bit (1); ... calc_matrix: proc ( v ): dcl  v (5,8)   bin fixed (15); ...

Recursive Procedures
A procedure may be invoked recursively if the RECURSIVE option is specified. example: proc options ( main ); dcl  number   bin fixed (15); call test ( 1 ); test: proc ( level ) RECURSIVE; dcl  level   pic   '9'; dcl  loc_a   pic  '99'; dcl  loc_s   pic '999'   static; dcl  pre     char (09); loc_a = level * 11; loc_s = level * 111; pre = substr ( '        ', 1 , 3 * level ); put skip list ( pre || 'Level ' || level || ' started' ); put skip list ( pre || 'loc_a = ' || loc_a ); put skip list ( pre || 'loc_s = ' || loc_s ); if level < 3 call test ( level + 1 ); put skip list ( pre || 'loc_a = ' || loc_a ); put skip list ( pre || 'loc_s = ' || loc_s ); put skip list ( pre || 'Level ' || level || ' ended' ); end test; end example; Output: Level 1 started loc_a = 11 loc_s = 111 Level 2 started loc_a = 22 loc_s = 222 Level 3 started loc_a = 33 loc_s = 333 loc_a = 33 loc_s = 333 Level 3 ended loc_a = 22 loc_s = 333 Level 2 ended loc_a = 11 loc_s = 333 Level 1 ended
 * Every invocation of the procedure has it's own value of the calling parameters
 * Every invocation of the procedure has it's own value of local variables which are automatic
 * All invocations of the procedure have a common value of local variables which are static

Entry Variables and Parameters
Like other data procedures may be used as variables and parameters.

example: proc options ( main ); dcl  e_var   ENTRY; e_var = proc_1; call e_var;      /* output: 'proc 1' */ e_var = proc_2; call e_var;      /* output: 'proc 2' */ proc_1: proc; put skip list ( 'proc 1' ); end proc_1; proc_2: proc; put skip list ( 'proc 2' ); end proc_2; end example;

example: proc options ( main ); call list ( 'square', square_proc ); call list ( 'cube' ,   cube_proc ); list: proc ( title, calc ); dcl  title   char (*); dcl  calc    entry ( bin fixed (15) ) returns ( bin fixed (15) ); dcl  i       bin fixed (15); put skip list ( title || '-table:' ); do i = 1 to 5; put skip list ( i || ':' || calc ( i ) ); end; end list; square_proc: proc ( v ) returns ( bin fixed (15) ); dcl  v   bin fixed (15); return ( v * v ); end square_proc; cube_proc: proc ( v ) returns ( bin fixed (15) ); dcl  v   bin fixed (15); return ( v * square_proc ( v ) ); end cube_proc; end example; Output: square-table: 1:     1         2:      4         3:      9         4:     16         5:     25    cube-table: 1:     1         2:      8         3:     27         4:     64         5:    125

External Procedures
A procedure may be external, i.e. the source may be contained in a separate file. This approach offers the possibility to use this procedure in more than one main program. Every program or procedure which uses an external procedure must declare this procedure.

Source File of external procedure EXT1: ext1: proc; put skip list ( 'Hello from EXT-1' ); end ext1; Source File of external procedure EXT2: ext2: proc ( message ); dcl  message   char (*); dcl  ext1      EXTERNAL ENTRY;   /* declaration of used external procedure */ put skip list ( 'EXT-2:' ); put skip list ( message ); call ext1; end EXT2; Source File of MAIN: example: proc options ( main ); dcl  ext2      EXT ENTRY ( CHAR (*) );   /* keyword EXTERNAL may be abbreviated */ call ext2 ( 'Called by Example' ); end example; Output running example EXT-2: Called by Example Hello from EXT-1

Static Linking
Making a program executable requires two steps:
 * 1) Compile the program and all the used external procedures in any order.
 * 2) Use the linkage editor to combine the compile objects to an executable program.

The executable program now contains the binary code of its procedures at linking time, i.e. if an external procedure is changed and recompiled after linking time this would not change the behaviour of the executable program.

Fetched Procedures
PL/I allows dynamic linking of external procedures, i.e. a main program may load the last compiled version of a procedure at run time.

The statement FETCH ABC loads the binary of procedure ABC from auxiliary storage into main storage (unless ABC already exists in main storage).

The statement RELEASE ABC frees main storage occupied by ABC[4].

example: proc options ( main ); dcl  abc         EXT ENTRY; dcl  dfg         EXT ENTRY; dcl ( b1, b2 )  bit (1); .....   if b1 then do; fetch  abc; call   abc; release abc; end; .....   if b2 then do; fetch  abc, dfg; call   abc; call   dfg; release abc, dfg; end; end example;

"Pseudo-Fetching"
If in the example above both b1 and b2 are true ... ... which is not very effective.
 * abc will be released after the first call
 * abc will be loaded again before the second call

PL/I has the following properties:
 * A procedure will be recognised as to_be_loaded_dynamicly iff the program somewhere contains a fetch or release statement, it is not necessary that this statement will be reached at any time.
 * If a procedure recognised as to_be_loaded_dynamicly is invoked at runtime it will be fetched automatically if necessary.

Using this two properties the program above may be rewritten as: example: proc options ( main ); dcl  abc         EXT ENTRY; dcl  dfg         EXT ENTRY; dcl ( b1, b2 )  bit (1); if 1 > 2 then         /* this will never be true */ fetch  abc, dfg;   /* but abc and dfg will be MARKED AS TO_BE_LOADED_DYNAMICLY */ .....   if b1 then do; /* COMMENT: IF ABC IS NOT YET IN MAIN STORAGE THEN LOAD IT AUTOMATICALLY */ call abc; end; .....   if b2 then do; /* COMMENT: IF ABC IS NOT YET IN MAIN STORAGE THEN LOAD IT AUTOMATICALLY */ call abc; /* COMMENT: IF DFG IS NOT YET IN MAIN STORAGE THEN LOAD IT AUTOMATICALLY */ call dfg; end; end example;