Volume 2 -- Issue 8 | May 1982

In This Issue...

- Secret RWTS Caller Inside DOS
- Some Patches to the S-C Macro Assembler
- Benchmarking Block MOVES
- New AED Features
- Another Recursive Macro
- RWTS Caller (Reading a Whole Track)
- Reading the Game Buttons Unambiguously
- Macro Branch Library

Macro Assembler in EPROM

A number of you have asked about the possiblility of getting the S-C Macro Assembler in EPROM to put on an Apple Firmware card, or a CCS 12K Eprom card. If you want to do it, and know how to modify the firmware card to accept EPROMs, I will send a set of 5 EPROMs containing the assembler for $64. If you also need the monitor in EPROM, add another $16. The assumption will be that you have Applesoft on the mother board, and that you already own the S-C Macro Assembler on disk.

Advertising in AAL

Due to the increased costs of printing more than 1600 copies per month, and with the desire to limit the percentage of advertising pages to less than 30% each month, I have decided to raise the page rate again.

For the June 1982 issue, the price will be $50 for a full page, $30 for a half page. So-called "classified" ads, of up to forty words, will be $5.

The Best Book So Far for Beginners

Roger Wagner's book for beginners wanting to learn assembly language programming is now out, at $19.95. (My price is only $18.) Called "Assembly Lines: The Book", it began as simply a reprint of the series Roger writes for Softalk Magazine. But there is a lot more material in the book, and 100 pages of Appendices. Appendix B, 70 pages, is a very lucid description of every 6502 opcode. If you rank yourself as a beginning assembly language programmer, this book will be a tremendous help.

Good Price on the NEC printers

I can ship you an NEC PC-8023A-C dot matrix printer for only $595. I believe the normal list price is $795, but mail order prices are generally less. I also have the Grappler interface card and cable, configured for the NEC printer, for only $150 (normally $175, I think). And if you want both printer and interface at the same time, the combined price is only $695.

I have two of these printers, and like them better than my Epson MX-80. Why? Faster: 100cps instead of 80cps. Fully equipped: standard features include graphics, tractor feed, and friction feed. Handier: the friction feed is just like a typewriter, platen and all; and option switches, should you wish to change them, are accessible without removing any screws. I run one of them with an Epson parallel interface, and the other with the Grappler.

If you would rather have a Spinwriter (that is what this newsletter is printed on), call me for prices.

Vinyl Diskette Pages for your S-C Assembler Binder

I am having 1000 special pages manufactured. They will fit the binder that comes with the Macro Assembler, and will hold one diskette each and a 3x5 index card. For $6 I'll send you ten of them. For $12 I'll send them in a binder. For $36 you can have a binder with ten blank diskettes in vinyl pages. The binder is also just right for storing back issues of AAL.

Secret RWTS Caller Inside DOS | Bill Parker

I found a portion of code tucked away in DOS that will perform an RWTS call for you, doing away with the necessity of finding a place to put a controlling subroutine, an IOB, etc.

As you know, RWTS (Read/Write Track and Sector) gives the programmer the ability to read a sector from any specified track and put it in a buffer in RAM. It also allows the programmer to manipulate the buffer and write it back out to any specified track and sector on the disk.

In this 48K DOS routine, which happens to be the same for 3.3 as well as 3.2, all the programmer has to do is to plug in the track and sector desired and whether he wants to read it from disk to the buffer, or write it from the buffer to the disk. (The buffer is a fixed 256-byte location beginning at $B4BB (46267).) A simple CALL 45111 or a JSR $B037 will then perform the transfer. (You must remember to restore the original Read/Write code back to "2" when you are finished, so that the system can write to the directory when it needs to).

Here is a disassembled and commented version of the routine, which (for lack of a better term) I have named "WRTDIR". This should aid in the development of programs that need to examine or alter the contents of a disk.

This routine, which normally writes a directory sector to the disk from the buffer at $B4BB.B5BA (46267-46522), can be used as a general RWTS utility by plugging in:

               Value         Name  $Loc    Loc
         Read/Write (1/2)     RW   $B041  45121
         Track No. ($0-$22)   TK   $B397  45975
         Sector No. ($0-$F)   SC   $B398  45976

Then call 45111 or JSR $B037 and set RW to 2 when done.

     1000           .OR $B037
     1010           .TF B.WRTDIR
     1020  *--------------------------------
     1030  BUFSTHI  .EQ $AAC6
     1040  BUFSTLO  .EQ $AAC5
     1050  CALLRWTS .EQ $B052
     1060  IOBBUF   .EQ $B7F0
     1070  RW       .EQ 2
     1080  SC       .EQ $B398
     1090  TK       .EQ $B397
     1100  *--------------------------------
     1110  WRTDIR   JSR SETBUFAD
     1120           LDX TK
     1130           LDY SC
     1140           LDA #RW
     1150           JMP CALLRWTS
     1160  *--------------------------------
     1170  SETBUFAD LDA BUFSTLO     PUT BUFFER'S
     1180           STA IOBBUF      STARTING ADDRESS IN
     1190           LDA BUFSTHI     INPUT OUTPUT BLOCK
     1200           STA IOBBUF+1
     1210           RTS

Some Patches for the S-C Macro Assembler | Bob Sander-Cederlof

1. Loading the Language Card version: When you type "EXEC LOAD LCASM", the language card is loaded with a copy of the monitor from the Apple mother board, and with the file S-C.ASM.MACRO.LC. The EXEC file also makes a small modification to the memory image depending on which language you have on the mother board.

If you have serial number M-5275 or earlier, the EXEC file does not do the final step of turning on the Assembler. I expected you to type the DOS command "INT" (if Applesoft is on the mother board) or "FP" (if Integer BASIC is on the mother board) to enter the Assembler.

You can make a minor change to the EXEC file to make it automatically turn on the assembler after loading. The next to the last line of the EXEC file is now "C082"; change it to "C080" for automatic turn-on.

Here is a step-by-step procedure for the change. Try it on a COPY of the master disk, in case you make a mistake.

1. Get into the S-C Macro Assembler, either regular of language card version, it doesn't matter which).
2. Type "AUTO". The Assembler will print ":1000 " and wait for input.
3. Type five (5) backspaces (left arrow) to position the cursor right after the prompt.
4. Type "EXEC LOAD LCASM", and the Assembler will load the EXEC file into memory. You will see a list of line numbers on the screen.
5. Type five backspaces and the word "MANUAL" to turn off the auto-line-number mode.
6. LIST the lines (type "LIST").
7. See line 1090? It should be "C082". Type "1090 C080" to change it.
8. Type "TEXT LOAD LCASM" to save the modified version.
9. That's all there is to it!

