Wednesday, October 12, 2016

Scan has a new parameter and a new BiF

changes to %scan and new %scanr bif

While it was not part of the fanfare for the release of IBM i 7.3, there were some enhancements made to RPG. Two of the changes were made to the Scan built in function (BiF):

  1. Addition of a fourth parameter to the BiF for the length of the string to scan.
  2. A new BiF Scan reverse, which the name suggests starts the scan at the end of the variable and moves towards the start.

Let me jump straight into my code example so I can explain what differences these additions make. First I need the definitions for my example program:

01  **free
02  ctl-opt dftactgrp(*no) ;

03  dcl-s String char(200)
      inz('RPGPGM.COM is a blog that writes about IBM i, including RPG') ;
        // ----+----1----+----2----+----3----+----4----+----5----+----6

04  dcl-s i packed(3) ;
05  dcl-s Text char(10) ;

Line 1: As this program is being written on a server running IBM i I have no excuse not to use totally free RPG, which means in this program the first line needs to be **FREE.

Line 2: I will be using a procedure rather than a subroutine in this program so I need to have the DFTACTGRP(*NO). Learn why I think I prefer "open" procedures rather to subroutines.

Line 3: This variable contains the string I will be using to illustrate the various types of scan. I have placed a ruler under it to make it easier for us all to understand which part of the string is being found.

Line 4: The Scan returns the position it first finds what I am looking for, so I need a numeric variable to contain that.

Line 5: I will be using this variable to help show which part of the string was found.

I am sure we have all used %SCAN like this.

06  i = %scan('RPG':String) ;
07  GetText() ;
08  dsply ('Scan No.1 = ' + %char(i) + ' ' + Text) ;

Line 6: The first parameter of the %SCAN is what I want to find. The second is the variable I am searching within. A number is returned to indicate where within the string what I was searching for is found. If it is not found then zero is returned.

Line 7: I am just using this procedure to format the Text field. I will explain how it does it shortly.

Line 8: I am using the DSPLY operation code to display what I found. In this example I get:

  DSPLY  Scan No.1 = 1 RPGPG

As the character "RPG" are found starting in the first position of the string the number is returned by the %SCAN. To explain the rest of what appears I need to explain the subprocedure GetText(), which is shown below.

31  dcl-proc GetText ;
32    if (i > 0) ;
33      Text = %subst(String:i:5) ;
34    else ;
35      Text = 'Not found' ;
36    endif ;
37  end-proc ;

Line 32: If the variable i is not zero then I found what I was looking for in the string.

Line 33: As the scan was successful I want to return in the variable Text what I found and the next few characters, which helps me to confirm where in the string the searched for characters were found.

Line 34 – 35: If i is zero then the scan was unsuccessful and I return "Not found".

Back to the main part of the program and to the second scan example.

09  i = %scan('rpg':String) ;
10  GetText() ;
11  dsply ('Scan No.2 = ' + %char(i) + ' ' + Text) ;

Scan is case sensitive, which means in this example, see line 9, zero is returned as "rpg" is not found in the searched variable. When line 11 is executed it displays zero and not found:

  DSPLY  Scan No.2 = 0 Not found

The third example shows the third parameter, which has been available for several releases. This parameter is used where to start the scan operation. In this example on line 12 I am starting my scan in the second position of the variable.

12  i = %scan('RPG':String:2) ;
13  GetText() ;
14  dsply ('Scan No.3 = ' + %char(i) + ' ' + Text) ;

The result is the scan returns 57, see below. As I am starting the scan in the second position of the variable the next place the string "RPG" is to be found is at the end.

  DSPLY  Scan No.3 = 57 RPG

And now for the new fourth parameter, the length to scan.

15  i = %scan('i':String:20:10) ;
16  GetText() ;
17  dsply ('Scan No.4 = ' + %char(i) + ' ' + Text) ;

In this example I am only searching from the 20th position to the 29th (that is 10 long).

  g that wri
  2----+----
  DSPLY  Scan No.4 = 29 ites

In my opinion this is a big improvement to the scan as now I can only scan part of a variable for a letter and number, and know if it is in that part of the variable. Rather than get a returned value and have to determine if it is within that part I am interested in or not.

And onto the new BiF %SCANR. As the name suggests it is just the same as the %SCAN, but it works in reverse order (end to start). If I repeat the first example, but use the %SCANR the code would look like:

18  i = %scanr('RPG':String) ;
19  GetText() ;
20  dsply ('ScanR No.1 = ' + %char(i) + ' ' + Text) ;

And I would find the first occurrence of "RPG", searching from the end to the start, is in the 57th position.

  DSPLY  ScanR No.1 = 57 RPG

I had to think a while of how to define the next example. I want to scan for the first letter "i" closest to the end of the variable. How do I code the third parameter, start position for that? Fortunately the %SCANR regards the last position of the variable as the first position, see line 21.

21  i = %scanr('i':String:1) ;
22  GetText() ;
23  dsply ('ScanR No.2 = ' + %char(i) + ' ' + Text) ;

Which gives me the position of the first "i" when scanning from right to left.

  DSPLY  ScanR No.2 = 53 ing R

The new fourth parameter is also available in the %SCANR.

24  i = %scanr('i':String:35:10) ;
25  GetText() ;
26  dsply ('ScanR No.3 = ' + %char(i) + ' ' + Text) ;

What I found interesting about this was the range it scanned. I almost expected it to scan from the 35th down to the 26th position. But it scanned from the 35th up to the 44th position. I will have to remember that when I start using this.

DSPLY  ScanR No.3 = 44 i, in

All of these examples of the %SCANR are fine but they are not "real world". What would be? In my last example, see below, I have a string that is the path in the IFS to a file, see line 27. What I need to do is to extract the name of the file only. I have no idea how deep in subfolders the file is, so a %SCANR is perfect as I scan for the first slash ( / ) knowing that that comes before the file name, see line 28.

27  String = '/folder/subfolder/file.txt' ;
28  i = %scanr('/':String) ;
29  Text = %subst(String:i + 1) ;
30  dsply ('File name = ' + Text) ;

Line 29: The substring extracts the name of the file from the variable, but I need to increment the value returned by the %SCANR as I do not want the slash in the file name.

My result is what I desired, the name of the file.

  DSPLY  File name = file.txt

By all means I have discussed many points about the %SCAN BiF that you can use at an earlier release than 7.3, but the fourth parameter and the %SCANR can only be used in 7.3 and 7.2 TR4.

 

You can learn more about this from the IBM website:

 

This article was written for IBM i 7.3, and some of the options should work for earlier releases too.

4 comments:

  1. %SCANR is probably the most needed BIF in daily application programming works. However, I am surprised by its behavior with the fourth parameter (I know what its doing within the given range though). Good post Simon...!! Very informative as usual.

    ReplyDelete
    Replies
    1. I miss testn in free format... :(

      Delete
    2. I know this does not replace TESTN but have you read this post on alternatives to TESTN

      Delete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.