Last Updated on 13 June 2021 by Roberto De Pedrini
Can you make a recursive call in RPG? No, impossible !
This is often the first answer you get to this question … That’s true, but only partially. Actually, in RPG it is not possible to recursively call a program. But an ILE program can have procedures, and here is another matter …
In fact, thanks to their specificity and their isolation level, procedures can be called recursively: after all, a procedure is a function that receives parameters, and returns a return value (or none). This feature means that a call to a procedure can safely take place even within the procedure itself. Everything is about properly writing the code, taking into account recursion.
Here we see a typical situation in which recursion allows to have a much simpler code: a bill of materials’ development.
For this example we created a master file with the item type and code, the component type and code, and the required quantity. The item type identifies the type of product (end, semi-assembled, etc …). Each item can obviously have more components, so in the file there will be one or more records for each “parent” item, and the same happens for its components (which in turn are “parents” of other items).
Here is the SQL statement for creating the table.
CREATE OR REPLACE TABLE BOMDT00F (
ITEMTYPE CHAR(1) NOT NULL DEFAULT ' ' ,
ITEMCODE CHAR(20) NOT NULL DEFAULT ' ' ,
COMPTYPE CHAR(1) NOT NULL DEFAULT ' ' ,
COMPCODE CHAR(20) NOT NULL DEFAULT ' ' ,
QUANTITY NUMERIC(9, 2) NOT NULL DEFAULT 0 )
RCDFMT BOMDT ;
ITEMTYPE and ITEMCODE columns identify the “parent” article, while COMPTYPE and COMPCODE are the component.
An index has been created over this table:
CREATE INDEX BomDT01L ON BomDT00F (
itemType, itemCode
)
RCDFMT BOMDT ;
These are the sample data for our test.
The sample program reads the BOM and writes the results to a work file. Starting from an initial item and type, received as input parameter, the program searches for its components, and for each component it calculates the requirement, writing the output to the work file. The process is then recursively repeated for each sub-component, down one level until reaching the raw materials (items that have no components). When using recursive procedures it is important to always set an exit condition, otherwise you could get caught into an infinite loop.
Below is the definition of the work file:
CREATE OR REPLACE TABLE BOMWRK (
LEVEL NUMERIC(4, 0),
ITEMTYPE CHAR(1) NOT NULL DEFAULT ' ' ,
ITEMCODE CHAR(20) NOT NULL DEFAULT ' ' ,
COMPTYPE CHAR(1) NOT NULL DEFAULT ' ' ,
COMPCODE CHAR(20) NOT NULL DEFAULT ' ' ,
QUANTITY NUMERIC(9, 2) NOT NULL DEFAULT 0 )
RCDFMT WBOM ;
Here the full source of sample program (FAQ40014B):
ctl-opt decedit('0,') datedit(*dmy.)
actgrp(*caller) dftactgrp(*no) option(*nodebugio:*srcstmt);
//-------------------------------------------------------------*
// Bill Of Material development
//
// Reads the BOM from base item and develops all components
// and related required quantities, down to raw material.
// Output is written to a work file
//
// INPUT PARMS:
// - item type CHAR(1)
// - item code CHAR(20)
//-------------------------------------------------------------*
// BOM master file
dcl-f BomDT01l disk usage(*input) keyed;
// work file
dcl-f BomWRK disk usage(*output) prefix(W_);
// ENTRY -------------------------------------------------
dcl-pi FAQ40014B;
dcl-parm pType char(1);
dcl-parm pCode char(20);
end-pi;
// Prototypes ------------------------------------------------
dcl-pr getBOM;
*n char(1);
*n char(20);
*n zoned(9:2);
*n zoned(4:0);
end-pr;
// Variables -----------------------------------------------
dcl-ds dsBOM extname('BOMDT00F') end-ds;
dcl-s qty0 like(Quantity);
dcl-s level0 zoned(4:0);
// PSDS ------------------------------------------------------
dcl-ds pgmDS psds qualified;
user char(10) pos(254);
end-ds;
//*********************************************************
// SQL options
Exec SQL
SET OPTION COMMIT = *None,
ALWCPYDTA = *Yes,
CLOSQLCSR = *EndMod,
DLYPRP = *Yes
;
// Clear work file
Exec SQL
DELETE FROM BomWRK ;
// Get initial item
chain (pType:pCode) BomDT01l;
if %found(BomDT01l);
qty0 = 1;
level0 = 1;
getBOM(pType:pCode:qty0:level0);
endif;
close *all;
*inLR = *on;
return;
// --------------------------------------------------
// BOM development
// --------------------------------------------------
dcl-proc getBOM;
// Procedure interface -------------------
dcl-pi getBOM ;
dcl-parm pType char(1); // item type
dcl-parm pItem char(20); // item code
dcl-parm pQty zoned(9:2); // required quantity
dcl-parm pLevel zoned(4:0); // depth level
end-pi;
// BOM master file
dcl-f BomDT01l disk usage(*input) keyed;
// Variables -------------------------------------------------
dcl-s qtyComp like(Quantity);
dcl-s level zoned(4:0);
dcl-ds dsBOM2 extname('BOMDT01L':*all) end-ds;
// read item's components and develop BOM
setll (pType:pItem) BomDT01l;
level = pLevel + 1;
dou %eof(BomDT01l);
reade (pType:pItem) BomDT01l dsBOM2;
if not %eof(BomDT01l) and CompCode <> *blank;
eval(h) qtyComp = pQty * quantity;
exsr wrtFile;
// recursive call
getBOM(CompType:
CompCode:
qtyComp:
level);
endif;
enddo;
// --------------------------------------------------
// write item component to work file
// --------------------------------------------------
begsr wrtFile;
Clear WBOM;
W_Level = pLevel;
W_itemType = pType;
W_itemCode = pItem;
W_compType = compType;
W_compCode = compCode;
W_quantity = qtyComp;
Write WBOM;
endsr;
// --------------------------------------------------
end-proc;
Let’s examine in detail the getBOM procedure: the input parameters are type and code of the “parent” item and the related required quantity. The procedure reads all the parent item’s components, calculates the requirement, writes a record to the work file and recursively calls the same getBOM procedure passing as parameters the type and code of the component, along with the calculated required quantity, which then propagates to lower level. Notice how the BOMDT01L file was declared locally in the procedure; in this way, with each recursive call (which actually creates a new instance of the procedure in the call stack), record positioning on the file is not lost within read loops by key.
After program run, the work file will include the full development of the bill of materials, down to raw materials with the related requirements. Launching the program for type 4 MADRID article, here is the result:
The LEVEL column shows the development depth: 1 is the end product, 2 the first semi-assembled product, etc. .. in this case it stops at level 3, whose components are the raw materials. From the content of this file it is therefore clear that in order to produce a unit of end product (article 4-MADRID), three raw materials are needed, WOOL-002, POLY-001, SILK-002 with a requirement of respectively 144, 104 and 14.29 “units” (to stay generic, no specific units of measurement are indicated).
The use of recursive procedures considerably eases some typical application situations, in addition to the bill of materials just think about the development of a menu tree, or the definition of routes in the logistics industry, with departures and arrivals.
In the next part we will see how SQL is not far behind and also allows you to manage recursion.
All sources of this example are available on GitHub at this link: https://github.com/MD2706GIT/FAQ400
DOCUMENTATION
IBM: Recursive Calls – https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_74/rzasc/recursv.htm