By the way, Bob Potts (from the Bank Of Louisville) was here last week. He brought along a Corvus 5-meg drive, so we put the Language Card version of the Assembler on it. For some reason which we can't explain, the EXEC file hangs up after the BLOAD (but only if the language card has not been loaded since power up). We changed the EXEC file slightly, and it always worked. Instead of doing the BLOAD while in the monitor, we did it from Applesoft. Here is the new version of the file:

         REM LOAD S-C MACRO ASSEMBLER
         REM INTO THE LANGUAGE CARD
         CALL-151
         C081 C081
         F800<F800.FFFFM
         BLOAD S-C.ASM.MACRO.LC
         300:A9 4C CD 00 E0 F0 12 8D 00 E0 A9 00 8D 01 E0 A9 D0 8D 02 E0 A9 CB 8D D1 03 60
         300G
         C080
         3D3G

You may also want to change the HELLO file to include an option to EXEC LOAD LCASM automatically.

2. If you have a Language Card, and your Assembler has a serial number of about M-5030 or earlier, the memory limits are not set up properly in all cases.

Check your copy of S-C.ASM.MACRO.LC by loading it into the language card and typing "$D2C6" from inside the assembler. If you don't have $A0 there, then you need to install this patch:

         1.  Type in these monitor commands:

             :$D2C6:A0 0C
             :$D2C8:20 1E D3 A9 00 91 58 85
             :$D2D0:D9 AD 00 E0 C9 4C F0 08

         2.  Type "BSAVE S-C.ASM.MACRO.LC,A$D000,L$231F".

3. If you have serial number M-5287 or earlier, a more difficult to apply patch is needed to correct a problem in printing the symbol table. If you are using the .TI directive, and if you have several lines of local labels in the symbol table listing, and if the page break comes between two such lines, the listing is messed up in a disastrous way.

To fix the S-C.ASM.MACRO, do the following (very carefully):

         1.  Get into the assembler by typing "BRUN S-C.ASM.MACRO".

         2.  Type the following monitor commands:
             :$2AF<26AF.26D5M
             :$26B1<2AF.2D5M
             :$26AF:84 2F
             :$26C1:CA

         3.  Type "BSAVE S-C.ASM.MACRO,A$1000,L$21D3"

To repair the language card version, do the following:

         1.  EXEC LOAD LCASM, and get into the assembler by typing
         INT (unless you already made the change to the EXEC file
         noted above).

         2.  Type the following monitor commands:
             :$C083 C083
             :$2AF<E7FB.E821M
             :$E7FD<2AF.2D5M
             :$E7FB:84 2F
             :$E80D:16

         3.  Type "BSAVE S-C.ASM.MACRO.LC,A$D000,L$231F"

If these patches and my instructions seem too difficult, you can send me $2.50 and your S-C Macro Assembler diskette; I will update it with the new HELLO program, the new LOAD LCASM file, and the patched copies of the assembler.

Benchmarking Block MOVES | William R. Savoie

While working on my new soon-to-be-released data file system, I came to see the importance of a speedy 6502 block move routine. I have resisted "moves" like the coming of winter. I have a super two pass sort routine that is very fast. Providing a person has less than 2000 files there is no need to do much file moving, since only pointers need move. (My system has a 64K card, giving me a 112K system.)

Unfortunately, the real world needs some 10,000 or more files, and these of course must be sorted too. By physically moving the files as directed by the sorted pointers, and then moving all this to disk, it is possible to use a merge sort to get the whole job done in the least amount of time. With this preamble behind us, let's get on the move!

I have benchmarked three approaches to moving blocks: the monitor move down (located at $FE2C) which I'm sure you all have used, and its variation, a similar move up routine. Next is the Applesoft block move, and third is a self modifying move which I call "Quick Move".

To ease such a tedious undertaking, I have included a BASIC connection to pass variables and determine the benchmark precision. To help further, I have added a hex converter, a memory dump routine, and an automatic 3D0G vector using the ctrl-Y command from the monitor. To help with the problem of what block of memory was moved where, I wrote a memory fill routine. This acts to place the memory address back into memory, on two-byte boundaries. You can easily read memory to see where it came from.

My first benchmark was a block move of 10,000 bytes made 100 times. The next was a move of 10,240 bytes, again made 100 times. Here are the conditions and resulting times:

                              mon up  mon dn     AS       QM
    Case I    Lo=18674=$48F0    47      53      17.2     15.3   seconds
    Case II   Lo=18432=$4800    48.7    54.7    16.7     14.7

Note: 0.5 seconds of these values due to BASIC overhead.

As you can see, the old monitor move is not made for high speed moves. For one thing, a two-byte subtraction is carried out for each byte that is moved. It is much more efficient to do the subtraction only once, before you start. A closer look shows that it is faster to move more data, providing you move a whole number of memory pages! The time needed to move the "extra" 240 bytes was negative 0.5 seconds for the Applesoft block move and negative 0.6 seconds for the "Quick Move". There was no sensitivity to start and destination boundaries. "Quick Move" was 3.7 times faster than the monitor move!

I tried putting the first half of Quick Move on page zero at $A0, but the speed improvement was only 0.7 seconds (about 5%) over the time it took when located at $3000.

