mere cat

IBM AS/400

AS/400

July, 2002. That’s when it all began. I was asked to learn RPG programming on an IBM AS/400. Unfortunately, nothing (well, almost nothing) I knew about computers and programming prepared me for working on the AS/400. I’ve learned a little bit since then, and this page contains some random notes about the AS/400 (or iSeries as it is now known), CL, and RPG programming. They are mostly for my benefit, but if you find anything useful, cool. This page had its last significant update in December, 2005, and that will probably be its last. We migrated off the AS/400 in October, 2006, so I won’t have much to add. Links will rot, etc. Please, look away. —Tony Green

General AS/400 Info

The best introduction to the AS/400 I’ve found (for a dummy) is an article by Jack Woehr, New to the AS/400 (no longer available for free). (Jack has his own site with many more AS/400 links.) There are links in this article to AS/400 pages on IBM’s site, but these are all obsolete. Apparently, the AS/400 has been supplanted by the iSeries servers.

The AS/400 was born in 1988 (IBM has a Brief History of the AS/400 [PDF] ) and renamed the iSeries in 2000. “AS” stands for “Application System.” I didn’t know what “400” stood for until a reader wrote to tell me that 400 refers to 4th generation language. Makes sense.

Some random links, some of which apply specifically to the version of OS/400 we’re using (While we’ve upgraded to v5r2, I haven’t upgraded my links yet):

Connecting

I don’t know where our AS/400 lives, and anyway I don’t think anyone sits down at its keyboard and works with it interactively. From a Windows PC, you would use a program called Client Access (the version depends on the version of Windows you are running). You can also connect to the machine via telnet using the TN5250 protocol, although you will be missing a number of handy features that Client Access provides.

Naturally I wanted to connect using my Mac instead of the software running on my Dull. (We are using IBM’s AS/400 Operations Navigator, Version 3, Release 2, Modification Level 0. The emulator itself is called eNetwork Personal Communications AS/400 Client Access/400 for Windows 95/NT.) I tried what seemed to be the most popular package: Mocha Mac tn5250, but this program doesn’t seem to support the Alt key (I tried control, option, and command) and there is no way to reconfigure keyboard mapping. Frankly, the demo just felt lame. Another possibility is Celview for Macintosh, AS/400 Edition. This is undoubtedly a capable and professional-quality package, but it’s $200. I don’t love my Mac that much, and I didn’t even bother downloading the demo. There is also an open-source TN5250 emulator for some Unices. This offered the possibility of compiling under OS X, but there wasn’t a precompiled binary (naturally). IBM recommends MacMidrange Client available from NLynx. I couldn’t find a price for this product anywhere. It turns out that the telnet client I was already using worked just fine, databeast’s dataComet.

AS/400 Tips and Tricks

Don’t get excited. None of these are really “tips and tricks” unless you are a total beginner.

Searching

Find a file by name

Enter WRKF or WRKOBJ on a command line and hit F4 to prompt for the filename. Enter the filename and hit enter. Obviously, you’ll want *LIBL as the Library. If the file is found, a Work with Files screen will appear with a list of the matches.

Find a string in all members of a file

Use your favorite method to work with the file object, such as STRPDM -> “Work with objects” or WRKOBJPDM. Once you’ve isolated the object on the Work with Objects Using PDM screen, enter 25 in the option column. The Find String screen will appear. Enter the string (it doesn’t have to be in quotes). If you just want a list of members containing the string, set the Option to *NONE and Print list to Y. You can have the command open the member for display or editing by specifying Option 2 or 5 (F1 on this field to see more options). The default mode is interactive, not batch. Apparently, batch mode will sometimes fail to find every member if there are a lot of hits.

One common task is documenting changes to existing source files (since the original programmer never bothered to do it...). To compare two source files: From the PDM (Programming Development Manager) menu, choose Work with members. Enter a library and source file name and either *ALL or a specific source member name. The Work with Members Using PDM screen should appear. Choose a source member and enter 54 (Compare) in the Opt (leftmost) column. This will run the command CMPPFM Compare Physical File Member. For the report type, use Change instead of Difference, which will provide some context to the changes.

Search the “text description” of files or members

