Classic Computer Magazine Archive COMPUTE! ISSUE 17 / OCTOBER 1981 / PAGE 126

THE PET® GAZETTE

Practical Pet Printing Primer For Perplexed Programmers

Ron Gunn
Livermore, CA

When you first connect a printer to your PET, it is for one primary purpose: to obtain program listings so you can see enough code at one time to rescue yourself from the latest paradox in your programming. As eminently useful as this is, it soon becomes but a step in the utilization of the printer for the output of data from your programs in an organized and permanent form.

Organizing data on a printed sheet implies just that; organizing. You don't want it scattered randomly and capriciously all over the page. Columns with labels are often desired, and so you find that the convenient TAB functions for the PET screen don't work on the printer. It is then that you perhaps start to reduce your data to strings, add blanks, and then partition these longer strings with the LEFT$, RIGHTS, and MID$ functions to make neatly justified columns.

Universal Printing

We are assuming that you are interested in programming that will allow you to print on any printer that will give a listing, a PET, Epson, or whatever. This kind of coding will be the subject of this article. Many printers, including Commodore's, have proprietary ways of handling this problem and I say more about the PET printer's formatting later. These proprietary methods will normally work only on that one make of printer.

You will be able to take the kind of code we are discussing here over to Joe's house and it will work on his PET, Anadex or Kung-Fu papergobbler.

The Answer That Creates A Problem

Here is a sample of code that outputs 3 variables in neat columns on any printer that will LIST. It is intended to illustrate how the string technique mentioned in paragraph two works in practice. There is a problem with this, especially in PET ROM sets earlier than 4.0, and it is not recommended for large data bases or more than a page of print for reasons we will get into.

2200 FOR I = 1 TO 30
2210 P$ = ""
2220 Q$ = " " + STR$(K%(I))
2230 P$ = RIGHT$(Q$,8)
2240 Q$ = " " + STR$(L(I))
2250 P$ = P$ + RIGHT$(Q$,8)
2260 Q$ = " " + STR$(M%(I))
2270 P$ = P$ + RIGHT$(Q$,8)
2280 PRINT#4, P$
2290 NEXT

This code is reasonably concise and straight-forward and will produce neat columns of numbers of varying length, all nicely right-justified. What, then, is the problem? It is a lot like watching a centipede on a treadmill. There is an awful lot going on, but not much progress. Concantenating strings and then printing them out is potentially very slow. As you increase the number of columns, the string handling becomes appreciably slower than the printer.

