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

edit

Procedures as Subroutines

edit

A PL/I program may contain user defined subroutines:

  • 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
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;

Procedures as Functions

edit

A PL/I programm may contain user defined functions:

  • 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
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;

Changes of Parameter Variables

edit

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

  • of simple type, e.g. char, number, ...
  • an array of simple type variables
  • a structure of simple type variables [1]

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;

Asterisk Notation

edit
  • 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.
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;

Locale Variables

edit

Scope

edit

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

edit

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

edit

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

edit

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

edit

A procedure may be invoked recursively if the RECURSIVE option is specified.

  • 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
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

Entry Variables and Parameters

edit

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

edit

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

edit

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

edit

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"

edit

If in the example above both b1 and b2 are true ...

  • abc will be released after the first call
  • abc will be loaded again before the second call

... which is not very effective.

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;

Notes

edit
  • [1] or an array of structures of simple type variables ...
  • [2] there are two more storage class (based and controlled for dynamically allocated storage) which will be explained in another wikibooks PL/I special.
  • [3] in PL/I default values may be altered using the DEFAULT statement.
  • [4] the FREE statement for a fetched procedure will also free the local static variables of this procedure.