Apple Assembly Line Volume 1 -- Issue 2 November, 1980 Our second issue is 33% larger than the first! And not only so, but also there is useful information on the back page! I found a source for 6x9 white envelopes, so your address can be external to the newsletter, and so your copy will arrive in better condition. In less than a month since the newsletter was first announced, we already have over 45 paid subscribers. They are sprinkled all over the map, including one in Japan! In This Issue... ---------------- A Bug in S-C Assembler II Version 4.0 . . . . . . . . . 1 Variable Cross Reference for Applesoft Programs . . . . 2 Bags, Boxes, et cetera . . . . . . . . . . . . . . . . 8 Assembly Source on Text Files . . . . . . . . . . . . . 9 A Use for the USR Command . . . . . . . . . . . . . . 15 A Simulated Numeric Key-Pad . . . . . . . . . . . . . 15 A Bug in S-C Assembler II Disk Version 4.0 ------------------------------------------ One real bug has turned up, and a few of you have had the bad luck to discover it the hard way. The assembler is free-format, in that opcodes and directives may start in any column after the blank which terminates the label field. However, the ".IN" directive will malfunction unless there are at least six spaces. If you tab over before typing ".IN" there will be no problem. However, if you type your line like "1230 .IN FILE1", with only two spaces between the line number and the period, you are in for a long wait. The processor goes into a loop printing D's. If you have the MONC mode on, you will see "LOADDDDDDDDD....." with D's forever appearing on your screen. Remember to TAB OVER, and it will not malfunction. One fancied bug has been reported, and I would like to explain it. A user pointed out that you cannont shorten the SAVE command to three letters if you wish to save the source program on a disk file. Why? Because "SAVE" or "SAV" with no file name is not a DOS command. It is an assembler command to save the source program on cassette tape! On the other hand, SAVE with a filename is not an assembler command. It is a DOS command, and the assembler never sees it. The same goes for "LOAD", "LOA", and LOAD with a filename. Variable Cross Reference for Applesoft Programs ----------------------------------------------- Besides illustrating a lot of programming techniques, the VCR program is a very useful tool when you are writing large Applesoft programs. As listed here, it requires a 48K Apple, and assumes that HIMEM is set to at least $8AA7. You BRUN it, and it sets up the &-vector. When you are ready to print a cross reference, you merely type "&" and a carriage return, and out it comes. It is VERY fast: about 15 times faster than the VCR program included in Apple's DOS Tool Kit. It also takes less memory than Apple's version, both for the program itself and for the tables it constructs during execution. The main body of the program is in lines 1400 thru 1460. After calling INITIALIZATION, the subroutine PROCESS.LINE is called until there are no more lines. Then PRINT.REPORT is called, and finally INITIALIZATION is called again to restore Applesoft's tables to their original form. INITIALIZATION sets up PNTR to point to the beginning of the program, and EOT to point to the end of the table area. It also clears out a set of 26 two-byte pointers in HSHTBL (hash table). PROCESS.ONE scans a single line looking for variables by calling SCAN.FOR.VARIABLES, until the end of the program is reached. PRINT.REPORT merely prints a nice orderly report from the data which has been stored in the table by SCAN.FOR.VARIABLES. The symbol table routines used in VCR are very similar to the ones used inside S-C Assembler II Version 4.0. There are 26 pointers starting at HSHTBL ($280), each one representing one letter of the alphabet. The first letter of a variable name selects one of these pointers. The pointer points at the first entry in a chain of variable names. When a new variable name is found, it is inserted in the appropriate chain at the place where it will be in alphabetical order. A sub-chain is kept for each variable name of all the line numbers from which it is referenced. The line number chain is maintained in numerical order. Thus there is no sorting necessary when it comes time to print the report. Since no routines from the Applesoft ROMs are used, VCR will work with no changes with the RAM version of Aplesoft. Since it loads below $9000, it will not conflict with Neil Konzen's PLE (Program Line Editor). Since it is just straight-forward code, with no address tables or embedded data, you can easily relocate it to a different running address; only the 3-byte instructions with the third byte equal to $88, $89, or $8A need to be changed. Or, you can type it in, and use a different origin (line 1040). If you like to modify programs, this one needs one improvement. (Only one?) I forgot to take note of the FN token, so any FN definitions or uses will look like references to an array variable. Another kind of modification, called "major" perhaps, will turn the VCR into LNCR (Line Number Cross Reference). 1000 *--------------------------------- 1010 * VARIABLE CROSS REFERENCE 1020 * FOR APPLESOFT PROGRAMS 1030 *--------------------------------- 1040 ZZ.BEG .EQ $8800 1050 .OR ZZ.BEG 1060 .TF B.VCR 1070 *--------------------------------- 1080 LDA #$4C AMPERSAND VECTOR 1090 STA $3F5 1100 LDA #VCR 1110 STA $3F6 1120 LDA /VCR 1130 STA $3F7 1140 RTS 1150 *--------------------------------- 1160 PNTR .EQ $18,19 POINTER INTO PROGRAM 1170 DATA .EQ $1A THRU $1D 1180 LZFLAG .EQ $1A LEADING ZERO FLAG 1190 NEXTLN .EQ $1A,1B ADDRESS OF NEXT LINE 1200 LINNUM .EQ $1C,1D CURRENT LINE NUMBER 1210 STPNTR .EQ $1E,1F POINTER INTO VARIABLE TABLE 1220 TPTR .EQ $9B,9C TEMP POINTER 1230 SYMBOL .EQ $9D THRU $A4 8 BYTES 1240 VARNAM .EQ SYMBOL+1 1250 HSHTBL .EQ $280 1260 ENTRY.SIZE .EQ $A5,A6 1270 *--------------------------------- 1280 PRGBOT .EQ $67,68 BEGINNING OF PROGRAM 1290 LOMEM .EQ $69,6A BEGINNING OF VARIABLE SPACE 1300 EOT .EQ $6B,6C END OF VARIABLE TABLE 1310 *--------------------------------- 1320 TKN.REM .EQ 178 1330 TKN.DATA .EQ 131 1340 *--------------------------------- 1350 MON.CH .EQ $24 1360 MON.PRBL2 .EQ $F94A 1370 MON.COUT .EQ $FDED 1380 MON.CROUT .EQ $FD8E 1390 *--------------------------------- 1400 VCR 1410 JSR INITIALIZATION 1420 .1 JSR PROCESS.LINE 1430 BNE .1 UNTIL END OF PROGRAM 1440 JSR PRINT.REPORT 1450 JSR INITIALIZATION ERASE VARIABLE TABLE 1452 LDA #0 CLEAR $A4 SO APPLESOFT WILL 1454 STA $A4 WORK CORRECTLY 1460 RTS 1470 *--------------------------------- 1480 INITIALIZATION 1490 LDA LOMEM 1500 STA EOT 1510 LDA LOMEM+1 1520 STA EOT+1 1530 LDX #52 # OF BYTES FOR HASH POINTERS 1540 LDA #0 1550 .1 STA HSHTBL-1,X 1560 DEX 1570 BNE .1 1580 LDA PRGBOT 1590 STA PNTR 1600 LDA PRGBOT+1 1610 STA PNTR+1 1620 RTS 1630 *--------------------------------- 1640 PROCESS.LINE 1650 LDY #3 CAPTURE POINTER AND LINE # 1660 .1 LDA (PNTR),Y 1670 STA DATA,Y 1680 DEY 1690 BPL .1 1692 LDA DATA+1 CHECK IF END 1694 BEQ .3 YES 1700 CLC SKIP OVER DATA 1710 LDA PNTR 1720 ADC #4 1730 STA PNTR 1740 BCC .2 1750 INC PNTR+1 1760 .2 JSR SCAN.FOR.VARIABLES 1770 LDA DATA 1780 STA PNTR 1790 LDA DATA+1 1800 STA PNTR+1 1810 * BNE .3 1820 .3 RTS 1830 *--------------------------------- 1840 SCAN.FOR.VARIABLES 1850 .1 JSR GET.NEXT.VARIABLE 1860 BEQ .3 END OF LINE 1870 JSR PACK.VARIABLE.NAME 1880 JSR SEARCH.VARIABLE.TABLE 1890 BCC .2 FOUND SAME VARIABLE 1900 LDA #0 1910 STA SYMBOL+4 START OF LINE NUMBER CHAIN 1920 STA SYMBOL+5 1930 LDA LINNUM+1 MSB FIRST 1940 STA SYMBOL+6 1950 LDA LINNUM 1960 STA SYMBOL+7 1970 LDA #8 ADD 8 BYTE ENTRY 1980 JSR ADD.NEW.ENTRY 1990 JMP .1 2000 .2 JSR SEARCH.LINE.CHAIN 2010 BCC .1 FOUND SAME LINE NUMBER 2020 LDA #4 ADD 4 BYTE ENTRY 2030 JSR ADD.NEW.ENTRY 2040 JMP .1 2050 .3 RTS 2060 *--------------------------------- 2070 GET.NEXT.VARIABLE 2080 .1 JSR NEXT.CHAR.NOT.QUOTE 2090 BEQ .2 END OF LINE 2100 CMP #TKN.DATA 2110 BEQ .3 2120 CMP #TKN.REM 2130 BEQ .2 SKIP TO NEXT LINE 2140 JSR LETTER LETTER? 2150 BCC .1 NO, KEEP LOOKING 2160 .2 RTS 2170 * DATA, SO SKIP TO NEXT STATEMENT 2180 .3 JSR NEXT.CHAR.NOT.QUOTE 2190 BEQ .2 EOL, RETURN 2200 CMP #': COLON? 2210 BNE .3 NOT END YET 2220 BEQ .1 ...ALWAYS 2230 *--------------------------------- 2240 NEXT.CHAR.NOT.QUOTE 2250 .1 JSR NEXT.CHAR 2260 BEQ .2 EOL, RETURN 2270 CMP #'" QUOTE? 2280 BEQ .3 YES, SCAN OVER QUOTATION 2290 .2 RTS RETURN 2300 .3 JSR NEXT.CHAR 2310 BEQ .2 EOL, RETURN 2320 CMP #'" TERMINAL QUOTE? 2330 BNE .3 NOT YET 2340 BEQ .1 ...ALWAYS 2350 *--------------------------------- 2360 * NEXT CHARACTER FROM LINE 2370 * CALL: JSR NEXT.CHAR 2380 * RETURN: (A)=CHAR FROM LINE 2390 * IF CHAR .NE. EOL, 2400 * INCREMENT PNTR AND 2410 * STATUS Z=0 2420 * IF CHAR .EQ. EOL, 2430 * STATUS Z=1 2440 *--------------------------------- 2450 NEXT.CHAR 2460 LDY #0 2470 LDA (PNTR),Y 2480 BEQ .1 EOL 2490 INC PNTR BUMP POINTER 2500 BNE .1 2510 INC PNTR+1 2520 .1 RTS 2530 *--------------------------------- 2540 PACK.VARIABLE.NAME 2550 STA VARNAM FIRST CHAR OF NAME 2560 LDA #' BLANKS FOR OTHER TWO CHARS 2570 STA VARNAM+1 2580 STA VARNAM+2 2590 JSR NEXT.CHAR 2600 BEQ .5 END OF LINE 2610 JSR LTRDIG 2620 BCC .2 NOT LETTER OR DIGIT 2630 STA VARNAM+1 2640 .1 JSR NEXT.CHAR IGNORE EXCESS NAME 2650 BEQ .5 END OF LINE 2660 JSR LTRDIG 2670 BCS .1 LETTER OR DIGIT 2680 .2 CMP #'$ DOLLAR SIGN? 2690 BEQ .3 YES 2700 CMP #'% PER CENT? 2710 BNE .4 NO 2720 .3 STA VARNAM+2 2730 JSR NEXT.CHAR 2740 BEQ .5 END OF LINE 2750 .4 CMP #'( LEFT PAREN? 2752 BEQ .6 YES 2754 CMP #'" QUOTE? 2760 BNE .5 NO 2762 LDA PNTR YES, BACK UP POINTER 2763 BNE .7 2764 DEC PNTR+1 2765 .7 DEC PNTR 2766 RTS 2770 .6 LDA VARNAM+2 SET HIGH BIT 2780 ORA #$80 TO FLAG ARRAY 2790 STA VARNAM+2 REFERENCE 2800 .5 RTS 2810 *--------------------------------- 2820 SEARCH.VARIABLE.TABLE 2830 SEC CONVERT 1ST CHAR TO 2840 LDA VARNAM HASH TABLE INDEX 2850 SBC #'A 2860 ASL 2870 ADC #HSHTBL 2880 STA STPNTR 2890 LDA /HSHTBL 2900 ADC #0 2910 STA STPNTR+1 2920 *--- FALL INTO CHAIN SEARCH ROUTINE 2930 *--------------------------------- 2940 CHAIN.SEARCH 2950 .1 LDY #0 POINT AT POINTER IN ENTRY 2960 LDA (STPNTR),Y 2970 STA TPTR 2980 INY 2990 LDA (STPNTR),Y 3000 BEQ .4 END OF CHAIN, NOT IN TABLE 3010 STA TPTR+1 3020 LDX #2 2 MORE CHARS IN SYMBOL 3030 LDY #2 POINT AT NAME IN ENTRY 3040 .2 LDA (TPTR),Y COMPARE NAMES 3050 CMP SYMBOL,Y 3060 BCC .3 NOT THIS ONE, BUT KEEP LOOKING 3070 BNE .4 NOT IN THIS CHAIN 3080 DEX 3090 BEQ .5 NAME IS THE SAME 3100 INY NEXT BYTE PAIR 3110 BNE .2 ...ALWAYS 3120 *--------------------------------- 3130 .3 JSR .5 UPDATE POINTER, CLEAR CARRY 3140 BCC .1 ...ALWAYS 3150 *--------------------------------- 3160 .4 SEC DID NOT FIND 3170 RTS 3180 *--------------------------------- 3190 .5 LDA TPTR 3200 STA STPNTR 3210 LDA TPTR+1 3220 STA STPNTR+1 3230 CLC 3240 RTS 3250 *--------------------------------- 3260 ADD.NEW.ENTRY 3270 STA ENTRY.SIZE 3280 CLC SEE IF ROOM 3290 LDX #1 3300 LDY #0 3310 STY ENTRY.SIZE+1 3320 .1 LDA (STPNTR),Y GET CURRENT POINTER 3330 STA SYMBOL,Y 3340 LDA EOT,Y 3350 STA (STPNTR),Y 3360 STA TPTR,Y 3370 ADC ENTRY.SIZE,Y 3380 STA EOT,Y 3390 INY 3400 DEX 3410 BPL .1 3420 *--- SEE IF GOING TO BE ENOUGH ROOM 3430 LDA EOT 3440 CMP #ZZ.BEG 3450 LDA EOT+1 3460 SBC /ZZ.BEG 3470 BCS .3 MEM FULL ERR 3480 *--- MOVE ENTRY INTO VARIABLE TABLE 3490 LDY ENTRY.SIZE 3500 DEY 3510 .2 LDA SYMBOL,Y 3520 STA (TPTR),Y 3530 DEY 3540 BPL .2 3550 LDA TPTR 3560 STA STPNTR 3570 LDA TPTR+1 3580 STA STPNTR+1 3590 RTS 3600 .3 JMP MEM.FULL.ERR 3610 MEM.FULL.ERR 3620 BRK 3630 *--------------------------------- 3640 SEARCH.LINE.CHAIN 3650 CLC ADJUST POINTER TO START 3660 LDA STPNTR OF LINE # CHAIN 3670 ADC #4 3680 STA SYMBOL 3690 LDA STPNTR+1 3700 ADC #0 3710 STA SYMBOL+1 3720 LDA #SYMBOL 3730 STA STPNTR 3740 LDA /SYMBOL 3750 STA STPNTR+1 3760 LDA LINNUM PUT LINE NUMBER INTO SYMBOL 3770 STA SYMBOL+3 3780 LDA LINNUM+1 3790 STA SYMBOL+2 3800 JMP CHAIN.SEARCH 3810 *--------------------------------- 3820 PRINT.REPORT 3830 LDA #'A START WITH A'S 3840 .1 STA VARNAM 3850 SEC 3860 SBC #'A CONVERT TO HSHTBL INDEX 3870 ASL 3880 TAY 3890 LDA HSHTBL+1,Y 3900 BEQ .2 NO ENTRY FOR THIS LETTER 3910 STA PNTR+1 3920 LDA HSHTBL,Y 3930 STA PNTR 3940 JSR PRINT.LETTER.CHAIN 3950 .2 INC VARNAM NEXT LETTER 3960 LDA VARNAM 3970 CMP #'Z+1 3980 BCC .1 STILL MORE LETTERS 3990 RTS FINISHED 4000 *--------------------------------- 4010 LTRDIG 4020 CMP #'0 DIGIT? 4030 BCC LD1 NO 4040 CMP #'9+1 4050 BCC LD2 YES 4060 LETTER 4070 CMP #'A LETTER? 4080 BCC LD1 NO 4090 CMP #'Z+1 4100 BCC LD2 YES 4110 CLC NO 4120 LD1 RTS 4130 LD2 SEC 4140 RTS 4150 *--------------------------------- 4160 PRINT.LETTER.CHAIN 4170 .1 LDA VARNAM FIRST LETTER 4180 JSR PRINT.CHAR 4190 LDY #1 4200 .2 INY 4210 LDA (PNTR),Y REST OF NAME 4220 AND #$7F 4230 CMP #' BLANK? 4240 BEQ .3 4250 JSR PRINT.CHAR 4260 .3 CPY #3 4270 BCC .2 4280 LDA (PNTR),Y CHECK IF ARRAY 4290 BPL .4 4300 LDA #'( 4310 JSR PRINT.CHAR 4320 .4 CLC POINT AT LINE # CHAIN 4330 LDA PNTR 4340 ADC #4 4350 STA TPTR 4360 LDA PNTR+1 4370 ADC #0 4380 STA TPTR+1 4390 JSR PRINT.LINNUM.CHAIN 4400 JSR MON.CROUT 4410 LDY #1 4420 LDA (PNTR),Y POINTER TO NEXT VARIABLE 4430 BEQ .5 NO MORE 4440 PHA 4450 DEY 4460 LDA (PNTR),Y 4470 STA PNTR 4480 PLA 4490 STA PNTR+1 4500 BNE .1 ...ALWAYS 4510 .5 RTS 4520 *--------------------------------- 4530 PRINT.LINNUM.CHAIN 4540 .1 JSR TAB.NEXT.COLUMN 4550 LDY #2 POINT AT LINE # 4560 LDA (TPTR),Y 4570 STA LINNUM+1 4580 INY 4590 LDA (TPTR),Y 4600 STA LINNUM 4610 JSR PRINT.LINE.NUMBER 4620 LDY #1 SET UP NEXT POINTER 4630 LDA (TPTR),Y 4640 BEQ .2 4650 PHA 4660 DEY 4670 LDA (TPTR),Y 4680 STA TPTR 4690 PLA 4700 STA TPTR+1 4710 BNE .1 ...ALWAYS 4720 .2 RTS 4730 *--------------------------------- 4740 TAB.NEW.LINE 4750 JSR MON.CROUT 4760 TAB.NEXT.COLUMN 4770 .1 LDA #7 FIRST TAB STOP 4780 .2 CMP MON.CH CURSOR POSITION 4790 BCS .3 PERFORM TAB 4800 ADC #6 NEXT TAB STOP 4810 CMP #33 END OF LINE? 4820 BCC .2 4830 BCS TAB.NEW.LINE ...ALWAYS 4840 .3 BEQ .4 ALREADY THERE 4850 SBC MON.CH CALCULATE # OF BLANKS 4860 TAX 4870 JSR MON.PRBL2 4880 .4 RTS 4890 *--------------------------------- 4900 PRINT.LINE.NUMBER 4910 LDX #4 PRINT 5 DIGITS 4920 STX LZFLAG TURN ON LEADING ZERO FLAG 4930 .1 LDA #'0 DIGIT=0 4940 .2 PHA 4950 SEC 4960 LDA LINNUM 4970 SBC PLNTBL,X 4980 PHA 4990 LDA LINNUM+1 5000 SBC PLNTBH,X 5010 BCC .3 LESS THAN DIVISOR 5020 STA LINNUM+1 5030 PLA 5040 STA LINNUM 5050 PLA 5060 ADC #0 INCREMENT DIGIT 5070 BNE .2 ...ALWAYS 5080 .3 PLA 5090 PLA 5100 CMP #'0 5110 BEQ .5 ZERO, MIGHT BE LEADING 5120 SEC TURN OFF LZFLAG 5130 ROR LZFLAG 5140 .4 JSR PRINT.CHAR 5150 DEX 5160 BPL .1 5170 RTS 5180 .5 BIT LZFLAG LEADING ZERO FLAG 5190 BMI .4 NO 5200 LDA #' BLANK 5210 BNE .4 ...ALWAYS 5220 PLNTBL .DA #1 5230 .DA #10 5240 .DA #100 5250 .DA #1000 5260 .DA #10000 5270 PLNTBH .DA /1 5280 .DA /10 5290 .DA /100 5300 .DA /1000 5310 .DA /10000 5320 *--------------------------------- 5330 PRINT.CHAR 5340 ORA #$80 5350 JSR MON.COUT 5360 RTS 5370 *--------------------------------- 5380 ZZ.END .EQ * 5390 ZZ.SIZ .EQ ZZ.END-ZZ.BEG Bags, Boxes, et cetera ---------------------- Since I sell software in stores, I buy a lot of zip-lock bags, cardboard mailing boxes, diskettes, and so on. I thought that maybe you need some of these, and haven't been able to find a source at good prices in small quantities. I will sell you some of mine, at the follwoing prices: 6"x9" zip-lock bags $8.50/100 9"x12" zip-lock bags $12/100 Verbatim diskettes without hubrings $30 for box of ten, $265 for 100 with hubrings $32 for box of ten, $285 for 100 Anything else you need? Let me know, maybe I have it or can get it for you or tell you where you can get it at a good price. Assembly Source on Text Files ----------------------------- Version 4.0 of the S-C Assembler II allows you to EXEC a source program, if it is on a DOS text file. This is handy if you have created it with a different editor, or perhaps with a compiler. But what if you want to go the other way? What if you want to SAVE a source program on a text file, so that it can be used in another editor, or by another assembler? There is no built-in command to allow it, so I have now written a separate program to do it. The program loads at $0800 thru $093C, and does not borrow any code from the assembler. It does use some routines in the Monitor ROMs, and the DOS I/O rehook routine. If you BRUN the program, it will assume the pointers at $CA,CB and $4C,4D are bracketing a valid assembly source program, and try to list it on a text file. The main body of the program is in lines 1190 thru 1630. Lines 1200 and 1210 serve to un-hook the S-C Assembler II from the output. They will also turn off your printer, if you had it on. Lines 1220 and 1230 tell DOS that it should recognize commands printed after a control-D. Lines 1240 and 1250 change the prompt symblol to a blank, so that the monitor input subroutine will not print a colon or some other character as the prompt when reading the file name. Lines 1290-1360 request you to enter a file name, read it into the monitor buffer starting at $0200, and move it to a safe place at $0280. It has to be moved, because when we print DOS commands later the area starting at $0200 will be written on by DOS. Once the file name you have typed is safely stored at $0280 and following, lines 1410 thru 1490 will set up the file for writing. This is done in five steps. First, close all files. Second, issue an OPEN-DELETE-OPEN sequence, with the file name (of course); this will make sure that we are writing on a fresh empty file. Then the WRITE command is sent, and we are ready to roll. Line 1530 calls a subroutine which lists your source program. Since the file is OPEN and in WRITE mode, the listing goes into your text file. If you have MON O mode set, you will also see the listing on your screen. Note that it is not really necessary for me to use a subroutine at this point. ASM.LIST is only called once, and it is not very long. But I did it anyway, to keep the main body short enough to fit on a page (of paper), easy to understand, modular, structured, etc. After the listing is completed, line 1570 will close the text file. Lines 1610 and 1620 turn off the DOS run flag, so that DOS will not look for control-D commands. And finally, line 1630 re-enters the S-C Assembler II through its soft entry point. Lines 1670 thru 1780 are text strings, printed by the subroutine named PRINT.QUOTE. Each string is written with the sign bit of every byte zero except for the last byte. The sign bit of the last byte is 1, telling PRINT.QUOTE that it is finished. For example, the first message is the word "CLOSE" and a carriage return. The carriage return is entered in hex with the sign bit 1 as in $8D. The second message is the word "OPEN", and the letter "N" is preceded by a minus sign in the .AS directive to indicate that the sign bit should be 1. The PRINT.QUOTE subroutine is at lines 2140 thru 2200. It expects the Y-register to contain the offset of the desired message from the beginning of all the messages at QTS. It calls on PRINT.CHAR to actually send each character. PRINT.CHAR, at lines 2020 thru 2100, calls on the monitor print character routine at $FDED. This branches through DOS, and DOS writes the character on the text file. PRINT.CHAR saves and restores the Y-register and A-register contents. It also sets the sign bit on each character before printing it. Upon exit, the status will reflect the value of the character printed. Lines 1820-1980 issue a DOS command. The Y-register points at one of the message strings in QTS. Control-D is printed, followed by the command key word, a space, and file name you previously typed. Since DOS does not allow slot and drive specifications on the WRITE command, and since it is sufficient to specify them only once, the subroutine chops them off after printing them once. The logic for this is in lines 1910-1940: after printing a comma, it is replaced with a carriage return. The next time the name is printed, the carriage return will be the end. The subroutine which really controls the listing is in lines 2330-2450. The first four instructions set up a zero-page pointer SRCP to point at the beginning of your source program. Lines 2380-2420 compare the pointer with HIMEM to see if the listing is completed. If you really had no source program, we would already be finished at this point. If there is another line (or more), the subroutine named ASM.LIST.LINE is called to list the next lne. The process is repeated until the last line has been printed onto your text file. At this point it might be helpful to explain how source lines are stored in memory. Each line begins with a single byte which contains the byte-count of the line. Next are a byte-pair containing the line number of the line, in the usual backwards 6502 format. The text of the line follows, and a final byte containing $00 ends the line. No carriage return is stored. Blanks are treated specially. A single blank is stored as $81. Two blanks in a row are replaced by one byte of value $82. Any string of blanks up to 63 blanks is thus replaced by a single token of value $80 plus the blank count. Longer strings of blanks will take more than one token. For example, the source line 1000 ABC LDA SAM is stored as: 0F (total of 15 bytes in line image) E8 03 (line number 1000) 41 42 43 84 ("ABC" and 4 blanks) 4C 44 41 81 ("LDA" and 1 blank) 53 41 4D ("SAM") 00 (end of line indicator) The subroutine ASM.LIST.LINE at lines 2490-2610 prints one source line. A subroutine named GNB ("get next byte") is called to skip over the length byte, and to pick up the line number. PRINT.LINNUM is called to convert the line number to decimal and print it, with leading zeroes if necessary, as a four digit number. The loop at lines 2570-2600 is seeded with a blank (because the blank between the line number and the label field is not actually stored in the source program), and the text of the line is printed. The loop prints a character, and then calls NEXT.TOKEN to get the next one. When the token returned equals $00, the line is finished. GNB, lines 2630-2690, clears the queued blank count, picks up the character pointed at by SRCP, and increments SRCP. NEXT.TOKEN, lines 2710-2820, tests the blank count. If it is non-zero, the count is decremented and a blank ($20) character is returned. If the count was zero, the next character is picked up from the line. If this character is not a blank count token, it is returned and the pointer in SRCP is incremented. If the character is a blank count token, it is saved, the SRCP pointer is incremented past the token, and then the count is decremented and a blank returned. The PRINT.LINNUM routine, lines 2860-3170, is a revision of a routine used in the Integer BASIC ROMs. I think it is commented well enough for you to follow. The general idea is to divide by 1000 and print the quotient; divide the remainder by 100 and print the quotient; then by 10; and finally print the remainder. Since several of you have asked me to provide the capability to list programs onto text files, you should be pleased with this program. If you do not need it, then maybe it has shed some light on the internal structure of part of the assembler, or served as a tutorial in programming. 1000 .LIST OFF 1010 *--------------------------------- 1020 * WRITE ASSEMBLY SOURCE ON A TEXT FILE 1030 *--------------------------------- 1040 .OR $800 1050 MON.PROMPT .EQ $33 1060 PP .EQ $CA,CB 1070 HIMEM .EQ $4C,4D 1080 DOS.RUNFLAG .EQ $D9 1090 MON.BUFFER .EQ $200 1100 DOS.BUFFER .EQ $280 1110 MON.GETLN .EQ $FD6A 1120 MON.CROUT .EQ $FD8E 1130 MON.COUT .EQ $FDED 1140 MON.SETVID .EQ $FE93 1150 DOS.REHOOK .EQ $3EA 1160 BLANK.COUNT .EQ $00 1170 SRCP .EQ $01,02 1180 LINNUM .EQ $03,04 1190 *--------------------------------- 1200 TEXT.LIST 1210 JSR MON.SETVID 1220 JSR DOS.REHOOK 1230 LDA #$FF 1240 STA DOS.RUNFLAG 1250 LDA #' +$80 SET PROMPT CHAR = BLANK 1260 STA MON.PROMPT 1270 *--------------------------------- 1280 * GET FILE NAME 1290 *--------------------------------- 1300 LDY #QFILNAM-QTS 1310 JSR PRINT.QUOTE 1320 JSR MON.GETLN 1330 LDY #$7F MOVE FILE NAME TO SEPARATE BUFFER 1340 .1 LDA MON.BUFFER,Y 1350 STA DOS.BUFFER,Y 1360 DEY 1370 BPL .1 1380 *--------------------------------- 1390 * SET UP THE TEXT FILE 1400 * (CLOSE, OPEN, DELETE, OPEN, WRITE) 1410 *--------------------------------- 1420 JSR CLOSE.FILE 1430 LDY #QOPEN-QTS 1440 JSR ISSUE.DOS.COMMAND 1450 LDY #QDELETE-QTS 1460 JSR ISSUE.DOS.COMMAND 1470 LDY #QOPEN-QTS 1480 JSR ISSUE.DOS.COMMAND 1490 LDY #QWRITE-QTS 1500 JSR ISSUE.DOS.COMMAND 1510 *--------------------------------- 1520 * LIST THE SOURCE PROGRAM 1530 *--------------------------------- 1540 JSR ASM.LIST 1550 *--------------------------------- 1560 * CLOSE THE FILE 1570 *--------------------------------- 1580 JSR CLOSE.FILE 1590 *--------------------------------- 1600 * RETURN TO CALLER 1610 *--------------------------------- 1620 LDA #0 1630 STA DOS.RUNFLAG 1640 JMP $1003 1650 *--------------------------------- 1660 * MESSAGE TEXT 1670 *--------------------------------- 1680 QTS .EQ * 1690 QCLOSE .AS /CLOSE/ 1700 .HS 8D 1710 QOPEN .AS /OPE/ 1720 .AS -/N/ 1730 QDELETE .AS /DELET/ 1740 .AS -/E/ 1750 QWRITE .AS /WRIT/ 1760 .AS -/E/ 1770 QFILNAM .HS 0D 1780 .AS /TEXT FILE NAME:/ 1790 .AS -/ / 1800 *--------------------------------- 1810 * ISSUE DOS COMMAND 1820 *--------------------------------- 1830 ISSUE.DOS.COMMAND 1840 LDA #$84 CONTROL-D 1850 JSR PRINT.CHAR 1860 JSR PRINT.QUOTE 1870 LDY #0 1880 LDA #' PRINT A SPACE 1890 .5 JSR PRINT.CHAR 1900 CMP #$8D 1910 BEQ .7 1920 CMP #$AC COMMA? 1930 BNE .6 1940 LDA #$8D 1950 STA DOS.BUFFER-1,Y 1960 .6 LDA DOS.BUFFER,Y 1970 INY 1980 BNE .5 ...ALWAYS 1990 .7 RTS 2000 *--------------------------------- 2010 * PRINT CHARACTER 2020 *--------------------------------- 2030 PRINT.CHAR 2040 PHA 2050 STY PC.SAVEY 2060 ORA #$80 2070 JSR MON.COUT 2080 LDY PC.SAVEY 2090 PLA 2100 RTS 2110 PC.SAVEY .BS 1 2120 *--------------------------------- 2130 * PRINT A QUOTATION 2140 *--------------------------------- 2150 PRINT.QUOTE.NEXT 2160 INY 2170 PRINT.QUOTE 2180 LDA QTS,Y 2190 JSR PRINT.CHAR 2200 BPL PRINT.QUOTE.NEXT 2210 RTS 2220 *--------------------------------- 2230 * CLOSE ALL FILES 2240 *--------------------------------- 2250 CLOSE.FILE 2260 JSR MON.CROUT 2270 LDA #$84 2280 JSR PRINT.CHAR CONTROL-D 2290 LDY #QCLOSE-QTS 2300 JMP PRINT.QUOTE 2310 *--------------------------------- 2320 * LIST SOURCE PROGRAM 2330 *--------------------------------- 2340 ASM.LIST 2350 LDA PP 2360 STA SRCP 2370 LDA PP+1 2380 STA SRCP+1 2390 .1 LDA SRCP 2400 CMP HIMEM 2410 LDA SRCP+1 2420 SBC HIMEM+1 2430 BCS .2 FINISHED 2440 JSR ASM.LIST.LINE 2450 JMP .1 2460 .2 RTS 2470 *--------------------------------- 2480 * LIST ONE SOURCE LINE 2490 *--------------------------------- 2500 ASM.LIST.LINE 2510 JSR GNB SKIP OVER BYTE COUNT 2520 JSR GNB GET LINE NUMBER 2530 STA LINNUM 2540 JSR GNB 2550 STA LINNUM+1 2560 JSR PRINT.LINNUM 2570 LDA #' BLANK 2580 .1 JSR PRINT.CHAR 2590 JSR NEXT.TOKEN 2600 CMP #0 2610 BNE .1 2620 JMP MON.CROUT 2630 *--------------------------------- 2640 GNB LDY #0 2650 STY BLANK.COUNT 2660 LDA (SRCP),Y 2670 GNBI INC SRCP 2680 BNE .1 2690 INC SRCP+1 2700 .1 RTS 2710 *--------------------------------- 2720 NEXT.TOKEN 2730 LDY #0 2740 LDA BLANK.COUNT 2750 BNE .1 2760 LDA (SRCP),Y 2770 BPL GNBI 2780 AND #$7F 2790 STA BLANK.COUNT 2800 JSR GNBI 2810 .1 DEC BLANK.COUNT 2820 LDA #' BLANK 2830 RTS 2840 *--------------------------------- 2850 * PRINT LINE NUMBER 2860 *--------------------------------- 2870 PRINT.LINNUM 2880 LDX #3 PRINT 4 DIGITS 2890 .3 LDA #'0 SET DIGIT TO ASCII ZERO 2900 .1 PHA PUSH DIGIT ON STACK 2910 SEC SUBTRACT CURRENT DIVISOR 2920 LDA LINNUM 2930 SBC PLNTBL,X 2940 PHA SAVE BYTE ON STACK 2950 LDA LINNUM+1 2960 SBC PLNTBH,X 2970 BCC .2 LESS THAN DIVISOR 2980 STA LINNUM+1 2990 PLA GET LOW BYTE OFF STACK 3000 STA LINNUM 3010 PLA GET DIGIT FROM STACK 3020 ADC #0 INCREMENT DIGIT 3030 BNE .1 ...ALWAYS 3040 .2 PLA DISCARD BYTE FROM STACK 3050 PLA GET DIGIT FROM STACK 3060 JSR PRINT.CHAR 3070 DEX NEXT DIGIT 3080 BPL .3 3090 RTS RETURN 3100 *--------------------------------- 3110 PLNTBL .DA #1 3120 .DA #10 3130 .DA #100 3140 .DA #1000 3150 PLNTBH .DA /1 3160 .DA /10 3170 .DA /100 3180 .DA /1000 A Use for the USR Command ------------------------- The S-C Assembler II Version 4.0 has one user-programmable command, called "USR". (The Quick Reference Card spells it erroneously "USEr".) One good use for it is to re-print the current symbol table. After an assembly, if the listing was not printed, it is often desirable to be able to see what the spelling or value of a symbol or group of symbols is. If the VAL command is not enough for you, then the following steps will set up the USR command to re-list the symbol table on the screen. And, if your printer is selected, it will also print there. Get into the assembler, by using BRUN ASMDISK 4.0 from either Applesoft or Integer BASIC. Type "$1E4EL" after the prompt. The first two lines listed should be "LDY #$02" and "STY $E1". If they are not, you have a different version. (It may still be version 4.0, but slightly different.) The "LDY#$02" line is the first instruction of the symbol table printing subroutine. Patch the USR vector by typing "$1007:4E 1E", and then BSAVE the result like this: :BSAVE ASMDISK 4.0 (WITH USR),A$1000,L$14FB This new version, whenever you type "USR", will print out the current symbol table. It will look exactly the same as the symbol table pritned out at the end of an assembly. A Simulated Numeric Key-Pad --------------------------- This little program will turn part of your Apple's keyboard into a simulated numeric key-pad. A lot cheaper than buying a real one! It is set up to run in page 3, and assumes you are using DOS. If not, just change line 1120 to an RTS. If you BRUN it or CALL it at 768, the input vector is patched to input all characters through the NKP program. Typing a control-S will toggle the numeric key-pad translator on and off. When the translator is off, all keyboard action is normal, except that another control-S will turn it back on again. When the translator is on, all keys which are not part of the simulated key-pad will input normally. The keys translated by the simulator are listed in line 1390. The slash key duplicates RETURN, because it is easier to hit when yu are entering a lot of numbers. For the same reason, the L-key duplicates "-", in case you are in a hurry to enter negative numbers too. The space bar is used for "0". I set it up to use "NM," for "123", "HJK" for "456", and "YUI" for "789". You shuld be able to easily change these translations to any other combination, by changing lines 1390-1420. The heart of the translator is the search loop in lines 1240-1280. If the input character is not found in CHRTBL, the search loop drops out and the character is not changed. If the character is found, line 1310 picks up the alias for the key, and returns. That's all there is to it! 1000 *--------------------------------- 1010 * NUMERIC KEY PAD FOR APPLE 1020 *--------------------------------- 1030 .OR $300 1040 .TF B.NKP 1050 *--------------------------------- 1060 LDA #1 1070 STA TOGGLE 1080 LDA #NKP 1090 STA $38 1100 LDA /NKP 1110 STA $39 1120 JMP $3EA 1130 *--------------------------------- 1140 TOGGLE .BS 1 1150 SAVEY .BS 1 1160 *--------------------------------- 1170 NKP 1180 JSR $FD1B 1190 CMP #$93 CONTROL-S 1200 BEQ .4 1210 BIT TOGGLE 1220 BMI .2 NOT IN NUMERIC MODE 1230 STY SAVEY 1240 LDY #TBLSIZ-1 1250 .1 CMP CHRTBL,Y 1260 BEQ .3 FOUND IN TABLE 1270 DEY 1280 BPL .1 1290 LDY SAVEY 1300 .2 RTS 1310 .3 LDA ALIAS,Y 1320 LDY SAVEY 1330 RTS 1340 .4 LDA TOGGLE 1350 EOR #$80 1360 STA TOGGLE 1370 JMP $FD0C 1380 *--------------------------------- 1390 CHRTBL .AS -"/L NM,HJKYUI" 1400 TBLSIZ .EQ *-CHRTBL 1410 ALIAS .HS 8D 1420 .AS -"-0123456789" 1430 *---------------------------------