A Simple Scan Variable Command

A Simple Scan Variable Command

Recently I did some CL programming for one client that made use of the %SCAN and %CHECKR built in CL functions. This client was at Release 7.2, which is apparently the first release to support these BIFs.

Then, I imported the CL source onto another client’s system (why code the same thing twice, right?). The compiles failed, and the errors pointed to those BIFs. Dang. The client’s system was only at 7.1.

Scanning for a Pattern in a String (SCANVAR)

I needed a solution. Recalling IBM‘s QCLSCAN API I had used long ago, I proceeded to create the following commands:

CMD PROMPT(‘Scan for a Pattern in a String’) +
MSGF(QUSRMSG) ALLOW(*IPGM *BPGM)

PARM KWD(STRING) TYPE(*CHAR) LEN(999) MIN(1) +
EXPR(*YES) INLPMTLEN(32) +
PROMPT(‘Character string (999)’)

PARM KWD(STRINGLEN) TYPE(*DEC) LEN(3) MIN(1) +
PROMPT(‘String length (3,0)’)

PARM KWD(STARTPOS) TYPE(*DEC) LEN(3) MIN(1) +
PROMPT(‘Start position (3,0)’)

PARM KWD(PATTERN) TYPE(*CHAR) LEN(999) MIN(1) +
EXPR(*YES) INLPMTLEN(32) PROMPT(‘Scan +
pattern (999)’)

PARM KWD(PATTERNLEN) TYPE(*DEC) LEN(3) MIN(1) +
PROMPT(‘Pattern length (3,0)’)

PARM KWD(UPPERCASE) TYPE(*CHAR) LEN(1) RSTD(*YES) +
DFT(*NO) SPCVAL((*NO 0) (*YES 1)) +
PROMPT(‘Translate to upper case?’)

PARM KWD(TRIMBLANKS) TYPE(*CHAR) LEN(1) +
RSTD(*YES) DFT(*NO) SPCVAL((*NO 0) (*YES +
1)) PROMPT(‘Trim trailing blanks?’)

PARM KWD(WILDCARD) TYPE(*CHAR) LEN(1) DFT(*NONE) +
SPCVAL((*NONE ‘ ‘)) PROMPT(‘Wilcard +
character (1)’)

PARM KWD(RESULT) TYPE(*DEC) LEN(3) RTNVAL(*YES) +
MIN(1) PROMPT(‘Result (3,0)’)
DEP1: DEP CTL(*ALWAYS) PARM((RESULT)) MSGID(CMD0002)

This command maps directly to the QCLSCAN parameters, so there is no need to create a special command processor. Just be sure to compile the command with PGM(QCLSCAN) as an override. (Unfortunately, overriding the Command Processor is not a value that can be defined in the CMD statement in the source. An oversight?).

Here’s a brief summary of the parameters involved.

String (to scan). This is the character string within which you want to find a smaller target string. In common PHP terms, this would be called the “haystack” (where we’d look for the “needle”).

The original specs from IBM allow for a variable string length, but since the the string length variable is set at 3.0, so it makes no sense to allow a string larger than 999.

String length. This is a 3,0 decimal variable (you can’t use an integer) that defines the length of the String variable. The value can be 1 (which wouldn’t make sense) to 999.

Starting position in string. Another 3,0 decimal variable (again, no integers) where the scan variable should start.

This serves two purposes: First, to “protect” a certain part of the string from being scanned (for instance, if you already know you will get a match within the first 100 characters, but that’s not actually what you’re looking for).

Second, if you are finding multiple matches in an iterative process. Each new scan variable will start where the last one left off.

Pattern. This is the “needle” you are trying to find in the “haystack”. Again, because of the limitations of the length variables, minimum is 1 and the maximum is 999 characters. (But, 999 wouldn’t make much sense, would it ? )

Pattern length. We need to tell the QCLSCAN API how many characters are in the pattern so it doesn’t have to work too hard ? . Another 3,0 decimal variable.