As a further note, each move routine requires its own parameter organization. If files are to be moved and not lost, attention must be paid to exact specification of end points and lengths.

      100  GOSUB 300: REM INITIALIZE BINARY PROGRAM
      105  A = 0: B = 0: C = 0: J = 0
      106  TEXT : HOME :
           PRINT : PRINT " BLOCK MOVE TESTING"; SPC( 8)"LOOP = ";L + 1:
           PRINT : PRINT "(0) END" SPC( 13)"(1) TEST OVERHEAD":
           PRINT : PRINT "(2) M.MOVE UP" SPC( 7)"(3) M.MOVE DOWN"
      107  PRINT : PRINT "(4) A.MOVE UP" SPC( 7)"(5) QUICK MOVE DOWN":
           PRINT : PRINT "(6) HEX DUMP" SPC( 8)"(7) ADDRESS FILL"
      108  PRINT : PRINT "(8) HEX CONVERT" SPC( 5)"(9) EDIT":
           PRINT : PRINT "(10) MONITOR WITH CTRL-Y RETURN": PRINT
      109  PRINT "(11) SET CALL LOOP = 100": PRINT
      110  PRINT "PICK TEST NUMBER (0 TO 11) ";:
           INPUT I: IF I < 0 OR I > 11 THEN 110
      112  HI = 28672: REM $7000 HI BLOCK
      114  LO = 18672: REM $48F0 LO BLOCK
      115  LO = 18432: REM $4800 LO BLOCK
      118  LE = 2056:  REM $808 LENGTH OF MOVE
      130  ON I GOSUB 150,160,170,180,190,200,210,220,230,240,250
      134  IF I = 0 THEN  END
      136  IF I > 5 THEN  PRINT :
           PRINT "HIT ANY KEY ";: GET A$: GOTO 105
      140  PRINT  CHR$ (7);I;" DONE ";J;" TIMES":
           GET A$: GOTO 105
      150  A = LO: B = LO: C = LO + 1:
           FOR J = 0 TO L:
           CALL BASE,A,B,C:
           NEXT :
           RETURN :
           REM TIME BASIC
      160  A = LO:
           B = HI:
           C = HI + LE:
           FOR J = 0 TO L:
           CALL BASE,A,B,C:
           NEXT :
           RETURN :
           REM M.MOVE UP
      170  A = LO:
           B = HI:
           C = LO - LE - 1:
           FOR J = 0 TO L:
           CALL BASE,A,B,C:
           NEXT :
           RETURN :
           REM M.MOVE DOWN
      180  A = HI:
           B = LO:
           C = HI + LE:
           FOR J = 0 TO L:
           CALL BASE + 3,A,B,C:
           NEXT :
           RETURN :
           REM A.MOVE UP
      190  A = LO:
           B = LO - LE:
           C = HI - LO:
           FOR J = 0 TO L:
           CALL BASE + 6,A,B,C:
           NEXT :
           RETURN :
           REM QUICK MOVE DOWN
      200  CALL BASE + 9,LO - LE - 32,0,HI + LE + 32:
           RETURN
      210  CALL BASE + 12,LO - LE - 16,HI + LE + 16,0:
           RETURN
      220  HOME :
           VTAB 8:
           PRINT "INPUT DECIMAL NUMBER ";:
           INPUT N:
           CALL BASE + 15,N:
           RETURN :
           REM HEX CONVERTER
      230  TEXT :
           HOME :
           POKE 33,33:
           LIST :
           END
      240  HOME :
           FOR I = 1017 TO 1018:
           A =  PEEK (I - 40):
           POKE I,A:
           NEXT :
           POKE 1016,76:
           POKE 72,0:
           CALL  - 151
      250  L = 99: GOTO 105
      300  HIMEM: 12287: REM $2FFF
      305  BASE = 12288:
           IF  PEEK (BASE) = 76 THEN  RETURN :
           REM .OR $3000
      310  D$ =  CHR$ (13) +  CHR$ (4)
      320  PRINT D$"BLOAD B.BLOCK MOVE BENCHMARKS"
      330  PRINT : RETURN

     1000  *******************************
     1010  *                             *
     1020  *  BENCHMARKING BLOCK MOVES   *
     1030  *                             *
     1040  *  BY WILLIAM R. SAVOIE  3/82 *
     1050  *   LIMERICK TRAINING CENTER  *
     1060  *  C/O GENERAL PHYSICS CORP.  *
     1070  *  341 LONGVIEW RD., LINFIELD *
     1080  *   PENNSYLVANIA   ZIP 19468  *
     1090  *******************************
     1100
     1110
     1120  *-------------------------------*
     1130  * APPLE II PAGE ZERO MEMORY USE *
     1140  *-------------------------------*
     1150
     1160  A1L    .EQ $3C      MONITOR
     1170  A1H    .EQ $3D       USE
     1180  A2L    .EQ $3E       FOR
     1190  A2H    .EQ $3F       BLOCK
     1200  A3L    .EQ $40       MOVE
     1210  A3H    .EQ $41
     1220  A4L    .EQ $42
     1230  A4H    .EQ $43
     1240  FACMO  .EQ $A0      FP REGISTER
     1250  FACLO  .EQ $A1      FP REGISTER
     1260
     1270  *-------------------------------*
     1280  * OTHER APPLE II MEMORY MAPPING *
     1290  *-------------------------------*
     1300
     1310  BLTU   .EQ $D39B    BLOCK TRANSFER
     1320  FRMNUM .EQ $DD67    FORMULA=>NUM
     1330  COMA   .EQ $DEBE    CHECK COMA
     1340  AYINT  .EQ $E10C    MAKE INTEGER
     1350  PRNTX  .EQ $F944    PRINT X
     1360  NXTA1  .EQ $FCBA    INCR POINTER
     1370  PRBYTE .EQ $FDDA    PRINT A
     1380  MOVE   .EQ $FE2C    MONITOR MOVE
     1390
     1400
     1410  *-------------------------------*
     1430       .OR $3000
     1440       .TF B.BLOCK MOVE BENCHMARKS
     1450  *-------------------------------*
     1460
     1470  *  THIS CODE ALLOWS SIMPLE ENTRY WITHOUT THE & COMMAND
     1480  BEGIN  JMP MONITOR.MOVE
     1490         JMP APPLESOFT.MOVE
     1500         JMP QUICK.MOVE
     1510         JMP DUMP     HEX OUTPUT
     1520         JMP FILL     LABLE MEMORY
     1530
     1540  * TO HELP A HEX CONVERTER
     1550  CONVERT
     1560         JSR GETVAR   GET VARIABLE
     1570         JSR PRNTX    HI BYTE OUT
     1580         LDA FACLO    GET LOW BYTE
     1590         JMP PRBYTE   HEX OUTPUT
     1600         .PG
     1610  * THIS CODE FILLS MEMORY WITH IT'S OWN ADDRESS
     1620  * WHICH IS VERY USEFULL FOR CHECKING BLOCK MOVES
     1630  FILL   JSR GM       GET VARIABLES
     1640  .01    LDY #$01     TWO BYTE OFFSET
     1650         LDA A1L      GET LOW BYTE
     1660         STA (A1L),Y  WRITE ADDRESS LO
     1670         DEY          MOVE LEFT
     1680         LDA A1H      GET HI ADDRES
     1690         STA (A1L),Y  WRITE TO MEMORY
     1700         JSR NXTA1    INCREMENT PTR
     1710         JSR NXTA1    TWICE
     1720         BCC .01      GO TELL DONE
     1730         RTS
     1740
     1750  * A UTILITY DUMP T0 SEE MEMORY FROM BASIC
     1760  DUMP   JSR GM       GET VARS
     1770         LDA $C010    CLEAR STROBE
     1780  .01    LDA $C000    GET KEY IF ONE
     1790         BPL .03      GO DUMP HEX
     1800         LDA $C010    CLEAR STROBE
     1810  .02    LDA $C000    READ KEY AGAIN
     1820         BPL .02      WAIT FOR KEY
     1830         CMP #$8D     'RETURN' KEY?
     1840         BEQ .04      EXIT
     1850         LDA $C010    CLEAR STROBE
     1860  .03    JSR $FDA3    8 HEX OUT
     1870         LDA A2L      END LOW
     1880         CMP $A1      DESIRED LOW
     1890         LDA A2H      HI TOO
     1900         SBC $A0      ENOUGH?
     1910         BCC .01      GO TELL DONE
     1920  .04    RTS
     1930
     1940  * GET VARIABLE FROM BASIC
     1950  * MUST BE <65357 OR SYNTAX ERR
     1960  * PLACE IN REGISTERS X AND A
     1970
     1980  GETVAR JSR COMA     MUST SEE COMA
     1990         JSR FRMNUM   GET NUMBER
     2000         JSR AYINT    MAKE INTEGER
     2010         LDX FACMO    HI BYTE
     2020         LDA FACLO    LOW BYTE
     2030         RTS
     2040
     2050
     2060  * GET BASIC VARIABLES INTO THE
     2070  * MONITORS WORK REGISTERS USED BY
     2080  * COMMANDS M,V,G,L,S,T,-,+,..ETC
     2090
     2100  GM     JSR GETVAR   BLOCK START
     2110         STA A1L      LOW BYTE
     2120         STX A1H      HI BYTE
     2130         JSR GETVAR   BLOCK END
     2140         STA A2L      LOW
     2150         STX A2H      HI BYTE
     2160         JSR GETVAR   DEST. START
     2170         STA A4L      LOW
     2180         STX A4H      HI
     2190         RTS
     2200         .PG
     2210  *-------------------------------*
     2220  * THE OLD MONITOR MOVE          *
     2230  * MOVE BLOCK OF MEMORY UP/DOWN  *
     2240  *-------------------------------*
     2250
     2260  MONITOR.MOVE
     2270         JSR GM       SET UP MOVE
     2280         LDA A1L      START LOW
     2290         CMP A4L      END LOW
     2300         LDA A1H      START HI
     2310         SBC A4H      WHICH BIGGER?
     2320         BCS MOVEDN   GO DOWN IN MEM
     2330
     2340  MOVEUP LDY #$00     CLEAR INDEX
     2350  .01    LDA (A2L),Y    GET DATA
     2360         STA (A4L),Y    PUT DATA
     2370         LDA A4L      GET INDEX
     2380         BNE .02      PAGE CROSS?
     2390         DEC A4H      HI BYTE
     2400  .02    DEC A4L      LOW BYTE
     2410         LDA A2L
     2420         CMP A1L      END YET?
     2430         LDA A2H
     2440         SBC A1H
     2450         LDA A2L
     2460         BNE .03      PAGE CROSS?
     2470         DEC A2H      HI BYTE
     2480  .03    DEC A2L      LOW BYTE
     2490         BCS .01      GO TELL DONE
     2500         RTS
     2510
     2520  MOVEDN LDY #$00     CLEAR INDEX
     2530         JMP MOVE     MONITOR MOVE
     2540
     2550
     2560  *------------------------------*
     2570  * AND ALONG CAME THE PEOPLE AT *
     2580  * MICROSOFT WITH THEIR MOVE    *
     2590  *------------------------------*
     2600
     2610  APPLESOFT.MOVE
     2620         JSR GETVAR   HI ADDRESS OF BLOCK TO MOVE
     2630         STA $96      LOW
     2640         STX $97      HI BYTE
     2650         JSR GETVAR   BLOCK END
     2660         PHA          SAVE TELL LAST
     2670         TXA          NEED 9B,9C
     2680         PHA          TO GETVARS
     2690         JSR GETVAR   HI ADDRESS OF DISTINATION
     2700         STA $94      LO&HI BYTES
     2710         STX $95
     2720
     2730  * WE USED $9B,9C TO GET THE TWO BYTE FP VALUE
     2740         PLA          END OF BLOCK
     2750         STA $9C      HI BYTE
     2760         PLA
     2770         STA $9B      LOW BYTE TOO
     2780         SEC          SUBTRACT COMMING
     2790         JMP BLTU     A.MOVE
     2800         .PG
     2810  *-------------------*
     2820  * MOVING IN RAM CAN *
     2830  * BE EVEN FASTER    *
     2840  *-------------------*
     2850
     2860  QUICK.MOVE
     2870         JSR GETVAR   GET START
     2880         STA .01+1    LOW BYTE
     2890         STA .06+1    COPY HERE TOO
     2900         STX .01+2    HI
     2910         JSR GETVAR   DESTINATION
     2920         STA .02+1    LO
     2930         STA .07+1    COPY HERE TOO
     2940         STX .02+2    HI BYTE
     2950         JSR GETVAR   END ADDRESS
     2960         TXA          SET TEST FOR
     2970         BEQ .05       MOVE<256?
     2980
     2990  * X=PAGE NUMBERS TO MOVE
     3000         LDY #$00     INDEX=0
     3010  .01    LDA $4800,Y  SOURCE
     3020  .02    STA $4000,Y  DESTINATION
     3030         INY          NEXT BYTE
     3040         BNE .01      SMALL MOVE
     3050  .03    INC .01+2    HI SOURCE
     3060  .04    INC .02+2    HI DESTINATION
     3070         DEX          DONE?
     3080         BNE .01      Y=0 SO MOVE PAGE
     3090
     3100  * SET UP REMAINING MOVE
     3110  .05    LDY $A1      LOW BYTE LENGTH
     3120         BEQ .08      GO IF NONE
     3130         LDA .01+2    COPY HI BYTE
     3140         STA .06+2    FOR SOURCE
     3150         LDA .02+2     AND
     3160         STA .07+2     DESTINATION
     3170
     3180  * NOW WITH X=0 START MOVING LOW BYTE OF LENGTH
     3185  * (Y) = REMAINING BYTES TO MOVE
     3190  .06    LDA $4800,X  SOURCE
     3200  .07    STA $4000,X  DESTINATION
     3210         INX          NEXT
     3220         DEY          MOVE ENOUGH?
     3230         BNE .06      GO TELL DONE
     3240  .08    RTS
     3250         .LIST OFF

