Guru: Quick And Handy RPG Output, Take 2
October 25, 2021 Ted Holt
I am pleased today to revisit a topic I wrote about just over seven and a half years ago. I do so for two reasons. First, I’ve made a slight improvement to my routine. Second, I’d like to provide more examples of this routine in action. My previous article suffered from a paucity of examples. I can’t believe I let that happen.
I’m talking about the writeln subprocedure, a handy routine that I use to write unstructured text to a spooled file. I derived the inspiration for this routine from Pascal, a programming language I used heavily when I was working on my computer science degree. It’s not a substitute for record output (think DDS and O specs), but a different tool for different situations. If you have not read my first article on this subject, please do so before continuing.
First, the improvement. Sometimes I like to indent certain lines of output for purposes of legibility, e.g. to align data in columns or to present the output in the form of an outline. When I found myself prepending inordinate numbers of blanks to the output string, I decided to add a second, optional parameter to indicate the starting position of the string in the output line. The default value is 1.
Now, the examples. One of the ways I most use my writeln routine is for batch debugging. “Batch debugging?” I hear you ask. “What’s that?” Very simply this: Rather than running a program interactively under the control of a debugger, endlessly clicking a button or pressing some command key to step through the program, and endlessly repeating some other action to examine the values of variables, I make the program build a spooled file of the same sort of information. When the program finishes, I can read the spooled file to determine how the program behaved and why the program behaved as it did. Batch debugging does not replace the interactive debugger. I use each one as it makes sense.
Let’s see an example. I’ve thrown together a short piece of nonsensical code that you can run on your own system if you like. Just make sure that library QIWS is in your library list.
**free ctl-opt option(*srcstmt: *nodebugio) actgrp(*new) main(WL01R); //undefine debugging /define debugging dcl-f qcustcdt usage(*input) qualified usropn; /if defined(debugging) dcl-f qsysprt printer(132) usropn; /endif dcl-proc WL01R; dcl-pi *n; inState char ( 2 ) const; ouTotalDue packed( 7: 2); end-pi; dcl-ds CustomerRec likerec(qcustcdt.cusrec); dcl-s Name varchar(24); /if defined(debugging) open qsysprt; writeln ('Enter ' + %proc() + ' ' + %char(%timestamp)); writeln ('> Selected state =/' + inState + '/'); /endif clear ouTotalDue; open qcustcdt; dow '1'; read qcustcdt.CusRec CustomerRec; if %eof(); leave; endif; if CustomerRec.State = inState; ouTotalDue += CustomerRec.BalDue; Name = ReformatName (CustomerRec.LstNam: CustomerRec.Init); /if defined(debugging) writeln ('Selected customer ' + %editc(CustomerRec.CusNum: '4') + ' Balance = ' + %editc(CustomerRec.BalDue: 'L') : 3); writeln ('Name=/' + Name + '/': 3); /endif endif; enddo; /if defined(debugging) writeln ('> Total due = ' + %editc(ouTotalDue: 'L')); writeln ('Leave ' + %proc() + ' ' + %char(%timestamp)); /endif close *all; return; end-proc WL01R; dcl-proc ReformatName; dcl-pi *n varchar(24); inLastName varchar(16) const; inInitials varchar( 3) const; end-pi; /if defined(debugging) writeln ('Enter ' + %proc() : 3); writeln ('> Last=/' + inLastName + '/ Init=/' + inInitials + '/' : 3); /endif return %subst(inInitials: 1: 1) + '. ' + %subst(inInitials: 3: 1) + '. ' + %trim(inLastName); end-proc ReformatName; /if defined(debugging) dcl-proc writeln; dcl-pi *n; inString varchar(132) const; inPosition uns(3) const options(*nopass); end-pi; dcl-ds ReportLine len(132) end-ds; dcl-s Position uns(3); if %parms() >= %ParmNum(inPosition); Position = inPosition; else; Position = 1; endif; %subst(ReportLine: Position) = inString; write qsysprt ReportLine; end-proc writeln; /endif
Notice the code that is conditioned to the debugging compiler condition. When I am working on the program, I /define the debugging condition. When the debugging condition is defined, I get a report like this one:
Enter WL01R 2021-10-25-10.46.45.798914 > Selected state =/NY/ Enter REFORMATNAME > Last=/Jones / Init=/B D/ Selected customer 839283 Balance = 100.00 Name=/B. D. Jones/ Enter REFORMATNAME > Last=/Tyron / Init=/W E/ Selected customer 397267 Balance = .00 Name=/W. E. Tyron/ Enter REFORMATNAME > Last=/Lee / Init=/F L/ Selected customer 192837 Balance = 489.50 Name=/F. L. Lee/ > Total due = 589.50 Leave WL01R 2021-10-25-10.46.45.799561
What you print is up to you. I find myself adding, removing, and modifying writeln commands as I work on the program.
When I am ready to put the program into production, I /undefine the debugging condition. The debugging code is still there for the next time I work on the program if I should need it.
Another way I commonly use the writeln routine is to log the progress of a program as it works through step after step. I’ll use the same illogical program to illustrate.
**free ctl-opt option(*srcstmt: *nodebugio) actgrp(*new) main(WL02R); dcl-f qcustcdt usage(*input) qualified usropn; dcl-f qsysprt printer(132) usropn; dcl-proc WL02R; dcl-pi *n; inState char ( 2 ) const; ouTotalDue packed( 7: 2); inOptions char ( 80 ) const options(*nopass); end-pi; dcl-s Logging ind; dcl-ds CustomerRec likerec(qcustcdt.cusrec); dcl-s Name varchar(24); if %parms() >= %parmnum(inOptions) and %scan('NOLOG': inOptions) = *zero and %scan('LOG' : inOptions) > *zero; Logging = *on; endif; if Logging; open qsysprt; writeln ('Enter ' + %proc() + ' ' + %char(%timestamp)); writeln ('> Selected state =/' + inState + '/'); endif; clear ouTotalDue; open qcustcdt; dow '1'; read qcustcdt.CusRec CustomerRec; if %eof(); leave; endif; if CustomerRec.State = inState; ouTotalDue += CustomerRec.BalDue; Name = ReformatName (CustomerRec.LstNam: CustomerRec.Init: Logging); if Logging; writeln ('Selected customer ' + %editc(CustomerRec.CusNum: '4') + ' Balance = ' + %editc(CustomerRec.BalDue: 'L') : 3); writeln ('Name=/' + Name + '/': 3); endif; endif; enddo; if Logging; writeln ('> Total due = ' + %editc(ouTotalDue: 'L')); writeln ('Leave ' + %proc() + ' ' + %char(%timestamp)); endif; close *all; return; end-proc WL02R; dcl-proc ReformatName; dcl-pi *n varchar(24); inLastName varchar(16) const; inInitials varchar( 3) const; inLogging ind const; end-pi; if inLogging; writeln ('Enter ' + %proc() : 3); writeln ('> Last=/' + inLastName + '/ Init=/' + inInitials + '/' : 3); endif; return %subst(inInitials: 1: 1) + '. ' + %subst(inInitials: 3: 1) + '. ' + %trim(inLastName); end-proc ReformatName; dcl-proc writeln; dcl-pi *n; inString varchar(132) const; inPosition uns(3) const options(*nopass); end-pi; dcl-ds ReportLine len(132) end-ds; dcl-s Position uns(3); if %parms() >= %ParmNum(inPosition); Position = inPosition; else; Position = 1; endif; %subst(ReportLine: Position) = inString; write qsysprt ReportLine; end-proc writeln;
The debugging compiler condition is gone. The printer file, the writeln routine, all the writeln calls, and the printer file open are compiled into the object code and are controlled by a variable named LOGGING in the main subprocedure. Whether the report is produced or not is controlled through an optional options parameter.
I can put the word LOG or NOLOG in the options parameter when I call the program. If I specify LOG, the program produces the same report shown above. I can easily enable and disable the report by changing the command string in the job scheduler.
The way I interpret the options parameter in this example is not foolproof, but it’s robust enough for my purposes, as I know that I will not put a value like FLOG or APOLOGY or HYDROBIOLOGICAL into the parameter string.
What we do now is no different from what people have been doing since the earliest days of business computing. We process input to create output. Output can be formal or informal. Both types have their place. My writeln routine has been invaluable to me in producing both types of output. If you can improve my routine, please do so and let me know about it.
how should WriteLn be compiled? I get error no open, among other errors
Hi, Dale. You’ll have to open the printer file yourself.
I include writeln in every source member where I use it. Someday I may turn it into a standalone module that you’ll just link to, and if I do, the module will handle the open on the first call to writeln.