This article was originally published in IBM Systems Magazine. Here we share some “out-of-the-box” ideas for solving “unsolvable” problems in RPG.
In this article, we want to introduce some RPG programming techniques and hopefully make you think about ways to combine them to solve “unsolvable” problems. One such case is how to provide user-controlled, spreadsheet-style calculations within an RPG program.
We’ve addressed this problem several times in the past, but on this occasion the trigger was a request during a customer training class for a simpler approach to pricing calculations. The customer’s existing solution was inflexible and required significant effort to introduce new pricing schemes. The end users (bless their little cotton socks) simply wanted to enter the names of the variables and the required calculation steps entered into a database. The program would then retrieve these details, apply them to a set of data and report the results. The calculation steps were very similar to how we used to perform math in RPG/400 (e.g., add two variables, store the result in a temporary field, then multiply the temporary by a third field and so on). Had it been an option to write the new program in PHP, for example, this requirement wouldn’t have presented much of a problem – such tasks are simpler to handle in an interpreted language. But, naturally, the required solution needed to be integrated into an RPG application!
While a comprehensive solution to problems like this isn’t really practical, RPG provides the basic tools to handle the requirement providing the problem space is constrained. We'll discuss what we mean by that later. The problem has two basic elements. First, we need to be able to select the actual calculation performed (add, subtract, etc.) based on the database entry. Second, we need to perform the selected calculation on fields whose names were also supplied in the database entry.
Let's look at the calculation aspect first. Suppose you wanted to accommodate the basic add, subtract, divide and multiply operations. You might be tempted to do something like this:
If operation = add;
result = var1 + var2;
ElseIf operation = subtract;
result = var1 - var2;
ElseIf ...
This works if there are only a small number of calculations required, but as we add additional options, the code will quickly get ugly. To make the process more flexible, we could have the calculations performed by subprocedures, and then use a procedure pointer to invoke the appropriate routine. If we then store the symbols that identify the calculations and the related pointers in matching arrays, we can use RPG's %LOOKUP to identify the index of the required routine. Here are the definitions of the two arrays:
D operations DS
D opAdd 1a Inz('+')
D opSubtract 1a Inz('-')
D opDivide 1a Inz('/')
D opMultiply 1a Inz('*')
D opList 1a Dim(4)
Overlay(operations)
D pProcedures Ds
D pAdd * ProcPtr
Inz(%PAddr('ADD'))
D pSub * ProcPtr
Inz(%PAddr('SUBTRACT'))
D pDiv * ProcPtr
Inz(%PAddr('DIVIDE'))
D pMult * ProcPtr
Inz(%PAddr('MULTIPLY'))
D pProcedure * Dim(4)
Overlay(pProcedures)
D ProcPtr
Using these arrays, the complete calculation is now significantly simpler and would look like this:
index = %LookUp( operation: opList );
currentOpPtr = pProcedure(index);
result = calc( var1 : var2 );
We’ll leave to your imagination what the subprocedures look like! (Or if your imagination fails you, you can study the full source code.) But before we move on, we’ll just show you the prototype for the calc routine and one of the subprocedure prototypes (of necessity all of the others have the same basic format). Notice that the calc prototype uses the EXTPROC keyword and there are no single quotation marks around the name – this indicates that currentOpPtr is a procedure pointer and will determine which procedure is called. Notice also that the prototype for the add subprocedure has exactly the same definition in terms of return value and parameters as does calc.
D calc Pr
ExtProc(currentOpPtr)
D Like(number)
D f1 Like(number)
D f2 Like(number)
D add Pr Like(number)
D f1 Like(number)
D f2 Like(number)
Adding new operations is a breeze. Simply code the new subprocedures, add the required codes to the operations data structure and the related subprocedure information to the pProcedures data structure - remembering to modify the DIM clauses of course! The main calculations won’t change at all.
So Far, So Good – What Now?
We now have the mechanics in place to add as many different operations as we want. But what about referencing the variables by name? We could use the multi-level IF-block approach, but it has the same disadvantages as it had for the operation codes themselves. It may not be immediately obvious, but we can adopt a similar approach for the variables to the one we used for the operation codes.
One approach we considered was to modify the subprocedures to accept pointers to the variables to be processed, but that didn't help with another problem we needed to address – namely, that when you pass a parameter, the parameter definitions in the caller and the callee must match. The variables we want to use in our calculations probably won’t be the same size and type. With a normal subprocedure, we could solve this with the CONST or VALUE keywords. These would cause the compiler to generate the necessary code to convert the caller's variable to the size and type required by the subprocedure. But by definition our variables will change – users can select which ones to use.
To accommodate this, we’ll need to copy the data from the original variable into one that matches the common definition shared by the calculation subprocedures. Once we accept that necessity, we can set up the names of the variables and their pointers in matching arrays, much as we did with the procedure pointers and the operation codes. Given that our example program uses the simple variable names A, B, C and D, the three sets of data look like this:
// These are the working copies of the variables in the common format
D work DS Qualified
D A Like(number)
D B Like(number)
D C Like(number)
D D Like(number)
// This array lists the names of all variables available to use
// They _must_ be in the same sequence as the pVariables array
D varNames DS
D 10a Inz('A')
D 10a Inz('B')
D 10a Inz('C')
D 10a Inz('D')
D varName 10a Dim(4)
Overlay(varNames)
D
// This array supplies the addresses of all the variables
D pVariables DS
D pA * Inz(%Addr(work.A))
D pB * Inz(%Addr(work.B))
D pC * Inz(%Addr(work.C))
D pD * Inz(%Addr(work.D))
D pVariable * Dim(4)
Overlay(pVariables)
Notice that to simplify changes in the future, we’ve defined a single variable named number, which is referenced via the LIKE keyword when defining the numeric variables to be used in the calculations. By defining fields this way, it becomes simple to change the program to process values larger than the 15,5 packed fields currently supported, should we need to do so later.
In addition to these three structures, we also need to define the variables that will be passed to the subprocedures. These are defined as BASED and their basing pointers (pV1 and pV2) will be set from the addresses in the pVariable array.
D v1 s Like(number)
Based(pV1)
D v2
s Like(number)
Based(pV2)
Interestingly, once the grunt work of defining the various data structures is out of the way, the processing to use them is trivial. We begin by performing the %LOOKUP operations needed to determine the indexes for the variables and the operation code. We then check that all of the input was valid, and, if so, continue to set the appropriate pointers. Once done, we simply perform the calculation. The code looks like this:
// Validate requested variable names and operation code
// Issue error message if any of them are unknown
// If this program was "for real" we'd issue better diagnostics
v1Index = %LookUp( var1: varname );
v2Index = %LookUp( var2: varname );
opIndex = %LookUp( operation: opList );
If ( v1Index = 0 ) Or ( v2Index = 0 ) Or ( opIndex = 0 );
Dsply ( 'Invalid request - please check and re-enter' );
Iter;
EndIf;
// Now set the pointers for the variables and the calculation
pV1 = pVariable(v1Index);
pV2 = pVariable(v2Index);
currentOpPtr = pProcedure(opIndex);
// And go ahead and perform the calculation
result = calc( v1 : v2 );
dsply ('Result is: ' + %Char(result));
It really is that simple. It’s defining the basic structures and the subprocedures that takes time. Once the basics are in place, adding additional variables and/or calculations to the mix is quick and easy.
Constraining the Problem Space
We mentioned constraining the problem space. Simply put, with problems of this nature, it’s easy to say, “RPG can't do that.” And in absolute terms, this is as true of RPG as it is of most compiled languages. However, we’re dealing here with a specific problem space. Namely, that the range of calculations required is finite as are the names and number of variables that might be involved in the calculations. In fact, the variables are already defined in the program since the files that contain them are already being used by the program. So providing we don't mind doing a bit of grunt work to set up the basic definitions, while we may not be able to provide a total solution, we can go a long way.
When you face problems like this, rather than taking a head-on approach, try instead to determine exactly what inhibits you from doing what you want. Understanding that, ask yourself what constraints would have to be in place for you to overcome this problem. If, as in this case, those constraints are both acceptable and practical, then you have a solution.
In our next EXTRA article, we'll discuss some ideas for taking these techniques to the next level and making the overall process even more flexible. In the meantime, if you use these techniques or have any questions, please let us know.
Note: We didn’t have space to introduce you to using basing pointers and procedure pointers, and it isn’t necessary for you to understand them to use these techniques. However, if you’re unfamiliar with the topics and want to know more, check out “Some Pointers on Using Pointers in RPG IV” and the section on "Using procedure pointer calls" in the RPG Redbooks publication “Who Knew You Could Do That with RPG IV?”.