New AED Features | Bob Sander-Cederlof

Bill Linn, author of AED, stopped by the other day. Bill is a Vice President at Cullinane Corporation, and was in Dallas for a user convention. Since it was Sunday, and we are both earnest Christians, he spent the morning with Becky and me and the kids at church. Later we all went out for an excellent lunch at the local Harvey House.

He brought the latest version of AED along, and showed me all the new features. AED now has keyboard macros! They are user definable, but he has predefined quite a few for you. If you type two escapes in a row, the top 18 lines of the screen are used to display a menu of all the currently defined escape macros. Escape followed by some other character inserts the corresponding text string at the current cursor position.

There is a utility program on the disk for use in defining your own macro strings, and it is very easy to use. In fact, you use AED editing to modify simple DATA statements within the utility itself. When you are through with your changes, the utility modifies the macro table within AED and on the disk.

Again I say, if you are not fully satisfied with your current stable of Applesoft programming aids, you owe it to yourself to buy AED. It will save you countless hours of frustrating retyping as you create and edit and restructure and debug and modify Applesoft programs.

Another Recursive Macro | Lee Meador

Last month I sent Bob a recursive macro definition that he put in the AAL for everyone to see. In case you have forgotten what recursive means, let me explain it somewhat. If you have a macro that calls itself under certain conditions, that macro is called 'recursive'. It's kind of like the plastic cup I had when I was little. There was a picture on the cup of a little bear sitting in a high-chair. The bear was holding a plastic cup and on the cup was a picture of a little bear in a high-chair holding a plastic cup with a picture of a bear in a high-chair holding a cup with a picture of a bear......

