Wednesday, April 1, 2020

Playing with RPG's indicator array

changing indicators within the *in array

This all started with a conversation with a colleague. He asked about how he could change multiple RPG's numbered indicators without having to hard code the indicators. As the indicators are held in an array he was asking couldn't he just change a number of elements in one statement, rather than the individual indicators? He gave this example of fixed format RPG which changes indicators 10 - 13.

C                   MOVEA     '1001'        *IN(10)

When we place a debug breakpoint after this line we could see that indicators 10 – 13 contained the values he desired:

> EV *IN
*IN(10) = '1'
*IN(11) = '0'
*IN(12) = '0'
*IN(13) = '1'

Before I go any further I want to give my opinion about numbered indicators. I am not against indicators. I use them all the time in my work, and you have seen many examples of me using indicators in posts in this blog. But these are named indicators, not the numbered indicators those of us old enough to have programmed in RPG2 or RPG3 had to use. In RPG4 there is no excuse to use numbered indicators. They are confusing as a numbered indicator gives me no idea as to its function. Do you know what *IN80 is used for? If I use a named indicator instead then everyone knows its purpose.

The most common way I see people still using numbered indicators, in modern RPG, is when the program uses a display or printer file. But you can use the indicator data structure in the RPG code and define named indicators for the display files and printer files.

In conclusion DO NOT use *IN10 in your code as: RPG numbered indicators = bad, RPG named indicators = good

This example uses the indicator array as it is present in very RPG program, and we just could not be bothered to create another array.

Back to this example.

For my colleagues first attempt to change the indicator array he used the %SUBARR built in function:

01  %subarr(*in:10:4) = '1001' ;

But he could not get his program to compile. The compile message he received was:

Msg id  Sv Number Message text
*RNF7531 30      1 The expression is not valid for assignment to 
                   an indicator or indicator data structure.

Having received that error my colleague knew what to do. Create a new array, just like indicator array, then use a pointer to point to the indicator array, therefore, when he changes the new array it changes the indicator array too, and vice versa.

01  dcl-s WorkArray char(1) dim(99) based(WorkArrayPtr) ;
02  dcl-s WorkArrayPtr pointer inz(%addr(*in)) ;

03  %subarr(WorkArray:10:4) = '1001' ;

Line 1: Here is the definition of the new array. The array elements have to be character, rather than indicator, types. The BASED keyword gives the name of the pointer.

Line 2: The pointer is initialized with the address of *IN, the indicator array. This is almost like an "overlay", as the two arrays share the same space in memory.

Line 3: Now he tries to move "1001" to the new array using the %SUBARR.

Unfortunately this did not work that way he expected. He expected "1" to be placed in the 10th element, "0" in the 11th, etc. What happened was that "1001" was moved to all of the array elements in that range. As the array element is only one character the results look like below:

> EV *IN
*IN(10) = '1'
*IN(11) = '1'
*IN(12) = '1'
*IN(13) = '1'

After that he decided to seek some advice. We talked about ideas on how to make this work for a few minutes, then agreed to meet later to show the other the solution each of us would come up with.

Having both had a to play we came back with two different solutions.

This is my code is based upon my colleague's solution. I have made a few changes, which he agreed with, to make the code more efficient. It takes the string of "1001" and updates the indicator array one position at a time.

01  dcl-s Counter packed(2) ;
02  dcl-s StartPos like(Counter) ;
03  dcl-s Values char(99) ;

04  StartPos = 10 ;
05  Values = '1001' ;

06  for Counter = 1 to 99 ;
07    if (%subst(Values:Counter:1) = ' ') ;
08      leave ;
09    endif ;

10    *in(StartPos) = %subst(Values:Counter:1) ;
11    StartPos += 1 ;
12  endfor ;

Line 1: This variable will be used to condition a For group.

Line 2: This variable will be used for which element in the indicator array should be updated.

Line 3: This variable will contain the string of indicator values that the indicator value will be updated with.

Line 4: I want to start updating the indicators starting with *IN10.

Line 5: These are the values I want to update the indicators with.

