Whats New and Exciting in RPG
Whats New and Exciting in RPG
Presented by
Scott Klement
http://www.scottklement.com
This session is
organized by date…
i Next
IBM i 7.5
IBM i 7.4
IBM i 7.3
A chart at the end will
IBM i 7.2
detail the releases.
IBM i 7.1
3
IBM No Longer Waits For Next Release
• Prior to IBM i 7.1 ("V7R1") to get new RPG feature, you waited
for next release.
• Couldn't install it right away? Had to wait longer.
• Needed to support both newer and older releases? Wait longer.
• Could be 5+ years before you could use a feature you wanted.
5
Support in SEU
There is none.
SEU has not had updates since the release of IBM i 6.1
That was March 21 2008!
Enhancements to SND-MSG
• You can now send *STATUS, *DIAG, *COMP and *NOTIFY messages
• The target (%TARGET) call stack can be identified by *PGMBDY,
*CTLBDY, or *EXT (previously was only *SELF or *CALLER)
These enhancements were announced, but are not yet GA. PTFs will be
made for 7.4 and 7.5 "soon".
7
SND-MSG
(Spring 2022) in 7.5, PTF for 7.4, 7.3. (Enh in Spring 2024) PTFs for 7.5, 7.4
• SND-MSG opcode lets you send program messages from RPG (like SNDPGMMSG from CL)
• Defaults to an *INFO message but can also be *ESCAPE.
• Other types (*DIAG, *COMP, *NOTIFY, *STATUS) were added in Spring 2024.
• The %MSG BIF is used to control the msg id/msg file.
• Default for *INFO is no message id, *ESCAPE is CPF9898
• The %TARGET BIF can be used to control which call stack entry the message is sent to. It allows
*SELF or *CALLER, plus an optional stack count.
• In Spring 2024, *CTLBDY, *PGMBDY and *EXT were added to the %TARGET BIF as well.
// these send *INFO messages that will appear in the job log
One common use of sending messages in RPG (aside from reporting errors and putting
information in the job log) is to load a "message subfile" (DDS MSGSFL)
**free A DSPSIZ(*DS3)
ctl-opt DFTACTGRP(*NO) ACTGRP(*NEW); A R MSGSFL
A SFL
dcl-f MSGSFL4D workstn; A SFLMSGRCD(15)
A MSGKEY SFLMSGKEY
dcl-ds sds PSDS; A PGMQ SFLPGMQ(10)
PgmQ *proc; A*
end-ds; A R MSGCTL
A SFLCTL(MSGSFL)
dcl-s num packed(4: 0); A SFLDSP
A SFLDSPCTL
snd-msg 'This is a test message' A SFLINZ
%target(PGMQ); A N99 SFLEND
A SFLSIZ(20)
write msgctl; A SFLPAG(5)
exfmt msgscn1; A PGMQ SFLPGMQ(10)
A R MSGSCN1
*inlr = *on; A OVERLAY
9
Spring 2024 SND-MSG Examples
10
11
CONST on a Standalone Variable
dcl-proc myproc;
// ... code that may swap the current user profile goes here
originalUserid = *BLANK;
aaaaaaaaaaaaaa
endif;
end-proc;
12
dcl-proc create_new_account;
dcl-pi *n;
new_account likeds(account_t);
cust_id int(10) const;
cust_type int(10) const;
end-pi;
...
end-proc; 13
https://ibm.biz/rpg_cafe
%LEFT and %RIGHT
(Fall 2023) PTF for 7.4, 7.5
• %LEFT takes a substring of the leftmost portion of a string
• %RIGHT takes a substring of the rightmost portion of a string
• Supports the CharCount *NATURAL options (coming up soon)
New
// leftChars = 'abc'
// rightChars = 'efg’
Old
// leftChars = 'abc'
// rightChars = 'efg'
14
Simple Enumerations
(Fall 2023) PTF for 7.4, 7.5
• DCL-ENUM / END-ENUM declares simple enumerations.
• Basically is a grouping of related named constants, list possible values for an item
• Can be QUALIFIED like a data structure
dcl-enum STATUS;
Old
New
if ndesc = DB2NULL.notNull;
// use 'desc'
endif;
15
Simple Enumerations
(Fall 2023) PTF for 7.4, 7.5
exec SQL
select name, dept, type
into :name :nname, :dept, :type
from EMPMAST
where id = :myEmpNo;
if nname = DB2NULL.notNull;
// use 'desc'
endif;
select type;
when-is employee_type.regular;
// handle regular employee
when-is employee_type.manager;
// handle manager
when-is employee_type.executive;
// handle the execs
end-sl;
16
%PASSED
(Spring 2023) PTF for 7.5, 7.4
• %PASSED checks both if a given parameter was passed and wasn't *OMIT-ed.
• Previously would need to check %PARMS, %PARMNUM and %ADDR
dcl-pi *n; dcl-pi *n;
Old
New
// p1 is required, but *OMIT could be passed // p1 is required, but *OMIT could be passed
if %passed(p1); if %addr(p1) <> *null;
dsply ('Parameter p1 = ' + %char(p1)); dsply ('Parameter p1 = ' + %char(p1));
else; else;
dsply ('Parameter p1 is not available'); dsply ('Parameter p1 is not available');
endif; endif;
// p2 is optional // p2 is optional
if %passed(p2); if %parms >= %parmnum(p2);
dsply ('Parameter p2 = ' + p2); dsply ('Parameter p2 = ' + p2);
else; else;
dsply ('Parameter p2 is not available'); dsply ('Parameter p2 is not available');
endif; endif;
// p3 is optional, and *OMIT could be passed // p3 is optional, and *OMIT could be passed
if %passed(p3); if %parms >= %parmnum(p3) and %addr(p3) <> *null;
dsply ('Parameter p3 = ' + p3); dsply ('Parameter p3 = ' + p3);
else; else;
dsply ('Parameter p3 is not available'); dsply ('Parameter p3 is not available');
endif; endif;
17
%OMITTED
(Spring 2023) PTF for 7.5, 7.4
• %OMITTED checks if a given parameter was omitted (vs. not passed, etc.)
• Primarily useful to distinguish between no parameter passed vs. *OMIT passed.
• Equivalent to checking %parms() >= %parmnum(p2) and %addr(p2) = *null
dcl-pi *n;
p1 varchar(20) const options(*nopass : *omit);
end-pi;
18
SELECT/WHEN-IS/WHEN-IN
(Spring 2023) PTF for 7.5, 7.4
• The SELECT opcode now allows an expression to be provided after the opcode.
• WHEN-IN can be used to check if that expression is IN a list, range, array, etc.
• WHEN-IS can be used to check if it is equal to a value.
• Similar to the switch/case you see in some other languages.
dcl-pi *n;
customer packed(4: 0);
active ind;
end-pi;
<pcml version="8.0">
<program name="MYPGM" entrypoint="MYPGM">
<data name="customer" type="packed" length="4" precision="0" usage="inputoutput" />
<data name="active" type="char" length="1" boolean="true" usage="inputoutput" />
</program>
</pcml>
20
%SPLIT w/*ALLSEP
• %SPLIT was added in Spring 2021, but the *ALLSEP option is newer, Spring 2023!
21
RPGPPOPT w/Long Records
For example, suppose you compile from the IFS, and you have records that are long (maybe 200 characters
long – there is no limit in the IFS!)
This is because it preprocesses it into QTEMP/QSQLPRE – a source file, which has a limit of 100
characters per line.
22
OPTIONS(*CONVERT)
(Fall 2022) PTF for 7.5, 7.4
• OPTIONS(*CONVERT) on a prototyped parameter with CONST or VALUE will
automatically convert numbers, dates, times and timestamps to character. Pointers will be
treated like options(*string).
• Useful when a subprocedure may want data passed from different sources, and you don't
want to go to the extra level of implementing overloading.
WriteMsg(123);
WriteMsg(%date());
WriteMsg('Scott is cool!');
return;
dcl-proc WriteMsg;
dcl-pi *n;
msg varucs2(100) const options(*convert);
end-pi;
23
%CONCAT
(Fall 2022) in PTF for 7.5, 7.4
• %CONCAT will concatenate a list of items, joining them with a given separator.
• Alternately, you can specify *NONE if you do not wish to use a separator.
csv = %concat(',' : '1001' : 'Acme Inc' : 'Scott Klement' : '123 Main St');
// csv = '1001,Acme Inc,Scott Klement,123 Main St'
24
%CONCATARR
(Fall 2022) PTF for 7.5, 7.4
• Similar to %CONCAT, except %CONCATARR will concatenate the elements of an array.
data(1) = '1001';
data(2) = 'Acme Inc';
data(3) = 'Scott Klement';
data(4) = '123 Main St';
25
CHARCOUNT NATURAL
(Fall 2022) PTF for 7.5, 7.4
• When coding UTF-8 or UTF-16 data, the length of a character can vary. (UTF-8 characters
are 1-4 bytes long, UTF-16 are 2 or 4 bytes.)
• With CHARCOUNT(*NATURAL) BIFs like %SUBST, %SCAN, %XLATE and %SPLIT will
work with the number of characters (regardless of the byte size of each.)
• The old mode is called CHARCOUNT(*STDCHARSIZE) and the preceding BIFs will treat
the numbers as bytes (for CHAR) or double-bytes (for UCS-2) regardless of the length of
the individual character.
• Can specify ctl-opt (or h-spec) CHARCOUNT and CHARCOUNTTYPES
• Or /CHARCOUNT directive
• Or CHARCOUNT file keyword (dcl-f or f-spec)
• Or specify *NATURAL or *STDCHARSIZE on the BIF itself.
• The %CHARCOUNT BIF can be used to get the length in natural characters
string = 'ábcdë';
len = %len(string);
// len = 7 (á and ë are 2-byte characters)
len = %charcount(string);
// len = 5
26
CHARCOUNT NATURAL
dcl-s string varchar(20) ccsid(*utf8);
dcl-s string2 varchar(20);
string = 'ábcdë';
string = 'ábcdë';
/charcount stdcharsize
monitor;
QCMDEXC(cmd: %len(cmd));
on-excp 'CPF9801';
// file wasn't found
on-excp 'CPF9810';
// library wasn't found
endmon;
monitor;
QCMDEXC(cmd: %len(cmd));
on-excp 'CPF9801': 'CPF9810';
// Either the library or the file wasn't found
endmon; 28
dcl-pi *n varchar(30);
CustNo packed(7: 0) value;
end-pi;
end-proc;
monitor;
name = getCustomerName(custNo);
// got the name!
on-excp 'CPF9898';
// customer wasn't found
endmon;
29
%MAXARR / %MINARR
inv_amt(1) = 27700.95;
inv_amt(2) = 12345.67;
inv_amt(3) = -4;
lowest = %MINARR(inv_amt);
msg = 'Lowest total invoice: ' + %char(inv_amt(lowest));
highest = %MAXARR(inv_amt);
msg = 'Highest total invoice: ' + %char(inv_amt(highest));
30
31
DEBUG(*CONSTANTS)
(Fall 2021) in 7.5, PTF for 7.4, 7.3
• DEBUG(*CONSTANTS) is a CTL-OPT (or H-spec) keyword that lets you view
the values of constants while debugging.
32
myString = 'mIxEdcaSe';
myString = %lower(myString);
upperCase = %upper(myString);
titleCase = %lower(upperCase:2);
33
%SPLIT
(Spring 2021) in 7.5, PTF for 7.4, 7.3
• %SPLIT built-in function splits a string when a given substring is found.
• The result is an array of strings
array = %split(record:'|');
invoice.name = array(1);
invoice.amount = %dec(array(2):9:2);
In this example, two fields are stored in one string, separated by the pipe character.
%Split is used to split them into two array elements, which can then be assigned to
separate fields in a data structure.
34
FOR-EACH
(Fall 2020) in 7.5, PTF for 7.4, 7.3
• FOR-EACH loop opcode
Loops through all of the elements in an array. Can be used together with %SUBARR
35
FOR-EACH with %SPLIT
• Since %SPLIT returns an array, it can be used with a FOR-EACH loop
• In this example, "shelves" is a list of shelves separated by commas
• %SPLIT could be used in a similar manner to the way %LIST was used before.
endfor;
Split is very useful when reading IFS files in CSV, Tab or Pipe-Delimited format, or when a
database field or user input contains a delimited list of data.
36
%SPLIT w/*ALLSEP
(Spring 2023) PTF for 7.5, 7.4
• The %SPLIT built-in function now supports all separators via the *ALLSEP option.
• Without this option %SPLIT would ignore leading, trailing or consecutive separators.
Without *ALLSEP With *ALLSEP
dcl-s csv varchar(1000); dcl-s csv varchar(1000);
dcl-s fld varchar(20); dcl-s fld varchar(20);
if Qty IN %range(1:999);
msg = 'Quantity acceptable.';
endif;
Since %LIST returns a temporary array, it can also be used with the FOR-EACH
loop I discussed earlier.
39
EXPROPTS(*STRICTKEYS)
(Spring 2021) in 7.5, PTF for 7.4, 7.3
• EXPROPTS(*STRICTKEYS) CTL-OPT (H-spec) keyword requires the length,
ccsid, and decimal places to match (or be smaller than) that of the database
ctl-opt EXPROPTS(*STRICTKEYS);
itemno = 953.5;
chain (itemno) PRODP;
Without EXPROPTS(*STRICTKEYS) the above would compile and would retrieve record with
key=953 despite that the key shouldn't have decimal places.
DEBUG(*RETVAL)
(Fall 2020) in 7.5, PTF for 7.4, 7.3
• DEBUG(*RETVAL) control option and _QRNU_RETVAL
end-proc;
41
EXPROPTS(*ALWBLANKNUM:*USEDECEDIT)
myChar = '';
myDec = %dec(myChar: 7: 2);
// With *ALWBLANKNUM, myDec = 0
// Without: RNX0105: A character representation of a numeric value is in error.
myChar = '0.12';
myDec = %dec(myChar: 7: 2);
// With *USEDECEDIT, myDec = 12.00
// Without: myDec = 0.12
myChar = '10.000,05';
myDec = %dec(myChar: 7: 2);
// With *USEDECEDIT, myDec = 10000.05
// Without: RNX0105: A character representation of a numeric value is in error.
42
// These two will never be the same, but digits after the 6th
// are not accurate times.
KEY.PostZip = 53207;
KEY.PostCity = 'SAINT FRANCIS';
KEY.PostState = 'WI';
// retrieves the first record that matches the first key (PostZip)
num_keys = 1;
chain %kds(KEY:num_keys) POSTP REC;
45
OVERLOAD
(Fall 2019) in 7.5, PTF for 7.4, 7.3
• OVERLOAD keyword allows multiple "candidate prototypes" to be considered
• RPG automatically picks the right prototype for the parameter types passed
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
result = format(%date());
result = format(%time());
result = format('MSG0100' : filename);
46
DATA-GEN
(Fall 2019) in 7.5, PTF for 7.4, 7.3
• DATA-GEN opcode to generate JSON, XML, etc documents.
DATA-GEN opcode can be used to generate a structured file format from an RPG
variable.
• Most often, used with a data structure
• Can be a DS array, and can have other DSes or arrays embedded/nested
• Works with a 3rd-party generator program (like DATA-INTO)
• You need a generator program that understands how to create the format that
you wish to create (such as XML or JSON)
myFile = '/tmp/example.json';
DATA-GEN req %DATA(myFile: 'doc=file output=clear')
%GEN('YAJLDTAGEN');
47
OPTIONS(*EXACT)
(Fall 2019) in 7.5, PTF for 7.4, 7.3
• OPTIONS(*EXACT) option for a prototyped parameter.
• Previously, RPG has allows any character variable if it is larger than the
parameter in the prototype
• With this feature, the parameter must match exactly,
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
dcl-pr p1;
parm5 char(5);
end-pr;
dcl-pr p2;
parm5Exact char(5) OPTIONS(*EXACT);
end-pr;
dcl-s fld10 char(10) inz('abcdefghij');
p1 (fld10);
p2 (fld10); // Error, fld10 is not char(5)!!
48
Variable-Dimension Arrays
(Spring 2019) Only in 7.4, 7.5 (and later)
• DIM(*VAR: max-size) lets you create an array with a variable-length. (*VAR is to
arrays what VARCHAR is to CHAR).
• DIM(*AUTO: max-size) lets you create a variable-length array that increases
automatically.
• %ELEM can be used to return the current length of the array, or to
increase/decrease the length
• %ELEM(array:*KEEP) can be used to increase the length without initializing the
new elements
• %ELEM(array:*ALLOC) can be used to get/set the allocated size of the array
• *NEXT can be used to assign a value to the next available array element
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
// x will be set to 5
x = %ELEM(myArray);
49
The *VAR Keyword on DIM
• *VAR keyword means that
• The length of the array isn't always at max
• With *VAR, the size doesn't increase automatically, you control it with %ELEM
• RPG utilizes less memory for the array to begin with
• When the size is changed with %ELEM, RPG will automatically increase the amount of memory
used
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
// Return length to 200, without resetting values. (101 will still be 'xyz')
%ELEM(myArray:*KEEP) = 200;
// If you remove *KEEP in the last example, it would set elements 6-200 to
// the default value (blank in this case, or whatever was set with INZ)
51
The *AUTO Keyword on DIM
• *AUTO keyword allows RPG to automatically increase the array
• Similar to *VAR, except when you access an element beyond the current length
• If beyond the current length, it is automatically increased
• Array does not shrink when you access a lower number
• %ELEM can still be used, and %ELEM will shrink the size
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
53
Variable-Dimension Restrictions
There are some critical restrictions on varying-dimension arrays:
• Must be either a standalone array, or the top-level definition of a DS (not a
subfield)
• Cannot be used in fixed format calculations
• Cannot be based on a pointer. Cannot be imported/exported.
• Cannot be null-capable
• Cannot be used with CTDATA or FROMFILE (compile-time or pre-runtime data)
• You cannot use *VAR or *AUTO in a prototype.
• You can pass a variable-dimension array with options(*VARSIZE), CONST or VALUE
• However, it will not be a variable-length in the procedure you're calling
• You will need to track the length yourself by passing the current length in a separate parameter
• XML-INTO and DATA-INTO do not set the length automatically.
54
DIM(*CTDATA)
(Spring 2019) Only in 7.4, 7.5 (and later)
• DIM(*CTDATA) = array has as many elements as CTDATA records
• Requires you to code **CTDATA at end of module
• Requires one element per record
• If using ALT, the alternating array must also be defined with DIM(*CTDATA)
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
.
.
date zoned(8: 0) dim(3) pos(1); // Old! It works, but hard-coded position
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
.
.
date zoned(8: 0) dim(3) samepos(date01); // New!
56
ON-EXIT
Released in 7.4, PTF for 7.2, 7.3
• ON-EXIT begins a section of code that is run when a procedure ends
• Code is always run, even if procedure "crashes" (ends abnormally)
• Typically used to "clean things up"
• Must be placed in a subprocedure or linear main procedure
• Will not work in a cycle main procedure
dcl-proc MYPROC;
// do whatever here
// For example, update a file, do calculations, etc.
// Set 'in progress' back to 'N', even if the above has an error.
on-exit;
in *lock INPROGRESS;
INPROGRESS = 'N';
out INPROGRESS;
end-proc;
57
Nested Data Structures
Released in 7.4, PTF for 7.2, 7.3
• Qualified data structures can now be nested inside each other
• Previously had to use LIKEDS, and this wasn't always as intuitive
Before After
....+....1....+....2....+....3....+.... ....+....1....+....2....+....3....+....
This is especially helpful with XML-INTO or DATA-INTO since it makes it easier to line up the
DS fields with the document's fields.
58
proc1();
proc2();
. . .
dcl-proc proc1;
name = %PROC(); // will be 'PROC1', uppercase because there's no extproc
end-proc;
dcl-proc proc2;
dcl-pi *n extproc(*dclcase);
end-pi;
name = %PROC(); // will be 'proc2', because of extproc(*dclcase)
end-proc;
60
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
DATA-INTO
• RPG won't try to understand the document
• Calls 3rd-party tool ("parser") which interprets the document
• ...but, DATA-INTO maps result into RPG variable
• ...all you need is the right parser to read any format!
62
DATA-INTO
64
65
Reverse Scanning
Released in 7.3
• %SCANR is like scan, but scans right-to-left
• it will find the last occurrence of a string rather than the first
Pathname = '/home/sklement/test/sales2015.csv';
// pos = 20
Stmf = %subst(Pathname:pos+1);
// Stmf = sales2015.csv
DirName = %subst(Pathname:1:pos-1);
// dirname = /home/sklement/test
*inlr = *on;
66
Limit Scanning
Released in 7.3
• %SCAN now has an optional length parameter
• previously had start position, but not length
• works with %SCANR as well.
// from last slide:
// Pathname = /home/sklement/test/sales2015.csv
// pos = 20
....+....1....+....2....+....3....+....4....+....5....+....6....+..
.
A R CUSTREC
A CUSTNO 4S 0 ALIAS(CUST_NUM)
A CUBLAD 30A ALIAS(CUST_BILLING_ADDRESS)
A CUBLCT 20A ALIAS(CUST_BILLING_CITY)
A CUBLST 2A ALIAS(CUST_BILLING_STATE)
A CUBLZP 10A ALIAS(CUST_BILLING_ZIP)
A CUSHNM 30A ALIAS(CUST_SHIPPING_ADDRESS)
A CUSHCT 20A ALIAS(CUST_SHIPPING_CITY)
A CUSHST 2A ALIAS(CUST_SHIPPING_STATE)
A CUSHZP 10A ALIAS(CUST_SHIPPING_ZIP)
A K CUSTNO
68
....+....1....+....2....+....3....+....4....+....5....+....6....+...
Create Table CUST (
CUST_NUM for CUSTNO numeric(4, 0) not null,
CUST_BILLING_ADDRESS for CUBLAD char(30) not null,
CUST_BILLING_CITY for CUBLCT char(20) not null,
CUST_BILLING_STATE for CUBLST char(2) not null,
CUST_BIllING_ZIP for CUBLZP char(10) not null,
CUST_SHIPPING_ADDRESS for CUSHAD char(30) not null,
CUST_SHIPPING_CITY for CUSHCT char(20) not null,
CUST_SHIPPING_STATE for CUSHST char(2) not null,
CUST_SHIPPING_ZIP for CUSHZP char(10) not null,
primary key (CUST_NUM)
)
rcdfmt CUSTREC;
69
Original 7.1 Alias Support
ALIAS keyword would generate long names
• but only worked with data structure I/O
• required "qualified" keyword on file to avoid I-specs and O-specs
....+....1....+....2....+....3....+....4....+....5....+....6....+...
FCUST UF E K DISK QUALIFIED ALIAS
D CUST_IN E DS extname(CUST:*input)
D qualified alias
D CUST_OUT E DS extname(CUST:*output)
D qualified alias
D Key s like(CUST_IN.CUST_NUM)
/free
key = 1000;
chain key CUST CUST_IN;
if %found;
eval-corr CUST_OUT = CUST_IN;
if cust_out.cust_billing_address = *blanks;
cust_out.cust_billing_address = cust_out.cust_shipping_address;
cust_out.cust_billing_city = cust_out.cust_shipping_city;
cust_out.cust_billing_state = cust_out.cust_shipping_state;
cust_out.cust_billing_zip = cust_out.cust_shipping_zip;
update CUST.CUSTREC CUST_OUT;
endif;
endif;
70
....+....1....+....2....+....3....+....4....+....5....+....6....+...
D Key s like(CUST_NUM)
/free
key = 1000;
chain key CUST;
if %found;
if cust_billing_address = *blanks;
cust_billing_address = cust_shipping_address;
cust_billing_city = cust_shipping_city;
cust_billing_state = cust_shipping_state;
cust_billing_zip = cust_shipping_zip;
update CUSTREC;
endif;
endif; 71
Relaxed DS I/O Rules
Prior to this update, if you use data structures for I/O, you must have separate
ones for *INPUT and *OUTPUT.
**FREE
dcl-f CUST disk keyed usage(*input: *output: *update);
dcl-ds rec extname('CUST':*ALL) qualified alias end-ds;
For some time, RPG has had support for database nulls in it's native I/O (i.e. "F-
spec files") using ALWNULL(*USRCTL) and the %NULLIND BIF.
In version 7.3 (no PTFs for older versions), RPG has extended this support with the
NULLIND keyword. This keyword enables you to:
• Define your own (standalone) fields as null-capable.
• Define your own indicators to replace the %NULLIND BIF
• Associate a null map data structure with a record data structure to handle nulls in
data structure I/O
73
Standalone Null-Capable Field
D ShipDate s D nullind
%nullind(ShipDate) = *ON;
74
75
Use Your Own Indicator for Nulls
ctl-opt alwnull(*usrctl);
H alwnull(*usrctl)
D NullShipDate s N
D ShipDate s D nullind(NullShipDate)
76
FirstOrd, AmtOwed and SalesRep are null capable (but CustNo is not)
77
LIKEREC/EXTNAME *NULL
Released in 7.3
• Requires ALWNULL(*USRCTL)
• Still need to say *INPUT/*OUTPUT/*ALL so it knows which fields to include
• Add *NULL to make it the null map for the record
• Add NULLIND to the "normal" record DS to associate the null map to it.
**FREE
ctl-opt alwnull(*usrctl);
if null.FirstOrd = *ON;
null.FirstOrd = *off;
Rec.FirstOrd = %date();
update NULLTESTF rec;
endif; 78
if n.AmtOwed = *off;
dsply (%char(r.CustNo) + ' owes ' + %char(r.AmtOwed));
endif;
if n.SalesRep = *ON;
dsply (%char(r.CustNo) + ' has no sales rep');
endif;
end-proc; 79
Free Format (Definitions)
Released in 7.2, PTF back to 7.1
• CTL-OPT replaces H-spec
• DCL-S replaces D-spec for standalone variables
• DCL-F replaces F-spec
• DCL-DS replaces D-spec for data structures.
• New POS keyword sometimes nicer than OVERLAY(var:pos)
• Sequence of F and D specs no longer matters.
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
ctl-opt dftactgrp(*no) option(*srcstmt:*nodebugio);
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
dcl-ds *N;
field1 char(10);
field2 packed(9: 2);
field3 zoned(7: 1);
end-ds;
81
Free Format (Procedures)
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
dcl-proc myProcedure;
return myVar;
end-proc;
82
EXPORT(*DCLCASE)
Previously, when procedure name was case-sensitive, you had to repeat it in ExtProc:
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
D MiXeDcAsEnaMe PR ExtProc('MiXeDcAsEnaMe')
D parm1 10a const
P MiXeDcAsEnaMe B Export
D MiXeDcAsEnaMe PI
D parm1 10a const
/free
... whatever procedure does ...
/end-free
P E
83
EXTPROC(*DCLCASE)
Can be used to call procedures with case-sensitive names without ExtProc.
Released in 7.2, PTF back to 7.1
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
MiXeDcAsEnaMe(myVariable);
... can also be used with APIs, such as the Unix-type ones ...
unlink('/tmp/deleteMe.txt');
84
x = 27.50;
C eval Y = 25.50
85
"Fully Free" Columns Support
Released in 7.3, PTF for 7.1 and 7.2
• Start with **FREE keyword in col 1 of line 1 of your source
• Start with column 1, no limit in line length (aside from record length )
• No length limit on line at all when using IFS files
• cannot use fixed format statements (except via copy book)
• copy books that want **FREE also code it on col 1, line 1 of copy book.
• After line 1 starting with ** is for CTDATA
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
**FREE
dcl-s states char(12) dim(4) ctdata;
dcl-s x int(10);
for x = 1 to %elem(states);
dsply states(x);
endfor;
**
California
Ohio
Mississippi
Wisconsin
86
87
CCSIDs Are Really Important
Funny how many programmers, especially in the USA, seem to ignore CCSIDs.
If you don't think about the CCSID of your data, the computer assumes the default.
• Shouldn't you, the programmer know what's going on?
• When the CCSID is different, don't you want to be in control of what's happening?
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
dcl-s var1 char(100); // default is job CCSID (37 for me)
dcl-s var2 char(100) ccsid(*utf8); // special value *UTF8 = CCSID 1208
/restore ccsid(*char)
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
dcl-s var1 ucs2(10) ccsid(*utf16);
dcl-s var2 char(10) ccsid(*hex);
dcl-s var3 char(10) ccsid(*jobrun);
90
....+....1....+....2....+....3....+....4....+....5....+....6
H ccsid(*char: *utf8)
-or-
ctl-opt ccsid(*char: *utf8);
What should happen if you convert a character that cannot exist in the result CCSID?
Default: A substitution character is inserted that shows that some character is missing.
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+
01 **FREE
02 ctl-opt ccsidcvt(*excp: *list);
03 ctl-opt ccsid(*CHAR: *UTF8) ccsid(*ucs2: *utf16);
04
05 dcl-s var1 ucs2(2);
06 dcl-s var2 char(10);
07 dcl-s var3 char(10) ccsid(*jobrun);
08
09 var1 = u'4f605978'; // Chinese characters for "Ni Hao" in UTF-16
10 var2 = var1; // converted to UTF-8
11 var3 = var2; // converted to US-EBCDIC
CCSIDCVT(*LIST)
....+....1....+....2....+....3....+....4....+....5....+....6....+....7...
01 **FREE
02 ctl-opt ccsidcvt(*excp: *list);
03 ctl-opt ccsid(*CHAR: *UTF8) ccsid(*ucs2: *utf16);
04
05 dcl-s var1 ucs2(2);
06 dcl-s var2 char(10);
07 dcl-s var3 char(10) ccsid(*jobrun);
08
09 var1 = u'4f605978'; // Chinese characters for "Ni Hao" in UTF-16
10 var2 = var1; // converted to UTF-8
11 var3 = var2; // converted to US-EBCDIC
C C S I D C o n v e r s i o n s
From CCSID To CCSID References
1200 1208 10
1208 *JOBRUN 11
* * * * * E N D O F C C S I D C O N V E R S I O N S * * * * *
93
Disabling Database CCSID Conversion
Historically, RPG didn't work with multiple CCSIDs in the same program.
• but the database did!
• therefore, RPG converted all database CCSIDs to/from the job's CCSID
• for backward compatibility, it still does!
Now that it does work with lot of CCSIDs (as we just discussed)
• It's often better to keep the original database CCSIDs in RPG
• We can use RPG's features to convert them if needed.
....+....1....+....2....+....3....+....4....+....5....+....6....+....7...
**FREE
ctl-opt openopt(*nocvtdata); // Default for whole program
Expanded Timestamps
Released 7.2
You can now specify how many fractional seconds you want. You can have up to
12 digits of fractional seconds in a timestamp field.
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8
The INZ() keyword, TIME opcode and %timestamp() BIF still only set the first 3
fractional digits. The remainder of the timestamp is set to zeroes unless you
initialize it yourself with an API or similar.
95
%SUBDT Digits/Decimals
Released in 7.2
The %SUBDT BIF can specify
• a number of digits returned in 3rd parameter
• a number of decimal places when working with *SECONDS
....+....1....+....2....+....3....+....4....+....5....+....6....+...
**FREE
dcl-s ts timestamp inz(z'2016-09-16-18.09.33.123456');
dcl-s year4 packed(4: 0);
dcl-s year2 packed(2: 0);
dcl-s secs packed(4: 2);
96
There are so many enhancements, I don't even have time to cover them all in-
depth! Here are some that I didn't cover in this talk:
97
Soooo Many Enhancements!
…and around 86 since SEU stopped being enhanced (yet, people still use it!)
98
6.1 7.1
%SCANRPL BIF X
%LEN(*MAX) X
ALIAS Support X
RTNPARM X
%PARMNUM X
EXPORT(*DCLCASE) X
101
During 7.4 Development Cycle
Compile from Unicode Source (TGTCCSID compile keyword) ptf ptf ptf X
PCML 7.0 Support ptf ptf X
ALIGN(*FULL) ptf ptf X
%MIN and %MAX BIFs ptf ptf X
Support for Complex Qualified Names in %ELEM, %SIZE, DEALLOC and RESET ptf ptf X
%PROC BIF ptf ptf X
DATA-INTO opcode ptf ptf X
Nested Data Structures ptf ptf X
ON-EXIT section ptf ptf X
PSDS subfields for internal job id and system name ptf ptf X
SAMEPOS DS keyword ptf X
DIM(*CTDATA) X
Varying-dimension arrays X
102
103
During i+1 (7.6?) Development Cycle
7.3 7.4 7.5
104
7.3
You can install any of the features (if Compiler TGTRLS(*CURRENT) SI83429
available for your release of IBM i) by QOAR source files SI71517
installing these PTFs. Runtime SI83428
Debugger SI81625
There is no need to install separate
7.4
PTFs for each feature, these PTFs Compiler TGTRLS(*CURRENT) SI84886
(and their pre-requisites) include it all. Compiler TGTRLS(V7R3M0) SI83483
QOAR source files SI71518
Runtime SI83471
Check RPG Café for the latest Debugger SI81626
features:
https://ibm.biz/rpg_cafe 7.5
Compiler TGTRLS(*CURRENT) SI85009
Compiler TGTRLS(V7R4M0) SI85043
Compiler TGTRLS(V7R3M0) SI83494
Runtime SI83468
105
This Presentation
Thank you!
106