I always used to wonder how many bears there were and how big the littlest one was. Now, when we are using recursion in our macros we want to be sure we know that there is a last little bear -- a last call -- a way of leaving the lowest level of recursion. Otherwise (since each recursive call uses up some memory/stack space) we will soon run out of room to store the return information and BOOM goes the assembly.

My macro uses the principle of 'divide and conquer' to allocate a chosen number of bytes all of which hold the value we want them to have. We might use this macro with a table of 128 bytes. All non-alphabetic characters (codes $0 to $40, plus assorted others) will have the value $FF in their corresponding bytes in the table. All alphabetics will have a number indicating their relative frequency in English text. We could set up the table with lines and lines of hex strings for all the non-alphabetics. Or we could let the program filter out the alphabetics and use a shorter table. But for the sake of the example let us assume we need the program to be as fast as possible and memory space is no object.

Here is the macro definition I came up with:

         .MA DB          Macro name is "DB"
         .DO ]1<2        If only one left,
         .DA ]2             generate a data byte
         .ELSE           If more than one left,
         >DB ]1/2,]2        call DB for half of them,
         >DB ]1+1/2,]2      and call DB again for the other half
         .FIN
         .EM

Here is the table I talked about:

         >DB $41,#$FF   65 bytes filled with $FF
         .HS 00000000   upper case (fill in your own frequencies)
         ......
         >DB 5,#$FF     5 bytes filled with $FF
         .HS 00000000   lower case
         ......
         >DB 5,#$FF     5 bytes filled with $FF

Here is a sample program to use such a table:

         LDA CHARACTER.BYTE    get character
         TAY
         LDA TABLE,Y           get frequency
         ......                and then you have to use it

The macro calls itself to set up half of the area desired and then calls itself again to set up the other half. The adding of one to the second call makes sure that both odd and even values for the first parameter will work. If the call only needs one byte to be set up then a .DA is used to take care of it. That provides the end of the little bears -- when the first parameter is one.

When I needed a macro like this my first idea was to have each recursive call take care of one byte and then call itself to take care of the rest. If the macro was called with zero repetitions then nothing would be done except end the macro. The problem with that method is the amount of stack space used as the recursion goes to very deep levels. The method used in the example will only recurse, for example, 8 levels to generate 127 bytes of data.

By the way, notice that you must put the pound sign (#) on the second parameter if you want to generate single bytes. Leaving it off will generate two-byte values of data. I chose that method to make the macro more flexible. You might want to put the pound sign (#) inside the macro to make it safer in case you always want to generate single bytes of data. Also, you can use calculated values like #'F+$80 to generate tables of some character value.

The assembly of recursive macros produces quite a few extra lines in the listing, so after checking it out you will probably want to turn off the listing of the macro expansion with ".LIST MOFF". Here is a sample listing with the macro expansion listing on:

                   1000  *--------------------------------
                   1010  *      LEE MEADOR'S SECOND RECURSIVE MACRO
                   1020  *--------------------------------
                   1030         .MA DB
                   1040         .DO ]1<2
                   1050         .DA ]2
                   1060         .ELSE
                   1070         >DB ]1/2,]2
                   1080         >DB ]1+1/2,]2
                   1090         .FIN
                   1100         .EM
                   1110  *--------------------------------
        0800-      1120         >DB 3,#0
                   0000>         .DO 3<2
                   0000>         .ELSE
        0800-      0000>         >DB 3/2,#0
                   0000>         .DO 3/2<2
        0800- 00   0000>>         .DA #0
                   0000>>         .ELSE
                   0000>>         .FIN
        0801-      0000>         >DB 3+1/2,#0
                   0000>>         .DO 3+1/2<2
                   0000>>         .ELSE
        0801-      0000>>         >DB 3+1/2/2,#0
                   0000>>>         .DO 3/2/2<2
        0801- 00   0000>>>         .DA #0
                   0000>>>         .ELSE
                   0000>>>         .FIN
        0802-      0000>>         >DB 3+1/2+1/2,#0
                   0000>>>         .DO 3/2+1/2<2
        0802- 00   0000>>>         .DA #0
                   0000>>>         .ELSE
                   0000>>>         .FIN
                   0000>>         .FIN
                   0000>         .FIN

RWTS CALLER (Reading a Whole Track) | Bill Morgan

Here is a routine to directly call RWTS (Read/Write Track/Sector), the subroutine in DOS that actually reads for or writes to the disk. Many programs use a routine like this to handle disk I/O, without all the time-consuming overhead of the DOS file manager.

