Volume 5 -- Issue 11 | August 1985

- //c + Z-RAM = 576K Printer Buffer
- How Many Bytes for Each Opcode?
- Generic Conversion Routines
- Wildcard Filename Search

The 65816 continues to make news. We hear of at least two major books on 65816 Assembly Language, which should be in print soon. We also hear that sales of the chip are taking off, with some firms ordering multiplied thousands. Although we have yet to SEE one, we keep hearing reports of plug-in boards for Apples that contain a 65816 and lots of RAM: ComLog, MicroMagic, Checkmate Technology, and others.

Meanwhile, we contemplate the future advantages to just enhancing existing Apples with 65802's and big RAM boards. Applied Engineering or Checkmate will be delighted to stuff 512K additional RAM into your //c. You can add five times that much to your //e with AE's latest version of RAMWorks. Apple's forthcoming Slinky card will add up to a megabyte to any II, II Plus, or //e with a spare slot (1-7). Call APPLE's latest magazine offers the BIG BOARD for slot 0-7 use, one megabyte addressable either in Slinky fashion or with "standard" D000-FFFF mapping, for only $599. If you hurry, they have a special (even lower) price good until Sept 30th.

6800 Cross Assembler for ProDOS

The S-C 6800 Macro Cross Assembler is now also available in a ProDOS version. This is the Version 2.0 level Cross Assembler, including the additional opcodes of the Motorola 6801 and Hitachi 6301 microprocessors. Either the DOS or the ProDOS Version 2.0 Cross Assembler is $50; if you already have one you can add the other for only $20.

//c + Z-RAM = 576K Printer Buffer Another Auxiliary Memory Program | David C. Johnson Applied Engineering

What has 640K of memory and is as cute as a button? My Apple //c! It didn't come with all that memory, "only" 128K of it. Before I even powered it up for the first time, I installed a 512K Z-RAM. Ready to take on Blue's 640K machine? Maybe.

I've had quite a few Apple Computers, my first had Integer ROMs and a serial number in the thirty one thousands, and my current workhorse is an Apple //e with the works. So why a //c? Well, for one it's cute, and secondly its firmware was written by Ernie Beernink and Rich Williams, the same guys that wrote the //e Enhanced ROMs and Extended Debugging Monitor. These guys write slick code. Finally, I can type control-reset with one hand.

Well, what to do after getting it home? I tried my mouse out on it, but moved it back to the //e. My paddles and joysticks all have 16-pin plugs, so I couldn't use them. I don't have an RGB interface for the //c yet, so the color monitor has to stay put. That leaves my Imagewriter printer to play with.

Having two computers and only one printer is an old problem. One usually solved with a rotary switch. I figured that I could do a little better. What I did is connect the Imagewriter to the //c's Printer port, and the //e's Super Serial Card (SSC) to the //c's Modem port. I then wrote the program that follows this article. It implements a 576K buffer for the //e, in the //c. Now I can use the printer from the //c just by typing pr#1. When I want to print from the //e, I just boot a disk on the //c, then type pr#1 on the //e. However, the printing, for the //e, goes MUCH faster. I've setup the link between the //e and the //c to transmit at 19200 baud! Assembling a listing of the buffering program takes about 7 seconds (and half of that is writing the target file)!

The SSC is in slot 1, it is configured as follows:

         SW1: off off off off off  on  on
         SW2:  on off off  on  on off off
         The jumper block is installed pointing towards modem

The Imagewriter's swiches are set:

         SW1:   open   open   open   open closed closed open open
         SW2: closed closed   open   open.

The pieces are connected with two DIN 5-Pin(m) to DB-25(m) cables, Apple Model Number: A9C0308 (4-2, 2-3, 1-6, 3-7, and 5-20). The cable from the //e to the //c is plugged into a //c System Clock which in turn is plugged into the Modem Port.

The program should work with most any serial printer, and serial card, however, if the serial card cannot "eliminate the modem", you will need a modem-eliminator cable extension, or will have to reverse pins 2 and 3 and pins 6 and 20 of the DB-25 connector. The Apple cable I used cannot be modified.

While the listing included with this article requires a 512K Applied Engineering Z-RAM board, I have also written versions that work in a 256K Z-RAM and in a stock Apple //c. More on these versions later. The memory on a Z-RAM is implemented as additional banks of auxiliary memory. Which of the auxiliary banks is the current auxiliary bank is controlled by a new hardware location at $C073. The Z-RAM powers-up disabled, that is, with the //c's built-in auxiliary bank as the current auxiliary bank. The //c powers-up with main memory enabled and all auxiliary memory disabled. Once selected as the current auxiliary bank, a Z-RAM bank is switched around by all the normal soft switches in the same manner as the //c's built-in auxiliary bank. A 512K Z-RAM has 8 additional banks and a 256K Z-RAM has 4 more. Which additional bank is the current auxiliary bank is selected by writing an ODD number between 1 and $F (inclusive) to the bank register at $C073. The 4 most significant data bits are ignored and any even number (usually zero) selects the //c's built-in auxiliary bank. A 256K Z-RAM only has bank numbers 3, 7, $B, and $F. To ease the task of writing programs that display 80 columns of text or double hires graphics, video data is always fetched from the //c's banks, even if a Z-RAM bank is the current auxiliary bank. Because the Z-RAM plugs into the processor and MMU sockets of the //c, and since only one board may be added this way, the Z-RAM includes a Z-80 processor. The Z-RAM is also totally compatible with the RamWorks board for the //e.

The //c's serial ports are a lot like Super Serial Cards in slots 1 and 2 of a //e. The ports and the SSC both use the 6551 ACIA (Asynchronous Communications Interface Adapter) and the firmware is quite similar. There is one significant difference that I found. The SSC tells an external source of data to stop transmitting by asserting the Data Terminal Ready bit of the ACIA command register (and thus the DTR pin when the jumper block is in the terminal position), while the //c's ports control the DTR pin with the Request To Send (and transmitter control) bits. It's right there on page 254 of The Apple //c Reference Manual Volume 1. Compare this to the schematic on page 100 of the SSC Manual.

Because every //c has a 65C02 processor, I can write code using the new opcodes and it will work in other peoples' machines. Of course if the code will also work in a //e, I can not be sure that it will be executed on a 65C02. With the release of the //e enhancement kit, this situation should improve. 65802 opcodes, being new and rare, must be reserved for programs intended for use in a very few machines.

