Apple Assembly Line
Volume 3 -- Issue 1 October 1982

In This Issue...

This issue of AAL is late. No sooner do I warn you of one magazine that is behind in their publication schedule, than I get behind myself! We plan to catch up with the next issue.

I just returned from 8 days in California. Some of you know that I am on the board of directors of the International Apple Core. After the board meeting I contacted a few long-time customers. I also attended the San Francisco Apple Corps Swap Meet.

I looked up Peter Meyer, author of SDS's "Routine Machine"; together we had dinner at the home of Pat Caffrey, co-author of "Doubletime Printer". Peter is now working on the fourth volume of additional Applesoft-extenders for his Routine Machine. I also spent two half-days with Henry Spragens, well known for his early contributions to Apple graphics lore. He bought his (first) Apple long ago in Kentucky, where he was one of the original members of LAUGHS (Louisville Apple User's Group for Hardware and Software!). Now he works at Beck-Tech in Berkeley, doing exotic things in the world of synthesized video graphics with the Apple and other machines.

A few weeks earlier I spent the afternoon with DeWayne Van Hoozer in Houston, at the HAAUG meeting (you're right, it is pronounced "hog"!). DeWayne's Genasys project is nearing the publication stage, so you'll probably be hearing more about it soon. I am looking forward to some more time in Houston around Halloween, at the AppleFest there. Look me up if you are there, in or near the International Apple Core booth.


Catalog Arranger Bill Morgan

We all have the problem: a disk starts getting full, we delete some files to make space, and our new files (from our latest project) end up scattered all through the catalog. A disk that has been used for a few months ends up with a thoroughly shuffled catalog.

There are programs available to alphabetize a catalog, but that's not always what I want to do. I want HELLO at the beginning, utilities next (assembler, text editor, ES-CAPE, disk zap, etc.), then various projects. The files for each project should all be grouped together, with the current job at the end of the catalog.

I decided that what I want to be able to do is to "pick up" one entry in the catalog, move it to exactly where I want it, put it down, then go get another one and put that one in its place, and so on. Here's my program to do just that.

Using Catalog Arranger

First BLOAD CATALOG ARRANGER, then insert the disk you want to modify. When you type CALL 2051 (or 803G from the monitor) the disk will spin for a little while as the catalog is read into a sort of string array. The first 22 entries in the catalog will then be displayed, with the first entry shown in inverse. You may notice that deleted files are also displayed, with a minus sign before the file type and a stray inverse character out at the end of the file name. Control characters are also displayed in inverse.

The inverted entry is a cursor showing the "active entry". If you press the arrow keys, this cursor will move up and down the display. When the cursor reaches the center of the screen, it will stop moving and the display will scroll up and down around it.

When you have the cursor on an item you wish to move, press RETURN. The word "MOVING" will appear in inverse in the lower left corner of the screen. When this "moving flag" is on, the entry in the cursor will be carried wherever the cursor goes. When it reaches the place where you want to put it, press RETURN again. The moving flag will disappear and that entry will stay where you just put it.

There are a couple of other commands as well. Pressing the "B" key moves the cursor to the beginning of the catalog, and the "E" key moves it to the end. If the moving flag is on, the item in the cursor will be carried right along. There is also an "R" command, to read in a new catalog. This is useful if you want to reread the current catalog and start all over again, or to move on to another disk.

When you have the catalog arranged just the way you want it, press the "W" key to write the revised catalog onto the disk. Press ESC when you want to exit the program.

Catalog Organization

If you are familiar with the internal structure of an Apple DOS catalog, you can skip ahead to the section labelled "How Catalog Arranger Works".

The first step in reading a disk catalog is to read the VTOC (Volume Table of Contents), which is always located at track $11, sector 0. The second and third bytes in the VTOC (offsets 1 and 2) contain the track and sector of the start of the catalog. On a standard DOS 3.3 disk these always point to track $11, sector $0F, and the rest of the catalog is always on track $11. These locations can be changed, however. For example, some programs to convert DOS 3.2 disks to DOS 3.3 leave the first catalog sector at track $11, sector $0C. Therefore, it is safest to follow the pointers rather than assuming that the catalog will always be in its usual place.

In a catalog sector, the first byte is not used. The second and third bytes point to the next track and sector of the catalog. If the track byte is zero, it means that there is no next sector, this is the end of the catalog. The fourth through the eleventh bytes are not used. The actual catalog information starts at the twelfth byte of the sector (offset $B) and fills the rest of the sector. Each catalog entry takes 35 bytes, so there are 7 entries in each sector.

The first two bytes of an entry contain the track and sector of the file's track/sector list. If the first byte is $FF, the file has been deleted. In that case, the track number has been moved to the end of the file name. If the first byte is zero, this entry has never been used, and we are past the end of the catalog.

The third byte tells the file type and whether the file is locked. Here are the type codes:

     00 -- TEXT file
     01 -- INTEGER BASIC program
     02 -- APPLESOFT BASIC program
     04 -- BINARY file
     08 -- S file -- not used
     10 -- R file -- DOS Toolkit relocatable object file
     20 -- A file -- Lazer Pascal source file
     40 -- B file -- Lisa assembler source file

If the file is locked, $80 is added to the type code.

The next 30 bytes are the file name. If the name is less than 30 characters long, it is filled out with spaces (ASCII $A0).

The last two bytes are the length of the file, in sectors. This is expressed as first low byte, then high byte.

Here's a diagram:

    0       1       2     3 . . . . 32      33         34
 --------------------------------- --------------------------
| Track | Sector | Type | Name . . . . | Len - Lo | Len - Hi |
 --------------------------------- --------------------------

You can find more information on the structure of the catalog in the DOS Manual on pages 129-134 or in the book Beneath Apple DOS on pages 4-4 through 4-7. It is impossible to recommend Beneath Apple DOS too highly; if you have any interest in the internals of DOS, get that book.

How Catalog Arranger Works

After initialization, the program builds a table of pointers into the storage area. We can build this table in advance because we know that all entries will be the same length. The catalog is then read into the array, each entry being placed according to the next pointer in the table. The end of the table is then marked with two zero bytes after the last element used.

The next step is to display the entries in the array. The display routine starts by checking ACTIVE.ELEMENT to see whether to start the display with the first element, or somewhere in the middle. It then scans up the table, displaying each catalog entry and inverting the one corresponding to ACTIVE.ELEMENT. The routine that actually displays each line borrows a couple of subroutines in DOS to decode the file type and display the file size.

When MOVING.FLAG is off, the arrow, B, and E commands simply change the value of ACTIVE.ELEMENT. When MOVING.FLAG is on, the arrows swap entries in the table up or down, and B and E repeatedly swap entries to move ACTIVE.ELEMENT to the beginning or end.

When writing the catalog back onto the disk we have to be careful to put the catalog sectors back in the same place they came from, since we can't assume that the catalog came from track $11. We do this by reading the first catalog sector into the buffer, scanning up the pointer table and moving the indicated entries into the catalog buffer, and then writing the buffer to the same disk sector it came from. We then get the track and sector pointers (which haven't been changed) from the buffer and use them to read the next sector. This whole process ends when we run out of entries in the pointer table.

