23
Mon, Dec
1 New Articles

The CL Corner: More Character-String Enhancements for CL

CL
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

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 programand 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 typesvariables 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 This email address is being protected from spambots. You need JavaScript enabled to view it..

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: