Write Your Own SCANRPL Built-In Function
November 28, 2012 Bob Cozzi
Note: The code accompanying this article is available for download here. Until IBM i v7.1 has made a pervasive appearance on virtually all IBM systems, the cool new v7.1 updates to RPG IV are out of reach for many RPG developers. Fortunately, with IBM i v7.1, IBM has finally stopped “point release” updates to the RPG compiler, giving us the ability to use anything implemented on (for example) technology refresh 5 all the way back to TR1. Life is good for RPG developers today. If you’re a software developer or, like me, consulting with clients that have everything from v5r4 through the latest and greatest release of IBM i, you can only envy those who have nothing more than v7.1 to burden their development cycle. One of those cool v7.1 enhancements is the %SCANRPL (Scan and Replace) built-in function. For years we’ve had the %SCAN and %REPLACE built-in functions, but the %REPLACE really never caught on. Sure a few people used it, but it really didn’t make sense to those writing end-user business applications or doing program maintenance. Using two built-in functions to accomplish what seems like one task? Fahgettaboudit. To that end, back some years ago, I wrote a FINDREPLACE (Find and Replace) subprocedure in RPG IV that basically did what IBM introduced in v7.1, and more. But while the FINDREPLACE subprocedure is cool, it included too many find/replace options that I mimicked from Microsoft Word Find/Replace dialogue box, things like “Match Case” and “Whole Word Only.” With RPG IV at v7.1, we now have a design for a subprocedure that could work on v5r4 and later and then easily be ported to %SCANRPL, when everyone is on v7.1 or later. The RPG IV %SCANRPL built-in function has five parameters: %SCANRPL( scan-pattern : replacement-text : scanned-variable [: start-position [ : length ]] );
To construct an operating system level, agnostic version of SCANRPL, we need to carefully define the parameter list of a subprocedure; a subprocedure named SCANRPL (no leading percent sign). To do this, a quick look at the %SCANRPL parameters indicates that the first three are character parameters and the final two are integers. It should be relatively simple to define a prototype with these basic characteristics: D scanRPL PR 65533A Varying D extProc('COZZI_scanAndReplace') D pattern 256A Const Varying D replacement 256A Const Varying D searchData 65533A Const Varying D searchStart 10I 0 Const OPTIONS(*NOPASS) D searchLen 10I 0 Const OPTIONS(*NOPASS) Since the IBM version of %SCANRPL does not modify any of the input parameters, I’ve included the CONST keyword for all of the parameters of the subprocedure. CONST makes it easier to pass values to subprocedures by doing some minimal conversion for you. In addition, since the scan-pattern and replacement-text is typically not that long, I’ve shortened these parameters to 256 characters and made them VARYING. Increase their lengths if you need to, but I’ve found this size to be good enough. The benefit of VARYING and CONST is that I can use the %LEN RPG built-in function to extract the length of the data passed on these parameters. This greatly simplifies things. The third parameter, searchData is the text that is searched for the pattern parameter. It, too, is CONST VARYING, but has a maximum length of 64k-ish. On v7.1, this limit is removed, and parameters may be declared with lengths of up to 16 MB, so this is one difference with SCANRPL (the subprocedure) versus %SCANRPL (the built-in function). The starting position and length (parameters 4 and 5) are both defined as 4-byte integers. RPG uses the interesting “10i 0” nomenclature to define 4-byte integers, and “5i 0” to define 2-byte integers. Again here, the CONST keyword allows the caller of this subprocedure to pass in an expression or a numeric literal or a variable. If that value isn’t identical to the parameter definition, CONST converts the value into a 4-byte integer that the subprocedure understands. Last, I’ve included the OPTIONS(*NOPASS) keyword. This tells the compiler that the caller does not need to specify either of these parameters. Only parameters that do not include OPTIONS(*NOPASS) are required. The last part of the SCANRPL subprocedure is the returned value. This is defined on the first line of code in a prototype of a subprocedure. It defines the data being sent back to the caller on a conditional statement, such as IF, or on an assignment statement, such as EVAL. In the context of our SCANRPL subprocedure, the returned value needs to have properties identical to that of the third parameter (searchData in our prototype). Without seeing the body or implementation of this subprocedure, this is how it would look in your code versus the native RPG IV %SCANRPL built-in function: Native RPG %SCANRPL Built-in Function: myName = %scanRPL('Bob' : 'Robert' : myName ); Custom SCANRPL Subprocedure: myName = scanRPL('Bob' : 'Robert' : myName ); The only difference is the percent sign for the built-in function versus no percent sign for the subprocedure. We could even take it on step further: /IF DEFINED(*V7R1M0) myName = %scanRPL('Bob' : 'Robert' : myName ); /ELSE myName = scanRPL('Bob' : 'Robert' : myName ); /ENDIF In this example, if the compiler is targeting v7.1 it uses the native RPG %SCANRPL built-in function, otherwise it uses our custom subprocedure. This way when the code is finally compiled for v7.1 it automatically uses the %SCANRPL built-in function. Pretty cool, huh? What’s that? Oh, you want to see the body/implementation of the SCANRPL subprocedure? Okay, here’s the full source code, but note: The /INCLUDE at the top of the source is simply including the prototype for the SCANRPL subprocedure (which is listed earlier in this article), that way you can include this in a service program and use it in all your apps without duplicating it all over the place. /include qcpysrc,scanRPL ********************************************************* ** ScanRPL - mimic the v7r1 %SCANRPL built-in function ** myName = %scanRPL('Bob' : 'Robert' : myName ); ** myName = scanRPL('Bob' : 'Robert' : myName ); ********************************************************* D buffer DS Qualified Inz D data 65533A VARYING ********************************************************* ** ScanRPL - mimic the v7r1 %SCANRPL built-in function ********************************************************* P scanRpl B Export D scanRpl PI 65533A Varying D pattern 256A Const Varying D replacement 256A Const Varying D searchedData 65533A Const Varying D searchStart 10I 0 Const OPTIONS(*NOPASS) D searchLen 10I 0 Const OPTIONS(*NOPASS) D nPos S 10I 0 D findLen S 10I 0 D rplen S 10I 0 D start S 10I 0 D findStart S 10I 0 Inz(1) /free findLen = %Len(searchedData); // Length of data to be searched if (%Parms()>= 4 and %addr(searchStart) <> *NULL); if (searchStart > 0); findStart = searchStart; endif; endif; if (%Parms()>= 5 and %addr(searchLen) <> *NULL); if (searchLen > 0); // Overriding search length? findLen = searchLen; endif; endif; if (findLen = 0 or findLen > %len(searchedData)); findLen = %len(searchedData); endif; buffer.data = searchedData; rplen = %len(replacement); nPos = %scan( pattern : buffer.data : findStart); dow (nPos > 0 and nPos <= %len(buffer.data)); buffer.data = %Replace(replacement:buffer.Data:nPos: %len(pattern)); if ((nPos + rpLen) < %Len(buffer.data)); nPos = %scan( pattern : buffer.data : nPos + rpLen); else; nPos = 0; endif; enddo; return buffer.Data; /end-free P scanRpl E I enjoy writing subprocedures that help me write applications faster. I have noticed that many RPG developers seem to prefer to embrace their legacy habits more than learning new techniques. For example, one developer told me, “I can use SEU and copy/paste my code faster than writing a subprocedure.” Another said, “I can’t see the /COPYs in my code, so I prefer using copy/paste in SEU.” Yikes! But more and more I see a new attitude among RPG developers, one that embraces new technologies, albeit at a bit slower adoption rate than I would prefer. Bob Cozzi is developer of COZTOOLS, the popular collection of CL and RPG functions. You can reach him at www.cozTools.com or via email at bob@coztools.com.
|