Limitations and Additional Features

There are a few points that need more work:

I plan to add several more commands to Catalog Arranger:

Adding many of these features would also require reworking the command structure (which wouldn't hurt anyway!)

Here is a summary of the commands:

          B -- Move the cursor to the beginning of the catalog.
          E -- Move the cursor to the end of the catalog.
          R -- Read the catalog from the disk.
          W -- Write the catalog to the disk.
        --> -- Move the cursor down one item.
        <-- -- Move the cursor up one item.
     RETURN -- Toggle the moving flag on or off.
        ESC -- Exit the program.
 1000  *SAVE S.CATALOG ARRANGER
 1010  *--------------------------------
 1020         .OR $803
 1030         .TF CATALOG ARRANGER
 1040  *--------------------------------
 1050  POINTER     .EQ 0
 1060  *
 1070  MON.CV      .EQ $25
 1080  PREG        .EQ $48
 1090  *
 1100  DOS.RESTART .EQ $3D0
 1110  DOS.RWTS    .EQ $3D9
 1120  *
 1130  CORNER      .EQ $7D0
 1140  *
 1150  KEYBOARD    .EQ $C000
 1160  KEYSTROBE   .EQ $C010
 1170  *
 1180  DOS.SIZEOUT .EQ $AE42
 1190  DOS.PRNTERR .EQ $A702
 1200  DOS.TYPTABL .EQ $B3A7
 1210  IOB         .EQ $B7E8
 1220  IOB.SLOT    .EQ $B7E9
 1230  IOB.DRIVE   .EQ $B7EA
 1240  IOB.VOLUME  .EQ $B7EB
 1250  IOB.TRACK   .EQ $B7EC
 1260  IOB.SECTOR  .EQ $B7ED
 1270  IOB.BUFFER  .EQ $B7F0,F1
 1280  IOB.COMMAND .EQ $B7F4
 1290  IOB.ERROR   .EQ $B7F5
 1300  IOB.OSLOT   .EQ $B7F7
 1310  IOB.ODRIVE  .EQ $B7F8
 1320  *
 1330  * MONITOR CALLS
 1340  *
 1350  MON.VTAB    .EQ $FC22
 1360  MON.CLREOP  .EQ $FC42
 1370  MON.HOME    .EQ $FC58
 1380  MON.PRBYTE  .EQ $FDDA
 1390  MON.COUT1   .EQ $FDF0
 1400  MON.SETINV  .EQ $FE80
 1410  MON.SETNORM .EQ $FE84
 1420  *
 1430  *  SYMBOLIC CONSTANTS
 1440  *
 1450  ZERO         .EQ 0
 1460  READ         .EQ 1
 1470  WRITE        .EQ 2
 1480  LINE.COUNT   .EQ 22
 1490  ENTRY.LENGTH .EQ 35
 1500  RETURN       .EQ $8D
 1510  SPACE        .EQ $A0
 1520  *--------------------------------
 1530  SETUP
 1540         LDA IOB.OSLOT     SET SLOT AND
 1550         STA SLOT          DRIVE TO WHERE
 1560         LDA IOB.ODRIVE    WE CAME FROM
 1570         STA DRIVE
 1580   
 1590         LDA #ZERO         INITIALIZE
 1600         STA VOLUME        VARIABLES
 1610         STA NUMBER.OF.ELEMENTS
 1620         STA MOVING.FLAG
 1630         LDA #$FF
 1640         STA ACTIVE.ELEMENT
 1650   
 1660         JSR BUILD.ARRAY.TABLE
 1670         JSR READ.CATALOG
 1680         JSR MON.HOME
 1690  *--------------------------------
 1700  DISPLAY.AND.READ.KEY
 1710         JSR DISPLAY.ARRAY
 1720   
 1730  .1     LDA KEYBOARD
 1740         BPL .1
 1750         STA KEYSTROBE
 1760         CMP #$95             -->
 1770         BEQ HANDLE.RIGHT.ARROW
 1780         CMP #$88             <--
 1790         BEQ HANDLE.LEFT.ARROW
 1800         CMP #$9B             ESC
 1810         BEQ HANDLE.ESC
 1820         CMP #RETURN
 1830         BEQ HANDLE.RETURN
 1840         CMP #$C2             B
 1850         BEQ HANDLE.B         BEGINNING
 1860         CMP #$C5             E
 1870         BEQ HANDLE.E         END
 1880         CMP #$D2             R
 1890         BEQ HANDLE.R         READ CATALOG
 1900         CMP #$D7             W
 1910         BEQ HANDLE.W         WRITE CATALOG
 1920         JMP .1            NONE OF THE ABOVE
 1930  *--------------------------------
 1940  HANDLE.RIGHT.ARROW
 1950  *  MOVE UP ONE ELEMENT
 1960         JSR CHECK.FOR.END.OF.ARRAY
 1970         BCS .2            DO NOTHING IF ALREADY AT END
 1980         BIT MOVING.FLAG   SKIP SWAP IF
 1990         BPL .1            NOT MOVING
 2000         JSR MOVE.ELEMENT.UP
 2010  .1     INC ACTIVE.ELEMENT  FOLLOW IT UP
 2020  .2     JMP DISPLAY.AND.READ.KEY
 2030  *--------------------------------
 2040  HANDLE.LEFT.ARROW
 2050  *  MOVE DOWN ONE ELEMENT
 2060         JSR CHECK.FOR.BEGINNING.OF.ARRAY
 2070         BCS .2            IF AT BEGINNING, DO NOTHING
 2080         BIT MOVING.FLAG   IF NOT MOVING,
 2090         BPL .1            SKIP SWAP
 2100         JSR MOVE.ELEMENT.DOWN
 2110  .1     DEC ACTIVE.ELEMENT
 2120  .2     JMP DISPLAY.AND.READ.KEY
 2130  *--------------------------------
 2140  HANDLE.B
 2150  *  MOVE CURSOR TO BEGINNING OF ARRAY
 2160  .1     JSR CHECK.FOR.BEGINNING.OF.ARRAY
 2170         BCS .3    DO NOTHING IF AT BEGINNING
 2180         BIT MOVING.FLAG
 2190         BPL .2
 2200         JSR MOVE.ELEMENT.DOWN
 2210  .2     DEC ACTIVE.ELEMENT
 2220         BPL .1
 2230  .3     JMP DISPLAY.AND.READ.KEY
 2240  *--------------------------------
 2250  HANDLE.E
 2260  *  MOVE CURSOR TO END OF ARRAY
 2270  .1     JSR CHECK.FOR.END.OF.ARRAY
 2280         BCS .3
 2290         BIT MOVING.FLAG
 2300         BPL .2
 2310         JSR MOVE.ELEMENT.UP
 2320  .2     INC ACTIVE.ELEMENT
 2330         BPL .1       ...ALWAYS
 2340  .3     JMP DISPLAY.AND.READ.KEY
 2350  *--------------------------------
 2360  HANDLE.W
 2370  *  WRITE CATALOG TO DISK
 2380         JSR WRITE.CATALOG
 2390         JMP DISPLAY.AND.READ.KEY
 2400  *--------------------------------
 2410  HANDLE.RETURN
 2420  *  TOGGLE MOVING FLAG
 2430  *  =FF IF MOVING
 2440  *  =0  IF NOT
 2450         LDA MOVING.FLAG
 2460         EOR #$FF
 2470         STA MOVING.FLAG
 2480         JMP DISPLAY.AND.READ.KEY
 2490  *--------------------------------
 2500  HANDLE.ESC
 2510  *  EXIT PROGRAM
 2520         JMP DOS.RESTART
 2530  *--------------------------------
 2540  HANDLE.R
 2550  *  READ NEW CATALOG
 2560         JMP SETUP  RESTART PROGRAM
 2570  *--------------------------------
 2580  READ.CATALOG
 2590         JSR READ.VTOC
 2600         JSR POINT.TO.FIRST.CATALOG.SECTOR
 2610  .1     JSR READ.CATALOG.SECTOR
 2620         BCS .4                  .CS. IF END OF CHAIN
 2630   
 2640  *  MOVE CATALOG SECTOR INTO ARRAY
 2650  *   X STEPS THROUGH BUFFER, $B-$FF
 2660  *   Y STEPS THROUGH ENTRY, 0-$23
 2670         LDX #$B
 2680  .2     LDA CATALOG.BUFFER,X
 2690         BEQ .4                  END OF CATALOG?
 2700         INC ACTIVE.ELEMENT      NO, WE HAVE
 2710         INC NUMBER.OF.ELEMENTS  A NEW ENTRY
 2720         LDA ACTIVE.ELEMENT
 2730         JSR POINT.TO.A          SET POINTER
 2740         LDY #ZERO
 2750  .3     LDA CATALOG.BUFFER,X
 2760         STA (POINTER),Y
 2770         INX
 2780         BEQ .1            END OF BUFFER?
 2790  *                        IF SO, READ NEW SECTOR
 2800         INY
 2810         CPY #ENTRY.LENGTH END OF ENTRY?
 2820         BCC .3            NO, KEEP GOING
 2830         BCS .2            YES, GET NEXT ONE
 2840   
 2850  .4     LDA ACTIVE.ELEMENT
 2860         CLC          GO ONE PAST
 2870         ADC #1       LAST ELEMENT
 2880         ASL          AND STORE
 2890         TAY          TWO ZEROES
 2900         LDA #ZERO
 2910         STA ARRAY.TABLE,Y
 2920         STA ARRAY.TABLE+1,Y
 2930         STA ACTIVE.ELEMENT
 2940         RTS
 2950  *--------------------------------
 2960  READ.VTOC
 2970         LDA #ZERO
 2980         STA SECTOR
 2990         LDA #$11
 3000         STA TRACK
 3010         LDA #VTOC.BUFFER
 3020         STA BUFFER
 3030         LDA /VTOC.BUFFER
 3040         STA BUFFER+1
 3050         LDA #READ
 3060         STA COMMAND
 3070         JMP RWTS.CALLER
 3080  *--------------------------------
 3090  READ.CATALOG.SECTOR
 3100         LDA CATALOG.BUFFER+1   GET NEXT TRACK
 3110         BEQ .1       END OF CATALOG CHAIN?
 3120         STA TRACK
 3130         LDA CATALOG.BUFFER+2   GET NEXT SECTOR
 3140         STA SECTOR
 3150         LDA #CATALOG.BUFFER
 3160         STA BUFFER
 3170         LDA /CATALOG.BUFFER
 3180         STA BUFFER+1
 3190         LDA #READ
 3200         STA COMMAND
 3210         JSR RWTS.CALLER
 3220         CLC
 3230         RTS
 3240   
 3250  * SET CARRY TO SHOW END-OF-CHAIN
 3260  .1     SEC
 3270         RTS
 3280  *--------------------------------
 3290  WRITE.CATALOG
 3300         LDA #$FF
 3310         STA ACTIVE.ELEMENT
 3320         JSR POINT.TO.FIRST.CATALOG.SECTOR
 3330  .1     JSR READ.CATALOG.SECTOR
 3340         LDX #$B 
 3350  .2     INC ACTIVE.ELEMENT
 3360         LDA ACTIVE.ELEMENT
 3370         JSR POINT.TO.A
 3380         BCS .5            .CS. IF AT END OF TABLE
 3390         LDY #ZERO
 3400  .3     LDA (POINTER),Y
 3410         STA CATALOG.BUFFER,X
 3420         INX
 3430         BEQ .4            END OF BUFFER?
 3440         INY
 3450         CPY #ENTRY.LENGTH END OF ENTRY?
 3460         BCC .3            NO, KEEP GOING
 3470         BCS .2            YES, GET NEXT ONE
 3480   
 3490  .4     JSR WRITE.CATALOG.SECTOR
 3500         JMP .1            AND READ THE NEXT SECTOR
 3510   
 3520  *  FILL THE REST OF THE BUFFER WITH ZEROES
 3530  .5     LDA #ZERO
 3540  .6     STA CATALOG.BUFFER,X
 3550         INX
 3560         BNE .6
 3570   
 3580         JSR WRITE.CATALOG.SECTOR
 3590         LDA #ZERO
 3600         STA ACTIVE.ELEMENT
 3610         JMP DISPLAY.AND.READ.KEY
 3620  *--------------------------------
 3630  WRITE.CATALOG.SECTOR
 3640         LDA #WRITE        WRITE THE SECTOR
 3650         STA COMMAND       BACK JUST WHERE
 3660         JMP RWTS.CALLER   IT CAME FROM
 3670  *--------------------------------
 3680  POINT.TO.FIRST.CATALOG.SECTOR
 3690  *  GET THE FIRST TRACK AND SECTOR FROM THE VTOC
 3700         LDA VTOC.BUFFER+1
 3710         STA CATALOG.BUFFER+1
 3720         LDA VTOC.BUFFER+2
 3730         STA CATALOG.BUFFER+2
 3740         RTS
 3750  *--------------------------------
 3760  DISPLAY.ARRAY
 3770         LDA #ZERO         START AT
 3780         STA MON.CV        TOP OF
 3790         JSR MON.VTAB      SCREEN
 3800         LDA ACTIVE.ELEMENT
 3810         SEC
 3820         SBC #LINE.COUNT/2
 3830         BPL .1            IF RESULT IS +, USE IT
 3840         LDA #ZERO         OTHERWISE, USE ZERO
 3850  .1     TAX               X KEEPS TRACK OF
 3860  .2     TXA               WHERE WE ARE
 3870         CMP ACTIVE.ELEMENT
 3880         BNE .3
 3890         PHA
 3900         JSR MON.SETINV    INVERT ACTIVE ELEMENT
 3910         PLA
 3920  .3     JSR POINT.TO.A    SET POINTER
 3930         BCS .5            .CS. IF AT END OF TABLE
 3940         JSR INTERPRET.ENTRY  WRITE A LINE
 3950         LDA #RETURN
 3960         JSR MON.COUT1
 3970         JSR MON.SETNORM   RESTORE NORMAL
 3980         INX
 3990         LDA MON.CV
 4000         CMP #LINE.COUNT   END OF SCREEN?
 4010         BCC .2            IF NOT, DO ANOTHER LINE
 4020  .5     JSR MON.CLREOP    CLEAR TO END OF PAGE
 4030         JSR MON.SETNORM   JUST IN CASE
 4040         BIT MOVING.FLAG
 4050         BPL .6
 4060         JSR SHOW.MOVING.FLAG  IF MOVING
 4070  .6     RTS
 4080  *--------------------------------
 4090  INTERPRET.ENTRY
 4100         LDY #ZERO
 4110         LDA (POINTER),Y   DELETED?
 4120         BPL .1            MINUS IF YES
 4130         LDA #$AD     -
 4140         BMI .3       ...ALWAYS
 4150  .1     LDY #2
 4160         LDA (POINTER),Y   LOCKED?
 4170         BPL .2            MINUS IF YES
 4180         LDA #$AA     *
 4190         BMI .3       ...ALWAYS
 4200  .2     LDA #SPACE        NEITHER DELETED NOR LOCKED
 4210  .3     JSR MON.COUT1
 4220         LDY #2
 4230         LDA (POINTER),Y   GET FILE TYPE
 4240         AND #$7F          MAKE POINTER
 4250         LDY #7            INTO DOS'S
 4260         ASL               TYPE TABLE
 4270  .4     ASL               (ROUTINE BORROWED
 4280         BCS .5            FROM DOS, $ADE8-ADF9)
 4290         DEY
 4300         BNE .4
 4310  .5     LDA DOS.TYPTABL,Y
 4320         JSR MON.COUT1     DISPLAY TYPE
 4330         LDA #SPACE
 4340         JSR MON.COUT1
 4350         LDY #$21
 4360         LDA (POINTER),Y    SET UP FOR
 4370         STA $44            ROUTINE TO
 4380         INY                DISPLAY FILE
 4390         LDA (POINTER),Y    SIZE
 4400         STA $45
 4410         JSR DOS.SIZEOUT    DO IT
 4420         LDA #SPACE
 4430         JSR MON.COUT1
 4440         LDY #3
 4450  .6     LDA (POINTER),Y    GET A CHARACTER
 4451         CMP #SPACE         CONTROL?
 4452         BCS .7             NO, GO ON
 4453         AND #$7F           YES, MAKE IT INVERSE
 4460  .7     JSR MON.COUT1      DISPLAY IT
 4470         INY
 4480         CPY #33            DONE WITH FILE NAME?
 4490         BCC .6
 4500         RTS
 4510  *--------------------------------
 4520  SHOW.MOVING.FLAG
 4530         LDY #5        PUT INVERSE
 4540  .1     LDA QMOVING,Y "MOVING" AT
 4550         STA CORNER,Y  BOTTOM OF
 4560         DEY           SCREEN
 4570         BPL .1
 4580         RTS
 4590  *--------------------------------
 4600  RWTS.CALLER
 4610         LDA SLOT     TRANSFER
 4620         STA IOB.SLOT  VALUES
 4630         LDA DRIVE      INTO
 4640         STA IOB.DRIVE   IOB
 4650         LDA VOLUME
 4660         STA IOB.VOLUME
 4670         LDA TRACK
 4680         STA IOB.TRACK
 4690         LDA SECTOR
 4700         STA IOB.SECTOR
 4710         LDA COMMAND
 4720         STA IOB.COMMAND
 4730         LDA BUFFER
 4740         STA IOB.BUFFER
 4750         LDA BUFFER+1
 4760         STA IOB.BUFFER+1
 4770         LDA #$00
 4780         STA IOB.ERROR
 4790  *
 4800         LDY #IOB     LOAD IOB
 4810         LDA /IOB     ADDRESS
 4820         JSR DOS.RWTS     CALL RWTS
 4830         LDA #$00
 4840         STA PREG    SOOTHE MONITOR
 4850         BCS ERROR.HANDLER
 4860         RTS
 4870  *--------------------------------
 4880  ERROR.HANDLER
 4890         LDA #$87     BELL
 4900         JSR MON.COUT1     RING
 4910         JSR MON.COUT1        ING
 4920         JSR MON.COUT1          ING
 4930         LDA #23
 4940         STA MON.CV        USE LINE BELOW DISPLAY
 4950         JSR MON.VTAB
 4960         LDX #8
 4970         JSR DOS.PRNTERR   DISPLAY "I/O ERROR"
 4980         JMP DOS.RESTART   EXIT PROGRAM
 4990  *--------------------------------
 5000  BUILD.ARRAY.TABLE
 5010         LDA #CATALOG.ARRAY  SET FIRST ENTRY
 5020         STA ARRAY.TABLE     TO POINT TO
 5030         LDA /CATALOG.ARRAY  START OF
 5040         STA ARRAY.TABLE+1   ARRAY
 5050         LDX #2
 5060  .1     LDA ARRAY.TABLE-2,X MAKE EACH 
 5070         CLC                 SUCCESSIVE
 5080         ADC #ENTRY.LENGTH   ENTRY $23
 5090         STA ARRAY.TABLE,X   LARGER THAN
 5100         LDA ARRAY.TABLE-1,X THE LAST
 5110         ADC #ZERO
 5120         STA ARRAY.TABLE+1,X
 5130         INX
 5140         INX
 5150         CPX #$FE            127 ENTRIES YET?
 5160         BNE .1
 5170         LDA #ZERO           END TABLE
 5180         STA ARRAY.TABLE,X   WITH TWO
 5190         STA ARRAY.TABLE+1,X ZEROES
 5200         RTS
 5210  *--------------------------------
 5220  POINT.TO.A
 5230         ASL                 MAKE (A) INTO INDEX
 5240         TAY
 5250         LDA ARRAY.TABLE,Y   CHECK FOR TWO
 5260         ORA ARRAY.TABLE+1,Y CONSECUTIVE
 5270         BEQ .1              ZERO BYTES
 5280         LDA ARRAY.TABLE,Y
 5290         STA POINTER         PUT TABLE ENTRY
 5300         LDA ARRAY.TABLE+1,Y INTO POINTER
 5310         STA POINTER+1
 5320         CLC
 5330         RTS
 5340   
 5350  .1     SEC                 END OF TABLE
 5360         RTS
 5370  *--------------------------------
 5380  CHECK.FOR.END.OF.ARRAY
 5390  *  RETURNS CARRY SET IF AT END
 5400  *     "      "   CLEAR IF NOT
 5410         LDA ACTIVE.ELEMENT
 5420         CLC
 5430         ADC #1
 5440         CMP NUMBER.OF.ELEMENTS
 5450         RTS
 5460  *--------------------------------
 5470  CHECK.FOR.BEGINNING.OF.ARRAY
 5480         LDA ACTIVE.ELEMENT
 5490         BNE .1
 5500         SEC          ACTIVE = 0, WE ARE AT BEGINNING
 5510         RTS
 5520   
 5530  .1     CLC          NONZERO, WE'RE OKAY
 5540         RTS
 5550  *--------------------------------
 5560  MOVE.ELEMENT.UP
 5570         LDA ACTIVE.ELEMENT
 5580         ASL          MAKE INDEX INTO TABLE
 5590         TAX
 5600         LDY #2       DO THIS TWICE, FIRST LO, THEN HI
 5610  .1     LDA ARRAY.TABLE,X
 5620         PHA
 5630         LDA ARRAY.TABLE+2,X
 5640         STA ARRAY.TABLE,X
 5650         PLA
 5660         STA ARRAY.TABLE+2,X
 5670         INX          NOW DO HIGH BYTES
 5680         DEY
 5690         BNE .1       DONE?
 5700         RTS
 5710  *--------------------------------
 5720  MOVE.ELEMENT.DOWN
 5730         LDA ACTIVE.ELEMENT
 5740         ASL
 5750         TAX
 5760         LDY #2
 5770  .1     LDA ARRAY.TABLE,X
 5780         PHA
 5790         LDA ARRAY.TABLE-2,X
 5800         STA ARRAY.TABLE,X
 5810         PLA
 5820         STA ARRAY.TABLE-2,X
 5830         INX
 5840         DEY
 5850         BNE .1
 5860         RTS
 5870  *--------------------------------
 5880  QMOVING
 5890  *  INVERSE "MOVING"
 5900         .HS 0D0F16090E07
 5910  *--------------------------------
 5920  SLOT    .BS 1  (USUALLY 6)
 5930  DRIVE   .BS 1  (USUALLY 1)
 5940  VOLUME  .BS 1  (0 = ANY)
 5950  TRACK   .BS 1  (USUALLY $11)
 5960  SECTOR  .BS 1  (0 TO F)
 5970  BUFFER  .BS 2  (VARIES)
 5980  COMMAND .BS 1  (1 OR 2)
 5990   
 6000  NUMBER.OF.ELEMENTS .BS 1  (1 TO N)
 6010  ACTIVE.ELEMENT     .BS 1  (0 TO N-1)
 6020  MOVING.FLAG        .BS 1  (0 OR FF)
 6030  *--------------------------------
 6040  END.OF.PROGRAM
 6050   
 6060  VTOC.BUFFER     .EQ END.OF.PROGRAM
 6070  CATALOG.BUFFER  .EQ END.OF.PROGRAM+$100
 6080  ARRAY.TABLE     .EQ END.OF.PROGRAM+$200
 6090  CATALOG.ARRAY   .EQ END.OF.PROGRAM+$300

So You Never Need Macros! Bob Sander-Cederlof

I have said it many times myself, "I don't need macros!" But now that I have them, I seem to find more and more uses for them. Not the traditional uses, to generate common sequences of opcodes. I am using them to build tables of data, rather than typing in line after line of very similar stuff.

I have been working some more on the Prime Number Generator program. You may remember the series: first the articles in BYTE Magazine, then my faster version in an early Apple Assembly Line, then Charles Putney's version at double my speed. Now Tony Brightwell has cut Charlie's time nearly in half. (His program will probably appear next month.) Anyway, I have done some more investigation.

One approach required a precomputed table of the squares of the odd numbers from 1 to 127. An easy way to enter this table might be:

     .DA 1*1,3*3,5*5,7*7,9*9
     .DA 11*11,13*13,15*15,17*17
     et cetera

I had typed about that much when I said, "There has to be an easier way." I made up the following macro definition:

            .MA SQ
     :0     .EQ ]1
     :1     .EQ ]1+2
     :2     .EQ ]1+4
     :3     .EQ ]1+6
     :4     .EQ ]1+8
     :5     .EQ ]1+10
     :6     .EQ ]1+12
     :7     .EQ ]1+14
            .DA :0*:0,:1*:1,:2*:2,:3*:3
            .DA :4*:4,:5*:5,:6*:6,:7*:7
            .DO ]2<8
            >SQ ]1+16,]2+1
            .FIN
            .EM

Then the single line of code

     2200         >SQ 1,1

generated all 64 squares for me.

How does it work? Good question.... The eight .EQ lines create 8 private labels with the values of 8 consecutive odd numbers starting with whatever the first parameter from the call line happens to be. Line 2200 has the first parameter "1", so the private labels will have values of 1, 3, 5, 7, 9, 11, 13, and 15 respectively. The two .DA lines generate the squares of these 8 values.

The next three lines are the tricky part. If the second parameter has a value less than 8 then the line between .DO and .FIN is assembled. It is a nested call on the SQ macro. Only this time the first parameter is 16 greater than it was, and the second parameter is one greater. After going through this nesting process 7 times, we will have generated 8 sets of 8 values each. When the second parameter has worked its way up to 8, the nested calls will exit in turn, and the table is finished.

If you have the macro expansion listing option on during assembly, the expanded form takes 2 1/2 pages


Converting ToolKit Source to S-C Bob Sander-Cederlof

I had the source code for FIG-FORTH on the Apple, entered by some members of the Dallas Apple Corps. For some reason they decided to use the DOS ToolKit Assembler when they typed in all those lines. Naturally, I had a strong desire to convert the source files to the format of my S-C Macro Assembler.

The first step, and one of the easiest in this case, is to figure out how to read the ToolKit source files into S-C. ToolKit source files are standard DOS text files (type "T"). There are no line numbers. S-C allows such files to be read in by typing the following commands:

     NEW
     AUTO
     <<<<<EXEC filename   (where "<" stands for backspace)
     <<<<<MANUAL