Line 6: I am using this For group to be performed up to 99 times. I do not need to perform it any more times as there are only 99 numbered indicators.

Lines 7 – 9: If the substring-ed position in the variable is blank I have reached the end of the indicators I want to change, and I leave the For group.

Line 10: Update the position in the indicator array with the value in the relevant part of the Value variable. The first time the For group is performed it will update the 10th element in the indicator row with "1", second time the 11th element with "0", etc.

Line 11: The start position is incremented to updated the next element of the indicator array.

This code works fine, and updates the desired range of indicators:

> EV *IN
*IN(10) = '1'
*IN(11) = '0'
*IN(12) = '0'
*IN(13) = '1'

My approach was much simpler:

01  dcl-s Indicators char(99) based(IndicatorsPtr) ;
02  dcl-s IndicatorsPtr pointer inz(%addr(*in)) ;

03  %subst(Indicators:10:4) = '1001' ;

Lines 1 and 2: I can define a character variable that is 99 long instead of an array. Thanks to the pointer, line 2, this "overlays" the indicator array.

Line 3: When I want to change some of the indictors I can just use a substring, %SUBST, built in function to update a part of the variable and the indicators in the indicator array are also changed:

> EV *IN
*IN(10) = '1'
*IN(11) = '0'
*IN(12) = '0'
*IN(13) = '1'

Both solutions work. But I think mine is better as it is fewer lines of code. In my opinion as it is less code it is easier to understand.

Which code do you think is easier to understand?

 

This article was written for IBM i 7.4, and should work for some earlier releases too.

6 comments:

  1. Personally prefer your approach Simon, minimal line of code. Thanks

    ReplyDelete
  2. Personally I don't like either that much to be honest. The problem in both cases is that there is no name associated with the group of indicators and therefore you are reliant on comments to know what the group represents.

    For years I've been using this basic technique and variants of it.

    dcl-s pIndicators Pointer Inz(%Addr(*In));

    dcl-ds displayControls Based(pIndicators);
    // Response indicators
    Exit_03 Ind Pos(3);
    Return_12 Ind Pos(12);
    // Conditioning indicators
    errorInds_31_33 Char(3) Pos(31);
    Error_31 Ind Pos(31);
    StDateError_32 Ind Pos(32);
    EndDateError_33 Ind Pos33);
    end-ds;

    Now I not only have meaningful names for everything I also have a name for the error indicators and can clear them all in one simple and obvious RESET or CLEAR. I use the same approach with subfile controls grouping them so I can use named constants to set the correct pattern e.g. SubfileControls = DISPLAYSUBFILE; or SubfileControls = CLEARSUBFILE.

    The first two lines are in an RDi snippet - or i could have them in a simple /Copy - don't even have to type them!

    ReplyDelete
    Replies
    1. I agree there is no reason to use numbered indicators. I gave a link within the post to my preferred method, using the Indicator Data Structure, when passing indicators between display and printer files and the program, see here (yes it is in fixed format definitions as free format definitions were not available at the time the post was written).

      Delete
  3. This is essentially what I do to force output fields to refresh new values in case an error indicator (27-49) is on.

    Ringer

    D IndArrPtr s * Inz(%Addr(*In))
    D ds Based(IndArrPtr)
    D InAll99 99a
    D ErrorInd1 n Dim(23) Overlay(InAll99:27)
    D*
    D SvErrorInd1 s n Dim(23)

    SvErrorInd1(*) = ErrorInd1(*) ;
    ErrorInd1(*) = *Off ;
    Write IN400SC1 ;
    ErrorInd1(*) = SvErrorInd1(*) ;
    Write IN400FM1 ;
    ExFmt IN400SC1 ;

    ReplyDelete
  4. Sometimes it’s fun to play with absurdity—you may learn something unexpected.

    I would say, avoid all the complexity and just write:

    *in10 = *on;
    *in11 = *off;
    *in12 = *off;
    *in13 = *on;

    This is elegantly horrendous.

    ReplyDelete
  5. %SubArr(*In:10:4) = *ALL'1'

    ReplyDelete

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.