On to the program. The target file is intended to load at $2000 in main memory. The code from lines 32 to 73 is executed in the $2000 area. This section does all of the setup for what is to come. The D and I flags are cleared and set respectively, ten soft switches are thrown, the screen is cleared, the remainder of the code is copied into ALL auxiliary zero pages and stacks, a text message is written to the screen, and the two ACIAs are initialized. The code copy and message printing share a loop. Lines 66 and 70 cheat a little. The INCs are assembled and the LDA #s are treated as comments. They work because the would-be operands of the LDA #s are one greater than the values just loaded by the previous LDA #s. The 'A' in line 74 is an open-apple MouseText character. The code in aux bank 0 is then entered at label 'Scan'.

The routines 'Write' and 'Read' (lines 79 and 88), handle all access to the buffer. In 'Write', the aux bank is selected, the address within that bank is written into the operand of a store absolute instruction (the copy in the bank just selected), and then the data byte is written. That's a total of four bytes of information passed in internal registers. The data byte had to be passed in the stack pointer! It couldn't have been passed in a memory location because it would have been switched out. 'Read' is a little simpler, it returns a data byte in the Acc. Since I'm using the S-reg for data and the aux bank 0 stack page for code, the program doesn't make any use of regular stack operations. After re-selecting aux bank 0, 'Write' and 'Read' jump back to the code just after the jumps that 'called' them. Even though the $2000 code copied the entire image into every aux bank, only 'Write' and 'Read' are not used as buffer in the Z-RAM banks.

Lines 99 to 108 allocate the (zero page!) variables required to keep track of the buffer. The 'Receive' variables indicate where the next byte received will be buffered, the 'Transmit' variables indicate where the next byte to be printed is buffered, and the 'Byte.Counter' variables keep track of how full (or empty) the buffer is. If the byte counter is zero, then the 'Transmit' variables are equal to the 'Receive' variables and the buffer is empty. 'RTS.Bit' is used to keep track of the //c's 'select' state.

Lines 110 to 128 run an indicator at the top-center of the screen and check to see if you've pressed a key. If you press the space bar, and if the program hasn't asserted the Request To (NOT) Send bit (because the buffer is nearly full), the //e may be halted. This works like a printer's select button.

Lines 129 to 207 handle buffering incoming data. If the Modem ACIA detects any transmission errors, you will see an indication of this at the left end of screen line three. If no character has been received, we go check the Printer port. When a character has been received, we test if the buffer is almost full. If it is, we assert RTS' (another character may already be on the way). The byte counter is incremented. If the buffer is completely full, we tick the third position of screen line one and go check the Printer port. This means that the RTS' handshaking isn't working. You will also get overrun errors. If we have room for the character, we increment the upper left screen position, and load the character from the RxD reg into the stack pointer. We then load the 'Receive' variables, maybe juggle the address high order nibble for the overlapping language card banks, and call 'Write'. Upon return, the 'Receive' variables are advanced through the buffer memory, avoiding our program and invalid aux banks. We then fall into the Printer port code.

Lines 208 to 271 handle printing buffered data as the printer can take it. This code is similar to the code for incoming data. Fewer things can go wrong, we of course test for an empty TxD reg and an empty buffer. We check to see if the buffer is somewhat less than almost full, and may release RTS'. The byte counter is decremented here. When a character is to be printed, we increment the upper right screen position, load the 'Transmit' variables, maybe juggle, call 'Read' and stuff the character into the TxD reg. Upon return, the 'Transmit' variables are advanced (same way), and we loop to 'Scan'. Forever. Reset exits the program.

The program loops VERY quickly. It has to. At 19200 baud, a character is received from the //e every half millisecond and at 9600 baud, a character may be printed every millisecond. The pair of locations at the top center of the screen, that are changed every time around the loop, give a good indication of how fast things are happening. The locations in the upper corners (my //e is to the left of the //c and the printer is to the right) are a good representation of the values of the 'Receive' and 'Transmit' variables. When buffering, the receive indicator races ahead while the transmit indicator lags behind, but since they are both initialized to blanks and the appropriate one is incremented when a character is moved, they come to rest displaying the same character when the buffer is empty.

The symbols 'Z.RAM.Banks.Avail', 'Z.RAM.Banks.Used', 'IIc.Aux.Bank.Avail' and 'BufLen' (lines 94, 96, 273-274) determine the size of the buffer. The ADC immediate operands in lines 195 and 259 cause the buffer to advance from bank 0 to 1 to 3 to 5... to $F. The listing is setup to use a //c's aux bank and a 512K Z-RAM. The changes for a 256K Z-RAM are easy: change the SAVE and .tf filenames (320K), change the 8 in line 96 to a 4, change the 9 in line 274 to a 5, and change the ADC #1s in lines 195 and 259 to ADC #3s. The changes for operating without a Z-RAM are not as simple. I removed all the bank stuff, made the byte counter only 16 bits, and combined the code copy with the screen clear instead of the message printing. It took about 5 minutes. The resulting code just fit into the aux zero page! The source code for all three versions will be on S-C Software's next quarterly disk, and I will send a paper listing of the //c only version to anyone who sends a self addressed stamped envelope to me care of Applied Engineering. I sometimes use the //c only version even though I have a Z-RAM. With the ProDrive disk emulation software, I can lock-out bank 0, leaving it available for double hires or a 64K buffer for my //e. With a 512K Z-RAM, I get a 1024 block /RAM volume.