"NEW" makes sure there are no lingering program lines from a previous load. "AUTO" starts generating automatic line numbers. The first line number generated will be 1000. Five backspaces will back up the cursor to the beginning of the input line, so the EXEC command can be typed. As the file is EXECing, each line will be read in with a prefixed line number. After the whole file has been read, five backspaces allow you to type the "MANUAL" command, thereby turning off the AUTO mode.

At this point you can LIST the program in memory and see what a ToolKit file looks like when you are using the S-C editor. You could use the EDIT and REPLACE commands to make all the necessary changes, and SAVE the converted program on a new file.

I was able to automate much of the conversion process, using an EXEC file of REPLACE commands. Several of you readers, including Graeme Scott of DFX fame, have sent me similar EXEC files for converting LISA source code.

Before I lay out the whole file, lets look at a simple case. The people who typed in the ToolKit source decided to separate individual sections of code with "SKP 1" lines. This causes a blank line on the assembly listing. S-C does not have an equivalent directive, but then again I personally don't like blank lines on my listings. (They always make me think my printer is broken!) Anyway, the command REP / SKP 1/*/A replaces all of the skips with empty comment lines. If you don't even want to see the asterisk on the line, use REP / SKP 1/ /A.

Notice that there is one space before "SKP" in the command above. ToolKit uses space as a tab character, and so the source file does not have nice neat columns for each field. If you list it with a regular text editor, the opcode field winds around like a snake; it always starting one space after the label, or in column 2 if there is no label. S-C uses control-I for a tab character, because control-I is the ASCII tab character. More on this later.

