rpg tips and techniques - tug · rpg tips and techniques notes about me: i am the co-founder of...

30
Jon Paris Jon.Paris @ Partner400.com www.Partner400.com www.SystemiDeveloper.com RPG Tips and Techniques Notes About Me: I am the co-founder of Partner400, a firm specializing in customized education and mentoring services for IBM i (AS/ 400, System i, iSeries, etc.) developers. My career in IT spans 45+ years including a 12 year period with IBM's Toronto Laboratory. Together with my partner Susan Gantner, I devote my time to educating developers on techniques and technologies to extend and modernize their applications and development environments. Together Susan and I author regular technical articles for the IBM publication, IBM Systems Magazine, IBM i edition, and the companion electronic newsletter, IBM i EXTRA. You may view articles in current and past issues and/or subscribe to the free newsletter at: www.IBMSystemsMag.com. We also write frequently for IT Jungle's RPG Guru column (www.itjungle.com). We also write a (mostly) monthly blog on Things "i" - and indeed anything else that takes our fancy. You can find the blog here: ibmsystemsmag.blogs.com/idevelop/ Feel free to contact me any time: Jon.Paris @ partner400.com © Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 1 of 30

Upload: others

Post on 23-Mar-2020

11 views

Category:

Documents


0 download

TRANSCRIPT

Jon Paris

Jon.Paris @ Partner400.com www.Partner400.com www.SystemiDeveloper.com

RPG Tips and Techniques

NotesAbout Me: I am the co-founder of Partner400, a firm specializing in customized education and mentoring services for IBM i (AS/400, System i, iSeries, etc.) developers. My career in IT spans 45+ years including a 12 year period with IBM's Toronto Laboratory.

Together with my partner Susan Gantner, I devote my time to educating developers on techniques and technologies to extend and modernize their applications and development environments. Together Susan and I author regular technical articles for the IBM publication, IBM Systems Magazine, IBM i edition, and the companion electronic newsletter, IBM i EXTRA. You may view articles in current and past issues and/or subscribe to the free newsletter at: www.IBMSystemsMag.com. We also write frequently for IT Jungle's RPG Guru column (www.itjungle.com).

We also write a (mostly) monthly blog on Things "i" - and indeed anything else that takes our fancy. You can find the blog here: ibmsystemsmag.blogs.com/idevelop/ Feel free to contact me any time: Jon.Paris @ partner400.com

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 1 of 30

AgendaBasic Thoughts

Ctl-Opt (H Spec)

Compiler Directives

Integers

Varying Length Fields

Indicators - they need names !

Templates - Design your own data types

Messaging - Versatility is key - lose your green screen habits

New Approaches to I/O operations

Using modern error handling options

Short form expressions

Use offsets and pointers when appropriate

Basic ThoughtsBe Free • Code EVERYTHING in free form • Consider changing old programs over to free when making

significant modifications ✦ Particularly if it is already in RPG IV

• There are excellent (and cheap) conversion tools out there ✦ And very very few compatibility issues

Use proper names • If you use a good editor like RDi there's no increase in typing • Use customerName not custNam not cusNo not wkCsNo

✦ messageType not msgTyp Speak the same language as the rest of the world • Table not Physical File • Index or View not Logical File

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 2 of 30

Basic ThoughtsLearn and adopt from other languages • Use /INCLUDE instead of /COPY • Constant names in all CAPITALS • Procedure names begin with upper case, variables with lower

Use a modern tool set • Preferably RDi

✦ Or miWorkplace or ILEditor if budget is an issue Embrace what is new • Learn new habits • Learn new languages

✦ They will improve your RPG !

H-spec / Ctl-OptThese have a new lease on life in RPG IV • They can supply defaults for dates/times in the program • Control debugging options • Compiler options - to ensure the same ones are always used

The compiler stops looking once the first of these is found • A Ctl-Opt (or H-spec) included in your source • A data area named RPGLEHSPEC in *LIBL • A data area named DFTLEHSPEC in QRPGLE

Tip: DO NOT use the data areas! • If you want standard set of options then use a /Copy or /Include

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 3 of 30

Compiler OptionsMany CRTBNDRPG/CRTRPGMOD keywords can be specified • Options supplied override any specified on the CRTxxxxxx command

Unsupported keywords: • DBGVIEW, OUTPUT, REPLACE, DEFINE, PGM, SRCFILE, SRCMBR,

TGTRLS Use Options( *SrcStmt : *NoDebugIO ) • *SrcStmt matches program statement numbers with source line numbers

✦ Easier for debugging and end user support • *NoDebugIO skips the individual field moves on I/O operations

✦ Faster step function during debugging ✦ Really useful when large numbers of fields are involved