Translate to uppercase? *YES or *NO. This is handy if you are making comparisons with strings and patterns that do not match case. So, instead of trying every possible combination (like BOISE, Boise, boise, etc.) as a pattern, we can make sure both strings are in upper case and execute the scan variable.

Trim trailing blanks? This is intended (I think) to make the API run as quickly as possible. The pattern is “shrunk” down to its smallest significant length before comparing it against the “haystack”.

So, if your pattern length is 20, but you have populated only populated the first 3 characters, the actual length used for the scan will be 3.

I have this defaulting to *NO – perhaps it should be *YES.

Result. This is the 3,0 decimal variable (typically &RESULT) that will receive the resulting position value from the API. Anything greater than zero is a match, zero means “not found”, and less than zero indicates an error of some sort. More details on various error conditions can be found here if you are so inclined.

Finally, the message CMD0002 simply tells the user a result value is required when SCANVAR is prompted. I had to do this because I decided to use default values for UPPERCASE, TRIMBLANKS, and WILDCARD. Since RESULT follows those default fields, MIN(1) is not honored.

Finding the End Position or Length (ENDPOS)

In my original CL programs I was using %CHECKR to determine the length of the data contained in a variable. I could have used an array-like process (yuck), or I could have used an RPG program as a command processor. But, I liked the idea of using my newly created SCANVAR command.

I created the following command:

CMD PROMPT(‘Find String End Position’) +
MSGF(FTPLIB/QUSRMSG) ALLOW(*IPGM *BPGM) +
PRDLIB(FTPLIB)

PARM KWD(STRING) TYPE(*CHAR) LEN(999) MIN(1) +
EXPR(*YES) INLPMTLEN(32) +
PROMPT(‘Character string (999)’)

PARM KWD(STRINGLEN) TYPE(*DEC) LEN(3) MIN(1) +
PROMPT(‘String length (3,0)’)

PARM KWD(RESULT) TYPE(*DEC) LEN(3) RTNVAL(*YES) +
MIN(1) PROMPT(‘Result (3,0)’)
DEP1: DEP CTL(*ALWAYS) PARM((RESULT)) MSGID(CMD0002)

I called it ENDPOS for lack of a better idea at the time.

Now, this command does have a special command processor, which is a CL program I also called ENDPOS. Details are included in the CL source comments. Note that I chose the “backward” apostrophe as the dummy character because I figured it would be safe.

PGM PARM(&STRING &STRINGLEN &RESULT)
DCL VAR(&STRING) TYPE(*CHAR) LEN(999)
DCL VAR(&STRINGLEN) TYPE(*DEC) LEN(3)
DCL VAR(&RESULT) TYPE(*DEC) LEN(3)

DCL VAR(&WORKSTRING) TYPE(*CHAR) LEN(999)

/* First, check to see if the string already has data in position 999. */
/* If it does, we already know how long it is. Return 999 to the caller. */

IF COND(%SST(&STRING 999 1) *GT ‘ ‘ *AND +
%SST(&STRING 999 1) *LE ‘9’) THEN(DO)
CHGVAR VAR(&RESULT) VALUE(999)
RETURN /* Exit Program */
ENDDO

/* Load the input string into the work variable. */
/* We do this because we add the dummy character ‘`’ to the string. */
/* The %TRIMR ensures we are getting rid of excess blanks. */

CHGVAR VAR(&WORKSTRING) VALUE(%TRIMR(&STRING) || ‘`’)

/* Execute scan for dummy character at the end of the string. */

SCANVAR STRING(&WORKSTRING) STRINGLEN(&STRINGLEN) +
STARTPOS(1) PATTERN(‘`’) PATTERNLEN(1) +
RESULT(&RESULT)

/* At this point the string will be 1 char larger than the true length. */
/* Adjust for the greater length and return the true length. */
/* If the scan result is 1 or less, we will pass back a negative value. */

IF COND(&RESULT > 0) THEN(CHGVAR VAR(&RESULT) +
VALUE(&RESULT – 1))

ENDPGM

Enjoy. Until next time…

Permalink to ‘A Simple Scan Variable Command’


Category: CLP

Leave a Reply

Wordpress SEO Plugin by SEOPressor