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
editProcedures as Subroutines
editA 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
editA 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
editWhen 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
editScope
editThe 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
editThere 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
editProcedures 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
editUsing 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
editA 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
editLike 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
editA 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
editMaking a program executable requires two steps:
- Compile the program and all the used external procedures in any order.
- 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
editPL/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"
editIf 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.