Commodore to ASCII For Printers
Thomas Henry
Here's a utility program that will convert certain types of print programs to standard ASCII code format. It will work on all Commodore computers. The program is 109 bytes long. And even if you don't need this utility for your printer, you may want to examine the program anyway. It provides some insight into how a BASIC program is stored and how it may be changed by a machine language routine. On the other hand, you don't need to understand machine language to make use of it.
Before describing the program, I should point out what sort of print programs it may be used for. A good example is instruction printouts. For instance, not long ago I joined the Toronto PET Users Group, and thus was able to draw on their program library. One of the disks I got had "Micromon" and "Basic Aid" programs on it, along with some programs to print out instructions for both of these. When I sent the instructions to the screen, everything was fine. However, sending the output to the printer resulted in a mess. Lowercase became uppercase, and uppercase became Epson graphic symbols. I really wanted a hard copy. That need provided the inspiration to write this program.
Automatic Rewrite
This utility will automatically rewrite programs filled with PRINT statements (such as the ones mentioned above), so that anything between quotes will print out in standard ASCII. The total conversion time for a large program is less than one blink of the cursor. Note that this is not really a general-purpose program, but instead works only on programs of the sort just described. However, between this program and those in Brian Niessen's "PETASCII to ASCII Conversion" article (COMPUTE!, April 1982, pp. 126, 128), just about every type of program is covered.
Let's examine the program. Only two machine dependent locations are used, BASIC and POINTR. BASIC is the zero page pointer to the start of a BASIC program. This is at $28 for all PETs and CBMs and generally points to $0401. For the VIC-20 and Commodore 64, the proper location is $2B. Where it points to depends on the amount of extra memory added. The location called POINTR is the "start of variables" pointer, or, considered another way, it points to the end of a BASIC program. POINTR is used throughout the program, but will be restored to its initial value eventually, thus preserving the BASIC operating environment. POINTR is location $2A for PETs and CBMS, and location $2D for the VIC and 64.
At the entry of the program, POINTR is loaded with the start of BASIC. For a PET or CBM it points to $0401. Next the accumulator is loaded with the first byte past the link addresses and line numbers. Indexing POINTR by Y, when Y equals $04, will accomplish this. If a zero is found, then we must be at the end of a line. If the accumulator contains $22, we have found a first quote and know that the conversion must start on the next character. To convert the string inside the quotes, we branch down to the label STRING.
Refer to that subroutine now. First we check if the end of a line has been found yet. If it hasn't, we check for an endquote. If that isn't found either, the character is ready to be converted. This conversion routine is very "hard-core" in the sense that it covers all 256 possible characters. This may be a bit more powerful than is actually needed here, but it's nice to be safe.
If the accumulator contains anything less than $20, it is replaced with a $01 which is a null character on my printer. It would be nice to use a $00 as a null, but this upsets the program in general by confusing the BASIC operating system into thinking it has found an end of the line. Use any number you want, as long as your printer ignores it.
If the accumulator is less than $41, the character must be punctuation or a numeral and can be printed as is. Next, if it lies between $41 and $5B, then $20 must be added. This will change the lowercase from the PET to true ASCII lowercase.
If the character is less than or equal to $61,it's safe to print again. However, if it's less than $81 but greater than $61, it's an "artificial" punctuation, and hex $40 must be subtracted to compensate.
The next test gets rid of all characters between $81 and $A0 by replacing them with a null symbol. Most of the characters in this range are cursor control symbols or system control symbols (like RUN and REVERSE OFF). We definitely don't want these going to the printer.
Next, graphic characters are replaced by blanks (as opposed to nulls), and finally capital letters are converted to true ASCII capitals by subtracting $80.
This is a pretty hefty "compare and convert" routine, but it is foolproof in that your printer will never get a weird code and become confused. You may have to review the routine several times to really understand how it works, but it might help to keep in mind that some characters need no conversion, some (such as cursor control symbols) should be replaced by null symbols, and graphic characters should be replaced by blanks. Finally, upper- and lowercase letters must be accounted for. To confirm that the routine covers all cases, you can get a chart of true ASCII and compare it with a chart of PETASCII.
I've mentioned that at various times some numbers must be added or subtracted to perform the necessary conversion. Actually, since the numbers fall within a certain range, it is simpler and more efficient to use AND instead of subtraction, and OR instead of addition. The results are the same, and a few bytes may be saved.
A Popular Shortcut
If you look at locations 705A through 7069 in Program 3, you will see a trick commonly employed by 6502 software designers. The various conversion subroutines, such as BAD1, BAD2 and so on, are all separated by a $2C. This is the BIT instruction which will have no effect on the operation of the program. Thus one master subroutine can have several entry points, all leading up to a common ending. For example, suppose the routine is entered at GOOD1 at address $7060. The accumulator will be ORed with $20. Next a BIT test is executed, but this has no effect other than setting some flags in the status register. Then another BIT is performed, and finally the routine concludes by storing the accumulator back in memory.
You may have to sit and stare at this a while to see why this works and why it saves some memory. Nevertheless, this technique of creating harmless op codes allows multiple entry points.
Resuming the analysis of the assembler listing, the STRING routine keeps looping around and around until every character has been converted. Either an endquote byte or a zero indicates that the string is done. If an endquote is found, then the next string is searched for. If, however, a zero is found, the end of a line is indicated, and the program goes to ENDLIN. ENDLIN will direct POINTR to the start of the next line in memory by examining the forward link address of the previous line. If the forward link points to a zero byte, then the end of the program has been found, otherwise control is directed back up to the label LINE, and the next line is converted.
EXIT tidies things up before returning to BASIC. As you probably know, the end of a BASIC program is marked by three consecutive zeros. POINTR is left pointing at the second of these three zeros. Next, the number $02 is added to it, so that it points to the start of variables. It is then safe to return to BASIC.
While back in BASIC, LIST the program and you will see a collection of nonsense between all the quote marks. It will look odd on the screen, but will turn out a perfectly printed hard copy on your ASCII printer. Depending on your needs, you may want to save the converted copy of your program, but in general this isn't necessary.
HOW To Load And Use The Program
For convenience, BASIC versions (Programs 1 and 2) will load the necessary machine language routine for you. Use Program 1 for all PET/CBM models. It locates the machine code at locations 28672 up. Use Program 2 for the VIC-20 and Commodore 64. It loads the code at the top of memory, protects it from BASIC, and indicates the proper SYS address to initiate the conversion. Once you have run the BASIC loader, you may want to save the machine language routine directly to tape or disk. This will enable you to use it in the future without having to run the BASIC loader program again. To save the machine code from memory on the PET/CBM, invoke the monitor (SYS 4), then type
S "CONVERT",01,7000,706d
to save to tape, or
S "CONVERT",08,7000,706d
to save to disk. For the VIC or 64, you will need VICMON, Supermon, Micromon, or one of the other available machine language monitors. Follow the directions for the PET/CBM, except that the beginning and ending addresses for the save will need to be adjusted depending on where in memory the routine is located.
Here's how to use the program. First, load in the PETASCII to standard ASCII converter. Next type NEW. This will clear up some of the pointers in zero page. Now load in the program to be converted. At this point, type SYS 7*4096 (or whatever address the loader indicates). The program will be "instantly" rewritten. LIST it. See how odd it looks? But now RUN the program, and direct all output to the printer. The result will be perfect hard copy.
This isn't the sort of program you're likely to need on a daily basis. But, when you need it, you really need it. So type it in, save it, and play with it a little. Then, when you get some program documentation on disk, you can create a hard copy at a moment's notice.
One final note: if you have some old programs written for the original model PET, you probably have noticed that upper- and lowercase are reversed. You could go back and rewrite the program, but why not let the machine do it? Make a few alterations in the utility presented above, and you can have instant conversion of your old-style programs.
Program 1:
BASIC Loader For All PET/CBM Models
100 REM BASIC LOADER FOR PETASCII TO ASCII CONVERTER
110 REM PET/CBM VERSION
120 HERE=7*4096
130 FOR ADRS=HERE TO HERE+108
140 READ DTA:POKE ADRS,DTA:CK=CK+DTA:NEXT
150 IF CK<>12485 THEN PRINT"CHECK FOR ERROR IN DATA STATEMENTS":STOP
160 PRINT"TYPE 'SYS";HERE;"{LEFT} ' TO ACTIVATE."
170 END
200 DATA 165,40,166,41,133,42,134,43
210 DATA 160,4,177,42,240,7,201,34
220 DATA 240,31,200,208,245,160,0,177
230 DATA 42,170,200,177,42,240,6,134
240 DATA 42,133,43,208,227,24,165,42
250 DATA 105,2,133,42,144,2,230,43
260 DATA 96,200,177,42,240,223,201,34
270 DATA 240,216,201,32,144,31,201,65
280 DATA 144,38,201,91,144,26,201,97
290 DATA 144,30,201,129,144,21,201,160
300 DATA 144,11,201,193,144,4,201,219
310 DATA 144,12,169,32,44,169,1,44
320 DATA 9,32,44,41,63,44,41,127
330 DATA 145,42,24,144,196
Program 2:
BASIC Loader For VIC-20 And Commodore 64
100 REM BASIC LOADER FOR PETASCII TO ASCII CONVERTER
110 REM VIC-20/C-64 VERSION
120 HERE=PEEK(56)-l:POKE 56,HERE:POKE 52,HERE:HERE=HERE*256
130 FOR ADRS=HERE TO HERE+108
140 READ DTA:POKE ADRS,DTA:CK=CK+DTA:NEXT
150 IF CK<>12533 THEN PRINT"CHECK FOR ERROR IN DATA STATEMENTS":STOP
160 PRINT"TYPE 'SYS";HERE;"{LEFT} ' TO ACTIVATE."
170 END
200 DATA 165,43,166,44,133,45,134,46
210 DATA 160,4,177,45,240,7,201,34
220 DATA 240,31,200,208,245,160,0,177
230 DATA 45,170,200,177,45,240,6,134
240 DATA 45,133,46,208,227,24,165,45
250 DATA 105,2,133,45,144,2,230,46
260 DATA 96,200,177,45,240,223,201,34
270 DATA 240,216,201,32,144,31,201,65
280 DATA 144,38,201,91,144,26,201,97
290 DATA 144,30,201,129,144,21,201,160
300 DATA 144,11,201,193,144,4,201,219
310 DATA 144,12,169,32,44,169,1,44
320 DATA 9,32,44,44,63,44,44,127
330 DATA 145,45,24,144,196
Program 3:
PETASCII To ASCII Converter For Print Programs
0000 BASIC = $28 ;START OF BASIC.
0000 POINTR = $2A ;START OF VARIABLES.
0000 *=$7000
7000 A5 28 ENTRY LDA BASIC ;INITIALIZE POINTR TO
7002 A6 29 LDX BASIC+1 ;COINCIDE WITH THE START
7004 85 2A STA POINTR ;OF BASIC.
7006 86 2B STX POINTR+1
7008 A0 04 LINE LDY #$04 ;GO PAST LINK & LINE#.
700A B1 2A CHECK LDA (POINTR),Y ;GET A PROGRAM CHARACTER.
700C F0 07 BEQ ENDLIN ;ZERO MEANS END OF LINE.
700E C9 22 CMP #$22 ;LOOK FOR FIRST QUOTE.
7010 F0 1F BEQ STRING ;GO CONVERT THE STRING.
7012 C8 NEXT INY ;NO STRING FOUND YET..
7013 D0 F5 BNE CHECK ;BRANCH ALWAYS.
7015 A0 00 ENDLIN LDY #$00 ;USING THE FORWARD
7017 B1 2A LDA (POINTR),Y ;LINK ADDRESS, DIRECT
7019 AA TAX ;POINTR TO NEXT LINE
701A C8 INY ;IN BASIC PROGRAM.
701B B1 2A LDA (POINTR), Y
701D F0 06 BEQ EXIT ;ZERO MEANS END OF PROGRAM.
701F 86 2A STX POINTR ;OTHERWISE, UPDATE POINTR.
7021 85 2B STA POINTR+1
7023 D0 E3 BNE LINE ;BRANCH ALWAYS TO NEXT LINE.
7025 18 EXIT CLC ;ADJUST POINTR BACK
7026 A5 2A LDA (POINTR) ;TO WHERE BASIC WOULD
7028 69 02 ADC #$02 ;LIKE IT, I.E.,
702A 85 2A STA POINTR ;START OF VARIABLES.
702C 90 02 BCC RETURN
702E E6 2B INC POINTR+1
7030 60 RETURN RTS ;RETURN TO BASIC.
7031 ; ROUTINE TO CONVERT A SINGLE CHARACTER:
7031 ; ENTER THE ROUTINE WITH THE ACCUMULATOR
7031 ; CONTAINING A PET-ASCII CHARACTER, LEAVE
7031 ; WITH IT REPLACED BY STANDARD ASCII
7031 C8 STRING INY
7032 B1 2A LDA (POINTER),Y ;GET NEXT CHARACTER.
7034 F0 DF BEQ ENDLIN ;ZERO MEANS END OF LINE.
7036 C9 22 CMP #$22 ;CHECK FOR SECOND QUOTE.
7038 F0 D8 BEQ NEXT
703A C9 20 CMP #$20 ;NON-PRINTABLE CHARACTER?
703C 90 1F BCC BAD2 ;YES, REPLACE WITH NULL.
703E C9 41 CMP #$41 ;IS IT PUNCTUATION OR NUMERALS?
7040 90 26 BCC GOOD4 ;YES, KEEP INTACT.
7042 C9 5B CMP #$5B ;LOWER CASE LETTER?
7044 90 IA BCC GOODl ;YES, GO ADD $20.
7046 C9 61 CMP #$61 ;BRACKETS, SLASHES, ETC.?
7048 90 1E BCC GOOD4 ;YES, KEEP INTACT.
704A C9 81 CMP #$81 ;MORE PUNCTUATION?
704C 90 15 BCC GOOD2 ;YES, GO SUBTRACT $40
704E C9 A0 CMP #$A0 ;NON-PRINTABLE CHARACTER?
7050 90 DB BCC BAD2 ;YES, REPLACE WITH A NULL.
7052 C9 Cl CMP #$Cl ;GRAPHIC CHARACTER?
7054 90 04 BCC BAD1 ;YES, REPLACE WITH A SPACE.
7056 C9 DB CMP #$DB ;IS IT A CAPITAL LETTER?
7058 90 OC BCC GOOD3 ;YES, GO SUBTRACT $80.
705A A9 20 BAD1 LDA #$20 ;REPLACE WITH SPACE.
705C 2C .BYTE $2C
705D A9 01 BAD2 LDA #$0l ;REPLACE WITH NULL CHARACTER.
705F 2C .BYTE $2C
7060 09 20 GOOD1 ORA #$20 ;'ADD' $20.
7062 2C .BYTE $2C
7063 29 3F GOOD2 AND #$3F ;'SUBTRACT' $40.
7065 2C .BYTE $2C
7066 29 7F GOOD3 AND #$7F ;'SUBTRACT' $80.
7068 91 2A GOOD4 STA (POINTR),Y
706A 18 CLC
706B 90 C4 BCC STRING ;BRANCH ALWAYS.
706D .END