So I wrote this code (old RPG II stuff):
C INP,X COMP 'a' 58
C 58 MOVE 'A' INP,X
Then the requirement changed: convert all the lowercase letters to uppercase. This presented a problem. How could I justify inserting 26 COMP and MOVE instructions to do simple conversion? So I redesigned the solution using fewer instructions, but this time, it worked with all 26 lowercase characters.
To convert any single character to uppercase, you simply need to set on bit 1 of the 8 bits in the EBCDIC character. For example, I rewrote the previous solution in one line of code, as follows:
Of course there was a looping routine that caused the BITON opcode to be run for each character of the field needing to be converted. Since capital letters already had their "bit 1" set on, using a BITON operation on them left them intact, while converting lowercase letters to uppercase.
To this day, this is still the fastest way to convert lowercase to uppercase. Unfortunately, it isn't very practical to use this kind of technique today since typically we work with fields of data rather than bits and bytes (in RPG anyway). Another minor factor is that it is not a portable technique. That is, it will not work on an ASCII-based RPG compiler, such as CrossWorks Inc.'s Open-RPG product. But I'm guessing that's not an issue for most iSeries RPG programmers.
I knew there had to be a more-refined method for converting lowercase to uppercase. Well, there is. In RPG III and RPG IV, there is an XLATE operation code. This operation code uses a simple translation "table" technique to translate each character in the field specified in Factor 2 . Factor 1 contains the translation characters as a pair of values. For example:
Factor 1 contains one or more "find" characters followed by a corresponding number of "replace" characters, with a colon separating the lists. Once a character in the find string is located in the field being translated (Factor 2), the corresponding character in the replace string is moved into the Result field. If no translation match is detected, the original character is moved into the Result field.
The replacement is done on a position-by-position basis. So the ordering of the data in Factor 1 is irrelevant. At least one find character and one replace character must be specified. Note the following example:
After the XLATE operation is performed, the NEWVALUE field contains the translated value 'BoB Cozzi'. Not necessarily useful, but you get the idea.
Likewise, if I wanted to create a cipher table, I might do this:
I would end up with '2o2 3ozzi' in the NEWVALUE field. Again, not so useful unless I subsequently do the following:
Which turns it back into 'bob cozzi'.
Okay, so how do you get the entire alphabet into Factor 1? It is, after all, only 14 positions wide. Easy--you use named constants. You can specify fields, literal values, or named constants as the from and to translation patterns in Factor 1. In order to fit the entire alphabet in Factor 1, you simply write a couple of named constants, as follows:
D upper C Const('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
Then, you use the LOWER and UPPER named constants in Factor 1 of the XLATE operation code to convert lowercase to uppercase, or, by switching them around, uppercase to lowercase.
Convert lowercase to uppercase:
Convert uppercase to lowercase:
To convert lowercase to uppercase, you only need to create two named constants (I keep them in a /COPY member that is included whenever they're needed). Then, use the XLATE operation code to do the conversion on an entire field. No more one-character-at-a-time process like we did back in RPG II.
Factor 2 and the Result field of the XLATE operation code can be different field names or the same field name.
XLATE can also be used for much more. It can be used to translate special characters from fields, create a cipher code, or filter out unwanted characters. For example, to change all occurrences of a decimal point in a field to blanks, use the following XLATE operation:
So what's next? What if you need to do conversion for a comparison operation? Suppose you need to compare an end-user's input data to the value of a field in your program. You could use something like the following :
0002 C lower:upper XLATE CompName UpperComp
0003 C lower:upper XLATE CM_COMPANY UP_COMPANY
0004 C if UpperComp = UP_COMPANY
0005 C ...
0006 C endif
In this example, the COMPNAME and CM_COMPANY fields are converted to uppercase (lines 2 and 3). Then, on line 4, they are compared for equality.
This code is simple--easy to understand and maintain. But there's a problem with it. First, you need to declare two additional work fields--UPPERCOMP and UP_COMPANY--that have the same attributes as the original fields. With the LIKE keyword on the Definition specification, this is relatively easy; however, it will need to be done for each field you convert in your program. Also, the XLATE function is replicated each time the process is performed. From a performance standpoint, this might be good, but from a code-reuse and maintainability standpoint, it is questionable.
So what's the solution? Why not write your own procedure to convert lowercase to uppercase? By creating a simple procedure, you can integrate the case conversion routine into your toolbox and reuse it whenever you need to--no named constants to declare, no work fields, no redundant coding. But what would such a procedure look like? Look at the TOUPPER procedure in Figure 1 below:
0001 P ToUpper B EXPORT
0002 D ToUpper PI 2048A VARYING OPDESC
0003 D InputStg 2048A VARYING VALUE
0004 D lower C 'abcdefghijklmnopqrstuvwxyz'
0005 D upper C 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
0006 C If %Parms=0
0007 C return ''
0008 C endif
0009 C If %len(InputStg) > 0
0010 C lower:UPPER xLate InputStg InputStg
0011 C endIf
0012 C Return InputStg
0013 P ToUpper E
Figure 1: TOUPPER--a procedure to convert lowercase to uppercase
This procedure utilizes a lot of the components necessary when writing a good procedure. It tests for error conditions before they occur and avoids the errors. It also supports variable-length fields.
Though technically a function, the TOUPPER procedure accepts a single input value name, INPUTSTG (input string). This parameter is declared with both the VARYING and VALUE keywords (see line 3). You may have used VARYING before, but VALUE is often not utilized in RPG code. The VALUE keyword changes the default parameter behavior. Normally, parameters are passed by reference. That is, a pointer to (or the address of) the parameter is passed between programs and procedures. By specifying the VALUE keyword, you change that dynamic.
"Pass by value," as it is called, causes the compiler to generate code that creates a copy of the parameter. So rather than pass the address of the parameter, a copy of the parameter's data is sent down into the procedure. This means that changes made to the value in the called procedure do not impact the original parameter value. Why? Because a copy of the data is what the procedure is working on.
When the VALUE keyword is specified, you end up with much the same benefits you do when CONST is specified. There are performance differences, of course, and CONST parameters cannot be modified by the called procedure. Other than that, they are effectively the same.
So by specifying VALUE instead of CONST, you can work with the input parameter, modify its data, and not worry about the caller's original value being changed.
In effect, pass by value gives you the ability to use the parameter as a local variable.
In the TOUPPER procedure, on lines 6, 7, and 8, the number of parameters passed to the procedure are tested. If no parameters are passed, an empty string is returned to the caller. The only time this could happen is when the procedure is called with the CALLB operation code without a corresponding PARM operation.
Line 9 checks the input value to ensure that it is at least one character in length. If not, no translation is necessary and the program skips over the XLATE operation. Line 10 uses the translation capabilities of the XLATE operation code to convert the input value to uppercase. Remember, since the input value is passed by VALUE, you can use the INPUTSTG variable as if it were a local variable.
Finally, on line 12, the converted input string is returned to the caller. Note that the return value also uses the VARYING keyword (line 2). Since VARYING fields can be easily moved between fixed-length fields (i.e., regular fields in RPG) and variable-length fields, it does not matter whether the input or output is fixed- or variable-length fields.
Figure 2 illustrates a simple example of using this procedure in a program:
0002 D ToUpper PR 2048A VARYING OPDESC
0003 D InputStg 2048A VARYING VALUE
0004 D myChar S 15A Inz('Bob Cozzi')
0005 D myVar S 40A Inz('Bob Cozzi')
0006 D nCount S 10I 0
0007 C if ToUpper(myChar) = ToUpper(myVar)
0008 C Add 1 nCount
0009 C endif
0010 C MOVE *ON *INLR
Figure 2: Example use of TOUPPER
In Figure 2, the TOUPPER procedure is used twice on line 7 to return the uppercase equivalent of both the MYCHAR field and the MYVAR field. The IF statement compares the two returned values for equality. If equal, the ADD operation on line 8 is performed and the counter field is incremented.
Lines 2 and 3 contain the prototype for the TOUPPER procedure. It must be /COPY'd into each source member that uses the TOUPPER procedure. I would suggest you place TOUPPER into an RPG TOOLS service program and bind it into the programs that need it.
To bind a service program to an RPG program, often the service program name is added to a binding directory and then the binding directory is specified on the BNDDIR parameter of either the CRTBNDRPG or the CRTPGM command. If the service program is not included in a binding directory (it really should be), only programs that use the CRTPGM command can bind to them using the BNDSRVPGM parameter.