All you need to do to use RWTS directly is to place certain information into an Input/Output control Block (IOB), and tell RWTS where the IOB is. Following is an explanation of the IOB (the addresses are those of DOS's own IOB; you can use it yourself, or build your own wherever is convenient):

         Address        Description

         $B7E8      Table type, always $01
         $B7E9      Slot number times 16, usually $60
         $B7EA      Drive number, $01 or $02
         $B7EB      Volume number expected, $00 matches anything
         $B7EC      Track number, $00 through $22
         $B7ED      Sector number, $00 through $0F
         $B7EE-F    Address of Device Characteristics Table,
                    $B7FB for DOS's own DCT
         $B7F0-1    Address of buffer, wherever you want
         $B7F2      Not used
         $B7F3      Byte count if partial sector, $00 normally
         $B7F4      Command     $00 = SEEK
                                $01 = READ
                                $02 = WRITE
                                $04 = FORMAT
         $B7F5      Error Code  $00 = No errors
                                $08 = Error in initialization
                                $10 = Write protect error
                                $20 = Volume mismatch
                                $40 = Drive error
         $B7F6      Last volume number
         $B7F7      Last slot number
         $B7F8      Last drive number

The Device Characteristics Table (whose address is at $B7EE,EF) is a four-byte block containing information about the disk drive. For a standard Apple Disk II this block always contains $00 01 EF D8.

For our purposes, the most important items in the IOB are track, sector, buffer address, and command. By manipulating these, you can read any sector of the disk into any area of memory. All you need to do is set up the IOB, load the A- and Y-registers with the address of the IOB, and JSR $3D9. And if you decide to use the file manager's IOB, you can even set up the A- and Y-registers by a simple JSR $3E3.

RWTS will read the track and sector you choose into your 256-byte buffer. If there was a disk error, RWTS will return with the carry bit set and an error code stored in the IOB. It is then up to the user's program to decide what to do about the error. If there was no error, carry will be clear.

This month we'll set up a short program using the RWTS Caller to read an entire track of the disk into 16 consecutive pages of memory. DOS stores information on a track starting at sector $0F and working back to sector $00, so we must read a sector into the buffer, decrement the sector in the IOB, and increment the buffer pointer.

RWTS.CALLER:

Lines 1680-1850 set up the IOB, transferring values from the program variables.

Lines 1870-1890 load the address of the IOB and call RWTS.

Lines 1900-1910 are necessary to avoid confusing the system monitor. (RWTS and the monitor both use location $48.)

Lines 1960-2030 ring a warning if a disk error occurred, and display the error code.

TRACK READ:

Lines 1260-1350 initialize the variables and call input routines.

Lines 1370-1440 read the sectors from $0f through $00 into the buffer. Line 1410 will end the program if an error occurred.

Line 1460 will become a display routine. (Or, whatever processing you want to do on the buffer.)

Lines 1500-1650 will become input routines; right now they just set the track, buffer and command variables.

CAUTIONS:

1) These routines have very little error-checking. It is very easy to make a trivial error and lose information from a diskette. Always test an RWTS-calling program on a diskette yhou don't care about.

2) If you store information on a blank area of a diskette using these techniques, DOS doesn't know you have taken some space. Unless you modify the VTOC to show that sectors are used, DOS can overwrite your data. (What's a VTOC?, you say. Volume Table of Contents. We'll go into that another time.)

More:

There is more about RWTS on pages 94-98 of Apple DOS Manual, and a goldmine of information in Beneath Apple DOS, by Don Worth and Pieter Lechner. (Quality Software, 1981.)

     1000  *SAVE TRACK READ
     1010  *--------------------------------
     1020  SLOT    .EQ $00     $60 OR $70
     1030  DRIVE   .EQ $01     1 OR 2
     1040  VOLUME  .EQ $02     0 = DON'T CARE
     1050  TRACK   .EQ $03     $00 TO $22
     1060  SECTOR  .EQ $04     $00 TO $0F
     1070  BUFFER  .EQ $05,06
     1080  COMMAND .EQ $07     1 = READ, 2 = WRITE
     1090  PREG    .EQ $48
     1100  *
     1110  RWTS    .EQ $3D9
     1120  *
     1130  IOB         .EQ $B7E8    DOS'S OWN IOB
     1140  IOB.SLOT    .EQ $B7E9
     1150  IOB.DRIVE   .EQ $B7EA
     1160  IOB.VOLUME  .EQ $B7EB
     1170  IOB.TRACK   .EQ $B7EC
     1180  IOB.SECTOR  .EQ $B7ED
     1190  IOB.BUFFER  .EQ $B7F0,F1
     1200  IOB.COMMAND .EQ $B7F4
     1210  IOB.ERROR   .EQ $B7F5
     1220  *
     1230  PRBYTE .EQ $FDDA
     1240  COUT   .EQ $FDED
     1250  *--------------------------------
     1260  SETUP
     1270         LDA #$60
     1280         STA SLOT     SLOT 6
     1290         LDA #$01
     1300         STA DRIVE    DRIVE 1
     1310         LDA #$00
     1320         STA VOLUME   ANY VOLUME
     1330         JSR GET.TRACK
     1340         JSR GET.BUFFER
     1350         JSR GET.COMMAND
     1360  *--------------------------------
     1370  READ.TRACK
     1380         LDA #$0F     START AT SECTOR $0F
     1390         STA SECTOR
     1400  .1     JSR RWTS.CALLER   READ ONE SECTOR
     1410         BCS EXIT     EXIT IF ERROR
     1420         INC BUFFER+1 NEXT BUFFER PAGE
     1430         DEC SECTOR   NEXT SECTOR
     1440         BPL .1       NOT DONE, READ NEXT SECTOR
     1450  *--------------------------------
     1460  DISPLAY
     1470  *--------------------------------
     1480  EXIT   RTS
     1490  *--------------------------------
     1500  GET.TRACK
     1510         LDA #$11
     1520         STA TRACK    TRACK $11 (DIRECTORY)
     1530         RTS
     1540  *--------------------------------
     1550  GET.BUFFER
     1560         LDA #0
     1570         STA BUFFER   BUFFER AT $4000
     1580         LDA #$40
     1590         STA BUFFER+1
     1600         RTS
     1605         .PG
     1610  *--------------------------------
     1620  GET.COMMAND
     1630         LDA #1
     1640         STA COMMAND  READ
     1650         RTS
     1660  *--------------------------------
     1670  RWTS.CALLER
     1680         LDA SLOT     TRANSFER
     1690         STA IOB.SLOT  VALUES
     1700         LDA DRIVE      INTO
     1710         STA IOB.DRIVE   IOB
     1720         LDA VOLUME
     1730         STA IOB.VOLUME
     1740         LDA TRACK
     1750         STA IOB.TRACK
     1760         LDA SECTOR
     1770         STA IOB.SECTOR
     1780         LDA COMMAND
     1790         STA IOB.COMMAND
     1800         LDA BUFFER
     1810         STA IOB.BUFFER
     1820         LDA BUFFER+1
     1830         STA IOB.BUFFER+1
     1840         LDA #$00
     1850         STA IOB.ERROR
     1860  *--------------------------------
     1870         LDY #IOB     LOAD IOB
     1880         LDA /IOB     ADDRESS
     1890         JSR RWTS     CALL RWTS
     1900         LDA #$00
     1910         STA PREG     SOOTHE MONITOR
     1920         BCS ERROR.HANDLER
     1930         RTS
     1940  *--------------------------------
     1950  ERROR.HANDLER
     1960         LDA #$87     BELL
     1970         JSR COUT     RING
     1980         JSR COUT        ING
     1990         JSR COUT          ING
     2000         LDA IOB.ERROR
     2010         JSR PRBYTE   DISPLAY ERROR CODE
     2020         SEC          EXIT WITH CARRY SET
     2030         RTS