The program does not use any main memory for the buffer because when you have 576K of aux memory, why bother programming for "only" another 64K? The //c only version, with 64K of buffer memory, is as big or bigger than most buffer boards/boxes. If anyone writes a 128K main/aux version of the program I would appreciate a copy.

      0000  .ti 81, B NApple //c w/512K Z-RAM buffering program 2.0 8/5/85 dcj
      0001 ;SAVE Buf.576K
      0002 ;--------------------------------
      0003 ; Dedicated to Allan B. Calhamer.
      0004 ;--------------------------------
      0005 Printer.ACIA.TxD     .eq $C098 (w)
      0006 Printer.ACIA.Status  .eq $C099 (r)
      0007 Printer.ACIA.Command .eq $C09A (r/w)
      0008 Printer.ACIA.Control .eq $C09B (r/w)
      0009 Modem.ACIA.RxD       .eq $C0A8 (r)
      0010 Modem.ACIA.Status    .eq $C0A9 (r)
      0011 Modem.ACIA.Command   .eq $C0AA (r/w)
      0012 Modem.ACIA.Control   .eq $C0AB (r/w)
      0013 Z.RAM.Bank.Reg       .eq $C073 (w) same as RamWorks
      0014 Keyboard             .eq $C000 (r)
      0015 Store80              .eq $C001 (w) on
      0016 RAMRd                .eq $C003 (w) aux
      0017 RAMWrt               .eq $C005 (w) aux
      0018 AltZP                .eq $C009 (w) aux
      0019 Vid40                .eq $C00C (w)
      0020 SetAltChr            .eq $C00F (w) w/MouseText
      0021 Clear.Key.Strobe     .eq $C010 (r)
      0022 Text                 .eq $C051 (r)
      0023 Page1                .eq $C054 (r) main
      0024 Page2                .eq $C055 (r) aux
      0025 Hires                .eq $C057 (r) $2000-$3FFF too...
      0026 LCRAM2               .eq $C083 (r/w; write doesn't
      0027 LCRAM1               .eq $C08B  change write enable)
      0028 ;--------------------------------
      0029        .op 65C02
      0030        .or $2000
      0031        .tf Bufit576K
      0032 dcj    CLD               rqd (now)
      0033        SEI               close this can of worms...
      0034        LDA LCRAM2        1x...switches setup
      0035        LDA Text
      0036        LDA Page1
      0037        LDA Hires
      0038        STZ Store80
      0039        STZ RAMRd
      0040        STZ RAMWrt
      0041        STZ AltZP
      0042        STZ SetAltChr
      0043        STZ Vid40
      0044        LDA #" "          clear 40 column screen
      0045        LDX #0
      0046 .1     STA $400,X
      0047        STA $500,X
      0048        STA $600,X
      0049        STA $700,X
      0050        INX
      0051        BNE .1
      0052        LDY #$0F          install Image in aux ZPs/Stacks
      0053 .2     STY Z.RAM.Bank.Reg
      0054 .3     LDA Image,X
      0055        STA $00,X
      0056        LDA Image+$100,X
      0057        STA $100,X
      0058        INX
      0059        BNE .3
      0060        LDA Msg,Y         put up a message
      0061        STA $50C,Y
      0062        DEY
      0063        BPL .2
      0064        LDA #%000.0.10.1.0         bop ACIAs
      0065        STA Printer.ACIA.Command
      0066   inc  LDA #%000.0.10.1.1         RTS' lo
      0067        STA Modem.ACIA.Command
      0068        LDA #%0.00.1.1110          9600 baud
      0069        STA Printer.ACIA.Control
      0070   inc  LDA #%0.00.1.1111          19200 baud!
      0071        STA Modem.ACIA.Control
      0072        LDA Modem.ACIA.RxD
      0073        JMP Scan          go 2 it
      0074 Msg    .AS 'A'           as in Apple
      0075        .AS -" //c buffer pgm"
      0076 Image  .ph $00
      0077 ; aux bank specified by Acc, bank adr lo by X-reg,
      0078 ;  bank adr hi by Y-reg, and byte passed in S-reg!
      0079 Write  STA Z.RAM.Bank.Reg  bank in Z-RAM
      0080        STX <.1+1         modify STX operand in "this" bank
      0081        STY <.1+2
      0082        TSX               get byte to a usable reg!
      0083 .1     STX $FFFF         abs adr modified for each write
      0084        STZ Z.RAM.Bank.Reg  revert to //c aux bank
      0085        JMP W.Ret
      0086 ; aux bank specified by Acc, bank adr lo by X-reg,
      0087 ;  bank adr hi by Y-reg, and byte returned in Acc.
      0088 Read   STA Z.RAM.Bank.Reg  bank in Z-RAM
      0089        STX <.1+1         modify LDA operand in "this" bank
      0090        STY <.1+2
      0091 .1     LDA $FFFF         abs adr modified for each read
      0092        STZ Z.RAM.Bank.Reg  revert to //c aux bank
      0093        JMP R.Ret
      0094 Z.RAM.Banks.Avail   .eq *-3
      0095 ; (-3 because JMP R.Ret never executed in Z-RAM)
      0096 Z.RAM.used          .eq Z.RAM.Banks.Avail*8
      0097 ;--------------------------------
      0098 ; buffer starts at first available location in //c aux bank
      0099 Receive.Adr.Lo      .da #IIc.Aux.Bank.Avail
      0100 Receive.Adr.Hi      .da /IIc.Aux.Bank.Avail
      0101 Receive.Bank        .da #$00
      0102 Transmit.Adr.Lo     .da #IIc.Aux.Bank.Avail
      0103 Transmit.Adr.Hi     .da /IIc.Aux.Bank.Avail
      0104 Transmit.Bank       .da #$00
      0105 Byte.Counter.Lo     .da #$000000        indicates empty
      0106 Byte.Counter.Mid    .da #$000000/256
      0107 Byte.Counter.Hi     .da #$000000/65536
      0108 RTS.Bit             .da #%000.0.10.0.0  RTS' lo
      0109 ;--------------------------------
      0110 Scan   LDA Page1         access main text screen
      0111        INC $413          show we're alive
      0112        DEC $414
      0113        LDA Page2         back to aux
      0114        LDA Keyboard      scan keyboard
      0115        BPL Scan.Modem.Port
      0116        CMP #" "          space toggles RTS' (DTR2B) to //e
      0117        BNE .2
      0118        LDA Modem.ACIA.Command
      0119        AND #%000.0.10.0.0
      0120        BNE .1            =>It's ok, you can turn it off...
      0121        LDA RTS.Bit
      0122        BNE Scan.Modem.Port  =>don't do it! (yet)
      0123 .1     LDA Modem.ACIA.Command
      0124        EOR #%000.0.10.0.0
      0125        STA Modem.ACIA.Command
      0126        AND #%000.0.10.0.0
      0127        STA RTS.Bit
      0128 .2     BIT Clear.Key.Strobe
      0129 Scan.Modem.Port
      0130        LDY Modem.ACIA.Status
      0131        TYA
      0132        AND #%0000.0111   error bits mask
      0133        BEQ .1            =>error-free operation
      0134        TAX
      0135        LDA Page1         access main text screen
      0136        INC $4FF,X        indicate error...
      0137        LDA Page2         back to aux
      0138 .1     TYA
      0139        AND #%0000.1000   receive data reg full mask
      0140        BEQ CantRx        =>not full
      0141        LDA Byte.Counter.Lo   received a byte,
      0142        LDX Byte.Counter.Mid  do we assert RTS' ?
      0143        LDY Byte.Counter.Hi
      0144        CMP #BufLen-256
      0145        BNE .2            =>buffer not @ full-256
      0146        CPX /BufLen-256
      0147        BNE .2            =>buffer not @ full-256
      0148        CPY ^BufLen-256
      0149        BNE .2            =>buffer not @ full-256
      0150        LDA #%000.0.10.0.0   assert RTS'
      0151        TRB Modem.ACIA.Command
      0152        LDA Byte.Counter.Lo  reload it
      0153 .2     INC               fig next byte count
      0154        BNE .3
      0155        INX
      0156        BNE .3
      0157        INY
      0158 .3     CMP #BufLen       do we have room for it ?
      0159        BNE Room          =>buffer not full
      0160        CPX /BufLen
      0161        BNE Room          =>buffer not full
      0162        CPY ^BufLen
      0163        BNE Room          =>buffer not full
      0164        LDA Page1         access main text screen
      0165        INC $402          indicate full
      0166        LDA Page2         back to aux
      0167 CantRx BRA Cant.Receive  =>buffer is full!
      0168 Room   STA Byte.Counter.Lo
      0169        STX Byte.Counter.Mid
      0170        STY Byte.Counter.Hi
      0171        LDA Page1         access main text screen
      0172        INC $400          show we received a byte
      0173        LDA Page2         back to aux
      0174        LDX Modem.ACIA.RxD
      0175        TXS               pass it in S-reg
      0176        LDX Receive.Adr.Lo
      0177        LDY Receive.Adr.Hi
      0178        BIT LCRAM2        normally use LC bank 2
      0179        TYA
      0180        AND #$F0
      0181        CMP /$C000        if adr is in $CXXX range
      0182        BNE .1
      0183        BIT LCRAM1         use LC bank 1
      0184        TYA
      0185        ORA /$D000
      0186        TAY
      0187 .1     LDA Receive.Bank
      0188        JMP Write
      0189 W.Ret  INC Receive.Adr.Lo  fig next receive adr
      0190        BNE Scan.Printer.Port
      0191        INC Receive.Adr.Hi
      0192        BNE Scan.Printer.Port
      0193        LDA Receive.Bank
      0194        CMP #1
      0195        ADC #1            clear carry if 0, else set it
      0196        CMP #$10
      0197        BCC .1            =>entering/still in Z-RAM
      0198        LDA #$00          wrap to //c bank 0
      0199        LDX #IIc.Aux.Bank.Avail
      0200        LDY /IIc.Aux.Bank.Avail
      0201        BRA .2
      0202 .1     LDX #Z.RAM.Banks.Avail
      0203        LDY /Z.RAM.Banks.Avail
      0204 .2     STA Receive.Bank
      0205        STX Receive.Adr.Lo
      0206        STY Receive.Adr.Hi
      0207 Cant.Receive
      0208 Scan.Printer.Port
      0209        LDA #%0011.0000   make transmit data reg empty and
      0210        AND Printer.ACIA.Status  Data Carrier Detect mask
      0211        CMP #%0001.0000   test empty and DCD' lo
      0212        BNE Cant.Transmit =>not empty or not ready
      0213        LDA Byte.Counter.Lo   printer can take another byte,
      0214        ORA Byte.Counter.Mid  do we have one ?
      0215        ORA Byte.Counter.Hi
      0216        BEQ Cant.Transmit     =>buffer is empty!!!
      0217        LDA Byte.Counter.Lo   do we release RTS' ?
      0218        LDX Byte.Counter.Mid
      0219        LDY Byte.Counter.Hi
      0220        CMP #BufLen-2048
      0221        BNE .1            =>buffer not @ full-2048
      0222        CPX /BufLen-2048
      0223        BNE .1            =>buffer not @ full-2048
      0224        CPY ^BufLen-2048
      0225        BNE .1            =>buffer not @ full-2048
      0226        LDA RTS.Bit
      0227        TSB Modem.ACIA.Command  release RTS' (maybe)
      0228 .1     STA Z.RAM.Bank.Reg+5
      0229        LDA Byte.Counter.Lo     fig next byte count
      0230        BNE .3
      0231        LDA Byte.Counter.Mid
      0232        BNE .2
      0233        DEC Byte.Counter.Hi
      0234 .2     DEC Byte.Counter.Mid
      0235 .3     DEC Byte.Counter.Lo
      0236        LDA Page1         access main text page
      0237        INC $427          show we printed a byte
      0238        LDA Page2         back to aux
      0239        LDX Transmit.Adr.Lo
      0240        LDY Transmit.Adr.Hi
      0241        BIT LCRAM2        normally use LC bank 2
      0242        TYA
      0243        AND #$F0
      0244        CMP /$C000        if adr in $CXXX range
      0245        BNE .4
      0246        BIT LCRAM1         use LC bank 1
      0247        TYA
      0248        ORA /$D000
      0249        TAY
      0250 .4     LDA Transmit.Bank
      0251        JMP Read
      0252 R.Ret  STA Printer.ACIA.TxD
      0253        INC Transmit.Adr.Lo  fig next transmit adr
      0254        BNE Next
      0255        INC Transmit.Adr.Hi
      0256        BNE Next
      0257        LDA Transmit.Bank
      0258        CMP #1            clear carry if 0, else set it
      0259        ADC #1
      0260        CMP #$10
      0261        BCC .1            =>entering/still in Z-RAM
      0262        LDA #$00          wrap to //c bank 0
      0263        LDX #IIc.Aux.Bank.Avail
      0264        LDY /IIc.Aux.Bank.Avail
      0265        BRA .2
      0266 .1     LDX #Z.RAM.Banks.Avail
      0267        LDY /Z.RAM.Banks.Avail
      0268 .2     STA Transmit.Bank
      0269        STX Transmit.Adr.Lo
      0270        STY Transmit.Adr.Hi
      0271 Cant.Transmit
      0272 Next   JMP Scan
      0273 IIc.Aux.Bank.Avail .eq *
      0274 BufLen .eq $90000-Z.RAM.Used-IIc.Aux.Bank.Avail
      0275  .lif

How Many Bytes for each Opcode? | Bob Sander-Cederlof

I have been thinking about a semi-automatic object code relocation scheme lately. Steve Wozniak wrote one for the 6502 back in 1976, published in various places such as Call APPLE's "Wozpak". But we are needing one for the 65C02, and maybe for the 65816.

Steve's version used his "Sweet-16" interpreter for some of the address arithmetic. That was okay, because Sweet-16 was in ROM in every Apple in those days. Not so now, although it is available to DOS 3.3 users as part of the Integer BASIC package. But we should write one that does not require Sweet-16.

Steve's relocator also used a ROM-based routine (part of the built-in disassembler) to determine how many bytes are used by each opcode. This routine has been modified in the //c monitor and the new enhanced //e monitor to include the 65C02 opcodes. That's nice, because that means Woz's program will automatically work with 65C02 programs if you run it with the new monitors. However, since I want to include all the 65816 opcodes, I need a new version.

The first step seems to be to write a program which will tell me how many bytes each opcode uses. I know that opcodes which are only one or two bytes do not need any relocation adjustments when a program is moved to a different place in memory. Most 3-byte and all 4-byte instructions contain absolute addresses; if an absolute address is inside the program being moved, it will have to be adjusted for the new location.

I haven't written the entire relocator yet, but I have written a program which will tell me all I need to know about the length of an opcode. My program returns the length in bytes and also two flags. One flag indicates the opcode is a 3-byte instruction which does include an absolute address. The other flag indicates the opcode was an immediate mode instruction. Immediate mode in 65816 code is ambiguous in length, except during execution. My program calls them two-byte instructions, but they may be three bytes each if the status bits so indicate at execution time. I am not sure how my relocator will handle this ambiguity, but for now I am content just to set a flag.

The code in the monitor which determines the length of opcodes uses a table lookup method. I figure that I could do that too, with a 64-byte table, using two bits for each opcode. I would still need a way to test for immediate mode and the special three-byte opcodes which do not have absolute addresses (MVP, MVN, PER, and BRL).

After looking at a chart which showed all the lengths, I decided to do it with bit analysis rather than table lookup. It is probably a little slower, but also a little smaller.

It turns out that almost all of the opcodes whose second hex digit is less than 8 use two bytes. There are only nine exceptions. One interesting case here is BRK, which assembles to only one byte but is considered by the microprocessor to be a two-byte opcode. I am not sure whether the relocator should considere BRK as a single byte or a two-byte opcode, but I think it should probably be one byte.

All opcodes of the with the hex values of $x8, $xA, and $xB are one byte, without exception. All opcodes with the hex values $xC, $xD, and $xE are three bytes with absolute addresses, with only one exception: $5C is a four-byte instruction. All opcodes with value $xF are four bytes each.

The column of opcodes with values $x9 are divided into two groups. Those with the first digit even ($09, 29, 49, etc.) are all three bytes each with absolute addresses. The odd ones are immediate mode opcodes, which may be either two or three bytes each depending on status bits during execution.

Here is a table of the various byte counts, which was actually computed by my program. I printed "2#" for immediate mode opcodes, and "3+" for three-byte opcodes with absolute addresses.

           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

       0   2  2  2  2  2  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       1   2  2  2  2  2  2  2  2  1  3+ 1  1  3+ 3+ 3+ 4
       2   3+ 2  4  2  2  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       3   2  2  2  2  2  2  2  2  1  3+ 1  1  3+ 3+ 3+ 4
       4   1  2  2  2  3  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       5   2  2  2  2  3  2  2  2  1  3+ 1  1  4  3+ 3+ 4
       6   1  2  3  2  2  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       7   2  2  2  2  2  2  2  2  1  3+ 1  1  3+ 3+ 3+ 4
       8   2  2  3  2  2  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       9   2  2  2  2  2  2  2  2  1  3+ 1  1  3+ 3+ 3+ 4
       A   2  2  2  2  2  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       B   2  2  2  2  2  2  2  2  1  3+ 1  1  3+ 3+ 3+ 4
       C   2  2  2  2  2  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       D   2  2  2  2  2  2  2  2  1  3+ 1  1  3+ 3+ 3+ 4
       E   2  2  2  2  2  2  2  2  1  2# 1  1  3+ 3+ 3+ 4
       F   2  2  2  2  3+ 2  2  2  1  3+ 1  1  3+ 3+ 3+ 4

The program which printed the table is in lines 1050-1320 below. The program which computes how many bytes in an opcode follows that. By inserting a "BEQ .6" between lines 1410 and 1420 I could make BRK a one-byte opcode.

My relocator should probably also be on the lookout for calls to ProDOS MLI. This is in effect a six-byte instruction. The first three bytes are $20, $00, $BF (JSR MLI). The fourth byte is the MLI function code. The last two bytes are the address of a parameter table, and so should be considered as a relocatable address.

I hope to continue to pursue this idea of a relocator, but I make no promises. Maybe one of you would like to write one and share it with the rest of us.

      1000 *SAVE S.BYTE TABLE
      1010 *--------------------------------
      1020 COUT   .EQ $FDED
      1030 CROUT  .EQ $FD8E
      1040 *--------------------------------
      1050 T
      1060        LDX #0
      1070 .1     TXA
      1080        AND #$0F
      1090        BNE .2
      1100        JSR CROUT
      1110 .2     TXA
      1120        JSR GET.LENGTH.OF.OPCODE
      1130        PHA
      1140        AND #$07
      1150        ORA #"0"
      1160        JSR COUT
      1170        PLA
      1180        ASL          POSITION XY FOR INDEX
      1190        ROL
      1200        ROL
      1210        AND #$03     0000 00XY
      1220        TAY
      1230        LDA TABLE,Y
      1240        JSR COUT
      1250        LDA #" "
      1260        JSR COUT
      1270        INX
      1280        BNE .1
      1290        JMP CROUT
      1300 *--------------------------------
      1310 TABLE  .AS -/ #+/
      1320 *--------------------------------
      1330 *   CALL WITH (A)= OPCODE
      1340 *   RETURN WITH (Y)= OPCODE
      1350 *               (A)= XY000LLL
      1360 *                   LLL = # OF BYTES, 1...4
      1370 *                    X  = 1 IF ABS ADDRESS
      1380 *                    Y  = 1 IF IMMEDIATE
      1390 *--------------------------------
      1400 GET.LENGTH.OF.OPCODE
      1410        TAY
      1420        AND #$0F
      1430        CMP #$08
      1440        BCC .4       XXXX 0XXX
      1450        CMP #$0C
      1460        BCC .3       XXXX 10XX
      1470        CMP #$0F
      1480        BEQ .2       XXXX 1111, L=4
      1490        CPY #$5C
      1500        BEQ .2       0101 1100, L=4
      1510 *---L=3, ABS ADDRESS-------------
      1520 .1     LDA #$83
      1530        RTS
      1540 *---L=4--------------------------
      1550 .2     LDA #4
      1560        RTS
      1570 *---XXXX 10XX--------------------
      1580 .3     CMP #$09
      1590        BNE .6       X8, XA, or XB
      1600 *---XXXX 1001--------------------
      1610        TYA
      1620        AND #$10
      1630        BNE .1       XXX1 1001, L=3
      1640 *---XXX0 1001, IMMEDIATES, L=2---
      1650        LDA #$42     OR 3 IF ## MODE
      1660        RTS
      1670 *---XXXX 0XXX--------------------
      1680 .4     LSR          CHECK ODD/EVEN
      1690        BCS .5       ODD, L=2
      1700        CPY #$22
      1710        BEQ .2       JSL LABS, L=4
      1720        CPY #$20
      1730        BEQ .1       JSR ABS, L=3
      1740        CPY #$40
      1750        BEQ .6       RTI, L=1
      1760        CPY #$60
      1770        BEQ .6       RTS, L=1
      1780        CPY #$62
      1790        BEQ .7       PER LREL, L=3
      1800        CPY #$82
      1810        BEQ .7       BRL LREL, L=3
      1820        CPY #$44
      1830        BEQ .7       MVP, L=3
      1840        CPY #$54
      1850        BEQ .7       MVN, L=3
      1860        CPY #$F4
      1870        BEQ .1       PEA ABS, L=3
      1880 *---L=2--------------------------
      1890 .5     LDA #2       L=2
      1900        RTS
      1910 *---L=1--------------------------
      1920 .6     LDA #1
      1930        RTS
      1940 *---L=3, NON-ABS ADDRESS---------
      1950 .7     LDA #3
      1960        RTS
      1970 *--------------------------------

Generic Conversion Routines | Bob Sander-Cederlof

I may have written hundreds of different versions of the elementary I/O conversion routines. The first few would have been for the IBM 704, back in college days. Then there was the G-15, the 1620, the 3100, the 3300, the 6600, the 1700, the 8090, the 960, the 980, the 990, and so on. Don't worry of those numbers don't mean anything to you. They are the "names" of computers out of the past, not micro chips.

What I am talking about is writing programs which convert input decimal characters representing decimal numbers into internal binary form, and the converse operation of converting binary numbers into decimal form. We have published several variations of both in previous newsletters, but I have some special ones to present here.

There are many variations of the basic routines, and that is one reason I have written so many. Thinking just of the output conversions (binary to decimal):

- Convert to a string in memory, or print it out.
- Number of bytes in binary number.
- Supply leading zeroes or blanks or neither.
- Integer, fraction, floating point, or fixed point.
- Signed or unsigned.

The routine I set out to write today works with unsigned integers, prints out the resulting characters rather than storing them in a string, and does not print any leading zeroes or blanks. I wrote it to work with two-byte values, between 0 adn 65535. As an added feature, I indicated in the comments how to expand it to work with larger values.

Lines 1800-2080 in the listing comprise the output conversion routine. I divide the number by ten, saving the remainder as the least significant digit; the quotient becomes the new number, so I repeat the process until the quotient is zero. Then the digits, which were all saved on the 6502 stack, are popped back off and printed.

Line 1810 starts the digit counter at 0, and line 1950 increments the counter each time a new digit is pushed onto the stack. Lines 2020-2060 pull the digits off the stack and print them in reverse order.

Lines 1970-2000 test the quotient: if it is non-zero, another division is performed; if not, we are ready to print the result. This is one place where you need to add code if your input values are larger than two bytes, as I indicated in line 1980. By the way, since we do one division before testing, an input value of zero will print as "0".

Lines 1830-1930 divide the input value by ten. It may look like I am dividing by five, but remember 5 = 10/2. I did more fiddling than analyzing in this loop, but it really does work. Line 1840 sets the loop count to 16, the number of bits in two bytes. If you want to convert three-byte values, change the 16 to 24. The loop needs to be executed once for each bit in the input value. If you are going to have values longer than two bytes, you also need to add more ROL instructions between lines 1880 and 1900, as indicated in my comment line 1890. If you were to need a three byte conversion routine, you could just remove the "*--" from the front of lines 1890 and 1980, and chane line 1840 to LDY #24.

Notice that this subroutine is very short, and fairly fast. I have an idea that some of you will think of ways to make it shorter and faster; if you do, try to keep it easily modifiable for the number of bytes in values.

Next I wrote a program to convert from a decimal string into binary, lines 1290-1720. It is also set up for unsigned two-byte integer values, with comments indicating how to modify it for longer values. I have written shorter routines before, but this one makes extension to longer values easy and tests for overflow.

The string is assumed to be in ASCII, with high bits = 1, starting at $0200, and terminated by any non-digit. It just so happens that these are just the conditions you usually find in an Apple, because almost all input routines use the buffer at $0200. Woz started it, and we all followed Woz.

Lines 1300-1330 clear the value, as well as starting the buffer index at zero. The rest of the routine scans through the digits. Each time the current value is multiplied by ten, and the next digit added. If at any point an overflow is detected (a value too large for the number of bytes) the routine rings the bell and quits. You can use some other error indication, and probably should, such as printing "NUMBER TOO LARGE".

In order to multiply by ten, I set aside another storage area equal in length to the value accumulator. At line 1380 the new digit is saved in the Y-register. The accumulated value at this point is in XH and XL. Lines 1390-1480 form the value*4 in SH and XL, leaving the original value in XH and SL. (Yes, they are criss-crossed.) Lines 1410-1420 show how you would extend this portion to longer values.

Lines 1490-1610 add value*4 to value to get value*5, and then double the result to get value*10. Again, lines 1530-1550 show how to extend the value. Lines 1630-1700 add in the new digit, and the comments show how to extend to longer values.

The top level routine in lines 1130-1270 is just a test routine. It calls the monitor line input routine. If you type an empty line, it will stop. Otherwise it calles the input conversion routine, prints the resulting value in hexadecimal, and converts it back to decimal with the output conversion routine.

      1000 *SAVE S.BINDEC
      1010 *--------------------------------
      1020 XL     .EQ $00
      1030 XH     .EQ $01
      1040 SL     .EQ $10
      1050 SH     .EQ $11
      1060 *--------------------------------
      1070 BELL   .EQ $FBDD
      1080 RDLINE .EQ $FD6A
      1090 PRBYTE .EQ $FDDA
      1100 COUT   .EQ $FDED
      1110 CROUT  .EQ $FD8E
      1120 *--------------------------------
      1130 T
      1140        JSR RDLINE
      1150        TXA
      1160        BNE .1
      1170        RTS
      1180 .1     JSR CONVERT.DEC.TO.BIN
      1190        LDA XH
      1200        JSR PRBYTE
      1210        LDA XL
      1220        JSR PRBYTE
      1230        LDA #"="
      1240        JSR COUT
      1250        JSR CONVERT.BIN.TO.DEC
      1260        JSR CROUT
      1270        JMP T
      1280 *--------------------------------
      1290 CONVERT.DEC.TO.BIN
      1300        LDX #0
      1310        STX XL       least significant byte
      1320 *--    STX XI    ---ANY INTERMEDIATE BYTES---
      1330        STX XH       most significant byte
      1340 .1     LDA $200,X
      1350        EOR #"0"
      1360        CMP #10
      1370        BCS .3       ...END OF NUMBER
      1380        TAY          SAVE CURRENT DIGIT
      1390        LDA XL
      1400        STA SL
      1410 *--    LDA XI    ---ANY INTERMEDIATE BYTES---
      1420 *--    STA SI    ---FOLLOW THIS PATTERN------
      1430        LDA XH
      1440        JSR SHIFT.X
      1450        BCS .2       ...OVERFLOW
      1460        JSR SHIFT.X
      1470        BCS .2       ...OVERFLOW
      1480        STA SH
      1490        CLC
      1500        LDA XL
      1510        ADC SL
      1520        STA XL
      1530 *--    LDA XI    ---ANY INTERMEDIATE BYTES---
      1540 *--    ADC SI    ---FOLLOW THIS PATTERN------
      1550 *--    STA XI    ----------------------------
      1560        LDA XH
      1570        ADC SH
      1580        BCS .2       ...OVERFLOW
      1590        JSR SHIFT.X
      1600        BCS .2       ...OVERFLOW
      1610        STA XH
      1620        INX          SCAN TO NEXT DIGIT
      1630        TYA          GET DIGIT
      1640        ADC XL       LEAST SIGNIFICANT BYTE
      1650        STA XL
      1660        BCC .1       ...NO CARRY
      1670 *--    INC XI    ---ANY INTERMEDIATE BYTES---
      1680 *--    BNE .1    ---FOLLOW THIS PATTERN------
      1690        INC XH       MOST SIGNIFICANT BYTE
      1700        BNE .1       ...UNLESS OVERFLOW
      1710 .2     JSR BELL     SIGNAL OVERFLOW
      1720 .3     RTS
      1730 *--------------------------------
      1740 SHIFT.X
      1750        ASL XL       LEAST SIGNIFICANT BYTE
      1760 *--    ROL XI    ---ANY INTERMEDIATE BYTES---
      1770        ROL          ...MOST SIGNIFICANT BYTE IN A
      1780        RTS
      1790 *--------------------------------
      1800 CONVERT.BIN.TO.DEC
      1810        LDX #0       DIGIT COUNTER
      1820 *---DIVIDE BY TEN----------------
      1830 .1     LDA #0
      1840        LDY #16      2*(# Bytes being converted)
      1850 .2     CMP #5
      1860        BCC .3
      1870        SBC #5
      1880 .3     ROL XL
      1890 *--    ROL XI    ---ANY INTERMEDIATE BYTES---
      1900        ROL XH
      1910        ROL
      1920        DEY
      1930        BNE .2
      1940        PHA          SAVE DIGIT ON STACK
      1950        INX          COUNT THE DIGIT
      1960 *---NEXT DIGIT-------------------
      1970        LDA XL
      1980 *--    ORA XI    ---ANY INTERMEDIATE BYTES---
      1990        ORA XH
      2000        BNE .1
      2010 *---PRINT DECIMAL----------------
      2020 .4     PLA
      2030        ORA #"0"
      2040        JSR COUT
      2050        DEX
      2060        BNE .4
      2070        RTS
      2080 *--------------------------------

A Wildcard Filename Search | Bob Sander-Cederlof

Over the years I have fallen into certain habits when it comes to naming files. I find it convenient to use names starting with "S." for assembly language source files, "B." for binary object code files, and so on. Others like to use suffixes like ".SRC" and ".OBJ" for the same reasons. Some operating systems, like CP/M for example, use suffixes to indicate file type. Others, like ProDOS, let you build sub-directories to categorize your files.

Sometimes I would like to have the ability to do the same operation on a whole group of files. For example, I might want to DELETE all files starting with "B.". Or I might want to copy a whole group of files from one disk to another. If the files happen to have similar names, and if DOS allowed wildcards in filenames, it would be easier.

Some DOS 3.3 programs do have this feature: Apple's FID program, Sensible Software's Super Disk Copy, and others. They have a method for specifying a filename without spelling out the entire name.

The subroutine inside DOS 3.3 which compares a filename you have specified with the names in a catalog is found at $B1F5:

           LDY #0
           INX
           INX
     .1    INX
           LDA ($42),Y  Filename you specified
           CMP $B4C6,X  Filename in catalog sector
           BNE ...      ...did not match
           INY
           CPY #30
           BNE .1
       ... matched ...

This is a very straightforward string comparison. It requires an exact match of all 30 characters of a filename. There is a similar routine at $A782 which compares a filename you specify with the filenames in the open file buffers.

I wrote a subroutine called MATCH which compares two 30-character strings, allowing wildcards. Unfortunately, it not a simple matter to plug such a subroutine into DOS 3.3, and I have not done that. It is more likely that this subroutine will find its way into some future utility programs.

I also wrote a testing program, so that I could see if my code worked. The program in lines 1110-1380 searches through a list of 30-character strings, printing those which match a key string. To simplify my test program (a good idea to keep testers simple, so they are not themselves more buggy than the testees!) I assembled in the key string and the list of strings to be searched. A slightly better test would allow me to type in the key string.

My MATCH program assumes that the address of the string to be compared with the key is stored at FN and FN+1. Characters in the filename are addressed by "(FN),Y", and in the key are addressed by "KEY,X". MATCH will return with carry set if the filename matches the key, and carry clear if not.

Both the filename and the key are stored "left-justified, blank-filled". That means there may be any number of non-significant blanks on the right end. Lines 1490-1530 scan the current filename from right-to-left, looking for the last non-blank in the name. Lines 1550-1590 do the same for the key. If there is any chance either filename or key could be completely blank, an extra line "BMI ERROR" should be inserted at 1505 and 1565.

I save the index to the right end of the key in KEY.START. Because the end of the filename and key strings is variable, I actually do the comparison from right to left. This makes the "end" actually the beginning.

Line 1610 could be "JMP .4" or "BNE .4", because the object is to get to line 1660. However, the "INX" allows me to fall through lines 1630-1640 and it takes only one byte rather than two or three.

The comparison begins at line 1660. Remember we are scanning backwards, from right to left. Lines 1660-1670 save the two string pointers. Line 1680 gets the next character from the key. If it is a wildcard, I branch back to line 1630. Note that all that happens is that the wildcard is skipped over!

If the key character is not a wildcard, it gets compared to the next character of the filename at line 1710. If it matches, lins 1730-1760 advance both pointers and the comparison continues. These lines also check to see if we have come to the left end of the key or of the filename.

If we are at the end of the filename, lines 1770-1820 check the rest of the key. If there are any characters left in the key which are not wildcards, then the current filename does not match. Otherwise, it does match. Lines 1830-1880 set the appropriate carry status and return.

