V2R2M0'S built-in %BIN function provides power worth waiting for.
It took a dozen years, but it was worth the wait. IBM has improved on CL, giving it support for binary numbers with the addition of the %BIN built-in function in V2R2M0. This new function allows you to convert two- or four-byte character strings into numeric values or, conversely, turn a numeric value into a two- or four-byte character string. For instance, command processing programs typically need to extract the number of items in a list parameter as a binary value from the first two bytes of a character variable. %BIN greatly simplifies this sort of conversion.
When I learned about binary support in CL, the first thing that crossed my mind was that the Declare (DCL) command would finally accept TYPE(*BIN) to declare binary variables. Alas, that's not the case. Your CL programs still have to accept binary parameters into character variables: TYPE(*CHAR), with a length of two or four, depending on the precision of the binary number.
Binary parameters received into character strings can also be passed along to other programs with the CALL or Transfer Control (TFR-CTL) command. Before V2R2M0, however, the CL program itself could not read the numeric value of these parameters-unless you used utility commands such as Convert Binary to Decimal (CVTBINDEC) from QUSRTOOL. I have a feeling that these utility commands will soon become obsolete.
Here's an example of using %BIN. The Change Library List (CHGLIBL) command has a parameter called LIBL, which is a simple list where you can enter from 0 to 25 library names. When you run CHGLIBL, the command passes the list of names to the command processing program (CPP) as one continuous string, prefixed with a two-byte binary counter that indicates how many values the user keyed in. This means that the CPP must receive the LIBL parameter into a 252-byte character variable (that's the two-byte prefix plus 25 times 10).
If the CPP were to be written in CL (which it isn't) and you were in charge of coding it (which you aren't), the first thing you'd have to do is determine how many names the user entered. Since CL could not process binary numbers before V2R2M0, you have to use QUSRTOOL's CVTBINDEC command or some similar technique.
The new %BIN function makes it much easier. You'd simply code:
PGM PARM(&LIBL &CURLIB) DCL VAR(&COUNT) TYPE(*DEC) + LEN(2 0) DCL VAR(&LIBL) TYPE(*CHAR) + LEN(252) DCL VAR(&CURLIB) TYPE(*CHAR) + LEN(10) CHGVAR VAR(&COUNT) + VALUE(%BIN(&LIBL 1 2))
Notice the similarity between %BIN and %SST. %BIN(&LIBL 1 2) literally means: extract the first two bytes of &LIBL and convert them to a numeric value, interpreting the two bytes as a binary number.
The %BIN function needs a character variable name in parameter 1, a starting position in parameter 2, and a length in parameter 3. Like in %SST, parameters 2 and 3 can be variables instead of literals, and must yield a valid ending position when added together.
%BIN can do the opposite too, again like %SST. You can code the %BIN function in the VAR parameter of the Change Variable (CHGVAR) command. This allows you to build a character string from its numeric representation-similar to the X2C function in REXX. For example, the letter A is hexadecimal C1, which is 193 decimal. Now consider the following piece of code:
DCL VAR(&CHARS) TYPE(*CHAR) LEN(2) CHGVAR VAR(%BIN(&CHARS 1 2)) + VALUE(193)
This CHGVAR command has the following meaning: change the first two bytes of &CHARS to the two characters that, when taken as a binary value, are equivalent to the number 193. When the CHGVAR command runs, &CHARS changes to hexadecimal value 00C1, which is a null character followed by a letter A. The accompanying sidebar shows how you can use %BIN in a CL program to convert lowercase to uppercase characters.
In the last example, we could have coded the CHGVAR command as follows:
CHGVAR VAR(%BIN(&CHARS)) + VALUE(193)
In general, you can omit parameters 2 and 3 of the %BIN function when you want to start at position 1 and want to continue for the full length of the variable coded in parameter 1. Since &CHARS is two bytes long, %BIN(&CHARS 1 2) and %BIN(&CHARS) have the same meaning.
A few other rules:
%BIN can also be spelled %BINARY. This is analogous to %SST and %SUBSTRING. Once you get used to %SST, you don't want to use %SUBSTRING. It wouldn't surprise me if no one would ever want to use %BINARY for the same reason; %BIN is more convenient.
Parameter 3 of %BIN can be either 2 or 4. It cannot have any other value, although it can be omitted. If omitted, the variable coded in parameter 1 must have a length of 2 or 4.
Parameters 2 and 3 can be both indicated or both omitted. However, you cannot indicate one and omit the other. This is another way of saying that %BIN better have either one or three parameters, but never two.
You can use %BIN in the IF command's COND parameter. For instance, you can code a condition such as:
COND(&COUNT *EQ %BIN(&LIST 1 2))
Parameter 1 of the %BIN function does not accept constants-only variables. That is, %BIN(X'52E0') is not acceptable, but %BIN(&X) is.
Command parameters that expect numeric values can accept a %BIN function. For example, these two commands are both valid:
CHGJOB RUNPTY(%BIN(&A 1 2)) CRTPF FILE(X) + RCDLEN (%BIN(&B 1 4))
%BIN and %SST cannot be embedded. For instance, this is invalid:
%SST(&A %BIN(&C 1 2) 4)
%BIN cannot appear in an expression whose value is destined to be passed to another program. This means that the following is invalid:
CALL PGM(X) PARM(%BIN(&D 1 2))
Is %BIN useful and important? To answer this question, you need to find out how often you'd use it. I create commands for everything, and commands always use binary prefixes in lists. I know that I'll quickly dump CVTBINDEC and embrace %BIN. Also, since DDS now supports variable-length character data, it's possible for an RPG/400 program to end up returning such a string to a CL program. The CL program can now determine the length of the string with the %BIN function so it can extract the valid portion with the %SST function:
CALL PGM(RPGPGM) PARM(&VARLEN) CHGVAR VAR(&LEN) + VALUE(%BIN(&VARLEN 1 2)) CHGVAR VAR(&VALID) + VALUE(%SST(&VARLEN 3 &LEN))
This portion of code interprets the first two bytes of &VARLEN as the length of the string, then uses %SST to extract from &VARLEN the valid portion (which begins on the third byte and has a length of &LEN).
Now I want floating point variable support, being able to read multiple files, structured programming features such as iterative DO, DO WHILE, DO UNTIL, DO FOREVER, CASE, subroutines, pointers...I can dream, can't I?
Using %BIN for Case Conversion
For illustration purposes only, allow me to introduce the Convert to Uppercase (CVT-UPC) command (A1). It takes a character string of any length (up to 3000 characters) and returns another string, converted to uppercase in a 3000-byte character variable. This command can only be used in CL or REXX programs.
For illustration purposes only, allow me to introduce the Convert to Uppercase (CVT-UPC) command (Figure A1). It takes a character string of any length (up to 3000 characters) and returns another string, converted to uppercase in a 3000-byte character variable. This command can only be used in CL or REXX programs.
Parameter STRING accepts its input as a varying-length string, as indicated by VARY(*YES). The command processor automatically places a two-byte binary prefix that indicates how long the string you have entered into the parameter is.
As seen in A2, program UPC001CL receives the STRING parameter into a 3002-character variable-two bytes for the binary prefix, plus the 3000 characters the string can have as its maximum. The program then initializes &RTN-STRING to blanks and converts the length to decimal using %BIN. If this length is zero, the program ends because it means that the user didn't supply any input.
As seen in Figure A2, program UPC001CL receives the STRING parameter into a 3002-character variable-two bytes for the binary prefix, plus the 3000 characters the string can have as its maximum. The program then initializes &RTN-STRING to blanks and converts the length to decimal using %BIN. If this length is zero, the program ends because it means that the user didn't supply any input.
The loop that follows processes one character at a time, performing the conversion only if the current character is a lowercase letter. To execute the conversion, it creates &PADCHAR equal to a null (hexadecimal 00) and the current character. This is then turned into a binary number with %BIN, to which we add 64. For instance, 'a' is the 129th character in the EBCDIC set; by adding 64 we obtain 193, and 'A' is the 193rd character of the EBCDIC set.
After adding 64, we assign this value to %BIN(&PADCHAR), meaning that &PADCHAR will turn into the character representation of the numeric result. All that's left to do is move the second character of &PADCHAR into the return string's current character, and continue the loop until the current character number (&N) exceeds the length of the original string (&LENGTH).
Earlier I said that this is an example for illustration purposes only. You could have performed the same conversion by calling QDC-XLATE, using the translation table QSYSTRNTBL. Or you could have used an RPG/400 program as the command processing program, instead of CL, using the XLATE opcode. But I hope this example has shown you the enormous power of the %BIN function.
Binary Support in CL
Figure A1 Command CVTUPC
CVTUPC: CMD PROMPT('Convert to Uppercase') PARM KWD(STRING) TYPE(*CHAR) LEN(3000) EXPR(*YES) + VARY(*YES) PROMPT('Character string') PARM KWD(RTNSTRING) TYPE(*CHAR) LEN(3000) + RTNVAL(*YES) PROMPT('Returned string (3000)')
Binary Support in CL
Figure A2 CL program UPC001CL
UPC001CL: + PGM PARM(&STRING &RTNSTRING) DCL VAR(&STRING) TYPE(*CHAR) LEN(3002) DCL VAR(&LENGTH) TYPE(*DEC) LEN(4 0) DCL VAR(&RTNSTRING) TYPE(*CHAR) LEN(3000) DCL VAR(&OFFSET) TYPE(*DEC) LEN(4 0) DCL VAR(&N) TYPE(*DEC) LEN(4 0) DCL VAR(&CHAR) TYPE(*CHAR) LEN(1) DCL VAR(&PADCHAR) TYPE(*CHAR) LEN(2) CHGVAR VAR(&RTNSTRING) VALUE(' ') CHGVAR VAR(&LENGTH) VALUE(%BIN(&STRING 1 2)) IF COND(&LENGTH *EQ 0) THEN(RETURN) CHGVAR VAR(&OFFSET) VALUE(3) CHGVAR VAR(&N) VALUE(1) LOOP: + CHGVAR VAR(&CHAR) VALUE(%SST(&STRING &OFFSET 1)) IF COND(&CHAR *GE 'a' *AND &CHAR *LE 'i' *OR &CHAR *GE 'j' *AND + &CHAR *LE 'r' *OR &CHAR *GE 's' *AND &CHAR *LE 'z') THEN(DO) CHGVAR VAR(&PADCHAR) VALUE(X'0000') CHGVAR VAR(%SST(&PADCHAR 2 1)) VALUE(&CHAR) CHGVAR VAR(%BIN(&PADCHAR)) VALUE(%BIN(&PADCHAR) + 64) CHGVAR VAR(%SST(&RTNSTRING &N 1)) VALUE(%SST(&PADCHAR 2 1)) ENDDO ELSE CMD(DO) CHGVAR VAR(%SST(&RTNSTRING &N 1)) VALUE(&CHAR) ENDDO CHGVAR VAR(&OFFSET) VALUE(&OFFSET + 1) CHGVAR VAR(&N) VALUE(&N + 1) IF COND(&N *LE &LENGTH) THEN(GOTO CMDLBL(LOOP)) ENDPGM