When you add to this the fact that enormous numbers of throwaway strings are created, and that pre-4.0 PETs can take many seconds to collect garbage, you have a serious problem. (See Butterfield, "Learning About Garbage Collection," COMPUTE! #10.) If you have not experienced this yet, try the experimental listing at the end of this article for an eye opener.

This code example is print test # 1 in the sample listing, and 100 seconds and more were required on my 32N to run it. This is admittedly an extreme example, but points up the trouble you can get into with a program that is large for your machine when you get heavily into string handling.

A Partial Answer

As the referenced article discussed, you can reduce the garbage collection, and you can get the printer to print while the computer is computing, with the following changes. If you have existing print routines that are structured like the above, and which could use some speeding up, then the following substitute coding will help:

2300 FOR I = 1 TO 30
2310 Q$ = " " + STR$(K%(I))
2320 PRINT#4, RIGHT$(Q$,8);
2330 Q$ = " " + STR$(L(I))
2340 PRINT#4, RIGHT$(Q$,8);
2350 Q$ = " " + STR$(M%(I))
2360 PRINT#4, RIGHT$(Q$,8)
2370 NEXT

The change, of course, is that an intermediate variable is not created and then concantenated. The variable is created as a string and is packed with blanks to the maximum needed length on the same line. It is then immediately printed using the appropriate string handling function to make it the correct number of spaces to fit the column you are creating.

You have saved in two ways. The forever concantenated P$ is not created; an advantage as pointed out by the redoubtable Mr. Butterfield. In addition, the printer can process each output while the next gets a head start.

This is print test #2 in the sample listing. Run times on my machine ran some 50 seconds, about half the total time required for test # 1.

Columns Of Names

These examples have been right justified. If you need to print a column of names, you will want to left justify it. This is not necessarily because it is better, it is just that it will be considered a bug or a product of indifference if you do not. Sample coding to do this is:

2800 P$ = N$ + ""
2810 PRINT#4, LEFT$(P$, 20)

In this case, the necessary blanks have been added after the name, and the resulting oversized string has been printed out starting with the left-most character using the LEFT$ function. Always add enough blanks to insure that the space is filled even if N$ turns out to be a null string. This insures that other columns will not be disturbed if that happens.

When you have strings to print, you have to print strings. You can avoid extra string creation as shown by tacking on the necessary blanks at once. Then print immediately using the LEFT$ function in the print statement.

Hints And Kinks

If you have a lot of fill blanks to add, it saves memory to create space variables as a substitue for the blanks-within-quotes as shown on line 2220. Create these handy variables when the program initializes:

6000 S4$ = "    " : S5$ = "    " : S6$ = "  "
6010 S7$ = "    " : S8$ = " "
6020 RETURN

Use a gosub to do this when the program runs. GOSUB6000 in the first line of a program would set up the space variables shown above for use later.

The eight blanks in a line like 2310 could then be added to each string as:

2310P$ = S8$ + STR$(K%(I))

Another point; you already know that the TAB function doesn't work on the printer. Well, the SPC function does. You may have wondered what the differences between these two seemingly similar functions are. This is one of those differences. We will see an example of use later.

WHAT #0$*! APPROACH WORKS?

That second word was copied verbatim from the cover of COMPUTE! #7. Anyhow, there is a way to get whole numbers into printed columns without having to cope with slow string handling. It may sound silly, but the way to do it is to keep it as data (numerical data that is). We will now examine the techniques that may allow you to avoid string renderings of data altogether. You will then put the data out as fast as the printer can handle it; all day if necessary and always in neat columns.

The Basic Idea

Let's say you want to print out a number that will range between 0 and 999 in a column eight spaces wide. Using the integer variable K%, we might write:

2900 IF K% = 0 THEN PRINT #4, S8$ ; : GOTO 2950
2910 IF K% < 10 THEN PRINT #4, S5$K% ; : GOTO 2950
2910 IF K% < 100 THEN PRINT #4, S4$K% ; : GOTO 2950
2930 PRINT #4, S3$K%;
2950 REM NEXT COLUMN

Here we have covered the full range from 0 to 999. If the number goes over 999 it will disturb the columnation unless another line of code is added to catch numbers less than 9999 to realign it. Only one of these print statements will actually execute. If you expect a lot of larger numbers then this routine would go faster with the larger numbers first, as fewer comparisons would take place before the correct line was found and executed. This code will place one column on the sheet. The next column would require a repeat of the whole routine, so coding this way could get long. It is fast, however, and it is effective and straightforward if only a few columns are needed.

In this example, zero is not printed, eight spaces are printed instead. A single digit, including zero if you want it to print, takes three spaces. The < 10 or single digit line (line 2910) has 5 spaces placed first, then the sign space, the numeral itself, then a space after. If you want 0 to print, then put in REM at the start of line 2900 to neutralize it. (Don't eliminate it as it is a GOSUB target line).

Note that the carriage return suppress semicolon is used on all of these lines. This leaves the printer poised on the start of the next column on the same line. A PRINT# statement must be added at the end of the line to go smoothly to the next line, as we will see in the next example. It is like bowling, where the tenth frame is handled differently to wrap things up.

The GOSUB Variable Width Column Maker

Now that the principle has been covered, let's go to some code that will produce an unlimited number of columns of varying width using a reusable subroutine for each column desired. This routine will compete in size with string handling code and is as fast as you'll want.

2400 FOR I = 1 TO 30
2410 P = K%(I) : S = 6 : GOSUB 2460
2420 P = L(I) : GOSUB 2460
2430 P = M%(I) : S = 8 : GOSUB 2460
2440 PRINT # 4
2450 NEXT
2460 IFP = 0 THEN PRINT #4, SPC(S) ; : GOTO 2510 : REM BLANK WHEN P = 0
2470 IFP < 10 THEN PRINT #4,SPC(S - 3)P ; : GOTO 2510
2480 IFP < 100 THEN PRINT #4, SPC(S - 4)P ; : GOTO 2510
2490 IFP < 1000 THEN PRINT #4, SPC(S - 5)P ; : GOTO 2510
2500 PRINT #4, SPC(S - 6)P;
2510 RETURN

Any practical number of columns can be created by reusing the GOSUB at 2460 over and over. The width of each separate column is determined by the variable S, set at line 2410 for six spaces for K%(I) and L(I), and then set again for eight spaces for the variable M%(I).

When the last column has been printed out, the carriage return suppress is cleared with a final PRINT# statement on line 2440. Any additional columns would be added after line 2430 and before 2440, so the PRINT# statement would still be last.

This is print test #3. It prints in about 16 seconds with no pauses. My 80 cps printer is apparently running at its full speed through the whole test.

Check the data that you are printing to make sure that there is a printing line for each possible number that will be sent to the printing subroutine. Any number that is out of range will skew the columnation. You may have to exclude numbers that are too small or too large, or which contain negative or decimal quantities.

Lines, sometimes a number of them, may have to be carefully added to care for everything that you want to include in your printout. Remember the words of Gerald Weinberg in The Psychology of Computer Programming: "To detect errors, the programmer must have a conniving mind, one that delights in uncovering flaws where beauty and perfection were once thought to lie."

The Commodore Answer

I have not seen an in-depth review on the PET 2022 printer, but I have worked with a number of them. Commodore has provided a neat way to format printed information using a format file which you instruct as needed to produce columns, with many useful options. This works only with PET printers and then with reservations, as explained below.

There are two ROM sets for the 2022. The early set is noteworthy for the fact that it supplies a carriage return for every linefeed. When it pages or passes blank lines, the print head moves clear across for each line. That is the bad news. The good news is that the formatting works, and works well.

The newer ROM set will give linefeeds without the time-consuming full carriage scan. There is a bug, however, that causes the machine to go into permanent lower-case mode when more than a few columns are sent to the format file and then printed to. This is a fatal error, as nothing short of a power down will restore normal operation. Complex formatted output that prints perfectly on the older ROM set cannot be made to run on the new set.

I have not been able to locate a corrected ROM, although it has been on the horizon for a year. I have not yet had a chance to try the new PET/MX-80 printer.

Epson

The standard MX-80 was built for use with the TRS-80 and contains the Radio Shack character set to prove it. It does not have the same logic regarding carriage return/linefeed that the PET printer does. With the switch set for listings, KEYPRINT (COMPUTE! #7) will not work. Wordpro 3 required an added If 1 (linefeed) in every file header, and so on. Hoo Boy!

There is a switch that adds linefeeds, but it is buried inside the machine. I almost had my cover trained to jump off when I snapped my finger, when I finally added an external SPST switch in parallel with switch 2-3. When that SPST wears out I will have to replace it.

David Lein's late 1980 manual does a good job of explaining and demonstrating printer features. Appendix D on using the unit with other than the TRS-80 is very brief and touches on the Apple only.

The important features like double width letters, variable line spacing, and double strike work from the keyboard and under program control if you leave those pernicious JPET controls on the IEEE interface board OFF.

I hope that these samples and the discussion will help you to avoid some of the long hours otherwise required to find out how to efficiently use your printer. Some of these problems had me almost carrying my head around under my arm a few times. Remember Weinberg's words; "Any fool without the ability to share a laugh on himself will be unable to tolerate programming for long."

The Demonstration Program Listing.

1000 M = PEEK (51000) : J = 140
1450 IFM = 0 THEN L = PEEK (135) : POKE 135, 26 : GOTO 2000
1500 L = PEEK (53)
1510 POKE 53, 26
2000 DIMA$(J), B$(J), C$(J), D$(J), K% (30), L(30), M% (30)
2020 FOR I = 1 TO J
2030 A$(I) = "TRY" : B$ (I) = A$(I) + "THIS" : C$(I) = "OUT" : D$ (I) = C$ (I) + "TO SEE"
2040 NEXT
2050 FOR I = 1 TO 30
2060 K% (I) = RND(1) * 9
2070 L (I) = INT ((K% (I)) ↑2
2080 M% (I) = (L(I)) ↑2
2090 NEXT
2100 OPEN4, 4
2110 T1 = TI2120 PRINT "PRINT TEST NUMBER (1, 2, OR 3)"
2130 INPUTB: ONBGOSUB2200,2300,2400
2135 T = (TI-T1)/60
2140 PRINT #4,T
2180 CLOSE4
2190 PRINT "THAT TOOK "T"SECONDS TO PRINT"
2192 PRINTFRE(O)
2193 IFM = OTHENPOKE135,L:END
2195 POKE53,L:END
2200 FOR I = 1 TO 30
2210 P$ = " "
2220 Q$ = " " + STR$(K%(I))
2230 P$ = RIGHT $ (Q$,8)
2240 Q$ = " " + STR$(L(I))
2250 P$ = P$ + RIGHT $ (Q$,8)
2260 Q$ = " " + STR$ (M%(I))
2270 P$ = P$ + RIGHT$ (Q$,8)
2280 PRINT #4,P$
2290 NEXT : RETURN
2300 FOR I = 1 TO 30
2310 Q$ = " " + STR $ (K%(I))
2320 PRINT #4,RIGHT$ (Q$,8);
2330 Q$ = " " + STR$(L(I))
2340 PRINT #4, RIGHT$ (Q$,8);
2350 Q$ = " " + STR $ (M%(I))
2360 PRINT #4, RIGHT $ (Q$,8)
2370 NEXT : RETURN
2400 FOR I = 1 TO 30
2410 P = K% (I) : S = 6 : GOSUB 2460
2420 P = L(I) : GOSUB2460
2430 P = M% (I) : S = 8 : GOSUB2460
2440 PRINT#4
2450 NEXT : RETURN
2460 IFP = OTHENPRINT #4, SPC (S); : GOTO2510 : REM THIS LINE LEAVES BLANK SPACE ON O
2470 IF P < 10 THENPRINT #4, SPC (S-3) P; :GOTO2510
2480 IF P < 100 THEN PRINT #4, SPC (S-4) P; : GOTO2510
2490 IFP < 1000 THENPRINT#4, SPC (S-5)P; : GOTO2510
2500 PRINT #4,SPC (S-6)P;
2510 RETURN
READY.