If we are at the end of the key, lines 1900-1910 check whether we are also at the end of the filename. If so, the filename matched. If not, maybe it did not match. I say maybe, because if there was a wildcard, we might come out with a match if we widen the amount matched by that wildcard. Lines 1920-1990 will handle that possibility.

Two conditions bring us to line 1930. Either a character in the key did not match the current character in the filename, or there are unmatched filename characters left over after the end of the key. In either case, if there has been no wildcard in the key (so far), then the filename does not match the key. If there has been a wildcard, we can try again to match from the most recent wildcard on. We can tell whether or not there has been a wildcard so far by comparing KEY.PNTR with KEY.START. If they are the same, there has been no wildcard. Lines 1920-1990 handle all these details.

I made the wild card character itself a variable, so that you could change it by program control. Since "=" is a valid character in a filename, you might want to use something else.

With this kind of MATCH subroutine, a key of "=.OBJ" would match all names ending with ".OBJ"; "S.=" would match all names starting with "S."; "=A=B=" would match all names containing "A" followed by "B".

You can see the similarity between MATCH and a global search capability such as you might find in a word processor, or in the S-C Macro Assembler. The FIND and REPLACE commands in S-C Macro allow wildcards. However, MATCH differs in that it anchors the key to the beginning and end of the file name (unless you specify a wildcard in those positions).

If string comparisons of this type intrigue you, the book "Software Tools" develops an extremely powerful one in chapter 5. "Software Tools" is a classic book by Kernighan and Plauger, available at many bookstores. (A "classic" in computer books is one still in print after five years; this one qualifies, since it was originally published in 1976.) Their string match routine allows single- and multi-character wildcards, semi-wildcards that match subsets of characters, control of anchoring, and more. It would be a worthwhile exercise to try implementing their algorithm in 6502 language.

      1000 *SAVE S.WILDCARD
      1010 *--------------------------------
      1020 COUT   .EQ $FDED
      1030 CROUT  .EQ $FD8E
      1040 *--------------------------------
      1050 KEY.PNTR   .EQ $00
      1060 BUF.PNTR   .EQ $01
      1070 FN         .EQ $02,03
      1080 KEY.START  .EQ $04
      1090 CNTR       .EQ $05
      1100 *--------------------------------
      1110 T
      1120        LDA #NAME.CNT
      1130        STA CNTR
      1140        LDA #FNLIST
      1150        LDY /FNLIST
      1160 .1     STA FN
      1170        STY FN+1
      1180        JSR MATCH
      1190        BCC .2       ...DID NOT MATCH
      1200        JSR DISPLAY
      1210 .2     LDA FN
      1220        CLC
      1230        ADC #30
      1240        LDY FN+1
      1250        BCC .3
      1260        INY
      1270 .3     DEC CNTR
      1280        BNE .1
      1290        RTS
      1300 *--------------------------------
      1310 DISPLAY
      1320        LDY #0
      1330 .1     LDA (FN),Y
      1340        JSR COUT
      1350        INY
      1360        CPY #30
      1370        BCC .1
      1380        JMP CROUT
      1390 *--------------------------------
      1400 *   COMPARE KEY TO A FILE NAME
      1410 *      KEY MAY CONTAIN WILDCARDS
      1420 *      TRAILING BLANKS DON'T COUNT
      1430 *      FILE NAME ADDRESSED VIA "(FN),Y"
      1440 *      KEY ADDRESSED VIA "KEY,X"
      1450 *      KEY AND FILE NAME ARE UP TO 30 CHARS LONG
      1460 *          (STORED LEFT-JUSTIFIED, BLANK-FILLED)
      1470 *--------------------------------
      1480 MATCH
      1490        LDY #30      FIND LAST NON-BLANK CHAR
      1500 .1     DEY              IN FILE NAME
      1510        LDA (FN),Y
      1520        CMP #" "
      1530        BEQ .1
      1540 *--------------------------------
      1550        LDX #30      FIND LAST NON-BLANK CHAR
      1560 .2     DEX              IN KEY
      1570        LDA KEY,X
      1580        CMP #" "
      1590        BEQ .2
      1600        STX KEY.START
      1610        INX
      1620 *---WILD CARD--------------------
      1630 .3     DEX          ADVANCE KEY POINTER
      1640        BMI .8       ...END OF KEY IS WILD, SO MATCHES
      1650 *--------------------------------
      1660 .4     STX KEY.PNTR
      1670 .5     STY BUF.PNTR
      1680 .6     LDA KEY,X
      1690        CMP WILD.CARD
      1700        BEQ .3      ...WILD CARD CHARACTER
      1710        CMP (FN),Y
      1720        BNE .11      ...NO MATCH
      1730        DEX
      1740        BMI .10       ...END OF KEY
      1750        DEY
      1760        BPL .6       ...STILL MORE TO COMPARE
      1770 *---END OF FILE NAME, MORE KEY---
      1780 .7     LDA KEY,X
      1790        CMP WILD.CARD
      1800        BNE .9       ...REST OF KEY NOT WILD, NO MATCH
      1810        DEX
      1820        BPL .7
      1830 *---VALID MATCH------------------
      1840 .8     SEC          SIGNAL MATCH
      1850        RTS
      1860 *---NOT A MATCH------------------
      1870 .9     CLC
      1880        RTS
      1890 *---END OF KEY-------------------
      1900 .10    DEY          MATCH IF END OF NAME
      1910        BMI .8       ...END OF NAME
      1920 *---IF AFTER WILDCARD, SLIP------
      1930 .11    LDX KEY.PNTR START KEY OVER AGAIN
      1940        CPX KEY.START
      1950        BEQ .9       ...NOT AFTER A WILDCARD
      1960        LDY BUF.PNTR SLIP TO LEFT IN BUFFER
      1970        DEY
      1980        BPL .5       TRY AGAIN
      1990        BMI .7       REST OF KEY BETTER BE WILD
      2000 *--------------------------------
      2010 WILD.CARD  .AS -/=/
      2020 *--------------------------------
      2030 KEY        .AS -/A=                            /
      2040 *--------------------------------
      2050 FNLIST     .AS -/A SIMPLE KEY                  /
      2060            .AS -/NOT SUCH A SIMPLE KEY         /
      2070            .AS -/NOT A SIMPLE KEY AT ALL       /
      2080            .AS -/A SIMPLE KEY AFTER ALL        /
      2090 NAME.CNT   .EQ *-FNLIST/30
      2100 *--------------------------------

Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $14 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).

All material herein is copyrighted by S-C SOFTWARE CORPORATION, all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)