Internal Logic: ROUTINE and PERFORM
Best Practice when using Parameters
Modular
Logic: Writing Subprograms
Even though
Jazz programs are much simpler than COBOL and other
low level languages, there is still a need for conditions, loops, and other
forms of program logic. Jazz provides IF, CASE, and
FOR statements to allow you to control
your program’s logic. ROUTINE
and PERFORM statements allow you write routines
within your program that can be used from several places, and may improve the
clarity of your code. CALL
and INVOKE combine with interface descriptions
to allow you to structure your code, reuse logic, and interact with pre-written
and remote services.
This
chapter provides a quick overview of the ways in which Jazz
logic is controlled. It applies to both batch and on-line programming.
As in other
languages IF statements provide the basic
mechanism for conditional logic. Like IF
statements in most languages you write
IF
condition
Action if true
[ELSEIF
Action if true]…
[ELSE
Action if false]
END IF
A
difference from both COBOL and PL/I is the ELSEIF clause. This provides one of several ways of writing
CASE logic, where you test several conditions in order, executing the first
true one. You can achieve the same
results with
IF
condition
Action if true
ELSE
IF condition2
Action if condition2 is true
ELSE
Action if both false
END IF
END IF
but in situations
where there is a simple “choose first true condition” with several alternatives
using several ELSEIF clauses is neater (code doesn’t get further and further to
the right) and clearer (it’s obvious that all of the conditions are related as
alternatives).
You can
write THEN after
the conditions – this is optional. Jazz
regards IF condition [THEN] as a
statement, and will insert a semicolon if you omit it, so that this syntax will
seem a little odd to users with a PL/I or Algol background: -
IF
W.N1 = 5 THEN;
W.N1 = 4;
ELSEIF
W.N1 = 6 THEN;
W.N1 = 7;
END
IF;
W.N1 = 5 in the IF statement above is a condition. In this case it looks like an assignment, but
in general a simple condition has the form: -
Field-Reference
In this
example the field reference is W.N1.
Conditions may not use generic references: it would have been invalid to
write W.*.
The
comparison operator is one of = (equal to), <> (not
equal to), > (greater
than), >= (greater
than or equal to), < (less than), or <= (less than or equal to). Thus the condition
W.N1
<= 5
is true for
any negative value of W.N1, and for values 0, 1, 2, 3, and 5.
The Value
may be a single constant (as in the examples so far), another field, or an
arithmetic expression. The expression
must be compatible with the field reference: if W.N1 is an INTEGER
field then you could not compare it with a string value. For example this would be invalid: -
IF
W.N1 = 'Name' THEN;
This is valid: -
IF
W.N1 = W.N2 + 3 THEN;
Simple conditions can be linked together into compound conditions with the Boolean operators & (AND) and | (OR). For example, for the condition
W.N1
= 5 & W.N2 = 6
to be true W.N1 must equal 5 AND W.N2 must equal 6. If either simple condition is false the compound condition is false
For those with a COBOL background: note that you do not use | (OR) in the same way as in COBOL. In COBOL you could have written
IF N1 = 5 OR 6
but in Jazz you write
IF W.N1 = 5 | W.N1 = 6
Jazz
disallows the “extended OR” syntax of COBOL because it is ambiguous.
When a
condition is a BOOLEAN
field you can leave out =
true. With
definition
IsCustomer
BOOLEAN,
the IF statement
IF
W.IsCustomer = true THEN;
is equivalent to
IF
W.IsCustomer THEN;
See JazzLRM_IF for more
information about IF statements, and for more information about conditions see JazzLRMConditions.
CASE statements
are ideal for multi-choice logic where you’d otherwise have to write a long
list of IF … ELSEIF ….
ELSEIF statements. For example,
CASE
(EIBAID);
WHEN
(DFHENTER) THEN;
Custf.Region =
1;
WHEN
(DFHPA1 OR DFHPA2 OR DFHPA3 OR 'x');
Custf.region =
2;
ELSE;
Custf.region =
3;
END
CASE;
Mostly it
is a matter of convenience whether you write logic with IF … ELSEIF
or with CASE. For more details see JazzLRM_CASE
PROCESS
creates an I/O loop, but sometime you want your program to loop without I/O,
for example handling every line of a repeating section of a screen, or every
element of a table. The FOR statement, which combines elements
of COBOL’s PERFORM, PL/I’s DO, and VB’s FOR, is used for this.
Here we use
FOR to add up the values in Area.MonthlySales.
DEFINE Area DATA(
MonthlySales(12) MONEY(5,2),
SalesTotal MONEY(7,2));
FOR Work.Mth
= 1 TO 12;
Area.SalesTotal += Area.MonthlySales(Work.Mth);
END FOR;
There are
three forms of FOR: -
1.
FOR INDEX is
used as above (the keyword INDEX can be omitted) to step through a
loop varying an index variable. The
index variable, Work.Mth in the example above, must be a
number and usually has type SMALLINT.
The default step is 1, but STEP may specify other increments.
2.
FOR TABLE steps
through a table. TABLE is implied if the FOR
variable has one or more dimensions. Thus the loop above could be written
FOR
Area.monthlySales(work.mth);
Area.SalesTotal += Area.MonthlySales(Work.Mth);
END
FOR;
This is preferable because Jazz can infer the table
extent, avoiding error possibilities when the program is modified. Here’s another example: here we are setting
all elements of a 6-dimensional table to 555.
Also, by giving unqualified index names in the FOR
statement Jazz has automatically defined suitable index variables: -
DEFINE
D DATA(
Gr1 (2,4,8) GROUP,
Z (3,5,7) SMALLINT,
end GROUP);
* Table Form. Will generate nested loops
FOR
d.z(IX1, IX2, IX3, IX4, IX5, IX6);
D.Z(JZ.IX1, JZ.IX2, JZ.IX3, JZ.IX4, JZ.IX5, JZ.IX6) = 555;
END
FOR;
3.
FOR EACH steps
through all possible values of a coded field.
Thus with
DEFINE
MyTypes DATA(
Month CODES(January,February,March,April,May,June,July,August,September,October,November,December));
a loop to print January, February
etc is: -
FOR
EACH mytypes.month;
PRINT
(mytypes.month);
END
FOR;
As above
with INDEX and TABLE, the
keyword EACH may be
omitted. It is implied if the FOR variable
has the CODES
property.
For more
information see JazzLRM_FOR.
Most
programming languages provide a way of re-using logic, so that if you have to do
something from two or more places in your program you don’t have to write the
logic twice, instead you write it in some kind of routine and invoke this
routine from each of the places in your program where the logic is needed. Even when the logic is used only once you may
find that writing logic within a routine makes your program clearer, allowing
you to focus on either the higher level logic or the detail of the calculation.
A routine
may be part of your overall program, or it may be a separate unit, possibly
written in another language and running in a different environment. Jazz supports both kinds of routines.
Where you want some logic to be used more than once in your program, or if you simply want to tuck it away to make your program easier to understand, you can use ROUTINE and PERFORM: -
1. Some logic can be written between ROUTINE and END ROUTINE statements. For example: -
ROUTINE
CalculateDiscount;
R1.SalesTotal
= 0;
FOR R1.MonthlySales(IX);
R1.SalesTotal
+= R1.MonthlySales(JZ.IX);
END FOR;
IF R1.SalesTotal >
1000 THEN;
W.Discount =
true;
ELSE;
W.Discount =
false;
END IF;
END
ROUTINE CalculateDiscount;
2. Where this logic is wanted you write PERFORM: -
PERFORM
CalculateDiscount;
There may be many such PERFORM CalculateDiscount; statements in your program.
PERFORM statements can be written anywhere, but ROUTINE/END ROUTINE must not be within another ROUTINE or any other logic (IF, FOR, etc). They are typically written at the beginning or end of a program.
COBOL programmers will recognise this as the same as COBOL’s concept of performing a paragraph. As with a COBOL paragraph, or a PL/I internal procedure, the routine uses data defined earlier in the program. However a little like PL/I’s internal procedure and methods in VB, they can be passed parameters. To do this,
1. Somewhere in the program that precedes both the ROUTINE statement and the first PERFORM referring to it there will be a DEFINE statement defining the parameters. Here’s an example of a parameter definition: -
DEFINE
CalculateDiscount PARAMETERS ROUTINE DATA(
Parm1 LIKE types.month
INPUT,
Result BOOLEAN OUTPUT);
A PARAMETERS definition defines the formats of data passed to and from a routine. The data type is PARAMETERS ROUTINE, and the individual elements are defined like any other field or group except that they have an additional property, INPUT, OUTPUT, or INOUT. Here we see that Parm1 is INPUT to Calculate Discount, i.e. it is passed from the PERFORM to the routine. Result on the other hand is set by CalculateDiscount so it will have a value put into it on return from the PERFORM.
2. The PERFORM statements must name compatible fields in their argument list: -
PERFORM
CalculateDiscount(W.CurrentMonth, W.Discount);
The arguments do not have to be identical in format to the parameters, as long as assignments between the parameter and argument would be valid.
3. Within the routine logic can refer to these variables by their parameter names: -
ROUTINE
CalculateDiscount;
IF calculatediscount.parm1 =
december THEN;
CalculateDiscount.Result = true;
END IF;
R1.SalesTotal
= 0;
FOR R1.MonthlySales(IX);
R1.SalesTotal
+= R1.MonthlySales(JZ.IX);
END FOR;
IF R1.SalesTotal >
1000 THEN;
CalculateDiscount.Result = true;
ELSE;
CalculateDiscount.Result = false;
END IF;
END
ROUTINE CalculateDiscount;
In fact, the program can refer to these CalculateDiscount. names anywhere following the DEFINE: this is not a true implementation of scope such as you find in PL/I, VB, and other modern languages. At the PERFORM the argument value is assigned to any INPUT or INOUT parameter, and OUTPUT parameters are set to their default value (usually blank or zero). After the PERFORM values are assigned from INOUT and OUTPUT parameters to the corresponding arguments.
Since the DEFINE
must precede both PERFORM and ROUTINE
it is recommended that you code the DEFINE and ROUTINE
into a COPY
book which you can write towards the top of your program.
See JazzLRM_Paragraph
for more information about PERFORM and ROUTINE.
Like most
languages, including COBOL, you can invoke a pre-compiled subprogram, passing
it parameters, and receiving modified parameters back.
CALL
RTN3(Work.X, Work.Y, Work.Z);
The routine RTN3 is not part of the current program. It may be written as a Jazz SUBPROGRAM, but it might also be written in another language (Assembler? COBOL? PL/I?)
Unlike COBOL, Jazz doesn’t just accept any old CALL statement, leaving it to you to find out that you’ve made a mistake with the parameter formats by attempting to run your program. Instead, in Jazz you pre-define the parameter formats, with COPY Rtn3; expanding to something like: -
DEFINE
Rtn3 PARAMETERS BOTH
DATA(
X SMALLINT INPUT,
Y CHAR(20) INPUT,
Z DATE OUTPUT);
The use of predefined parameter lists makes the CALL statement much more convenient in Jazz than in COBOL. Firstly, it means that errors are detected errors early when they are easy and cheap to correct. For example, if you write
DEFINE W DATA (…);
CALL Rtn3 (W.X, W.Y, W.Z);
you’ll get errors for any of these arguments that are incompatible with the parameters defined for it. In this case, the first argument must be a number, the second a character string, and the third a date field.
Secondly, if the arguments are different, but compatible, then Jazz will create parameters of the correct format by inserting conversions on either side of the actual CALL. Thus if W.X does not have format SMALLINT, but it DOES have another numeric format, then before the COBOL-Level CALL Jazz inserts an assignment from W.X to RTN3.X, ensuring that the routine receives exactly the format that it expects.
Thirdly, with INPUT parameters you can use constants in the CALL statement: -
CALL Rtn3(123, 'Y Value’, W.Z);
You don’t need to create working-storage fields of the appropriate type and use MOVE to set these before the CALL.
Of course with parameter definitions written into COPY definitions they will be the same in all programs calling the routine.
A PARAMETERS definition is like any other except for a few extra features: -
1. The sub-type, BOTH in the example above, defines the environments in which the subprogram can be used. It will be one of BOTH , BATCH ,or CICS:-
a. Use BOTH when the subprogram is available in both batch and CICS (including web services) environments. These subprograms may not use any I/O statements (PROCESS, GET, PRINT, etc), nor any communication statements (SEND, INVOKE).
b. Use BATCH for subprograms that are only available for use in a batch environment. These subprograms can use I/O statements such as PROCESS, GET, PRINT, but may not use any CICS-specific statements such as SEND or INVOKE.
c. Use CICS for subprograms that can be used only in the CICS environment. These may use I/O statements (GET, PROCESS etc) as well as communication statements like SEND, and they have access to the CICS control blocks such as EIB and COMMAREA. With this option the first two arguments passed are the CICS control areas, so that the Jazz statement
CALL Rtn2(Rtn2.X);
becomes COBOL
CALL 'Rtn2' USING DFHEIBLK DFHCOMMAREA X OF
Rtn2 .
2. Parameters have property INPUT, OUTPUT or INOUT. INPUT parameters are passed to the subprogram, but are not changed by it. OUTPUT parameters are set by the subprogram, but no values are passed. Jazz will initialise the parameter to its default values (usually blanks and zeros). INOUT is passed to the subprogram, and a value (which may have been modified) is returned.
3. Parameters may include property OPTIONAL.
4. Parameters may have type UNSPECIFIED. This allows the parameter to have any type. UNSPECIFIED is usually used in situations like this, where Rtn has a record as it’s argument but there are several different record formats that it might be passed: -
DEFINE
Rtn PARAMETERS BATCH DATA(
R UNSPECIFIED);
DEFINE
R1 DATA(
x SMALLINT,
Y CHAR(100),
Z DECIMAL(3));
DEFINE R2 DATA(
X2
SMALLINT,
Y2 CHAR(6),
Y2a DECIMAL(7,2),
Z INTEGER);
CALL
Rtn(R1.*);
CALL
Rtn(R2.*);
Care is needed with UNSPECIFIED arguments, which tells Jazz not to do any checks for the parameter/argument compatibility. R1 is 104 bytes, while R2 is only 16. If Rtn executes a statement like
R1.Y = COBOL.SPACES;
from the second CALL statement which passes R2.*,
then positions 2 to 102 of R2 will be set to SPACES. But R2 is only 16 bytes long! Unpredictable errors will result at run time.
Note that UNSPECIFIED is not available with type PARAMETERS ROUTINE. This is because with a ROUTINE parameters are not passed by reference, but
instead Jazz generates assignments to/from a working-storage data area that is
used by the ROUTINE.
In a CICS environment a CALL statement always passes two CICS control blocks as its first two arguments: -
CALL CICSR (Parm1, Parm2);
becomes the COBOL statement
CALL ‘CICSR’ USING DFHEIBLK DHFCOMMAREA Parm1 Parm2.
Within the CICSR routine, the logic may start with
PROCEDURE
DIVISION USING DFHEIBLK DHFCOMMAREA Parm1 Parm2.
or with
PROCEDURE
DIVISION USING Parm1 Parm2.
It doesn’t seem to matter.
In a batch environment
CALL BATCHR (Parm1, Parm2);
becomes
CALL ‘BATCHR’ USING Parm1 Parm2.
and PROCEDURE DIVISION USING will not reference DFHEIBLK or DFHCOMMAREA.
You can write routines like Rtn2 in Jazz, either by using the New/Logic/New Subprogram dialog, or by starting with a blank screen. To create a routine named XXX the process is: -
1. Start with COPY XXX;
2. Create a statement within this COPY book starting DEFINE XXX PARAMETERS [BATCH | CICS | ANY] DATA(…
3. Add Data definition. Each level 1 name is an argument/parameter. For example
DEFINE JZTM01 PARAMETERS ANY DATA(
TimeIn
TIME INPUT,
DPIC CHAR(12) INPUT,
TimeOut
CHAR(12) OUTPUT,
TimeOutLth
SMALLINT OUTPUT);
This anticipates statements in which subroutine
JZTM01 will be invoked by statements like
CALL JZTM01 (Tm, '99#99#99.99', TPrint, TLth);
For more information, see https://www.jazzsoftware.co.nz/Docs/JazzLRM_Type.htm#_Toc89848146
4.
Write the logic of XXX as you would a normal
program, except that it starts with SUBPROGRAM
instead of PROGRAM. Thus its
first two lines will be
COPY XXX;
SUBPROGRAM XXX [CICS];
If the parameter definition includes CICS then the SUBPROGRAM statement must also include CICS.
If you write a CICS subprogram then it can only be used in a CICS environment, either classical CICS or web services.
If you write a non-CICS subprogram to be used in both environments: -
a. If you have separate batch and CICS libraries then you will need to compile and link it into both libraries.
b. You may not use any I/O statements, or batch-only statements (PRINT etc) or CICS-only statements (SEND, LINK, ACCEPT with INSCREEN).