There were also a number of " SKP 2" lines. I decided to turn these into "*--------" lines, to indicated a greater separation than a mere empty comment line would.

ToolKit uses the semi-colon in column 1 to indicate a comment line; S-C uses an asterisk. ToolKit also uses a semi-colon to begin a comment field on a source line; S-C does not require any such character. The following two replace commands will make the necessary changes:

     REP / ;/  /A
     REP /;/*/A

The commands have to be in that order, or else you end up with an asterisk starting comment fields when they aren't necessary. Two lines had ";" in as ASCII literal constant. I had to hand-re-correct them later.

The most important changes are the directives. The files I was converting needed the following changes:

     REP / EQU / .EQ /A
     REP / DW / .DA /A
     REP / ORG / .OR /A
     REP / DS / .BS /A
     REP / DCI / .AT /A
     REP / DFB / .DA #/
     REP / ASC / .AS /

Immediate address mode also presented a problem. ToolKit uses the form "LDA #<SSS" to indicate the high byte, and "LDA #>SSS" to indicate the low byte. S-C uses "LDA /SSS" for the high byte, and "LDA #SSS" for the low byte. I fixed them with:

     REP " <#" /"A
     REP " >#" #"A

Now about those snaky columns.... I wanted to somehow put a tab before each opcode field, and before each comment field. I thought, "Why not just use the replace command to put in a control-I?":

     REP / EQU /^I.EQ /A    (where ^I means I typed control-I)
     et cetera

My first problem was that typing control-I when entering the REPLACE command made a tab. I overcame that by typing the sequence "control-O control-I". Control-O makes the next character become part of the input line regardless of its normal meaning. That worked, but....

My second problem was that getting a control-I into the source program did not make it a tab. Somehow the control-I had to be "executed". So I wrote the converted program on a text file, this time with line numbers, and then EXECed it back in.

     TEXT# filename
     EXEC filename

That "executed" the control-I's, and I had tabs. But....

My third problem was that I wanted to save all the REPLACE commands as an EXEC file, so that I did not have to manually retype them for every file to be converted. When I EXECed the REPLACE command file, the control-I's were executed immediately! I had to change my replace commands to include both a control-O and a control-I, so that the control-I in the REPLACE command would be read in from the EXEC file but not executed until it was later EXECed from the temporary source text file.

Still with me? If not, keep reading anyway, because I will show you what I mean.

Using S-C, I entered the following "program":

 1000  "TOOLKIT CONVERTER
 1010  "
 1020  "
 1030  "COMMENTS
 1040  REP/ SKP 1/*/A
 1050  REP/ SKP 2/^O^[L/A
 1060  REP/ ;/ ^O^I/A
 1070  REP/;/*/A
 1080  "DIRECTIVES
 1090  REP/ EQU /^O^I.EQ /A
 1100  REP/ DW /^O^I.DA /A
 1110  REP/ ORG /^O^I.OR /A
 1120  REP/ DS /^O^I.BS /A
 1130  REP/ DCI /^O^I.AT /A
 1140  REP/ ASC /^O^I.AS /A
 1150  REP/ DFB /^O^I.DA #/A
 1160  REP/ CHN /*** CHN /A
 1170  "OPCODE TABS
 1180  REP/ ADC /^O^IADC /A
 1190  REP/ AND /^O^IAND /A
 1200  REP/ ASL/^O^IASL/A
 1210  REP/ BIT /^O^IBIT /A
 1220  REP/ CMP /^O^ICMP /A
 1230  REP/ CPX /^O^ICPX /A
 1240  REP/ CPY /^O^ICPY /A
 1250  REP/ DEC /^O^IDEC /A
 1260  REP/ EOR /^O^IEOR /A
 1270  REP/ INC /^O^IINC /A
 1280  REP/ LDA /^O^ILDA /A
 1290  REP/ LDX /^O^ILDX /A
 1300  REP/ LDY /^O^ILDY /A
 1310  REP/ LSR/^O^ILSR/A
 1320  REP/ ORA /^O^IORA /A
 1330  REP/ ROL/^O^IROL/A
 1340  REP/ ROR/^O^IROR/A
 1350  REP/ SBC /^O^ISBC /A
 1360  REP/ STA /^O^ISTA /A
 1370  REP/ STX /^O^ISTX /A
 1380  REP/ STY /^O^ISTY /A
 1390  REP/ BPL /^O^IBPL /A
 1400  REP/ BMI /^O^IBMI /A
 1410  REP/ BEQ /^O^IBEQ /A
 1420  REP/ BNE /^O^IBNE /A
 1430  REP/ BVS /^O^IBVS /A
 1440  REP/ BVC /^O^IBVC /A
 1450  REP/ BCC /^O^IBCC /A
 1460  REP/ BCS /^O^IBCS /A
 1470  REP/ JMP /^O^IJMP /A
 1480  REP/ JSR /^O^IJSR /A
 1490  REP/ BRK/^O^IBRK/A
 1500  REP/ CLC/^O^ICLC/A
 1510  REP/ CLD/^O^ICLD/A
 1520  REP/ CLV/^O^ICLV/A
 1530  REP/ DEX/^O^IDEX/A
 1540  REP/ DEY/^O^IDEY/A
 1550  REP/ INX/^O^IINX/A
 1560  REP/ INY/^O^IINY/A
 1570  REP/ NOP/^O^INOP/A
 1580  REP/ PHA/^O^IPHA/A
 1590  REP/ PLA/^O^IPLA/A
 1600  REP/ PLP/^O^IPLP/A
 1610  REP/ RTS/^O^IRTS/A
 1620  REP/ SEC/^O^ISEC/A
 1630  REP/ TAX/^O^ITAX/A
 1640  REP/ TAY/^O^ITAY/A
 1650  REP/ TSX/^O^ITSX/A
 1660  REP/ TXA/^O^ITXA/A
 1670  REP/ TXS/^O^ITXS/A
 1680  REP/ TYA/^O^ITYA/A
 1690  "ADDRESS MODES
 1700  REP" #>" #"A
 1710  REP" #<" /"A
 1720  "WRITE ON TEMP FILE
 1730  TEXT#F

In the listing above, I have used "^O" to mean "control-O"; "^I" to mean "control-I"; and "^[" to mean "ESCAPE key". In order to get "control-O control-I" in a line, I had to type "control-O control-O control-O control-I".

Lines 1000-1030, 1080,1130, 1170,1690, and 1720 begin with a quotation mark. These are comment lines to the S-C input routine; they print on the screen when they are read from the EXEC file, but are otherwise ignored.

I saved the file as is using "SAVE TOOLKIT CONVERTER", in case I might want to modify it again. And I did, again and again and again. Then I wrote it on a text file without line numbers using "TEXT TKC".

Here is the sequence of steps I went through for each source file:

     NEW
     AUTO
     1000 *    filename
     <<<<<EXEC filename,D2   (where "<" means "backspace")
     <<<<<MAN
     EXEC TKC,D1
     EXEC F
     LIST 1000               (to see what filename to use)
     SAVE filename

There are three EXEC commands above; the first reads on the ToolKit source file; the second executes all the REPLACE commands, and writes the resulting source on a temporary text file named "F"; the third reads in that temporary text file to "execute" the control-I tabs and the "ESC-L" lines.

After all the files were converted, I built a little assembly control file like this:

     1000           .IN FILE1
     1010           .IN FILE2
       et cetera

I also added a ".TF" directive after the ".OR" line, to put the assembled code on a DOS binary file.

The first assembly did not go smoothly, because of lines containing "ROR A". In the four shift instructions, ToolKit requires the symbol "A" to signify Accumulator mode. S-C uses a blank operand field to signify Accumulator mode, and thinks "ROR A" means to shift the memory location labeled "A".

Once I was able to assemble with no errors, I compared the object code produced with that produced by ToolKit. They did not match! There were two lines in the ToolKit source causing the problem:

              DW LIT,$FFFF
and
      L1495   DFB  $C1,$DB

The "DW" directive in ToolKit does not recognize multiple items separated by commas; therefore the ",$FFFF" was ignored. The following line in the source was "DW $FFFF". The S-C form ".DA LIT,$FFFF" does assemble both items, so the $FFFF constant was duplicated.

The "DFB" directive in ToolKit recognizes multiple items. The conversion I did rendered the line into "L1495 .DA #$C1,$DB", so the $DB item became a 16-bit value. I changed the line to "L1495 .DA #$C1,#$DB" and all was well.

If you have a large Toolkit source to convert, chances are that you will find one or two more things to change that are not included above. Let me know what you come up with.

As I mentioned earlier, the same general techniques work when you have a LISA file to convert. If you can get the source code on a text file, with all tokens expanded, then you can read it into S-C and begin converting. If you want a challenging assignment, how about writing a program which will read LISA type-B source files and convert them to S-C type-I source files, all automatically!


Correction to Bob's Fast Screen Scroll Jim Church

If you tried the fast scroll from Bob Sander-Cederlof's article "Some Fast Screen Tricks" from the September issue, you might have been surprised. Bob goofed!

He copied characters from line 16 into line 15 before moving line 15 to 14; ditto with lines 8, 7, and 6. This in spite of his special attempt to save lines on the stack. The problem is that he ran the loop backwards from 119 to 0. If you change it to run from 0 up to 119, the scroll works correctly.

Change lines 1410, 1620, and 1630, and add line 1625:

     1410 SCROLL LDY #0

     1620 .2     INY
     1625        CMP #120
     1630        BCC .1

Bob, you are going to get a lot of mail (unless they are asleep)!


Using USR for a WEEK Bob Sander-Cederlof

The "&" and CALL statements are not the only ways to use machine language to enhance the Applesoft Language. USR is a third way, and provides an easy way to return a single value.

How many times have you seen the Applesoft code "PEEK(X) + 256*PEEK(X+1)"? It is used over and over again. What it does is look in memory at X and X+1 for a 16-bit value (stored low-byte first as are most 16-bit values in the 6502 environment). The high byte is multiplied by 256, and the low byte added in. Wouldn't it be nice to have a USR function which would convert a two-byte value directly? This function is sometimes called "WEEK", meaning "Word pEEK" (hence the awful pun in the title above).

When I was in California last week someone categorically and unequivocally assured me that it is impossible to use the USR function with a value of 32768. I tried it with the WEEK function, and it works fine. So much for the assurances! I think his problem was that he followed the instructions in the Applesoft manual, which are somewhat incomplete.

Here is the USR code, set up to run at $300. However, it is "run-anywhere" code, because there are no internal references. You do have to tell Applesoft where it starts, though. Line 100 in the example shows how to do that. Location 11 and 12 must be set to the low- and high-bytes of the address of the USR code.

 1000  *SAVE S.USR WEEK FUNCTION
 1010  *--------------------------------
 1020  *      USR (X) = PEEK(X)+256*PEEK(X+1)
 1030  *--------------------------------
 1040         .OR $300     OR WHEREVER YOU WISH
 1050  *--------------------------------
 1060  USR    LDA $9D      CHECK RANGE
 1070         CMP #$91
 1080         BCS .1       ERROR
 1090         JSR $EBF2    CONVERT TO INTEGER IN $A0,A1
 1100         LDA $A0      PUT HIGH BYTE AFTER LOW BYTE
 1110         STA $A2
 1120         LDY #1
 1130         LDA ($A1),Y  HIGH-ORDER BYTE
 1140         STA $9E      HIGH BYTE OF MANTISSA
 1150         DEY
 1160         LDA ($A1),Y  LOW-ORDER BYTE
 1170         STA $9F      NEXT BYTE OF MANTISSA
 1180         SEC          SIGN IS POSITIVE
 1190         LDX #$90     EXPONENT 2^16
 1200         JMP $EBA0    FINISH CONVERSION
 1210  .1     JMP $E199    "ILLEGAL QUANTITY" MESSAGE
 1220  *--------------------------------


     100 POKE 11,0: POKE 12,3
     105 INPUT X: VTAB PEEK(37): PRINT X":  ";
     110 PRINT USR(X) " = " PEEK(X)+256 + PEEK(X+1)
     120 GOTO 105

Automatic CATALOG in the Language Card Bill Morgan

It has been pointed out to me (loudly!) that I failed to mention the Language Card version of the Macro Assembler in my Automatic CATALOG routine last June. Well, here's what you need to do:

That should take care of it!


Another Lower Case Patch for S-C Macro Bob Sander-Cederlof

Graeme Scott pointed out another oversight of mine. All lower case characters inside macro definitions are currently converted to upper case, whether or not you want it that way. The following patches will fix it, assuming you have already installed the patches from AAL August 1982 page 28.

     Motherboard version:  $275E:BA 31

     Language Card version:  $E8AA:06 F3

I found another problem: ".EM" and ".eM" work, but ".em" and ".Em" do not. The following patches make them work too.

     Motherboard version:

          $31DB:B9 00 02 C9 60 90 02 29 5F 60
          $2979:20 DB 31

     Language Card version:

          $F327:B9 00 02 C9 60 90 02 29 5F 60
          $EAC5:20 27 F3

Writing for AAL Bob Sander-Cederlof

More and more of you are expressing interest in contributing articles to this newsletter. Fine with me!

I accept them in almost any form. It is by far the best if any source programs are on disk in S-C format, so I don't have to type them in. Other formats are OK, but more trouble.

I use my own word processor, which accepts standard DOS text files or Applewriter files. If you have a large article, a copy on disk saves a lot of time here.

I receive more articles than I can use, but if yours is as good as you think it is, I will probably print it. I usually spend a lot of time checking the programs and editing the articles before I print them.

Of course, I will return any disks you send.


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.)