ctl-Opt option(*SrcStmt :*NoDebugIO); ctl-Opt datFmt(*ISO) timFmt(*ISO) datEdit(*YMD-); ctl-Opt decPrec(63); ctl-Opt copyRight('This can be useful for version numbers’);

NotesMultiple options are separated with a colon (:) – e.g OPTIONS( *SRCSTMT : *NODEBUGIO) With *SRCSTMT specified, the statement number reported when an error occurs during run time will correspond directly to the SEU sequence number. Without this support, the statement number reported did not correlate directly to the source statement numbers. Therefore, support of end user problems was much more difficult. Many support desks kept compiler listings of all programs just to be able to match the program statement numbers to SEU statement numbers.

*NOSRCSTMT (default behaviour) indicates that the compiler just "makes them up" and assigns line numbers sequentially.

If *SRCSTMT is specified, statement numbers for the listing are generated from the source ID and SEU sequence numbers as follows: stmt_num = source_ID * 1000000 + source_SEU_sequence_number

If *DEBUGIO is specified, breakpoints are generated for all input and output specifications. *NODEBUGIO (the default) indicates that no breakpoints are to be generated for these specifications. This means that during debug sessions, doing a Step function on an I/O operation required many steps (one for each field in the format).

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 4 of 30

Compiler DirectivesCompiler directives test and act on Condition Names • They are used to control which source statements are included • Enables the use of a single source for different versions of an application

Within the source conditions can be set and cleared • by /DEFINE ( condition-name ) and /UNDEFINE ( condition-name )

They may also be set on the compiler commands • CRTBNDRPG and CRTRPGMOD

Directives available are: • /IF {NOT} DEFINED ( condition-name )

✦ Include the following source lines if the test is true • /ELSEIF {NOT} DEFINED ( condition-name )

✦ You can guess this one ... • /ENDIF

✦ End of the current conditional block • /EOF

✦ Jump straight to the end of the current source file

Compiler Directives - Predefined Condition Names

Compiler has a number of predefined condition names • *CRTBNDRPG – True if compiling with CRTBNDRPG • *CRTRPGMOD – True if compiling with CRTRPGMOD • *VxRxMx – True if compiling for the specified release or later

/if defined(*CRTBNDRPG) ctl-Opt dftActGrp(*NO) actGrp(‘MYACTGRP'); /endIf

ctl-Opt bndDir(‘MYBNDDIR'); ctl-Opt option(*srcStmt :*noDebugIO); ctl-Opt datFmt(*ISO) timFmt(*ISO) datEdit(*YMD-); ctl-Opt decPrec(63);

/if defined V7R3M0 // Use this sexy one liner new opcode /else // Use the old way of doing it /endIf

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 5 of 30

Use IntegersTrue Binary representation • Data types of INT (signed Integer) or UNS Unsigned Integer

Replaces the old BINDEC (Binary) data type • Has a broader range for the same size • No conversion to/from packed (as with binary) • Use instead of B data type with APIs

Use for compatibility with other languages • C, Java and MI (and CL in V5R3) • Used extensively with C functions and APIs

Better performance for math operations • Faster then packed (or zoned or binary)

Use integers for all loop counters etc. • And for any numeric that does not require decimals

Never use type B (BinDec) • Even if IBM's docs do show it in examples

Integer DetailsIntegers are identified in different ways in different places • It depends on the intended audience for the documentation • RPG, C and Java use different notations to those for APIs

RPG Bytes Range of Values API C and JavaINT(3) 1 -128 to 127 BIN1 charINT(5) 2 -32,768 to 32,767 BIN2 shortINT(10) 4 -2,147,483,648 to 2,147,483,647 BIN4 long or int

INT(20) 8 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

BIN8 long

UNS(3) 1 0 to 255 UBIN1 Unsigned char

UNS(5) 2 0 to 65535 UBIN2 short

UNS(10) 4 0 to 4,294,967,295 UBIN4 longUNS(20) 8 0 to 18,446,744,073,709,551,615 UBIN8 long long

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 6 of 30

Varying Length FieldsNeglected by far too many RPGers • But becoming more important due to increased usage in Db2

Excellent for string handling • Reduces requirement for %TRIMx operations

Length is set when data is moved into it • The field subsequently behaves as if it were fixed length of the current length

The %LEN function can be used to interrogate the length • And to reset it

Use caution when assigning *Blanks • The field will be filled with blanks to its maximum length

dcl-s firstName char(10) inz('Jon'); dcl-s lastName char(10) inz('Paris');

dcl-s v_firstName varChar(10) inz('Susan'); dcl-s v_lastName varChar(10) inz('Gantner');

dcl-s fullName varChar(21);

fullName = %trimR(firstName) + ' ' + %trimR(lastName); fullName = v_firstName + ' ' + v_lastName;

Notes

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 7 of 30

Storage Usage of Varying FieldsVarying length fields have two components • The current length of the field

✦ A two byte binary - Uns(5) - Or four byte binary - Uns(10) for fields > 65,535 in length

• This is followed by the actual data So always two (or four) bytes longer than the defined length • This is reflected in the results of %Size

dcl-ds *n; webOut varChar(2048); webDataLen int(5) overlay(webOut); webData char(2048) overlay(webOut:*next); end-ds;

webOut = 'HERE IS THE DATA';

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ...

00

16 H E R E I S T H E D A T A

%Size would return 2,050

NotesVariable length fields can give any program that builds a string (for example for a web page, or CSV file) can see huge performance improvements when varying length fields are used instead of fixed length fields. The reason is that, as long as you trim a string when it is loaded into a variable length field, you never need to trim it again. Compare this with fixed length fields where to add to an existing field you have to basically say: string = %TrimR( string ) + newStuff;

And do this for each new piece of data to be added to the string. The longer the string, the more inefficient this process is.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 8 of 30

DDS

Access Non-Normalized Data as Arrays Want to access fields in a record as an array? • The "secret" is to place them in a DS

This works even if the fields are not contiguous in the record • No length definition is required • The compiler uses the length supplied by the database

Use the POS keyword to cause the array to overlay the DS • Notice the use of the LIKE keyword

dcl-f MthSales;

dcl-ds SalesData; Q1; Q2; Q3; Q4;

SalesForQtr Like(Q1) Pos(1) Dim(4); end-ds;

R MTHSALESR CUSTOMER 4 STREET 32 CITY 24 STATE 2 DIVISION 2 Q1 7 2 Q2 7 2 Q3 7 2 Q4 7 2 K CUSTNO

NotesThe inspiration for this example comes from a commonly asked question on RPG programming lists: “How do I directly load fields from a database record into an array?” The question normally arises when handling old databases that are not normalized. Typically these are from old S/36 or S/38 applications. The type of record I mean contains a series of related values - for example, sales figures for January, February, ..., December.

To make our example fit on the page we're not going to show 12 months, because it wouldn't fit on the chart! Hopefully the example of sales figures for four quarters will give you the idea of how it all works. The DDS for the physical file is shown. One solution is depicted here. We'll look at a slightly different solution on the next chart. Our objective is to access the individual fields Q1-Q4 as an array of four elements after reading a record from the file.

Notice that we’ve incorporated the Quarterly sales fields into the DS by specifying their names. No length or type definition is required.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 9 of 30

Naming IndicatorsUse names for ALL indicators • INDDS is RPG's engineered approach

✦ Use it on all new code - more on retrofitting old programs in a moment

dcl-f screen workStn INDDS(displayControls);

dcl-ds displayControls; F3_Exit ind pos(3); F12_Cancel ind pos(12); errorIndicators char(3) pos(31); error ind pos(31); startDateError ind pos(32); endDateError ind pos(33); end-Ds;

errorIndicators = *zeros; // Turn off all error controls if (startDate < today); startDateError = *on ; // Start date too early endIf;

endDateError = ( endDate < startDate ); // End date before start date error = ( startDateError or endDateError ); // Set error status exFmt testRec;

if (F3Exit or F12_Cancel);

Remember ...

*IN32 means NOTHING!

NotesThe ONLY place where there is an "excuse" for using numbered indicators is in O-specs. And even that is a poor excuse for new code as you should be using externally described printer files!

The "IBM approved" method of naming indicators is the Indicator Data Structure (INDDS) option for files. But that only works with externally described files - so program described printer files cannot take advantage of the capability.

Also INDDS creates a separate set of 99 indicators for each structure used. That can be useful but it also means that INDDS can be hard to use in existing code where *INnn indicators are already in use since indicator 99 in a display file that uses INDDS is NOT the same thing and *IN99. I'll look at how to address that problem on the next chart.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 10 of 30

Mapping *INnn Indicators to NamesUse a pointer to map the standard *IN indicator array • An easy way to give names to all of your indicators • _nn at the end of the name documents actual indicator number

✦ Thanks to Aaron Bartell for introducing us to this variation I have also used a group field to map the set of error indicators • Enabling you to clear or set them as a group

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 Pos(31); Error_31 Ind Overlay(errorInds_31_33); StDateError_32 Ind Overlay(errorInds_31_33: *Next); EndDateError_33 Ind Overlay(errorInds_31_33: *Next); end-ds;

NotesNote that I used a group field to define the set of indicators 31 - 33 as the single field errorInds_31_33. I could also have defined it as being Char(3) Pos(30). This allows me to simply code things like Clear errorInds_31_33; I use this technique for subfile control indicators and use constants with names such as DISPLAYSUBFILE and CLEARSUBFILE which are defined as patterns of character 1s and 0s as appropriate. Helps to make the code far more readable.

Unlike the INDDS approach, these named indicators DO directly affect the content of their corresponding *IN indicator. So, using the above example, if we code Error = *On then indicator *IN30 was just turned on. This often makes this a better approach for those who use program described (i.e. O-spec) based files rather than externally described printer files.

Those of you who use the *INKx series of indicators to identify function key usage need not feel left out. A similar technique can be used. In this case the pointer is set to the address of *INKA. The other 23 function key indicators are in 23 bytes that follow. IBM have confirmed many times that for RPG IV this will always be the case.

dcl-ds FunctionKeys Based(pFunctionKeys);

F3_Pressed Ind Pos(3); F12_Pressed Ind Pos(12);

end-ds;

dcl-s pFunctionKeys Pointer Inz(%Addr(*InKA));

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 11 of 30

Templates for Data Structures etc.Templates are a great way of ensuring standards in the shop • The same definition is used in all programs

✦ Therefore the same field names, sizes and data types You can also use them to create your own "data type" • e.g. A phone number, account number, part number, etc.

dcl-ds baseAddress_T TEMPLATE; street1 varchar(50); street2 varchar(50; city varChar(30); state char(2) inz(‘TX'); zip char(5); zipPlus char(4); end-ds;

dcl-ds invoiceInfo qualified; mailAddress likeDS(baseAddress_T) inz(*likeDS); shipAddress likeDS(baseAddress_T) inz(*likeDS); end-ds;

I use the _T naming convention for all

templates.

NotesTemplates are an incredibly useful way of ensuring consistency in definitions between programs.

They can also have standard initialization values which will be inherited by any any DS that "clones" them if the Inz(*LikeDS) option is used.

I use these all the time for defining DS that are used as parameters. being able to simply code LikeDS(xxxxx) on a parameter definition is such an easy way of ensuring consistency and in the called routine avoids the need to define the DS as the individual fields can be referenced directly.

The use of _T at the end of the name makes it obvious that my LikeDS reference is to a template. It was a convention I first encountered in IBM documentation and have used it ever since.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 12 of 30

Handling Error MessagesTraditional Approach • ERRMSGID in DDS • Use a Message Subfile

✦ Send messages to program message queue ✦ Message subfile displays messages in program message queue

This approach only works green screen environments • And that is not where we are headed

You need an approach that is far more versatile • But could still be used by green screen apps

My colleague Paul Tuohy has published an alternative approach

Handling Error Messages - Paul's ApproachOriginally published by ITJungle • "Getting the Message" Parts 1 and 2

✦ itjungle.com/fhg/fhg101409-story01.html ✦ itjungle.com/fhg/fhg102109-story01.html

Basically it uses a service program that stores messages • They are stored in an array

✦ But could be stored anywhere • Subprocedures are supplied to:

✦ Add a message ✦ Clear stored messages ✦ Retrieve a message ✦ Return the number of stored messages ✦ Etc.

On the next chart I'll show you briefly how it is used

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 13 of 30

Using Paul's Message ProceduresThis is the basic message layout • A DS template of course!

And this is how the APIs are used dcl-Ds message likeDS(def_MsgFormat) end-Ds;

clearMessages(); addMessage('ERR0001': employeeID: %char(salary)); addMessage('ERR9001': *omit: employeeID); addMessageText('Bad things happened!!!'); if (messageCount() > 0); for i = 1 to messageCount(); getMessage( i: message); // Do what you will with the message endFor; endIf;

dcl-Ds def_MsgFormat qualified template; msgId char(7); msgText char(80); severity int(10); help char(500); forField char(10); end-Ds;

NotesIf you use a standard method for reporting errors, such as this, you can more easily change the interface. Use a green screen and the errors can be retrieved and placed on the message queue as before. Use a browser interface and the messages can be retrieved via Javascript/JSON calls. Use the code in a web service and the error messages can be wrapped in XML/JSON/whatever.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 14 of 30

Data Structure I/O - Useful in Many WaysDefine the DS using LIKEREC • DS will have the same format as the named record

✦ Similar to externally described DS but can also be used to define parameters or nested DS

• Record must be for a file used by the program ✦ Or at least a file defined as a Template

Control of which fields are included is based on the field category • Specify *INPUT, *OUTPUT, *KEY or *ALL depending on type of operation

✦ When *ALL is used compiler works out which fields to use for input and for output

// Record formats EmpSel, and Select are included in this file Dcl-F MSTDSP Workstn; Dcl-ds SubFileRecs LikeRec( EmpSel : *OUTPUT ) Dim(20); Dcl-ds DisplayData Qualified; SelectInp LikeRec( Select : *INPUT ) SelectOut LikeRec( Select : *OUTPUT

NotesDS I/O offers many advantages not the least of which (as with all qualified references) being that there is never any doubt as to the source of the data.

Another benefit is that you can keep multiple copies of a record in the program and easily compare the same field in multiple records.

In the example that follows we are only going to be comparing the record at the global level - but we could of course compare on a field by field basis. For example, you could compare the record image and if there was a mis-match go ahead and compare individual fields to see if the changed fields impact the planned update. If the field changed between the initial read and subsequent access is not one that was changed by the user then perhaps it is fine to go ahead and perform the update.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 15 of 30

Avoiding Record Locks Using DS I/O These are the basic file and record definitions for my sample • I used IndDs for the display file as an alternative to mapping *In • Record image for the customer file is defined as a 2 element array • Image for display file will be used for ExFmt so it also uses *All

For a more detailed description see my article in RPG Guru • itjungle.com/fhg/fhg030315-story01.html Dcl-F TestFile Usage(*Update:*Delete) Keyed; Dcl-F CustUpdd WorkStn IndDS(displayInds);

Dcl-Ds displayInds; exit_03 Ind Pos(3); update_06 Ind Pos(6); // Request Update of Record canUpdate_96 Ind Pos(96); // Update option available End-Ds;

Dcl-Ds custRec Likerec(TestRec: *All) Dim(2);

Dcl-Ds displayRec LikeRec(Disprec: *All);

NotesToo often master file update programs are written that pay no attention to the record lock created. Locking the record while the user makes changes is not a problem - as long as the user doesn't stop to take a phone call in the middle of the update resulting in a 5 minute lock. Even worse is the case where they wander off to grab a coffee, meet a colleague and chat for 10 minutes. That lock is just a little landmine waiting for some other application to trip over it.

The process used here involves simply reading the record without a lock initially and storing an image of the record. Once the user has made the changes and submits the update the record is read again (this time with a lock) and the new record image compared with the one stored. If the two match the update can proceed - if not the user must be notified, shown the current data, and invited to try again.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 16 of 30

Avoiding Record Locks ... - Main LoopMain logic is in LoadRecord() and ProcessUpdate() procedures • This is just the main driver logic

Note the use of the displayRec DS with the ExFmt operations

ExFmt DispRec displayRec; // Output initial display

DoW Not exit_03; // Continue until user says to exit If update_06; ProcessUpdate(); ElseIf exit_03; // Exit when requested *InLR = *On; Leave; Else; // Treat as a new request and load the data LoadRecord(); EndIf; ExFmt DispRec displayRec; canUpdate_96 = *Off; EndDo; Return;

Avoiding Record Locks ... LoadRecord()Notice the use of the N(o lock) extender • The record will be displayed without being locked

The record is loaded into element 1 of the array • That image is then used in Eval-Corr to load the display record

Dcl-Proc LoadRecord; Dcl-Pi *N End-Pi;

Chain(N) displayRec.ARCode TestFile custRec(1);

If %Found(TestFile); Eval-corr displayRec = custRec(1); displayRec.message = 'Press F6 to update customer + or <Enter> to view new customer'; canUpdate_96 = *On; Else; displayRec.message = ('** Error: Customer ' + displayRec.ARCode + ' not found **'); EndIf;

End-Proc LoadRecord;

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 17 of 30

Avoiding Record Locks ... ProcessUpdate()Record is read into element 2 of the array - this time record is locked • It is then compared with element 1 (the original) to check for changes

If no changes then the update proceeds ✦ Using the data from the display record to update element 1 ✦ Then that element is used as the source data for the Update operation

If a change was detected • Operator is notified and the latest record is set as the current one

Dcl-Proc processUpdate; Dcl-Pi *N End-Pi;

Chain displayRec.ARCode TestFile custRec(2); // Read and lock record If custRec(1) = custRec(2); // Compare latest record with original Eval-corr custRec(1) = displayRec; // Update record image Update TestRec custRec(1); // and apply the update displayRec.message = ('Updated Customer ' + displayRec.ARCode); Else; displayRec.message = ('** Error: Customer ' + displayRec.ARCode + ' had been updated - please try again.'); Eval-corr displayRec = custRec(2); // Reset display screen custRec(1) = custRec(2); // and set image 1 to current record canUpdate_96 = *On; Unlock TestFile; EndIf;

NotesAfter the article I wrote on this topic appeared in IT Jungle a reader wrote in and queried my use of a DS array to hold the record images. He preferred to use two different DS, each based on the same record layout. But unlike my example that uses *All, in his case he defines one using the input layout and the other the output - which of course are normally the same thing for a database but RPG can still be fussy about using the right one for the right task.

For him this was less confusing. Perhaps you feel the same but for me I prefer to use the array approach for two reasons.

First using an array makes it very obvious that the two structures being compared are identical. With the two DS approach you no assurance that both DS used the same definition. Secondly, the approach I use is an evolution of an earlier approach that used a Multiple Occurrence DS (MODS) to achieve the same thing. I felt that anyone familiar with that approach would find this one easier to follow.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 18 of 30

MONITOR for ErrorsMonitor allows you to trap any errors that occur in a block of code • Block can range from a single line to the entire program

Can be used for all operations • Even those that do not support the (E) error extender

Similar in some ways to CL's MONMSG Errors that are not caught will behave as normal • i.e. Get caught by the PSSR, or cause the green screen of death

Monitor; Read Customer; If Not %EOF(Customer); line1 = %SUBST(inputData : %SCAN('***': inputData) + 1); EndIf; On-Error 1211; // << Use named constants - but you can hard code // ... handle file-not-open On-Error 00100; // ... handle string error and array-index error On-Error; // ... handle all other errors EndMon;

NotesA monitor group monitors all of the code between the Monitor operation and the first On-Error operation. If an error is detected, then control passes to the first On-Error operation etc. If it matches the condition specified there the specified action is taken and the exception considered to have been handled. If the condition is not matched then the next On-Error is checked and so on until either the exception has been handled or it is determined that there is no action specified. At that time the normal RPG exception handling will kick in. In addition to specific error code values, the special values of *PROGRAM, *FILE and *ALL can be specified.

On-Error; and On-Error *All; are equivalent.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 19 of 30

MONITOR for ErrorsThat first example used numeric literals for the error codes • DON'T DO THAT ! • Use constants with meaningful names

A standard set can easily be incorporated via a /COPY • Feel free to email me for copies of the ones I use

✦ An extract is shown on the Notes page

Monitor Read Customer; If Not %EOF(Customer); line1 = %Subst(inputData : %Scan('***': inputData) + 1); EndIf; On-Error FILEISCLOSED; // ... handle file-not-open On-Error STRINGRANGEERR; // ... handle string error e.g. line1 = *Blanks On-Error *All; // ... handle all other errors EndMon;

Extracts From a Sample Status Code File

// Value out of range for string operation

Dcl-C STRINGRANGEERR 00100;

// Variable-length field has a current length that is not valid.

Dcl-C FIELDLENERR 00115;

// Table or array out of sequence. Dcl-C ARRAYSEQERR 00120;

// Array index not valid

Dcl-C ARRAYINDEXERR 00121;

// OCCUR outside of range Dcl-C INVALIDOCCUR 00122;

// I/O operation to a closed file.

Dcl-C FILEISCLOSED 01211;

// OPEN issued to a file already open. Dcl-C FILEISOPEN 01215;

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 20 of 30

A Different Approach to Program ErrorsThis example shows the traditional way of doing things • Often code like this has a change marker on it

✦ i.e. the idea that stock would ever be zero was not considered by the original programmers

Dcl-S QtyInStock Packed(5); Dcl-S CostOfGoods Packed(9:2); Dcl-S AverageCost Packed(9:2);

// The "Old" way If QtyInStock <> 0; AverageCost = CostOfGoods / QtyInStock; // and other related calcualtions Else; AverageCost = 0; // etc. etc. EndIf;

NotesOne of the problems with this type of code is that we often leave future maintenance programmers wondering just why we were testing for zero. They may not really find out for a page or two - particularly if viewing the program using the limited visibility of SEU - i.e. a mere 18 lines at a time.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 21 of 30

A Different Approach to Program ErrorsThis is my preferred approach because ... • It is far more efficient if a stock level of zero is a rare situation • It makes the expected execution path more obvious • It is also easy to add "just in case" logic to a block of calculations

Dcl-S QtyInStock Packed(5); Dcl-S CostOfGoods Packed(9:2); Dcl-S AverageCost Packed(9:2);

Dcl-C DIVBYZERO 102;

// The New Way Monitor; AverageCost = CostOfGoods / QtyInStock; // and other related calculations On-Error DIVBYZERO; AverageCost = 0; // etc. etc. EndMon;

NotesThe nice thing about this approach is that our main line logic doesn't become cluttered by "we hope it never happens but ..." type code. If we wished to trap separately for other possible types of error, we could simply add more ON-ERROR operations together with their associated code. Don't forget that by coding ON-ERROR *ALL (or simply leaving the extended factor2 field blank) we can supply catch-all coding for any other error that may occur. As the example is written, any other error will simply blow up with the normal two-line "screen of death" or trigger the PSSR if one is present.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 22 of 30

Another Example - Using DS I/OHere DS I/O is used to avoid decimal data errors (DDEs) on a Read • So errors only occur when and if the field is used • You can then programmatically deal with the error or report it

DS I/O can resolve Decimal Data Errors temporarily • At least until you can fix the program writing the "bad" data in the first place

Dcl-F MyFile; Dcl-DS inputData LikeRec(Record1);

Dcl-S DEC_DATA_ERR 907;

Read Record1 InputData; // Read directly into DS - so no data error

Monitor; Num1 = InputData.InpNum1; Num2 = InputData.InpNum2; Num3 = InputData.InpNum3;

On-Error DEC_DATA_ERR; // Place code here to react to Dec Data Errors EndMon;

NotesA few things to note about this example: First, it wouldn't be sufficient to simply put in the MONITOR code without also adding the DS name to the READ operation. We need to avoid the possibility that a Decimal Data Error could be triggered on the read itself as the data is moved to its internal storage area. Using DS I/O prevents the error from being signalled on the READ because the database record is moved as a single large character field to the data structure named InputData.

In order to ensure that the DS exactly matches the layout of the record buffer, we are taking advantage of the LIKEREC keyword. When we use LIKEREC the compiler guarantees that the layout of the DS exactly matches the layout of the record buffer.

Because of the use of the LIKEREC keyword, the DS implicitly becomes a Qualified DS. Therefore we use the DS name qualification syntax to specify we want the data from the DS fields. Note that because of the DS I/O "normal" fields (e.g. InpNum1, InpNum2, etc.) will not contain the data from the record. We bypassed those fields by reading the data directly into the DS .

Of course, the code to report and fix the errors can be challenging in an example like this, since there are 3 possible fields in error. If you only plan to report that the record as a whole is in error, this code will do the job. But if you want to attempt to identify the specific field in error and take corrective action you will need to do a little more work.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 23 of 30

Take Advantage of I/O EnhancementsKLISTs are so 1900s! • %KDS is a free-form replacement for KLIST • Used in Factor 1 position instead of a KLIST name

Syntax: %KDS(keyDSName { : numberOfKeysToUse } ) • The Optional 2nd parameter specifies # of fields to be used in partial key

All keyed operations can use this new BIF • Automatically generate required DS by using LIKEREC(recname : *KEY)

// The ProductKeys DS contains the three fields that make up the // key for the Product File (Division, PartNumber and Version) Dcl-DS ProductKeys LikeRec(ProductRec : *Key);

// Read a specific record using full key Chain %KDS(ProductKeys) ProductRec;

// Position file based on first 2 key fields SetLL %KDS(ProductKeys : 2 ) ProductRec;

NotesUntil this feature was introduced, there were two ways to specify the key for a keyed operation. Specify the name of a single field or the name of a KLIST. KLISTs always annoyed me because you had to wander off elsewhere in the program to actually find the list. Only then did you know what keys were being used. Not only that, but if you wanted to be able to use all keys, or two keys, or ... then you needed a separate KLIST for each set of keys. This new support offers both an improved alternative to the KLIST approach and a new method of directly specifying the keys on the operation itself.

The new "KLIST" (actually a BIF called %KDS - Key Data Structure) references key definitions in the D specs where they belong. It uses the *KEY option of LIKEREC. You can use this to automatically generate a DS containing the file's key fields. This structure can then be referenced in the I/O operation by specifying the DS name to the new %KDS function.

So how do you specify that a partial key is to be used? Just use the second parameter of %KDS to tell the compiler how many of the key fields are to be used.

We will look at the second method on the next chart.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 24 of 30

Take Advantage of I/O Enhancements (Contd.)Keys can now be simply specified as a list of values • List is specified in Factor 1 position, and enclosed in parentheses

Any type of field, literal or expression can be used • As long as the base type matches AND the key(s) are in parentheses • Compiler performs any required conversions

✦ Using the same rules as for EVAL operations • Note that the absence of parentheses changes the behavior

✦ See final example below

// Read using specified keys Chain (Division : PartNumber : Version) ProductRec;

// Position file based on first 2 key fields SetLL (Division : PartNumber) ProductRec;

// Position file to specified Part number in Division 99 SetLL ( '99' : PartNumber) ProductRec;

Chain custNum Record; // custNum must exactly match attributes of key Chain (Field) Record; // custNum only needs to match key's base type

NotesThe second method is an extension of the current ability to specify a single field as the key (the old Factor 1).

Instead of a single field, you can now supply a list of fields. The list should be specified within parentheses with colons (:) used to separate the individual key elements.

Note that the key elements do not have to be fields, they can be any character expression. The compiler will perform conversion if required.

Note that even if you have a single field key, it is often better to enclose it in parentheses because with the parentheses the field specified is not required to match exactly in type and size, but without parentheses they must match. This is much like the size & type accommodation made by the compiler with the use of the CONST keyword in prototypes for parameters.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 25 of 30

Update in Safety - %FIELDSWhich fields in the file does the first UPDATE modify?

Can you rely on the comment ?

With %Fields you have a 100% guarantee • Only the requested fields will be updated

Dcl-F Product Keyed Usage(*Update); // OK - now we update the Unit Cost and Price fields Update Productl; // Update only UnitCost and UnitPrice fields Update Product %Fields( UnitCost : UnitPrice );

NotesIn many ways this is both one of the best of the new I/O features and one of the most under utilized. You should be using this in EVERY program that is supposed to only perform updates on specific fields.

It provides a great way to protect your code from the worst efforts of (shall we say) "less-gifted" programmers. The list of fields is specified using the new BIF %Fields. Only those fields specified will be updated.

Why is this so useful? Suppose that, during the operation of the program, only certain fields in the file should be subject to change. By specifying those fields to the UPDATE op code, you are assured that only those fields will be changed. If during subsequent maintenance tasks a mistake is made and the value of a field that should not be modified is accidentally changed in the code, it will have no effect on the database. Only if the %Fields list is also modified can this error result in database corruption.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 26 of 30

Use Short-form Expression SupportA lot of RPGers really don't like this • They're wrong ...

It is great when using long/subscripted/qualified names • Particularly if like me you are are a poor typist !! • And it is a notation style that will be familiar to newcomers

Any math or string expression can be shortened • e.g. X = X + 1; becomes X += 1;

✦ += is used for addition or string concatenation ✦ -= for subtraction ✦ and you can guess what the others are ...

// This calculation MonthTotal = MonthTotal + TotalSale; // can be shortened to this MonthTotal += TotalSale;

// Be careful though! =+ and += are not the same x =+ 1; // This is the same as x = 1;

NotesNumeric operations now support short-form notation for certain functions. Prior to this release, an addition of the type X = X + 1 required that you repeat the name of the target field. Some people considered this a step backwards since the old ADD op-code offered a short-form notation that only required the target field to be specified once, in the result field. e.g. ADD 1 X.

With this new feature, the expression can be written as X += 1. Similar shorthand can be used for subtraction, multiplication, division, and exponentiation.

Do not make the mistake of coding it like this: X =+ 1 This simply puts a positive value of 1 into X.

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 27 of 30

Short Form ExpressionsStill Don't like it ?

Why not ?

• It is just a straight reversal - just like all other free-form options ! ✦ Result comes first ✦ Followed by operation ✦ And then factor 2 !

MonthTotal += TotalSale;

* Which version would you have coded ? C MonthTotal ADD TotalSale MonthTotal

C ADD TotalSale MonthTotal

Always Use Offsets !Many IBM supplied programs use special “buffers” • Many APIs • Trigger buffers

If documentation mentions an offset – USE IT! • Do NOT hard code positions • Bad things can happen

An example • Prior to V5R1 many programmers hard coded positions for before/

after images in the trigger buffer • In V5R1, IBM changed the rules for the buffer

✦ Each image is now aligned on a 16 byte boundary • So you only had a 1 in 16 chance that your layout was correct ! • “Lucky” programmers just got a decimal data error

✦ Others had their programs "run" - but the results ...

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 28 of 30

Using Offsets - Tigger BuffersThe layout of the actual buffer is shown on the notes page • This code sample just shows how the offsets are used

// Before and After Record Images Dcl-DS OldRecord ExtName('YOURFILE') Qualified Based(pOldRecord) End-DS; Dcl-DS NewRecord ExtName('YOURFILE') Qualified Based(pNewRecord) End-DS;

Dcl-Pi play ExtPgm; triggerBuffer LikeDS(triggerBuffer_T); triggerBufferLength Int(10); End-Pi;

// Map record image DS to Trigger Buffer using Pointers pOldRecord = %Addr(triggerBuffer) + triggerBuffer.oldOffSet; pNewRecord = %Addr(triggerBuffer) + triggerBuffer.newOffSet;

// Any Trigger Logic needed goes here . . .

NotesThis is a basic template for the Trigger Buffer parameter.

The *N fields are reserved filler areas and are not currently used.

Dcl-DS TriggerBuffer_T Template;

FileName Char(10); LibraryName Char(10);

MemberName Char(10);

Event Char(1); Time Char(1);

CommitLock Char(1);

*N Char(3); CCSID Int(10);

RRN Int(10);

*N Int(10); OldOffset Int(10);

OldLength Int(10);

OldNullOff Int(10);

OldNullLen Int(10); NewOffset Int(10);

NewLength Int(10);

NewNullOff Int(10); NewNullLen Int(10);

End-DS;

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 29 of 30

That's All Folks !

Any Questions ?

© Partner400, 2018 RPG Tips and Techniques Lecture Notes: Page 30 of 30