Files and file members can have a plain text description of up to 50 characters, which allows you to see at a glance what the purpose of a given file or program is. You can browse these descriptions either for all files within a single library or for all members of a specific file, but let’s say you want to search these descriptions. One way to do that would be to use DSPFD (Display File Description) on a group of files and route the command’s output to a spool file that you can then search. In this example, we’ll restrict DSPFD to file descriptions of type *MBRLIST, which provides “An alphabetical list and brief description of all file members in the specified file.” The text description is part of that “brief description” and is what we’re after. We’ll also restrict the command to work only on physical files. This example will output the descriptions of all members of all files in the library “SOMELIB.”

DSPFD FILE(SOMELIB/*ALL) TYPE(*MBRLIST) OUTPUT(*PRINT) FILEATR(*PF)

When the command finishes, use WRKSPLF (Work With Spooled Files) to find the
output spool file (named probably QPDSPFD) and display it. Note that the
Find command in Display Spooled File is case-sensitive.

Uploading PC Files

About the only thing you have to remember when downloading a file from an AS/400 is making sure that “Convert CCSID 65535” is checked. I haven’t had much trouble downloading files, but I was stumped trying to upload one. I found the answer by searching comp.sys.ibm.as400.misc. Here’s the procedure I used to get an Excel spreadsheet into a form that could be read by an RPG program:

Prepare the PC file by saving the Excel spreadsheet as space-delimited (with a *.prn extension). You’d think that would be it. Just choose Receive in Client Access, right? But no. The tricky part is that you can’t just upload this file without something called a “field reference file,” which tells the AS/400 about the data structure of the PC file. In this case, I didn’t have a “data structure.” I just wanted to get the file uploaded into one big field per record, but I still needed a file reference file.

To create the field reference file, we start by creating a new file on the AS/400. Run the command Create Physical File (CRTPF). Enter a file name and library and enter a record length that is at least as long as the longest record in the PC file (it can be longer). You will download this file to the PC to create the field reference file, but don’t try it yet, or you will receive an error. That’s because the new file is empty, and you can’t transfer an empty file. We now need to add one record to this file just so it won’t be empty.

Prompt on the command WKRMBRPDM and enter the library and file names. Enter 18 (Change using DFU). You should see “Work With Data in a File.” Hit F10 to switch to Entry mode, which will add new records at the end of the file. You should see two lines available for data entry. Just type “Dummy record” or something equally imaginative and hit return. Hit F3 to exit entry mode. You should see the “End Data Entry” screen indicating that one record was added. Hit return again. Now we can download the file to the PC.

Choose Receive in Client Access. In the top (AS/400) pane, enter the library/filename and check “Convert CCSID 65535” in Properties. In the bottom (PC) pane, choose File as the Output Device, and click Details. Check “Save transfer description” and give the file a name, for example, the AS/400 file name plus a FDF extension. Click OK to confirm these settings. For file name, it can be anything you want; all we’re after is the FDF file.

Click “Transfer data from AS/400.” Now we have an FDF file ready for the upload phase.

Choose Send in Client Access. In the top (PC) pane, browse to the *.prn file you want to upload. Check “Convert CCSID 65535” in Properties. In the bottom (AS/400) pane, enter the library and filename of the dummy file you created above. Click the Details button and check “Use PC file description” and browse to the FDF file created above. In the bottom (AS/400) pane, choose “No, replace member only.” Click OK to confirm these settings. Click Transfer data to AS/400. The contents of the PC file should now be on the AS/400.

What if you want to upload a file with a data structure, not just a flat file as in the example above? In that case, you need to tell the 400 what the field layout of your data file will be. Here’s what I did when I wanted to upload a simple Excel spreadsheet with two columns.

First of all, I needed to create a physical file on the 400 with the field layout I wanted. To do this, I created a DDS (Data Definition Specification), which involves creating a new, empty source file member and entering record and field specifications. Here’s the source:

Unlike other program specifications in RPG, the “A” in an “A spec” apparently doesn’t stand for anything. In fact, the “A” is optional--you can leave it out entirely, and your file will compile just fine. Just be sure that your source file’s Type is “PF” (Physical File).

That’s all there is to it. In this example, the source file begins with a couple of (optional) Header comment records (H*) that provide a place to describe the file you are creating. The rest of the file consists of “A” specs that actually define the format. Line 3 specifies the record format; note the “R” in position 17. The record format is named “SOMEREC.” Clever name, isn’t it? The Function field in position 45 provides a number of features in defining the record format, but in this case, I’m just using it to enter a text description. Lines 4 and 5 specify the actual fields. The ID number field is 4 character/alphanumeric, and the DATE field is 6 characters “zoned decimal” to store a date in Julian format. Note that there can be only one record format per file.

To create the actual file, simply compile the source by entering 14 at the “Work With Members” screen. The product will be a physical file with two fields. By default, the physical file that is created will have the same name as the source member, but you can change this simply by prompting before compiling (hit F4 after you’ve entered 14). What you’ve created, by the way, is an “externally-described” file.

Using Client Access, choose “Receive File From Host.” Fill in the top AS/400 half with the file name of the physical file you’ve created (in LIBNAME/FILENAME format) and the bottom PC part with the full path to the new file. (Or click “Browse,” navigate to a directory, and enter a file name.) But don’t worry, you’re not going to get a file on your PC, because the physical file on the AS/400 is empty. You’re just going through this exercise to have Client Access create an FDF (Field Definition Format) file for you. When you’re all set, click “Transfer Data from AS/400.” You’ll receive the message, “No data was matched to the specified options” or something like that, but you should also see a file with the same name as your physical file with an FDF extension. Believe me, it takes a lot longer to explain it than to do it.

Now you have everything you need to upload. Oh, just one more thing. Your Excel file should be saved as space-delimited (.prn). In my (limited!) experience, Excel will put a variable number of spaces between fields, but you really want just one. In my case at least, it wouldn’t work any other way, so I used a text editor to change multiple spaces to one.

In Client Access, choose “Send File To Host.” For the PC file name, browse to your .prn file. For the Library/File on the 400, it’s simplest to enter the physical file you just created. That will fill the empty file with records from your .prn file--probably just what you want--but you can specify a new file if you like.

Click the Details button, and check “Use PC file description.” Click the Browse button and select the FDF file you just downloaded. If you are going to fill your empty physical file, choose “No” for “Create AS/400 object.” If you want to make a new file, choose “Yes” and enter the location of your DDS source file: LIBNAME/SOURCEFILENAME(SOURCEMEMBERNAME). The 400 will use the source file to create the physical file on the fly.

That was a simple example. Things become more complicated when you have an Excel file with variable-length data. Exporting the file as *.prn has some serious limitations and annoyances. For one thing, if any of your lines are longer than 240 characters, Excel will wrap the long lines by putting the excess at the end of the file, in effect breaking up your file into “pages.” That’s the nature of a prn file. Another annoyance to watch out for is the way Excel “calculates” each field’s exported width. If any data happens to be “hidden” because the column is too narrow, then the hidden data is chopped off! I’m not kidding. What are your options then? Well, you would think you could Select All and choose Format:Column:AutoFit Selection. None of the data will be cut off, but you’ll find that in some cases there will be no spaces between fields (good!), while in others there are two... or three... or... Well, that just won’t work. If you are extremely patient, you could Select All, change the font to something monospaced (Courier) and fiddle with each column’s width until you get it just right. Good luck.

What I did was use formulas to extract the columns. Here’s a formula to pad variable-length data with spaces.

LEFT(C2,30)&IF(LEN(C2)<30,REPT(" ",30-(LEN(C2))),"")

In English, take the first 30 characters of column C. This will force whatever is in column C to fit into a 30-character field. If the length of the data is shorter than the field length, we need to pad it with spaces. The REPT function does this. With a formula limit of 1,024 characters, you’ll be able to concatenate about 20 columns this way.

I did this once because I needed to feed an interface that was expecting a fixed-field file. Read on for a more practical solution using comma-delimited files.

Uploading Comma-Delimited (csv) Files

While you can upload a PC file directly into a physical file, it’s a fiddly process to ensure that the PC file is properly formatted (fixed-width fields with one space between records). It’s much easier to create a CSV file, but how do you upload one of these to a physical file?

There are two steps. First, use CRTPF to create a “flat file” (one field per record) on the 400 with a record length long enough to accommodate the longest line in your CSV file.

Only the file name and record length are relevant since we aren’t using a DDS file. Enter a number at least as long as the longest line in the CSV file, in this example 128.

Now use Client Access to upload the CSV file to the flat file. The next step requires that you have a physical file with a record layout compatible with your CSV. If you expect any of the fields in your CSV file to be empty, then be sure the corresponding field in your physical file is defined with ALWNULL in the DDS.

Use CPYFRMIMPF to copy the flat file into your physical file. This command does all the heavy lifting of parsing the flat file at the comma and loading the physical file. That’s all there is to it.

Miscellaneous

Copy a database file including the deleted records

Why would anyone want to do this, you wonder? Deleted records are evil. Well, I was learning about reorganizing files and needed a test file to work with. We had a file with over a million deleted records, so I copied that. The relevant defaults are FROMRCD = *START; TORCD = *END and COMPRESS = *YES. You might think you can just set COMPRESS to *NO, but that’s not enough to preserve the deleted records. You must also change FROMRCD to “1.” If you are copying the file for the first time, be sure to set CRTFILE to *YES otherwise nothing will happen.

Queries

At the heart of the AS/400 is the integrated DB2 “universal” database (UDB). One of the first things I needed to learn was how to write queries and produce reports. I had no documentation to work with, but noticed that the (excellent) context-sensitive online help (F1) kept referring to a “Query Users Guide.” I found it in PDF format on IBM’s site.

To use Query Management (not to be confused with Query Manager), type WRKQRY (Work With Queries) on the command line. I found it very easy to create a query, even though the query didn’t do what I wanted! In troubleshooting my query, I started to use the command ANZQRY (Analyze Query), which provides useful diagnostic messages. If the messages seem too cryptic, just position the cursor on the message and hit F1 for a detailed discussion of the message as well as suggested solutions.

I find that I use Query Manager most of the time these days, mostly because I can pass parameters into the query. To use this feature, rewrite your query by replacing any hard-coded values with variables wherever you need one. Variable names start with an ampersand. Here’s a simple example:

select field1, field2 from sometable where field1 = &VALUE

You can call the query right from the command line, using STRQMQRY. If you do, you will be prompted for the value of &VALUE. To set the value programmatically as in a CL program, use the SETVAR parameter to set your variable.

....+... 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+...7...+...8
/* &MYVAR is a CL variable which you can set to either a constant */
/* or a calculated value using your hard-won programming wizardry. */
/* The SQL variable is used without an ampersand. */
STRQMQRY QMQRY(QRY1) SETVAR(VALUE &MYVAR)

The variable name itself can be up to 30 characters long. Get in the habit of making your variable names all upper case. It’s not a requirement, but for some reason, lower-case letters in variables in the STRQMQRY command are converted to upper case when the command is processed. Since the case of the variables must match, it follows that the variables in the query must be in upper case.

Be advised that the value of your SQL variables has a 55-character limit. This can be a significant limitation if you are putting longer query fragments in your variables. (I was summing a group of columns and ran into this limit.)

Other than these two gotchas, quoting is the most confusing aspect of using variables. It can drive you mad. The way you quote your value depends how you want the variable to be interpreted in the query. Basically, the outermost quotes in the CL program will be stripped away. This means that if you need a quoted value in your query, as for a string, then you will need to triple quote the string in your CL program; '''my string''' will become 'my string'

CL Programming

Seems like I need to learn CL programming as well as RPG... Herewith, some random notes to myself.

A procedure usually begins with a PGM statement and ends with an ENDPGM statement. Comments: /* and a blank, blank and */ unless the /* is in the first two characters of the command (which column would that be?). (Can you tell I’m a beginner?)

Sending Email Using SNDDST

It is quite useful to be able to send email programmatically, for example to notify users that a process ran or to send a report as an attachment. There are two ways (at least) to do this from within a CL program. One is to use the QtmmSendMail API (Send MIME Mail), which is the more complicated although more flexible method. I have never attempted this. What I have used is the simpler and more limited method: the SNDDST (Send Distribution) command. This command can be used to send a message or a file to an AS/400 user or “distribution list” of users, but it can also be used to send SMTP mail so users without 400 accounts can receive messages. (This assumes, of course, that your 400 has SMTP mail already set up.)

The documentation referenced above is a guide to all the ins and outs of this command, but here is an elementary example using the minimum number of required parameters to send a simple message with a subject and a body:

The “+” (line continuation character) allows you to enter multiple lines for the LONGMSG if necessary. Note that the message includes an apostrophe, which is escaped by a second apostrophe. For some reason, if you prompt to create the command, the email address will be enclosed in a double set of parentheses: (('tony@ domain.com')). Single or double, it works either way.

Setting the message type to *LMSG instead of *MSG has several advantages. For one thing, you must use *LMSG instead of *MSG in order to use the TOINTNET parameter to send using SMTP. That’s a show-stopper right there. In addition, you can send a much larger message (up to 5000 characters instead of 256), but long messages arrive in the body of the email and not as an attachment. In addition, other parameters are supported with this type.

Sending an attachment is a bit more complicated, simply because we first have to convert the 400’s native EBCDIC database file into something a PC can deal with, namely a “stream file” in ASCII format. One way to do this is to use the CPYTOPCD translation command. Here’s an example:

Note that the FROMFILE must be a member of a source file and not a database file. In this example, MYFILE is a source file with only one member. The destination of the copy is a shared folder in QDLS (the Document Library System). (To see the existing folders in QDLS, enter WRKLNK and look for QDLS, or just enter WRKFLR.) The TODOC parameter lets you specify a new name for the copy. Note that this name must conform to the 8.3 (DOS) naming convention. TRNTBL and TRNFMT specify the translation table and format used for the copy, good ole ASCII text.

Now we have a file suitable for attaching to our email. We use SNDDST again, this time with a type of *DOC instead of *LMSG.

Note that you don’t have to use *LMSG for TOINTNET to work. In fact, you can’t. At least that’s consistent. :-/ Also, setting the email’s subject is a little tricky. When the type is *DOC, DSTD is just the description and is not used as the email’s subject. (The DSTD is required, nevertheless. Your program will compile fine without it, but it won’t run.) There is a “SUBJECT” parameter of the SNDDST command, but I was never able to get it to work. Instead, I learned that the subject of the email is (by default) the attachment’s filename, so to set the email subject, set the attachment’s document description.

RPG Programming

The only thing I’m sure of is that RPG stands for Report Program Generator. :-)

If you want to write an RPG program on the AS/400 you have two choices of editor: you can use either the SEU (Source Entry Utility) or the SEU (Source Entry Utility). The SEU is documented in this PDF from IBM.

Control Language Programming on the AS/400 (2nd Edition) by Bryan Meyers, Dan Riehl

Modern RPG Language: With Structured Programming (4th Edition) by Robert Cozzi

I received these books in early January, 2003. Judy Yeager’s book is always by my side, and I refer to the other two often as well. Robert Cozzi is a generous contributor on the RPG400-L mailing list; Judy Yeager has left computers behind for a new life in Belize.

RPG Tips and Tricks

As I said about my AS/400 tips and tricks, don’t get excited. None of these are really “tips and tricks” unless you are a total beginner. I should also mention that they apply mainly to RPG III (RPG/400), not the current version RPG IV.

Messages

On our system, the RPG error messages are in two message files: QRPG/QRPGMSG and QSYS/QRPGMSGE. To read the text of a message, use Work Message Description (WRKMSGD) and specify the message file and library. Then use the “Position To” field to enter the message ID. To see message text spelled out in your compiles, enter 14 next to your source member in PDM and then prompt (F4). Hit F10 for additional parameters and enter *SECLVL in the first field, “Source listing options.”

Calling CL Commands from RPG

Wouldn’t it be handy to be able to use a CL command from your RPG program? Well, you can, using QCMDEXC. QCMDEXC is not part of the RPG language, but is a program provided by IBM, which you invoke in your RPG program by using the opcode “CALL.” Here’s an example:

As you can see, QCMDEXC takes two parameters, the first is the location in your RPG source file of the CL command, and the second is the length of the command. The “location” CMD refers to something called a “compile-time table,” which is simply a data structure you create in your source member that is compiled in to the program. In other words, you hard-code it.

You create your compile-time table (which simply allocates the space to store the CL command) using an “Extension” specification. In this example, we’ve called the table CMD and specified it as having one entry per record and one record per table and a length of 30. A compile-time table can’t get much simpler than that. (Make sure the length is adequate for your command; it can be longer than necessary, but not shorter.) Through magic, CMD refers to some non-RPG text entered after two asterisks ** at the very end of your RPG program.

Now for the second parameter of QCMDEXC, the length, we create a field to store the length of the command we are passing to QCMDEXC. We zero the field and set it to 30. By the way, the length and decimal positions are always 15 and 5, which is required by QCMDEXC. Note: Factor 2 must not be longer than the length of the table. If it is, your program will throw error RPG0202, I think that’s because QCMDEXC is trying to read junk past the length of the table.

What if you wanted to run two (or more) CL commands? Simply create multiple compile-time tables, one for each command, or one table with multiple records. The first table will correspond to the first array at the end of your RPG. Then they will load in order.

....+... 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7...+... 8
E* Two compile-time tables, one for each CL command
E CMD1 1 1 80 Command Line Arry
E CMD2 1 1 40 Command Line Arry
C* Bunch of RPG code...
C
C* last line of RPG code...
** The command on the next line will go “into” CMD1. Back up a file...
CPYF FROMFILE(QGPL/TEMP) TOFILE(QGPL/TEMPBAK) MBROPT(*REPLACE)
** The command on the next line will go “into” CMD2. Clear a file...
CLRPFM(QGPL/TEMP)

Compiling Problems

Usually my programs fail to compile and run for obvious reasons—I have a syntax error or a problem in my logic. These bugs are relatively straightforward to fix, but there are a couple of more obscure problems that bite me over and over again.

If you recompile or otherwise modify the structure of any of the files in your file specifications, you will likely encounter at runtime message CPF4131, a “level check” error. This is caused by a discrepancy between the record format level identifier stored in your RPG program and the one in the actual file. The format level identifier is system-generated and is based on the attributes of the file, so it will change if you add or rename a field.

You can compare format level identifiers, using DSPPGMREF to display the identifiers in your program and using DSPFFD to display the identifier in each of your files, but it’s not that important to know what the actual number is, since you can’t modify it. The simplest solution is to recompile your RPG program whenever you make a change to one of your files. That will synchronize the identifiers. Then all you have to do is fix those syntax and logic errors. More info from IBM.

Another runtime error occurs when your data file has null values, but your RPG program was compiled without null value support. Ouch. Recompile using CRTRPGPGM and hit F10 for Additional Parameters. Set the last parameter, Allow null values, to *YES.

Bare Bones Debugging

Compile your RPG program with debugging. Enter 14 as usual, but before pressing Enter, press F4 and then F10 for Additional Parameters. Enter *SRCDBG next to “Source listing options.” Once the program is compiled successfully, start the debugger. At the command line, enter STRDBG PGM(LIBRARY/PROGRAM) TRCFULL(*WRAP) UPDPROD(*YES) OPMSRC(*YES). After pressing Enter, the “Display Module Source” screen will appear with your source code. When you’ve finished admiring your code, you have the opportunity at this point to set breakpoints using F6 or just press either F3 or F10 to exit. Finally, you can run your program. Back at the command line, enter CALL PGM(LIBRARY/PROGRAM). The source will appear stopped on the first executable statement. If you had set a breakpoint at some point in your program, execution will proceed up to that line and then stop. From here, press F10 to step through your code. Use F11 to display the variable on the current line. To see the value of any variable, enter EVAL YOURVARIABLE on the command line and hit Enter. The value of the variable will be shown at the bottom of the screen.

You can’t get any more bare bones than the ever-popular “print” statement. If I want to see a value at runtime, I use SNDUSRMSG.

Populate &JOBUSR using RTVJOBA USER(&JOBUSR) so the message will come to you, and populate &MSG with whatever data you like.

Source Entry Utility (SEU)

RPG programming is not free-form (except for RPG IV), so each line of code is more akin to a database record, where each field’s data has to start in a fixed position. Wait, it is a database record. That explains it. Apparently, RPG programmers used paper forms to facilitate program entry. What doesn’t make sense is how this helps. You still have to enter the code using the SEU, and then the paper form wouldn’t seem to offer much help. Maybe that’s why you don’t see paper forms used anymore. Anyway, the SEU provides an aid to entering code in the correct column on a line. It’s called a “format line.” There are about 40 different kinds for all the languages the AS/400 supports. To be prompted for a list, enter F? at the beginning of a line; type F? right over the record-sequence number on the left. The format line will appear above the line you’re on. To get a free format line, in other words a “ruler” with column numbers, enter F**. To remove the format line, hit F5 (Refresh).

In addition to format lines (and even better), you can be prompted to “fill in the blanks” of a specification. In RPG, enter the letter corresponding to the kind of specification you are entering (e.g., I, O, F, C) and hit F4. The same thing works for CL programming. You don’t even have to enter anything, although it’s more efficient if you enter the command first. In any case, hit F4, fill in the options, and when you’re finished, a perfectly-formatted command will appear in your source file. AS/400 programming is so easy. Yeah, right.