Are you still looking for reasons to move to free-format coding?
Editor's note: This excerpt is from chapter 10 of Evolve Your RPG Coding: Move from OPM to ILE ... and Beyond.
There's nothing worse for a programmer who has just come to RPG from a "modern" language than not being able to understand how data is defined in RPG. The fixed-format D-specs can be a real nightmare. (They were for me, when I started.) This is because you have to remember which letter corresponds to which data type, and the connection between the data type and its single-letter D-spec definition isn't always obvious. (You know what I mean …"Z" for timestamp?!) In the free-format version, programmers from both the "outside world" and "traditional RPG" find intuitive data-type definitions, as you'll see later in the examples.
While the syntax of control and file definition statements is similar to a "regular" free-format statement, some of the data definition statements require a structure that clearly marks the beginning and end of the definition. Let's pause for a moment to recall what types of data definitions exist in fixed-format RPG:
- Standalone fields, defined by an "S" in position 24
- Constants, defined by a "C" in the same position
- Data structures, identified by the "DS" abbreviation in positions 24 and 25
- Prototype and respective return values (if any), identified by "PR" in positions 24 and 25
- The prototype interface, identified by "PI" in positions 24 and 25
These are very different types of data definitions, so it shouldn't come as a surprise that they require different coding. Let's start with the simplest: the standalone fields and the constants.
Make Your Variable Definitions Crystal Clear
Declaring a standalone field, more commonly known as a variable, in RPG is now remarkably similar to how this task is done in "modern" languages. For example, if I wanted to define a 10-character field named MyString, all I would have to type is this:
DCL-S MyString CHAR(10);
Notice the syntax:
- DCL-S indicates the type of definition, a standalone field.
- The name of the variable can be (almost) as long as you want, because you're now free of the positional constraints.
- The data type is CHAR(10), not "A," unambiguously stating the data type in the "modern language" way.
After the data type, you can specify any of the usual keywords, such as INZ and LIKE. By the way, LIKE's new syntax is also less cryptic! Here's an example:
DCL-S W_Small_Amount Packed(11 : 2);
DLC-S W_Large_Amount Like(W_Small_Amount : +2);
I mentioned that the variable names can be almost as long as you want. This is not new. In fixed format, you'd use an ellipsis ("…") when the space available for the variable name was not enough, remember? Well, in free format it's basically the same thing, but you need to keep in mind how the compiler determines where the name of the variable ends and its data-type keyword begins: there should be an empty space between them. This is particularly important when the variables have long names. For instance, back in chapter 4, I mentioned a procedure named Check_Item_In_Inv. Taking a cue from this, let's say I have a variable, defined in that procedure, named W_Is_Item_In_Inventory. Here are some correct and incorrect ways to define that variable in free format:
Dcl-s W_Is_Item_In_Inventory... // This is incorrect syntax:
Char(1); // This is assumed as part of the name
Dcl-s W_Is_Item_In_Inventory Char(1); // This is correct syntax
Dcl-s W_Is_Item_In_Inventory
Char(1); // Also correct syntax
Dcl-s W_Is_Item_...
In_Inventory Char(1); // And this is also possible
In the first example, the compiler sees the ellipsis and assumes that the name of the variable continues in the next line, which means that CHAR(1) will be assumed to be part of the name. This will cause a syntax error because the data type is missing, and parentheses are not acceptable characters in a variable name. The other definitions are valid because the compiler can figure out where the variable name ends and where its data type keyword begins.
To help you figure out how to define fields, either as standalone fields or as part of data structures (discussed later), Table 10.1 lists the correspondence between the data type keywords used in free format and their fixed-format abbreviations.
Table 10.1: Free-Format Data-Type Keywords and Their Fixed-Format Counterparts
Data Type Name |
Free-Format Keyword |
Fixed-Format Abbreviation |
Binary-Decimal |
BINDEC(digits {: decimal-positions}) |
B |
Character |
CHAR(length) |
A |
Date |
DATE{(format{separator})} |
D |
Float |
FLOAT(bytes) |
F |
Graphic |
GRAPH(length) |
G |
Indicator |
IND |
N |
Integer |
INT(digits) |
I |
Object |
OBJECT{(*JAVA:class-name)} |
O |
Packed-Decimal |
PACKED(digits {: decimal-positions}) |
P |
Basing Pointer |
POINTER{(*PROC)} |
* |
Procedure Pointer |
POINTER{(*PROC)} PROCTPRT(name) |
* |
Time |
TIME{(format{separator})} |
T |
Timestamp |
TIMESTAMP |
Z |
UCS-2 |
UCS2(length) |
C |
Unsigned Integer |
UNS(digits) |
U |
Variable-Length Character |
VARCHAR(length {:2 | 4}) |
A |
Variable-Length Graphic |
VARGRAPH(length {:2 | 4}) |
G |
Variable-Length UCS-2 |
VARUCS2(length {:2 | 4}) |
C |
Zoned-Decimal |
ZONED |
S |
At first glance, it would seem that there's a direct correspondence between the traditional single-letter abbreviations and the free-format keyword. In reality, however, there are some subtle differences:
- DATE{(format{separator})}: Instead of defining a field as date and adding the DATFMT keyword, the date format is specified as the parameter to the DATE keyword. For instance, the following defines a date data type variable that uses the *YMD date format:
DCL-S W_MyDate Date(*YMD);
- OBJECT{(*JAVA:class-name)}: Instead of defining a field as an object and adding the CLASS keyword, the class is specified as the parameter to the OBJECT keyword. For example, the following defines a Java class named MyClass in RPG, linked to the Java strClass class:
DCL-S MyClass OBJECT(*JAVA:strClass);.
- POINTER{(*PROC)} PROCTPRT(name): Instead of defining a field as a pointer and adding the PROCPTR keyword, *PROC is specified as a parameter to the POINTER keyword. For instance, this line declares a pointer named Ptr, pointing to a procedure named MyProc:
DCL-S Ptr POINTER(*PROC) ProcPtr(MyProc);
- TIME{(format{separator})}: Instead of defining a field as time and adding the TIMFMT keyword, the time format is specified as a parameter to the TIME keyword. This is similar to the DATE keyword. The following defines a time data type variable in *ISO format:
DCL-S W_MyTime Time(*ISO);
- VARCHAR(length {:2 | 4}), VARGRAPH(length {:2 | 4}), and VARUCS2(length {:2 | 4}): Instead of defining a field as character, UCS-2, or graphic and adding the VARYING keyword, the field is defined using the VARCHAR, VARUCS2, or VARGRAPH keyword. The length {:2 | 4} part of the definition is related to the two- or four-byte prefix that stores the current length of the contents of the variable-length field. The size of the prefix is specified by the second parameter of VARCHAR, VARGRAPH, or VARUCS2. The parameter must be two or four. If you don't specify the prefix size, two is assumed if the specified length is between one and 65,535, or four if the length is greater than that. You can specify either prefix size for definitions whose lengths are between one and 65535, but for larger fields, only four is allowed.
In short, the new data-type definition keywords make the code less cryptic, therefore "friendlier" to new and old RPG programmers alike.
There's a New Way to Define Constants—and New Uses for Them
Constants are defined in a similar manner to standalone fields, with slight differences. Here's an example of the definition of a constant named C_MaxLines, set to a value of 100:
DCL-C C_MaxLines CONST(100);
I simply replaced the DCL-S for DCL-C, removed the data-type definition (it's inferred by the compiler when the value of the constant is evaluated), and added the partially optional CONST keyword. It's "partially optional" because I need to specify the value, but I can do without the keyword itself.
This is another possible way of specifying the C_MaxLines constant:
DCL-C C_MaxLines 100;
This is not new—you could do the same in fixed format. However, I suggest that you try to avoid it, because this "shortcut" doesn't improve readability—quite the contrary. What's new is the fact that you can use constants in other definitions. For instance, you can use constants to define the length of variables, as shown in the following example:
DCL-C C_MaxLines CONST(100);
DCL-S W_SomeText Char(127) Dim(C_MaxLines);
In this example, I'm declaring an array of 127-character strings with C_MaxLines (100) elements. Similarly, you can use named constants for file definitions:
DCL-C C_Sales_Report_File 'SRPT';
DCL-F Sales_Report Printer
OflInd(I_OverFlow)
ExtDesc(C_Sales_Report_File)
ExtFile(*ExtDesc);
Another novelty, which you might have noticed in the previous examples, is that you can now mix the specification types. I've shown two examples in which a data definition line (the declaration of the C_MaxLines and C_Sales_Report_File constants) appeared before other definitions. In the first example, this was already possible because variable or constant field definition belongs in D-specs. In the second example, I'm defining a file after defining a constant; in other words, I have a D-spec after an F-spec. This can be particularly useful whenever there's some sort of connection between files and externally described data structures, like this:
DCL-F InvMst;
DCL-DS InvMst_DS ExtName(InvMst : *Output);
As you can see from this example, a data structure declaration follows the same principles as standalone fields and constants: the data definition type that you use in positions 24-25 of the fixed-format line is used as a suffix to the "DCL-" string to form the operation code. This is a good way to remember which operation code to use when defining variables, constants, and data structures in full-fledged free format.
This example shows, arguably, the shortest possible definition of a data structure. I could swap the "ExtName…" for a "LikeDS…" and the definition would still be short, but this isn't always useful; sometimes, you want a tailor-made data structure. Let's see how to define a regular data structure, with the usual subfields that can potentially overlap each other.
Simplifying the Data Structure Definition
Defining a regular data structure requires a slightly more complex structure:
DCL-DS W_MyDate_DS;
W_Date Zoned(8:0);
W_Year Zoned(4:0) Overlay(W_Date);
W_Month Zoned(2:0) Overlay(W_Date : *Next);
W_Day Zoned(2:0) Overlay(W_Date : *Next);
End-DS W_MyDate_DS;
Unlike the previous one-line declarations, this data structure is composed of multiple lines, like a regular data structure usually is. The logic here is that you define it like you'd write a subroutine. There's a DCL-DS line with the data structure name, which signals the beginning of the data structure declaration (equivalent to the subroutine's BegSR) and an END-DS line, which ends the declaration (the same as the EndSR line for a subroutine). In between, you define the data structure subfield names, respective data types, lengths, and how they "fit" in the data structure.
In case you're not familiar with the OVERLAY keyword, it overlays the storage of one subfield with another subfield of the data structure. Specifying OVERLAY(<name> : *NEXT) positions the subfield at the next available position within the overlaid field. In this particular case, the W_Month subfield definition overlays the fifth and sixth bytes of W_Date, because W_Year overlays the first four. In a free-format definition, the <name> parameter of the OVERLAY keyword cannot be the name of the data structure, as it could be in fixed format. To explicitly specify the starting position of a subfield within the data structure the "old-school" way, use the POS keyword instead.
Find out more in the book Evolve Your RPG Coding: Move from OPM to ILE ... and Beyond.
LATEST COMMENTS
MC Press Online