Let's look at the new %CHECK, %CHECKR, and %SCAN built-ins of CL.
In last month's article, "How Would You Like That Character String Trimmed?," we reviewed the new CL built-ins %TRIM, %TRIML, and %TRIMR. This month, we'll look at three more CL built-ins that IBM has provided by way of 7.1 PTF SI49061. The new built-ins, which are supported with both the OPM and ILE compilers, are %CHECK, %CHECKR, and %SCAN.
Note that while this PTF is only available with 7.1, the PTF does provide the 7.1 CL compiler support so that you can (by way of the TGTRLS parameter of the CRTCLPGM, CRTCLMOD, and CRTBNDCL commands) generate code that will run on V5R4 and 6.1 systems within your network (in addition to obviously supporting systems running at 7.1).
The %CHECK Built-In
The first built-in we'll look at, %CHECK, allows you to check (or verify) that all characters within a character variable are, or are not, in a discrete list of characters.
The %CHECK built-in is defined with three parameters. The first parameter is required and can be either a CL variable, defined as Type(*Char), or a character literal value of one or more bytes in length. This parameter represents the discrete list of characters to check for and is referred to as the comparator-string. The second parameter is also required and can be either a CL variable, defined as Type(*Char), or the special value *LDA. This parameter represents the character string that is to be checked and is referred to as the base-string. The third parameter is optional and can be a CL variable defined as Type(*Int), Type(*UInt), Type(*Dec) with zero decimal positions, or a numeric literal value with zero decimal positions. Referred to as the starting-position, this parameter represents the first position within the base-string where the check is to start. If this parameter is not specified, it defaults to the first position of the base-string; otherwise, you can specify any starting position that is greater than zero and not greater than the length of the base-string.
The %CHECK built-in essentially steps through the base-string one character at time, starting at the starting-position value and moving left to right, comparing each character of the base-string to the characters found in the comparator-string. The built-in then returns the position of the first character within the base-string that is not found in the comparator string or, if all characters in the base-string are found in the comparator-string, the value 0. This returned value can be used anywhere that you can code an arithmetic expression within CL or specify a CL variable of Type(*Int), Type(*UInt), Type(*Dec) with zero decimal positions, or a numeric literal value with zero decimal positions. That is, you can use it as the VALUE parameter of a CHGVAR command to set a CL variable, as the COND parameter of an IF or WHEN command to test a value, or as a parameter to a quite a few other CL commands. One note concerning the returned value: it is always relative to the start of the base-string even if a starting-position value is specified.
To demonstrate how %CHECK might be used, suppose we have a CL variable defined as follows:
Dcl Var(&A_Number) Type(*Char) Len(20)
If we want to verify that variable &A_Number contains only blanks and/or the numeric values 0 through 9, the following check would accomplish that. Note that there is a blank character preceding the '0' in the comparator-string parameter.
If Cond(%Check(' 0123456789' &A_Number) *NE 0) +
Then(SndPgmMsg Msg('Invalid character found'))
If we wanted to further verify that &A_Number was not just any number but rather a valid telephone number comprised of blanks, parentheses, dashes, and the numeric values 0 through 9, then the following could be used (ignoring that this doesn't check for a valid telephone number format, just valid characters).
If Cond(%Check(' –()0123456789' &A_Number) *NE 0) +
Then(SndPgmMsg Msg('Invalid telephone number found'))
If we wanted to tell the user what characters of &A_Number do not conform to a valid telephone number, we could also add a variable &Pos and do the following.
Dcl Var(&Pos) Type(*Int)
ChgVar Var(&Pos) Value(%Check(' –()0123456789' &A_Number))
DoWhile Cond(&Pos *NE 0)
SndPgmMsg Msg('Invalid character' *BCat +
%sst(&A_Number &Pos 1) *BCat +
'found.')
If Cond(&Pos *EQ 20) Then(Leave)
ChgVar Var(&Pos) Value(&Pos + 1)
ChgVar Var(&Pos) +
Value(%Check(' –()0123456789' &A_Number &Pos))
EndDo
In the preceding example, setting variable &A_Number to the value '(507) 993-0229' will result in no error being reported, while setting &A_Number to the value '(507] 993_0229' will result in the closing bracket and underscore characters (] and _) being reported, in that order, as invalid characters. Note that this example also demonstrates how the returned value is relative to the start of the base-string as opposed to the starting-position within the base-string.
The %CHECKR Built-In
The %CHECKR built-in is essentially the same as the %CHECK built-in except that %CHECKR steps through the base-string in reverse order. That is, rather than the way %CHECK defaults the starting-position parameter to the first position of the base-string and then steps through the base-string in a left to right order, %CHECKR defaults the starting-position parameter to the last character of the base-string and then steps through the base-string in a right to left order. The returned position is, however, still relative to the start of the base-string (not the end of the base-string).
The following example implements the previous %CHECK demonstration, where we display the invalid characters in a telephone number, using %CHECKR.
ChgVar Var(&Pos) Value(%CheckR(' –()0123456789' &A_Number))
DoWhile Cond(&Pos *NE 0)
SndPgmMsg Msg('Invalid character' *BCat +
%sst(&A_Number &Pos 1) *BCat +
'found.')
If Cond(&Pos *EQ 1) Then(Leave)
ChgVar Var(&Pos) Value(&Pos - 1)
ChgVar Var(&Pos) +
Value(%CheckR(' –()0123456789' &A_Number &Pos))
EndDo
Where %CHECK displays the invalid characters in the order ']' and '_', %CHECKR displays the invalid characters in the order '_' and ']' (assuming the same invalid telephone number of '(507] 993_0229').
One use of the %CHECKR built-in that I enjoy is the ability to determine the blank-trimmed length of a character variable. The following example, for instance, will set the variable &Length to the value 4, the last position within the variable &Text that is not a blank.
Dcl Var(&Text) Type(*Char) Len(20)
Dcl Var(&Length) Type(*Int)
ChgVar Var(&Text) Value('More')
ChgVar Var(&Length) +
Value(%CheckR(' ' &Text))
When concatenating character variables, it's sometimes nice to know in advance if truncation of the data will occur. Of course, determining if truncation of a result field will occur does require knowledge of how large the result field is to begin with. The %CHECKR built-in can provide that answer (allowing us to avoid having to hardcode the allocated size either as a VALUE in a second variable DCL or in a statement "somewhere" in the program—and then forgetting to update the value when we change the declared size of the result field) by using the following approach.
Dcl Var(&Result) Type(*Char) Len(50)
Dcl Var(&Size) Type(*Int)
ChgVar Var(&Result) Value(' ')
ChgVar Var(&Size) +
Value(%CheckR('A' &Result))
By setting the variable &Result to all blanks and then using %CHECKR with a non-blank comparator-string, the value of variable %Size will be set to 50, the declared length of the &Result variable..
The %SCAN Built-In
The %SCAN built-in allows you to locate a character string within another character string. The %SCAN built-in is defined with three parameters. The first parameter is required and can be either a CL variable, defined as Type(*Char), or a character literal value of one or more bytes in length. This parameter represents the character string or pattern to be scanned for and is referred to as the search-argument. The second parameter is also required and can be either a CL variable, defined as Type(*Char), or the special value *LDA. This parameter represents the character string that is to be scanned and is referred to as the source-string. The third parameter is optional and can be a CL variable defined as Type(*Int), Type(*UInt), Type(*Dec) with zero decimal positions, or a numeric literal value with zero decimal positions. Referred to as the starting-position, this parameter represents the position within the source-string where the scan is to start. If this parameter is not specified, it defaults to the first position of the source-string; otherwise, you can specify any starting position that is greater than zero and not greater than the length of the source-string.
The %SCAN built-in returns the position (in a left to right order) of the first character within the source-string that is part of the search-argument or, if the search-argument is not found within the source-string, the value 0. This returned value can be used anywhere that you can code an arithmetic expression within CL or specify a CL variable of Type(*Int), Type(*UInt), Type(*Dec) with zero decimal positions, or a numeric literal value with zero decimal positions. That is, you can use it as the VALUE parameter of a CHGVAR command to set a CL variable, as the COND parameter of an IF or WHEN command to test a value, or as a parameter to many other CL commands. One note concerning the returned value: it is always relative to the start of the base-string even if a starting-position value is specified.
To demonstrate how %SCAN might be used, suppose we have two CL variables defined as follows:
Dcl Var(&Text) Type(*Char) Len(50) +
Value('CL now supports %CHECK, %CHECKR, and %SCAN')
Dcl Var(&Pos) Type(*Int)
The following use of %SCAN will result in variable &Pos being set to the value 17, the first occurrence of '%' within the variable &Text.
ChgVar Var(&Pos) Value(%Scan('%' &Text))
The following use of %SCAN will result in variable &Pos being set to the value 38, the first occurrence of '%S' within the variable &Text.
ChgVar Var(&Pos) Value(%Scan('%S' &Text))
The following use of %SCAN would result in variable &Pos being set to the value 0 as there is no occurrence of '%R' within the variable &Text.
ChgVar Var(&Pos) Value(%Scan('%R' &Text))
Some Additional Notes
Before closing on this introduction to the new %CHECK, %CHECKR, and %SCAN built-ins, I would like to point out two usage considerations and make one wish for a future CL enhancement. First the usage considerations:
First, the various character strings used as parameters to the built-ins (the comparator-string and base-string of %CHECK and %CHECKR along with the search-argument and source-string of %SCAN) are case-sensitive. Changing the previous %SCAN example to use a search-argument of '%s', for instance, will result in variable &Pos being set to the value 0 as there is no percentage sign followed by a lowercase s within the variable &Text.
Second, an additional consideration will generally apply when using the special value *LDA as the second parameter of these built-ins (the base-string of %CHECK and %CHECKR, the source-string of %SCAN). The built-ins, when checking or scanning the contents of the second parameter, do not provide an explicit parameter where you can specify the length of the second parameter; they offer only a starting-position parameter that allows you to optionally indicate where, within the second parameter, to start the processing of the built-in. In the case of discrete CL variables, the built-ins use the declared length of the variable to constrain the processing of the built-in and, in the case of the special value *LDA, the built-ins consider the *LDA to be a character variable with a length of 1024 characters. As the *LDA is generally shared across applications within a job, you should, in addition to checking for 0 (or non-0) return values, also check for return values outside of your expected range.
Returning to our earlier %SCAN examples, let's assume that &Text, rather than being a declared variable, is found in the *LDA starting at position 51 and, with a length of 50 bytes, ends in position 100. In that situation, you would want to use the following statement in order to start the processing of the %SCAN built-in at the proper location within the *LDA.
ChgVar Var(&Pos) Value(%Scan('%S' *LDA 51))
But in addition to using a &Pos value of 0 to indicate that the string '%S' was not found, you will also want to check for a value of &Pos that is greater than 100. Any value greater than 100, in this example, is another indication that the string was not found in the 50 bytes logically associated with &Text.
Note that the need to range check the returned position is true (with two exceptions discussed later) even when you "know for a fact" that no other user of the LDA will ever store the character value '%S'. The reason is that other data types—variables not defined as Type(*Char)—can represent a value that is encoded the same as '%S'. As an example, the string '%S' is encoded as x'6CE2' and it is this encoded value that %SCAN would be scanning for. If another application were to be using positions 201 through 204 of the LDA to hold a 4-byte integer value and that integer value just happened to be 27874 (which of course would be the case at 2:00 a.m. during year-end close), then %SCAN could return the value 203 as an integer value of 27874, which would be stored in the LDA as x'00006CE2', contains the scanned-for value. For any given character string, you can always come up with an equivalent encoding using a mixture of zoned decimal, packed decimal, and integer values. In the current example of a single 4-byte integer at positions 201 through 204 of the LDA, there are over 100,000 numeric values that would cause the sequence x'6CE2' to appear within those 4 bytes, with 27874 simply being one of them.
The two exceptions to LDA range checking are 1) when using %CHECK or %SCAN and the range you want to process does include byte 1024 of the LDA, and 2) when using %CHECKR and the range you want to process does include byte 1 of the LDA. In all other cases, remember to range check the returned LDA position values (or extract the LDA value to a discrete variable using the %sst built-in).
Now, as for my wish list… While I was coding the various examples found in this article, it struck me that allowing built-ins to use expressions as parameter values would have allowed me to further simplify my code (or at least reduce my typing). For instance, in the %CHECK example, I currently use the following two statements within the DoWhile loop.
ChgVar Var(&Pos) Value(&Pos + 1)
ChgVar Var(&Pos) +
Value(%Check(' –()0123456789' &A_Number &Pos))
If I could have used an expression for the third parameter of the %CHECK built-in I could, instead, have used this:
ChgVar Var(&Pos) +
Value(%Check(' –()0123456789' &A_Number (&Pos + 1)))
Likewise, if a built-in such as %sst could be used as the second parameter of the %SCAN built-in, then I could simply check the returned value for 0 (and not have to worry about range checking) by using the following statement.
ChgVar Var(&Pos) Value(%Scan('%S' %sst(*LDA 51 50)))
The %SCAN built-in would simply start at position 1 of the sub-stringed LDA range and stop processing after 50 bytes.
I do not, however, want my wish list to detract from the great work the IBM CL team has done. Getting six new built-ins in the last few months is fantastic, and it's certainly a boost to the general productivity of CL users. Not to mention the clear evidence that IBM is listening to our CL wish lists!
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online