Reading the Game Buttons | Jim Kassel

Recently I was asked to come up with a maching language subroutine that involved using the Apple game buttons. Fortunately for me at the time, I forgot that my paddles were not plugged in. I was in for a rude awakening because when I tested the program, it said that all of the buttons were constantly being pushed!

I needed some additional programming to check whether the buttons were even plugged in. The problem occurs because the Apple button logic returns the same value for a pushed button as for a missing button. To get technical: in TTL logic, when an input pin of an IC chip is left unconnected, the chip thinks the pin is at a logic "1". The Apple buttons supply a logic "1" when they are plugged in and pushed. Hence, the hardware cannot tell the difference between a plugged-in-pushed-down button and a missing button.

What the hardware does know for sure is when a button is plugged in and not pushed. This is the only case in which a logic "0" is developed. I had to use this knowledge to write a program which could tell what a logic "1" really means. Since an installed unpushed button does unambiguously announce its presence by a "0" in bit 7 of the input byte, I could make a mask indicating which buttons appear to be installed.

I started by writing the GET.BUTTON.STATUS subroutine. It reads each of the three buttons, and packs the three bit-7's into one byte. The way I wrote it, bit 7 of the returned byte represents button 1, bit 6 is button 2, and bit 5 is button 3. If a button is installed and not pushed, the corresponding bit will be "1"; otherwise it will be "0".

Look at the listing, lines 1250-1350, and I'll describe how GET.BUTTON.STATUS works. I used an indexed loop, where X goes from 2 to 0, step -1, addressing all three of the buttons. Only bit 7 of a button byte is significant. I invert this bit (line 1280), and shift it into the carry status bit (line 1290). Then line 1300 rolls the bit into GB.PUSH. After all three have been read and rolled, I pick up GB.PUSH and zero out the lower five bits at line 1340.

Now lets look at the other three subroutines. GAME.BUTTON.INITIALIZE simply clears out GB.STAT. We have to start with GB.STAT = 0, so that as we discover each installed button we can set its bit. Call this subroutine once at the beginning of your overall run.

GAME.BUTTON.INSTALLED reads the current button status; remember that a "1" here indicates an installed but unpushed button. So line 1140 merges all "1" bits into GB.STAT. You need to call this subroutine several times, at time intervals of several seconds at least, to be sure that every installed button is noticed at least once. (The first few times you call it, you might be pushing down an installed button; then finally you let go, so this subroutine sees the button.)

GAME.BUTTON.PUSHED reads the current button status, and with a little boolean logic comes out with the final result: a "1" indicating an installed and pushed button, and a "0" meaning either a missing button or an unpushed button. The result is in the A-register, and also in GB.PUSH.

Here is a truth table of the logic involved:

         GB.STAT        CURRENT READING    EOR  AND
                                           STAT STAT
         0 (no button)  0 (none or pushed)  0    0
         0 (no button)  1 (unpushed)        1    0
         1 (button)     0 (none or pushed)  1    1
         1 (button)     1 (unpushed)        0    0

There are other possible complications in reading buttons, which I have not handled here. You might want to "debounce" the buttons, so that you don't get false indications of multiple pushes when the button begins to make or break contact. And, you might want to guarantee that any action which happens from a pushed button only happens once per push.

Lines 1400-1910 demonstrate the usage of the subroutines. After clearing the screen, the buttons are continuously monitored until you press any key on the keyboard. The status of each button will be displayed on the screen: button 1 on the to line, button 2 on the second line, and button 3 on the third line. If you hold a button pushed and then start the program, it will say "not installed" until you release the button; from then on it will track the button properly.

If you have the shift key mod installed in button 3, it will say "not installed" until you press the shift key; from then on it will say "pushed" when you are not pushing, and "not pushed" when you are. This is because the sense of the shift key is the opposite of the normal game paddle buttons.

     1000  *--------------------------------
     1010  *      GAME BUTTON SUBROUTINES
     1020  *--------------------------------
     1030  GAME.BUTTON .EQ $C061  BASE ADDRESS
     1040  *--------------------------------
     1050         .OR $800
     1060  *--------------------------------
     1070  GAME.BUTTON.INITIALIZE
     1080         LDA #0
     1090         STA GB.STAT
     1100         RTS
     1110  *--------------------------------
     1120  GAME.BUTTON.INSTALLED
     1130         JSR GET.BUTTON.STATUS
     1140         ORA GB.STAT  SET BITS OF ANY BUTTONS
     1150         STA GB.STAT  PLUGGED IN AND NOT PUSHED
     1160         RTS
     1170  *--------------------------------
     1180  GAME.BUTTON.PUSHED
     1190         JSR GET.BUTTON.STATUS
     1200         EOR GB.STAT  MASK OUT BUTTONS WHICH
     1210         AND GB.STAT    ARE NOT PLUGGED IN
     1220         STA GB.PUSH
     1230         RTS
     1240  *--------------------------------
     1250  GET.BUTTON.STATUS
     1260         LDX #2
     1270  .1     LDA GAME.BUTTON,X
     1280         EOR #$80     INVERT SENSE
     1290         ASL          INTO CARRY BIT
     1300  .2     ROR GB.PUSH
     1310         DEX          NEXT BUTTON
     1320         BPL .1
     1330         LDA GB.PUSH
     1340         AND #$E0     CLEAR EXTRANEOUS BITS
     1350         RTS
     1360  *--------------------------------
     1370  GB.STAT .BS 1
     1380  GB.PUSH .BS 1
     1390  *--------------------------------
     1400  MON.CV     .EQ $25
     1410  MON.HOME   .EQ $FC58
     1420  MON.VTAB   .EQ $FC22
     1430  MON.CLREOL .EQ $FC9C
     1440  MON.COUT   .EQ $FDED
     1450  TEST.MASK  .EQ $00
     1460  *--------------------------------
     1470  TEST   JSR MON.HOME
     1480         JSR GAME.BUTTON.INITIALIZE
     1490  .1     LDA #0
     1500         STA MON.CV
     1510         JSR MON.VTAB
     1520         JSR GAME.BUTTON.INSTALLED
     1530         JSR GAME.BUTTON.PUSHED
     1540         LDA #$84
     1550         STA TEST.MASK
     1560  .2     LDA TEST.MASK
     1570         AND GB.PUSH
     1580         BNE .3       PUSHED
     1590         LDA TEST.MASK
     1600         AND GB.STAT
     1610         BNE .4       NOT PUSHED
     1620         LDY #QTGONE-QTS   NOT INSTALLED
     1630         .HS 2C
     1640  .3     LDY #QTPUSHED-QTS
     1650         .HS 2C
     1660  .4     LDY #QTNOTPSH-QTS
     1670         JSR MSGOUT
     1680         LSR TEST.MASK
     1690         BCC .2
     1700         LDA $C000
     1710         BPL .1
     1720         STA $C010
     1730         RTS
     1740         BCS .1       ...ALWAYS
     1750  *--------------------------------
     1760  MSGOUT LDA QTS,Y
     1770         PHA
     1780         ORA #$80
     1790         JSR MON.COUT
     1800         INY
     1810         PLA
     1820         BPL MSGOUT
     1830         JSR MON.CLREOL
     1840         LDA #$8D
     1850         JMP MON.COUT
     1860  *--------------------------------
     1870  QTS
     1880  QTPUSHED   .AT /PUSHED/
     1890  QTNOTPSH   .AT /NOT PUSHED/
     1900  QTGONE     .AT /NOT INSTALLED/
     1910  *--------------------------------

