Apple Assembly Line
Volume 1 -- Issue 2November 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 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 cannot 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 ProgramsBob Sander-Cederlof

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 FilesBob Sander-Cederlof

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 CommandBob Sander-Cederlof

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-PadBob Sander-Cederlof

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  *---------------------------------

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 5537, Richardson, TX 75080. Subscription rate is $12/year, in the U.S.A., Canada, and Mexico. Other countries add $6/year for extra postage. All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)