Macro Branch Library | R. F. O'Brien

When I received my copy of the S-C Macro Assembler, my first task was to make up a set of branch macro definitions to use in all my programs. This set will finally eliminate the need to check usage of BCC, BCS, etc., and generally make the programs more readable.

There are six branch-on-tests: >BLT, >BLE, >BGE, >BGT, >BEQ, AND >BNE. All of these would normally be used with two parameters, e.g. >BGT P1,P2...reads: if contents of accumulator is greater than P1 then branch to P2. The first four of these can be gainfully used with only one parameter, after a comparison. Sample program:

         LDA #$8D
         CMP #$8E
         >BLT THERE ... results in a branch to THERE

While >BEQ and >BNE are defined so as to work with only one parameter, there is no reason to so use them - it is easier to just use BEQ and BNE.

The macro >BRA (BRanch Always) is used with one parameter - any others are ignored. >BRA LABEL causes a jump to LABEL, up to +- 127 bytes away. To overcome this limitation I decided to put the macro facility to good use to provide for easy branching to any part of a program - this is necessary for writing relocatable code. I settled for a two-paramter code >JMP:

>JMP P1,P2 ... where P1 is the intermediate or final label you wish to branch to and P2 is the label for this instruction.

Instructions such as the foregoing are inserted anywhere you wish in the program (within 127 bytes of each other) to allow for unlimited branching whilst retaining relocatable code. The following is an example of how you might use the >JMP code:

         1000 A    etc.
                   (more code....)
         2000      >JMP A,B
                   (more code....)
         3000      >BRA B

When a program designed as the above is run it will simulate an absolute jump to A. The >BRA B will branch to label B, which contains a >BRA A. This sequence of instructions is transparent to the rest of the program, as the first instruction in the >JMP is to skip around the the >BRA within the definition.

This use of macro definitions can easily be extended to the X and Y registers; simply substitute CPX's or CPY's for the CMP's.

Following are some examples of these macros at work:

         >BLT #3,THERE....if (A) is less than 3 then go THERE
         >BGT $40,THIS....if (A) is greater than ($40) then go THIS
         >BEQ #'A,THAT....if (A) is equal to $41 then go THAT

To use these macros in all your programs, place the command .IN MACRO.BRANCH.LIBRARY at the beginning of your source program.

     1000  *  MACRO BRANCH LIBRARY
     1010  *  BY R.F. 0'BRIEN
     1020  *--------------------------------
     1030  *   >BLT P1 (,P2)   BRANCH IF (A) < P1...TO P2
     1040         .MA BLT
     1050         .DO ]#>1
     1060         CMP ]1
     1070         BCC ]2
     1080         .ELSE
     1090         BCC ]1
     1100         .FIN
     1110         .EM
     1120  *--------------------------------
     1130  *   >BLE P1 (,P2)   BRANCH IF (A)<=P1...TO P2
     1140         .MA BLE
     1150         .DO ]#>1
     1160         CMP ]1
     1170         BEQ ]2
     1180         BCC ]2
     1190         .ELSE
     1200         BEQ ]1
     1210         BCC ]1
     1220         .FIN
     1230         .EM
     1240  *--------------------------------
     1250  *   >BGE P1 (,P2)   BRANCH IF (A)>=P1...TO P2
     1260         .MA BGE
     1270         .DO ]#>1
     1280         CMP ]1
     1290         BCS ]2
     1300         .ELSE
     1310         BCS ]1
     1320         .FIN
     1330         .EM
     1340  *--------------------------------
     1350  *   >BGT P1 (,P2)   BRANCH IF (A)>P1...TO P2
     1360         .MA BGT
     1370         .DO ]#>1
     1380         CMP ]1
     1390         BEQ :1
     1400         BCS ]2
     1410  :1
     1420         .ELSE
     1430         BEQ :1
     1440         BCS ]1
     1450  :1
     1460         .FIN
     1470         .EM
     1480  *--------------------------------
     1490  *   >BRA P1         BRANCH ALWAYS...TO P1
     1500         .MA BRA
     1510         CLV
     1520         BVC ]1
     1530         .EM
     1540  *--------------------------------
     1550  *   >BEQ P1 (,P2)   BRANCH IF (A)=P1...TO P2
     1560         .MA BEQ
     1570         .DO ]#>1
     1580         CMP ]1
     1590         BEQ ]2
     1600         .ELSE
     1610         BEQ ]1
     1620         .FIN
     1630         .EM
     1640  *--------------------------------
     1650  *   >BNE P1 (,P2)   BRANCH IF (A)<>P1...TO P2
     1660         .MA BNE
     1670         .DO ]#>1
     1680         CMP ]1
     1690         BNE ]2
     1700         .ELSE
     1710         BNE ]1
     1720         .FIN
     1730         .EM
     1740  *--------------------------------
     1750  *   >JMP P1,P2      BRANCH ALWAYS TO P1 BY
     1760  *                   BRANCHING TO P2 (SEE ARTICLE)
     1770         .MA JMP
     1780         CLV
     1790         BVC :1
     1800  ]2     CLV
     1810         BVC ]1
     1820  :1
     1830         .EM

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 280300, Dallas, TX 75228. Phone (214) 324-2050 Subscription rate is $15 per year, in the USA, sent Second Class Mail; $18 per year sent First Class Mail in USA, Canada, and Mexico; $28 per year sent Air Mail to other countries. Back issues are available for $1.50 each (other countries add $1 per back issue for 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.)