Volume 7 -- Issue 7 | April 1987

In This Issue...

- 80-Column Screen Display Subroutine
- Revision to DOS 3.3 for UniDisk 3.5
- A PER-fect Tool for 65816 Position-Independent Code
- Simple Line-Input Subroutine
- Friendly Decimal Print Subroutine
- Note from American Foundation for the Blind
- Some Handy MOVE Macros
- New Supplement to "Beneath Apple ProDOS"
- A Note about Chessmaster 2000

Bytestalker Software Analyzer Card

Old-fashioned computers always came with a front panel, loaded with lights and switches. You could usually stop a program, single-step it, and even set a breakpoint address to halt execution at a particular address. Personal computers don't include front panels, but now you can add part of one to your Apple. J. Stuart Enterprises, Box 310, Grass Valley, CA 95945, is offering the Bytestalker Software Analyzer Card. The card includes 16 switches for selecting a breakpoint address, a three-position mode switch (run, stop, trap), and a two position step-hold switch. There are also six single hex digit displays, to show you address and contents. You can choose single-stepping at the machine cycle level, or the instruction level. The price is only $88.50, plus postage and insurance. Contact J. Stuart at the above address or at phone (916)273-9188.

We're Gaining on it...

We've gained about a week in our efforts to get AAL back on schedule: it looks like the April issue will actually get to the printer during April! Next month we'll be shooting to get the May issue in the mail before the end of May, and soon you'll be receiving each month's issue during the appropriate month. Thanks for your patience.

80-Column Screen Display Subroutine | Bob Sander-Cederlof

In the very first issue of AAL, back in October 1980, I wrote an article about an interpretive screen display subroutine. A lot of things have happened since 1980! Although perhaps it is insignificant compared to the rise and fall of nations, one change which dramatically affects Apple programmers is that now almost everything is done on an 80-column screen. That old message printer was tied to the 40-column screen, so I think an upgrade is in order.

The following program has more features than the 1980 version, and is also easier to use. Another nice change since 1980 is that most of the assemblers available for the Apple now include a macro capability. Macros make it easier to use special coded sequences of bytes like those necessary in the message printer.

Where is the message?

There are many different ways to tell a message-printing subroutine where the message to be printed is located in memory. The method I used in my 1980 article was to assign a message number to each message, and pass that number in the A-register; the message printer used the message number as an index to a table of message addresses. Sometimes I have used the message number technique without a table of message addresses: I put all the messages together in one consecutive area of memory; the message printer searched through the messages counting end-of-message characters until it found the N-th message, and then started printing.

I show two other techniques in the program which follows. The first one involves loading the full address of the message to be printed into the A- and Y-registers. In my program I put the high-byte of the address in the Y-register, and the low-byte in the A-register. Of course you could use any registers any way you wish, but this is what I used. The code for MESSAGE.PRINTER is shown in lines 1250-1380. I defined a macro named "MSG" in lines 1190-1230 to make it easier to write the code to call the message printer. Calls to the message printer may be written simply as one-line macro calls, such as:

                   >MSG TITLE
                   >MSG COPYRIGHT

where TITLE and COPYRIGHT are the names of specific messages assembled elsewhere in a program.

The other technique I show here is probably the most popular one, if the programs I have disassembled are any indication. In this method the text of the message to be printed immediately follows the JSR to the message printer. The message printer uses the return address placed on the stack by the JSR to locate the message. After the message is printed, the return address is modified so that it points to the next instruction after the message before RTSing. ANOTHER.MESSAGE.PRINTER is shown in lines 1420-1640.

Both message printers begin by storing the address of the message into a page-zero pointer. Then a subroutine named GET.NEXT.CHAR in lines 1680-1750 is called to pick up characters of the message. Since the last instruction in this subroutine is the one which picks up the character, the Zero-status will be set if the $00 byte indicating end-of-message is picked up.

What is in the Message?

The real intelligence of my message printers is in the subroutine PUT.CHAR, starting at line 1760. We have already mentioned that a $00 byte indicates end-of-message. PUT.CHAR expects the X-register to contain a repeat count, and the A-register to contain the character to be printed. If the byte in the A-register has a value between $40 and $FF, it is sent to COUT to be printed (X) times. However, if the byte in the A-register has a value between $01 and $3F, it is interpreted in special way.

Values between $80 and $9F are control characters, and will be interpreted by the monitor firmware. If you are in an 80-column //e, //c, or //gs, the interpretation will be as shown in lines 1850-2070. In 40-column mode not all of the functions are active. A Viewmaster or other type of 80-column card will have its own set of controls.

Values between $01 and $18 are interpreted by PUT.CHAR as the first byte of a two-byte cursor position code. This first byte indicates the line number (1-24 decimal) to put the cursor on, and the following byte is the column number (0-79 decimal). I defined a macro "POS" in lines 2100-2130 to simplify putting position codes into messages.

Values between $19 and $3F are used to index a special function table. My table is shown in lines 2700-2840. Notice that I used a macro "FUN" to write each entry in the table. This macro defines the special function code as "F.name", and assembles the address-1 of the code which performs the function. I thought of seven handy functions, but you can probably think of a lot more. As you do, just add >FUN lines to the table and write subroutines to perform the functions. Use mine as models, and you should be successful.

The methods I used for setting up the cursor position and handling other special functions work with Apple's firmware in the 40- and 80-column modes; if you have a Viewmaster or other 80-column card, you will need to make some changes in PUT.CHAR and its subroutines.

I made macros for the all of the functions, shown in lines 2140-2460. You might also find it handy to define macros for some of the control-characters processed by the 80-column firmware, but I didn't do it here.

Even though I have the "POS" function for positioning the cursor on the screen, I still wanted the ability to do HTAB and VTAB independently. These are my first two functions. By writing ">HTAB xx" you can position to column xx on the current line. ">VTAB yy" will put the cursor on line yy without changing the column. Again, yy is a number between 1 and 24, and xx runs from 0 to 79 (decimal).

If you remember, normal characters are printed N times according to the value in the X-register when you call PUT.CHAR. The REP function lets you set up the repeat count. ">REP n,char" will cause char to be printed n times.

The CEN funtion is perhaps the most powerful one. It centers a string on the current line. You simply write something like

           >CEN "This string will be centered"

...and it will be. The code for this function is in lines 3020-3180. All characters which follow the CEN function code are counted, up to the next function code or control character. Then the position for the first character is computed by halving the difference between window width and string length, and an HTAB performed.

FIL and FCL will fill an entire line with a specified character. FIL lets you say which line, and FCL fills the current line. You could do the same thing by putting the cursor at the beginning of the line to be filled and then using a repeat count larger than the window width followed by the fill character, but this way is easier. Let the computer work for you!

WIN lets you specify a window on the screen. This is very rudimentary as window routines go, because all it does is load the four locations the monitor uses to define the window: left, width, top, and bottom. "Left" is the column number to start the window at, and is a value from 0 to 79. "Width" is the number of characters in a line, and has a value from 1 to 80. Just be sure that these two numbers do not add to a value greater than 80, unless you really want to clobber your program. "Top" is the line number of the top of the window, having a value from 0 to 23. "Bottom" is the line number of the bottom line of the window, plus 1; the value goes from 1 to 24. The full 80-column window would be set up with ">WIN 0,80,0,24".

How about some examples?

I wrote two sample programs. "T", in lines 3500-3540, calls MESSAGE.PRINTER to print the messages in lines 3560-3790. "TT", in lines 3950-4170, calls ANOTHER.MESSAGE.PRINTER to print embedded messages.

      1000        .LIST MOFF   Do not list macro expansions
      1010 *SAVE S-C.MSG.MACHINE
      1020 *---------------------------------
      1030 MON.LEFT   .EQ $20
      1040 MON.WIDTH  .EQ $21
      1050 MON.TOP    .EQ $22
      1060 MON.BOTTOM .EQ $23
      1070 MON.CH     .EQ $24
      1080 MON.CV     .EQ $25
      1090 *--------------------------------
      1100 MON.CH80   .EQ $57B
      1110 *--------------------------------
      1120 MON.VTAB   .EQ $FC22
      1130 MON.COUT   .EQ $FDED
      1140 *---------------------------------
      1150 MSG.PNTR   .EQ $00,01
      1160 *---------------------------------
      1170 *   Address of message in Y,A registers
      1180 *--------------------------------
      1190        .MA MSG   >MSG <message address>
      1200        LDA #]1
      1210        LDY /]1
      1220        JSR MESSAGE.PRINTER
      1230        .EM
      1240 *---------------------------------
      1250 MESSAGE.PRINTER
      1260        STA MSG.PNTR      Store message address
      1270        STY MSG.PNTR+1        into page-zero pointer
      1280        TXA               Save X-register
      1290        PHA
      1300        LDX #1            Clear repetition counter
      1310        JSR GET.CHAR      First char of message
      1320        BEQ .2            ...empty message!
      1330 .1     JSR PUT.CHAR      Process the character
      1340        JSR GET.NEXT.CHAR    get another one
      1350        BNE .1            ...not at end of message yet
      1360 .2     PLA               Restore X-register
      1370        TAX
      1380        RTS               Return to caller
      1390 *--------------------------------
      1400 *   Message follows JSR to message printer
      1410 *--------------------------------
      1420 ANOTHER.MESSAGE.PRINTER
      1430        PLA               Store message address (-1)
      1440        STA MSG.PNTR         into page-zero pointer
      1450        PLA
      1460        STA MSG.PNTR+1
      1470        TXA               Save X-register
      1480        PHA
      1490        TYA               Save Y-register
      1500        PHA
      1510        LDX #1            Clear repetition counter
      1520        BNE .2            ...Always
      1530 .1     JSR PUT.CHAR      Process a character
      1540 .2     JSR GET.NEXT.CHAR
      1550        BNE .1            ...not at end of message yet
      1560        PLA               Restore Y-register
      1570        TAY
      1580        PLA               Restore X-register
      1590        TAX
      1600        LDA MSG.PNTR+1    Get return address back on stack
      1610        PHA
      1620        LDA MSG.PNTR
      1630        PHA
      1640        RTS               Return to caller
      1650 *--------------------------------
      1660 *   Get a message character
      1670 *---------------------------------
      1680 GET.NEXT.CHAR
      1690        INC MSG.PNTR
      1700        BNE GET.CHAR
      1710        INC MSG.PNTR+1
      1720 GET.CHAR
      1730        LDY #0
      1740        LDA (MSG.PNTR),Y
      1750        RTS
      1760 *--------------------------------
      1770 *   Process a message character
      1780 *
      1790 *      $00     END OF MESSAGE
      1800 *      $YY XX  where is between $01 and $18, position
      1810 *                   the cursor as vtab YY, htab XX.
      1820 *      $19-3F   special functions
      1830 *      $40-FF   print as is
      1840 *
      1850 *   Characters between $80 and $9F are interpreted by
      1860 *   the Apple 80-column firmware as follows:
      1870 *
      1880 *       $87      bell
      1890 *       $88      backspace
      1900 *       $8A      move cursor down one line
      1910 *       $8B      clear from cursor to end of window
      1920 *       $8C      clear window and home cursor
      1930 *       $8D      carriage return
      1940 *       $8E      normal video
      1950 *       $8F      inverse video
      1960 *       $91      switch to 40-column
      1970 *       $92      switch to 80-column
      1980 *       $95      de-activate firmware
      1990 *       $96      scroll display down one line
      2000 *       $97      scroll display up one line
      2010 *       $98      disable Mouse Text
      2020 *       $99      home cursor without clearing window
      2030 *       $9A      clear current line
      2040 *       $9B      enable Mouse Text
      2050 *       $9C      move cursor forward one space
      2060 *       $9D      clear to end of line
      2070 *       $9F      move cursor up one line
      2080 *--------------------------------
      2090 *   Message Content Macros
      2100 *--------------------------------
      2110        .MA POS      Position cursor (vtab, htab)
      2120        .DA #]1,#]2
      2130        .EM
      2140 *--------------------------------
      2150        .MA REP      Repeat character N times
      2160        .DA #F.REP,#]1,#"]2
      2170        .EM
      2180 *--------------------------------
      2190        .MA CEN      Center a string on current line
      2200        .DA #F.CEN
      2210        .AS -"]1"
      2220        .EM
      2230 *--------------------------------
      2240        .MA STR      normal Ascii string
      2250        .AS -"]1"
      2260        .EM
      2270 *--------------------------------
      2280        .MA FIL      Fill specified line with character
      2290        .DA #F.FIL,#]1,#"]2
      2300        .EM
      2310 *--------------------------------
      2320        .MA FCL      Fill current line with character
      2330        .DA #F.FCL,#"]1
      2340        .EM
      2350 *--------------------------------
      2360        .MA WIN      Define a new text window
      2370        .DA #F.WIN,#]1,#]2,#]3,#]4
      2380        .EM
      2390 *--------------------------------
      2400        .MA HTAB     Htab the cursor
      2410        .DA #F.HTAB,#]1
      2420        .EM
      2430 *--------------------------------
      2440        .MA VTAB     Vtab the cursor
      2450        .DA #F.VTAB,#]1
      2460        .EM
      2470 *--------------------------------
      2480 PUT.CHAR
      2490        CMP #$40     IS IT PRINTABLE?
      2500        BCC .2       ...NO, SPECIAL FUNCTION
      2510 .1     JSR MON.COUT
      2520        DEX          Decrement repeat count
      2530        BNE .1       ...repeat the character
      2540        INX          Put the repeat count back to 1
      2550        RTS
      2560 *---Process function char---------
      2570 .2     CMP #$19     >POS VV,HH or special function?
      2580        BCC P.POS    ...>POS call
      2590 *---Branch to function code-------
      2600        ASL          Double function # to make index
      2610        TAY
      2620        CPY #FUN.TBL.SZ
      2630        BCS .3       ...Ignore illegal function
      2640        LDA FUN.TBL+1,Y
      2650        PHA          Put function code address-1 on stack
      2660        LDA FUN.TBL,Y
      2670        PHA
      2680 .3     RTS          Go to it!
      2690 *--------------------------------
      2700        .MA FUN
      2710 F.]1   .EQ *-FUN.TBL/2
      2720        .DA P.]1-1
      2730        .EM
      2740 *--------------------------------
      2750 FUN.TBL .EQ *-$19-$19
      2760  >FUN VTAB          Vtab YY
      2770  >FUN HTAB          Htab XX
      2780  >FUN REP           Repeat, count, character
      2790  >FUN CEN           Center, string, null
      2800  >FUN NUL           ...ends centered string
      2810  >FUN FIL           Fill, line #, char
      2820  >FUN FCL           Fill current line, char
      2830  >FUN WIN           Window, left, width, top, bottom
      2840 FUN.TBL.SZ .EQ *-FUN.TBL
      2850 *--------------------------------
      2860 P.POS  JSR MY.VTAB       Do vertical tab
      2870 P.HTAB JSR GET.NEXT.CHAR ...get horoz value
      2880        CMP MON.WIDTH     Stay inside current window
      2890        BCC .1            ...it is inside
      2900        LDA MON.WIDTH     ...use right edge
      2910        SBC #1
      2920 .1     STA MON.CH        40-column horiz pos'n
      2930        STA MON.CH80      80-column horiz pos'n
      2940        RTS
      2950 *--------------------------------
      2960 P.REP  JSR GET.NEXT.CHAR
      2970        TAX          REPETITION COUNT
      2980        RTS
      2990 *--------------------------------
      3000 *   Center a string on current line
      3010 *--------------------------------
      3020 P.CEN  LDY #0       Count # chars in string
      3030 .1     INY          from next char to char below $40
      3040        LDA (MSG.PNTR),Y        or btwn $80 and $9F
      3050        BMI .2       80-FF
      3060        CMP #$40
      3070        BCS .1       40-7F, keep counting
      3080 .2     CMP #$A0
      3090        BCS .1       A0-FF, keep counting
      3100        DEY          00-3F or 80-9F, end of string
      3110        TYA          (Line.width - string.len) / 2
      3120        EOR #$FF          gives starting horiz posn
      3130        SEC
      3140        ADC MON.WIDTH
      3150        LSR
      3160        STA MON.CH
      3170        STA MON.CH80
      3180 P.NUL  RTS
      3190 *--------------------------------
      3200 P.FIL  JSR GET.NEXT.CHAR
      3210        JSR MY.VTAB
      3220 P.FCL
      3230        LDA #0
      3240        STA MON.CH
      3250        STA MON.CH80
      3260        LDX MON.WIDTH
      3270        RTS
      3280 *--------------------------------
      3290 P.VTAB JSR GET.NEXT.CHAR
      3300 MY.VTAB
      3310        CMP MON.BOTTOM    Stay inside current window
      3320        BCC .1            ...it is inside
      3330        LDA MON.BOTTOM    Go to bottom line
      3340 .1     STA MON.CV        We use line # 1 to N, but
      3350        DEC MON.CV            monitor uses 0 to N-1
      3360        JMP MON.VTAB
      3370 *--------------------------------
      3380 P.WIN
      3390        JSR GET.NEXT.CHAR
      3400        STA MON.LEFT
      3410        JSR GET.NEXT.CHAR
      3420        STA MON.WIDTH
      3430        JSR GET.NEXT.CHAR
      3440        STA MON.TOP
      3450        JSR GET.NEXT.CHAR
      3460        STA MON.BOTTOM
      3470        LDA #$99
      3480        JMP MON.COUT
      3490 *--------------------------------
      3500 T      >MSG MSG0
      3510        >MSG MSG2
      3520        >MSG MSG1
      3530        >MSG MSG3
      3540        RTS
      3550 *---------------------------------
      3560 MSG0   .HS 8C       HOME SCREEN
      3570        >WIN 10,38,0,24
      3580        >FIL 1,-          FILL TOP LINE WITH DASHES
      3590        .HS FC            ONE BAR ON NEXT LINE
      3600        .HS 8D.88.FC.FC   2 BARS
      3610        .HS 8D.88.FC.FC   2 BARS
      3620        .HS 8D.88.FC.FC   2 BARS
      3630        .HS 8D.88.FC.FC   2 BARS
      3640        .HS 8D.88.FC.FC   2 BARS
      3650        .HS 8D.88.FC.FC   2 BARS
      3660        .HS 8D.88.FC      1 BAR AT END OF LINE
      3670        >FCL -            FULL CURRENT LINE WITH DASHES
      3680        >POS 3,0          VTAB 3, HTAB 0
      3690        .HS 8F            INVERSE MODE
      3700        >CEN "Demonstration of Message Printer"
      3710        .HS 8E.8D.8D      Normal mode
      3720        >CEN "S-C Software Corporation"
      3730        .HS 8D
      3740        >CEN "P. O. Box 280300"
      3750        .HS 8D
      3760        >CEN "Dallas, TX 75228"
      3770        .HS 8D.8D.8D
      3780        >WIN 0,80,0,24    Full screen again
      3790        .HS 00            End of message
      3800 *---------------------------------
      3810 MSG1   >POS 11,1         VTAB 11, HTAB 1
      3820        .HS 9D            CLR EOL
      3830        >STR "SELECT ONE:  "
      3840        .HS 00            End of message
      3850 *---------------------------------
      3860 MSG2   >POS 24,1         VTAB 24, HTAB 1
      3870        .HS 8F       INVERSE MODE
      3880        >STR " <SPACE> FOR MENU, <RETURN> FOR MORE "
      3890        .HS 8E.00    NORMAL MODE, EOM
      3900 *---------------------------------
      3910 MSG3   .HS 87.87.8D
      3920        >STR "Wasn't that a nice demonstration?"
      3930        .HS 8D.00
      3940 *--------------------------------
      3950 TT     JSR ANOTHER.MESSAGE.PRINTER
      3960        .HS 8D.8D
      3970        >CEN "This is another way to print messages."
      3980        >HTAB 0
      3990        >STR "<<<"
      4000        >HTAB 99     TAB TO END OF LINE
      4010        .HS 88.88    BACK UP TWO SPACES
      4020        >STR ">>>"
      4030        .HS 8D.00
      4040        JSR ANOTHER.MESSAGE.PRINTER
      4050        >REP 10,M-$40     10 CARRIAGE RETURNS
      4060        >WIN 0,1,14,24
      4070        >REP 10,|
      4080        >WIN 79,1,14,24
      4090        >REP 10,|
      4100        >WIN 0,80,13,24
      4110        >FCL _
      4120        >FIL 23,-
      4130        >WIN 0,80,0,24
      4140        >VTAB 24
      4150        .HS 00            End of message
      4160        RTS
      4170 *--------------------------------
      4180        .LIST OFF

Revision to DOS 3.3 for UniDisk 3.5 | Bill Morgan

Since we published my patches to allow DOS 3.3 to use the UniDisk 3.5 last May and June I've been using it every day, and just loving all that space. After basking in readers' praise for such a useful piece of code, you can imagine how I felt when I first went to boot that disk on the IIgs and watched it crash into the dirt!

After a little detective work the cause is found: When we boot from the //e Protocol Converter Interface it passes slot*16 in the A- and X-registers and the slot number in location $58. In my boot code in FORMAT.UNIDISK I was using the A-register and the contents of $58 to set up my RWTS. Come to find out, the IIgs only gives us the slot*16 in the X-register, so we have to do a little more manipulation to create the values we need. Here is the revised code:

    3080 BOOT   TXA
    3082        PHA         save slot*16
    3090        LSR         .divide
    3092        LSR         ..by 16
    3094        LSR         ...to get
    3096        LSR         ....slot

To update the boot image on an existing disk you can use the instructions at the top of page 23 of the July '86 issue. Be sure to note the caution in the last paragraph of page 21 in that issue, about not installing a DOS image that already contains RAMdisk patches: I tripped over that again myself!

A PER-fect Tool for 65816 Position-Independent Code | Sandy Mossberg

Digging into the 65816 Apple IIgs has provided more entertain- ment than I've had since the introduction of the Apple II. So far, my programming efforts have been in the ProDOS 8 environment. When Apple gets its act together on ProDOS 16, writing position-independent code will become critical. Why not do a bit of preparing now? (The following techniques will apply to older Apples which have been upgraded with a 65802 cpu chip, also.)

Absolute internal references are the bugaboo of the position- independent programmer. (By the way, I am not referring to those individuals who program as well standing on their heads as they do flat on their backs or seated in a chair; I am referring to programs which run correctly no matter where they are loaded into memory, without any modifications to reflect the position in memory.) The three most common absolute internal references are:

1. the JMP to a program location;
2. the JSR to a subroutine location; and
3. the reading from or writing to program text or data tables or variables.

The first problem can be solved simply by using the BRL instruction instead of JMP. For avoiding the second and third traps, the PER opcode is an ideal tool. The solutions outlined below pertain only to code and data within one 64K bank of memory. Solutions which handle programs and data in multiple banks will have to wait for future articles.

PER is one of the new opcodes found in the 65802 and 65816. PER stands for "Push Effective program counter (PC) Relative data word onto stack", and employs a hybrid stack/PC-relative addressing mode. PER adds the 16-bit signed displacement in its two-byte operand to the current value for the PC and pushes the result onto the stack. As usual, the high byte is pushed first, then the low byte. Regardless of the values for the m-, x-, and e-status bits, two bytes are always pushed onto the stack. Because the operand is a displacement relative to the PC, the top two bytes of the stack always will point to the designated program code or data, irrespective of whether it is internal or external to the program. Thus, an internal JSR can be simulated by the following sequence:

                   PER CONTINUE-1
                   BRA SUBROUTINE
          CONTINUE ...
                   ...
                   ...
        SUBROUTINE ...
                   ...
                   RTS

Note that the continuation address to be placed on the stack must be one less than the desired address (CONTINUE-1 above), because the RTS opcode increments the address it finds on the stack before jumping to that address. Also note that the BRA opcode works well if the subroutine is nearby (within about + or - 128 bytes of the BRA itself); if the subroutine is further away, you will need to use the BRL opcode here.

I have constructed two macros in the listing of the program which follows. >BSR calls nearby subroutines, and >BSL calls both nearby and distant subroutines. The macro definitions are in lines 1130-1210, and I have shown them in use in lines 1320-1330. The listed program itself is rather trivial, designed just to illustrate my contrived solutions. Lines 1270-1340 clear the screen and then print out the hex values from $00 to $F0, with an interval of $10. The two loops in lines 1350-1560 show two different ways to access data which is part of a program and still be position-independent; their function is to print out a string.

To access internal data, PER again comes to the rescue by allowing us to establish an external (zero page or stack) pointer to the data. Here, the operand must represent the exact address of the target data. The first example stores the pointer generated by PER into a pointer-variable in page zero (lines 1360-1400). PER puts the pointer onto the stack, and two PLAs pull it back off. If we were in Native mode with the m-bit cleared for 16-bit operation, only one PLA and STA would be needed. We could replace lines 1370-1400 with:

           CLC
           XCE             ENTER NATIVE MODE
           REP #$20        M=0, 16-BIT DATA
           PLA             PULL OFF BOTH BYTES
           STA PTR         STORE BOTH BYTES
           XCE             BACK TO EMULATION MODE

The second approach is to leave the REP-generated pointer on the stack. Line 1500 shows how we can use it with stack- relative indirect-indexed address mode to access the same text string. Lines 1550 and 1560 are then absolutely necessary, to remove the pointer from the stack when we are finished with it.

Though it is not appropriate for this sample program, the power of native mode is evident when working with 16-bit address pointers. The problem of using native mode in simple examples is that you cannot call the old standard monitor entry points (such as HOME, COUT, and PRBYTE) from native mode.

If you are going to write code which will be crossing 64K banks, you can still use the PER instruction. However, you also have to take into account the program bank and data bank registers. More on this at another time.

      1000 *SAVE S.POSITION
      1010 *--------------------------------
      1020 *   POSITION INDEPENDENT CODE WITH THE 65802/16
      1030 *--------------------------------
      1040        .OP 65816
      1050 *--------------------------------
      1060 NUM    .EQ $06
      1070 PTR    .EQ $07,08
      1080 *--------------------------------
      1090 HOME   .EQ $FC58
      1100 PRBYTE .EQ $FDDA
      1110 COUT   .EQ $FDED
      1120 *--------------------------------
      1130        .MA BSR      BRANCH RELATIVE TO SUBROUTINE
      1140        PER :1+1     When Subroutine is Nearby
      1150 :1     BRA ]1
      1160        .EM
      1170 *--------------------------------
      1180        .MA BSL      BRANCH RELATIVE TO SUBROUTINE
      1190        PER :1+2     When Subroutine is Far Away
      1200 :1     BRL ]1
      1210        .EM
      1220 *--------------------------------
      1230 TRIVIAL.EXAMPLE
      1240        SEC          SET EMULATION MODE
      1250        XCE          IN CASE CALLED FROM NATIVE MODE
      1260        PHP          SAVE E-BIT
      1270 *---Show Position-Independent subroutine calls--------
      1280        JSR HOME     CLEAR SCREEN
      1290        LDA #0       FOR NUM = 0 TO $F0 STEP $10
      1300 .1     STA NUM
      1310        JSR PRBYTE   Print in hexadecimal
      1320        >BSL PRBLNK  Print two blanks
      1330        >BSR ADD.10  ...NEXT NUM
      1340        BCC .1       ...NOT FINISHED YET
      1350 *---Show Page-Zero Indirect data access---------------
      1360        PER TEXT     PUT POINTER TO TEXT ON STACK
      1370        PLA          Now put pointer into page zero
      1380        STA PTR
      1390        PLA
      1400        STA PTR+1
      1410        LDY #0       Point to beginning of text string
      1420 .2     LDA (PTR),Y  Get next character of string
      1430        BEQ .3       End of string
      1440        JSR COUT
      1450        INY
      1460        BNE .2       ...ALWAYS
      1470 *---Show Stack-Indirect data access-------------------
      1480 .3     PER TEXT     POINTER TO TEXT ON STACK
      1490        LDY #0       Point to beginning of text string
      1500 .4     LDA (1,S),Y  Get next character of string
      1510        BEQ .5       End of string
      1520        JSR COUT
      1530        INY
      1540        BNE .4       ...ALWAYS
      1550 .5     PLA          POP OFF TEXT POINTER
      1560        PLA
      1570 *---Return to caller-------------
      1580        PLP          GET SAVED E-BIT
      1590        XCE          RESTORE E
      1600        RTS
      1610 *--------------------------------
      1620 ADD.10 CLC
      1630        LDA NUM      ADD $10 TO NUM
      1640        ADC #$10     SET CARRY IF WRAPS AROUND
      1650        RTS
      1660 *--------------------------------
      1670        .BS 256      FILLER TO MOVE PRBLNK FURTHER AWAY
      1680 *--------------------------------
      1690 PRBLNK LDA #" "
      1700        JSR COUT
      1710        JMP COUT
      1720 *--------------------------------
      1730 TEXT   .HS 8D
      1740        .AS -/Position-Independent Text String/
      1750        .HS 00
      1760 *--------------------------------

Simple Line-Input Subroutine | Bob Sander-Cederlof

The other day I needed a simple subroutine to read a line of text into an assembly language program. The routine had to be short, work in both 40- and 80-columns, and allow very simple editing. It had to read directly from the keyboard, regardless of where the input vector at KSWL ($38,39) might be pointing.

The following listing shows the result. READ.A.LINE is indeed short and fulfills all the needs I had. Backspace is the only editing character. When you type a printing character, it is displayed and the underline cursor advances. When you type a backspace, the cursor backs up and replaces what was previously at that position.

When you type a RETURN, the routine returns with the X-register pointing into the line buffer at the position where the RETURN is stored. If you type an ESCAPE, it nulls the line you just typed. The line is still in the buffer, but READ.A.LINE returns with the X-register equal to zero.

Inside READ.A.LINE I use the X-register to track position in the line buffer. I don't explicitly keep track of position on the screen, at least in absolute terms. Wherever the cursor is when you call READ.A.LINE is the place where the first cursor will be displayed. Since I do all moving around on the screen by simply printing out characters or backspacing, all screen positions are relative to the beginning position. You can use READ.A.LINE inside a small window if you like, and the input line will wrap nicely when it comes to the edge of the window.

Lines 1100-1120 handle the cursor by first printing an underline, and backspacing over it. Then when you type a character I will put it over the top of the cursor, and come back to line 1100 to display the next cursor.

Lines 1130-1150 are the standard way to read the keyboard when you don't want any firmware interference. (The Apple IIgs has more hardware bells and whistles here, with the Front Desk Processor and all, but this method will still work.)

When you type a backspace, I have to output a space to erase the cursor, then backspace twice and place a new cursor over the top of the character which was right before the old cursor. This is in lines 1350-1380. If the cursor is already at the beginning of the line, lines 1330-1340 will simply start over. Because of the way lines 1330 and 1340 are written, the maximum length line you can type and still use backspace is 128 characters.

I wrote a quick and dirty test program, to illustrate how to use READ.A.LINE. Lines 1500-1560 print out a prompting message. Line 1580 calls READ.A.LINE. The cursor will start right after the prompting message. Line 1600 tests for an ESCAPE, signifying a null entry. If you type RETURN, lines 1610-1670 will display the message you just entered.

      1000 *SAVE S.READLINE
      1010 *--------------------------------
      1020 KEYBOARD   .EQ $C000
      1030 STROBE     .EQ $C010
      1040 *--------------------------------
      1050 CROUT      .EQ $FD8E
      1060 COUT       .EQ $FDED
      1070 *--------------------------------
      1080 READ.A.LINE
      1090 .0     LDX #0       Start at beginning of line
      1100 .1     LDA #"_"     Use underline for a cursor
      1110        JSR COUT     Print the Underline
      1120        JSR BSOUT    ...and back up over it
      1130 .2     LDA KEYBOARD
      1140        BPL .2       ...wait until key pressed
      1150        STA STROBE
      1160        STA WBUF,X
      1170        CMP #$A0
      1180        BCC .4       CONTROL CHAR
      1190        CPX #MAXLEN
      1200        BCS .2
      1210        JSR COUT  SHOW IT ON SCREEN
      1220        INX
      1230        BNE .1       ...ALWAYS
      1240 .4     CMP #$88
      1250        BEQ .5       BACKSPACE
      1260        CMP #$9B
      1270        BEQ .6       ESCAPE -- ABORT INPUT
      1280        CMP #$8D
      1290        BNE .2       ...NOT <RETURN>, IGNORE IT
      1300        JSR SPCOUT   ERASE CURSOR FROM SCREEN
      1310        INX          INCLUDE <RETURN> IN X-VALUE
      1320        RTS          RETURN TO CALLER
      1330 .5     DEX
      1340        BMI .0       ...ALREADY AT BEGINNING OF LINE
      1350        JSR SPCOUT     ...SPACE
      1360        JSR BSOUT      ...BACKSPACE
      1370        JSR BSOUT      ...AND ANOTHER
      1380        JMP .1
      1390 .6     LDX #0       SIGNAL ESCAPE TYPED
      1400        RTS
      1410 *--------------------------------
      1420 SPCOUT LDA #" "     SPACE
      1430        .HS 2C
      1440 BSOUT  LDA #$88     BACKSPACE
      1450        JMP COUT
      1460 *--------------------------------
      1470 MAXLEN     .EQ 127
      1480 WBUF       .BS MAXLEN+1
      1490 *--------------------------------
      1500 TEST
      1510        LDX #0       PRINT OUT A PROMPT MESSAGE
      1520 .0     LDA PROMPT,X
      1530        JSR COUT
      1540        INX
      1550        CPX #PROMPT.LEN
      1560        BCC .0
      1570 *--------------------------------
      1580        JSR READ.A.LINE
      1590 *--------------------------------
      1600        BEQ .2       ESCAPE ABORTED IT
      1610        JSR CROUT    <RETURN> OUTPUT
      1620        LDX #0       PRINT OUT THE LINE YOU TYPED IN
      1630 .1     LDA WBUF,X
      1640        JSR COUT
      1650        INX
      1660        CMP #$8D     UP TO A <RETURN>
      1670        BNE .1
      1680 .2     RTS
      1690 *--------------------------------
      1700 PROMPT .AS -/Enter a line:  /
      1710 PROMPT.LEN .EQ *-PROMPT
      1720 *--------------------------------

Friendly Decimal Print Subroutine | Jan Eugenides

Several years ago Bob S-C published a little subroutine to convert two-byte hexadecimal values to decimal and print them. I have written a similar subroutine, but mine allows you to print any character as a leading character when the number has less than five digits, or to have no leading characters at all. Bob's subroutine always printed leading zeroes.

To use my subroutine, you put the number in the A- and X-registers (lo-byte in A, hi-byte in X), and the fill character in the Y-register. If you do not want any fill character, call my subroutine with Y=0. To make calling it easier, Bob S-C constructed a macro named PDEC, defined in lines 1490-1580 below. To print a number with no fill character, simply use the macro like this: >PDEC VALUE; to print with a fill character, use the macro with the fill character as the second parameter like this: >PDEC VALUE,"#".

Now let's look at my subroutine. Lines 1110-1120 store the high byte of the number, and the fill character. I waited to store the low-byte of the number in line 1160, inside the conversion loop, to save a few bytes and cycles.

Lines 1150-1380 comprise a loop controlled by X, counting down from 3 to 0. The loop prints out the first four digits and/or fill characters. X indexes into a power-of-tens table. I first subtract 10000 as many times as possible, counting each subtraction in the Y-register. Since Y starts out as ASCII 0, it increments through successive ASCII digits. When the remainder is finally less than 10000, I print the first digit. Then the process is repeated for 1000, 100, and 10.

Leading-zeroes may be either suppressed, or printed with your choice of fill character. The work is done in lines 1280-1340. I keep a flag in bit 7 of FLAG. This bit starts out 0 (see line 1140). As soon as I get a non-zero digit, I change the flag bit to 1 (see lines 1280-1300). If the flag bit is 1, lines 1310-1320 will decide to print the digit regardless of its value. (We don't want to omit or replace ALL zeroes, only LEADING zeroes.) Lines 1330-1340 will decide whther to print a fill character or nothing at all.

After printing the ten's digit, the remainder in NUM is the units digit in binary. All I have to do is add an ASCII "0" to change this remainder to ASCII, which I do in lines 1390-1400. By handling the unit's digit in this special way I save extra logic which would have been required to make the value $0000 print at least one zero and two bytes in my power-of-tens table, saving a total of six bytes. The cost is lines 1390 and 1400 versus a simple RTS, or four bytes. This method is also faster, because I don't have to cycle though the loop counting down the unit's digit.

For an exercise, you might try extending my subroutine to convert 3- or 4-byte values. It is really not too difficult. Of course, you would no longer be able to pass the value to be converted in the A- and X-registers. For 3-byte values, the power-of-ten tables would have to be extended in both dimensions:

           TENTBL .DA #10,#100,#1000,#10000
                  .DA #100000,#1000000,#10000000
           TENTBM .DA /10,/100,/1000,/10000
                  .DA /100000,/1000000,/10000000
           TENTBH .DA #0,#0,#0,#0
                  .DA /100000/256,/1000000/256,/10000000/256

You would also have to extend the comparison and subtraction code inside the loop. Why not try it?

      1000 *SAVE S.PRINT.DECIMAL
      1010 *--------------------------------
      1020 *   By Jan Eugenides and Bob S-C
      1030 *
      1040 *      Call with A, X, and Y as follows:
      1050 *          (A) = low-byte of number to be printed
      1060 *          (X) = high byte of number
      1070 *          (Y) = fill character (or 00 if no fill)
      1080 *--------------------------------
      1090 COUT   .EQ $FDED
      1100 *--------------------------------
      1110 PRDEC  STX NUM+1    Store high byte of number
      1120        STY FILL     Store fill character
      1130        LDX #3       FOR X = 3 TO 0
      1140        STX FLAG     Clear bit 7 in leading-zero flag
      1150 .1     LDY #"0"     Start with digit = ASCII 0
      1160 .2     STA NUM      Compare number to power of ten
      1170        CMP TENTBL,X      10^(X+1) ... lo-byte
      1180        LDA NUM+1
      1190        SBC TENTBH,X      10^(X+1) ... hi-byte
      1200        BCC .3       Remainder is smaller than 10^(X+1)
      1210        STA NUM+1    Store remainder hi-byte
      1220        LDA NUM      Get remainder lo-byte
      1230        SBC TENTBL,X
      1240        INY          Increment ASCII digit
      1250        BNE .2       ...always
      1260 *---Print a digit----------------
      1270 .3     TYA          digit in ASCII
      1280        CMP #"0"     Is it a zero?
      1290        BEQ .4       ...yes, might be leading zero
      1300        STA FLAG     ...no, so clear leading-zero flag
      1310 .4     BIT FLAG     If this is leading-zero, will be +
      1320        BMI .5       ...not a leading zero
      1330        LDA FILL     ...leading zero, so use fill-char
      1340        BEQ .6       ...Oops, no fill-char
      1350 .5     JSR COUT     Print the digit or fill-char
      1360 .6     LDA NUM      Get lo-byte of remainder
      1370        DEX          Next X
      1380        BPL .1       Go get next digit
      1390        ORA #"0"     Change remainder to ASCII
      1400        JMP COUT     Print Unit's digit & RTS
      1410 *--------------------------------
      1420 TENTBL .DA #10,#100,#1000,#10000
      1430 TENTBH .DA /10,/100,/1000,/10000
      1440 *--------------------------------
      1450 FLAG   .BS 1
      1460 FILL   .BS 1
      1470 NUM    .BS 2
      1480 *--------------------------------
      1490        .ma pdec
      1500        lda ]1
      1510        ldx ]1+1
      1520        .do ]#>1
      1530        ldy #"]2"
      1540        .else
      1550        ldy #0
      1560        .fin
      1570        jsr prdec
      1580        .em
      1590 *--------------------------------
      1600 CROUT  .EQ $FD8E
      1610
      1620 T      >PDEC VAL1,#
      1630        JSR CROUT
      1640        >PDEC VAL2,^
      1650        JSR CROUT
      1660        >PDEC VAL3
      1670        JSR CROUT
      1680        >PDEC VAL4
      1690        JSR CROUT
      1700        >PDEC VAL5," "
      1710        JSR CROUT
      1720        RTS
      1730
      1740 VAL1   .DA 1234
      1750 VAL2   .DA 79
      1760 VAL3   .DA 65535
      1770 VAL4   .DA 6987
      1780 VAL5   .DA 23
      1790        .LIST OFF

Note from American Foundation for the Blind | Bob Sander-Cederlof

The American Foundation for the Blind is managing a database of individuals with hands-on experience with computers, low vision aids, talking products, and other devices for the blind and visually-impaired. About 500 users and evaluators are currently listed in their database, and are available to help in training, purchase decisions, and evaluation of new products. If you have related experience are interested in helping, or could use such help yourself, you can contact their National Technology Center at 1-800-232-5463.

Some Handy MOVE Macros | Bob Sander-Cederlof

Some time ago Bruce Love, a mathematics teacher at Hillcrest High School in New Zealand, sent me a macro he has found quite handy. It is shown in lines 1010-1090 in the listing below. It can be used to generate in-line loops to copy a group of bytes from one place to another. The macro works with any group up to 256 bytes long. It is really quite flexible, as the several examples in lines 1460-1550 reveal. Note that the source and destination may be either absolute or indirect in form. Furthermore, the length may either be stored in a variable or in the immediate form.

I added the SET macro (lines 1170-1230) to set up a pointer, to simplify using the MOVE macro with pointers in page zero. An example of its use is in lines 1490 and 1500.

There is another macro that I have found useful for moving larger chunks of RAM around. I use it especially in ProDOS SYSTEM files for relocating program sections. See lines 1100-1160 for the definition of the COPY macro, which moves up to 256 pages from source to destination. The parameters for COPY are reminiscent of the monitor M-command. For example, to copy $4B00 through $5FFF to a destination beginning at $6A00 the monitor command would be "6A00<4B00.5FFFM". The equivalent COPY macro would be "COPY $6A00,$4B00,$5FFF".

With both of these macros, beware of overlapping the range of the source and destination just as you would with the monitor M-command. Unless, of course, you know what you are doing and are doing it on purpose. For example, you can copy the first character of a block to all positions in the block like this:

           >MOVE BUFFER,BUFFER+1,LEN

As is usually the case with macros which generate code, the code will probably not be as efficient as you could write given more knowledge of the overall situation. However, macros like these can shorten the time to develop programs, and thus they are worthy in spite of their possible inefficiencies.

      1000 *SAVE MOVE.MACROS
      1010 *--------------------------------
      1020        .MA MOVE source,destination,length
      1030        LDY #0
      1040 :1     LDA ]1,Y     GET SOURCE BYTE
      1050        STA ]2,Y     STORE DEST. BYTE
      1060        INY          NEXT BYTE
      1070        CPY ]3
      1080        BCC :1       ...IF MORE
      1090        .EM
      1100 *--------------------------------
      1110        .MA COPY destination,src.start,src.end
      1120        LDA /]1      DESTINATION PAGE
      1130        LDY /]2      SOURCE BEGINNING PAGE
      1140        LDX /]3-]2+256      # OF PAGES
      1150        JSR COPY.PAGES
      1160        .EM
      1170 *--------------------------------
      1180        .MA SET pointer,value
      1190        LDA #]2
      1200        STA ]1
      1210        LDA /]2
      1220        STA ]1+1
      1230        .EM
      1240 *--------------------------------
      1250 SPTR   .EQ $00,01
      1260 DPTR   .EQ $02,03
      1270 LENGTH .EQ $04
      1280 BUFFER .EQ $200
      1290 *--------------------------------
      1300 COPY.PAGES
      1310        STA DPTR+1
      1320        STY SPTR+1
      1330        LDY #0
      1340        STY DPTR
      1350        STY SPTR
      1360 .1     LDA (SPTR),Y
      1370        STA (DPTR),Y
      1380        INY          NEXT BYTE
      1390        BNE .1       ...KEEP GOING
      1400        INC SPTR+1   ...END OF PAGE
      1410        INC DPTR+1
      1420        DEX          NEXT PAGE
      1430        BNE .1       ...KEEP GOING
      1440        RTS          ...FINISHED
      1450 *--------------------------------
      1460 SAMPLE.MOVES
      1470        >MOVE $500,$600,#40
      1480 *--------------------------------
      1490        >SET SPTR,BUFFER+10
      1500        >SET DPTR,BUFFER+40
      1510        LDA #9
      1520        STA LENGTH
      1530        >MOVE (SPTR),(DPTR),LENGTH
      1540 *--------------------------------
      1550        >MOVE BUFFER,(DPTR),#10
      1560 *--------------------------------
      1570 SAMPLE.COPIES
      1580        >COPY $6A00,$4B00,$5FFF
      1590        >COPY $5000,$2200,$4AFF
      1600        >COPY $1000,BUFFER,BUFFER+255
      1610        RTS
      1620 *--------------------------------

New Supplement to "Beneath Apple ProDOS" Available | Bob Sander-Cederlof

Quality Software has published a new supplement to for "Beneath Apple ProDOS," which includes information on ProDOS versions 1.2 and 1.3. It is 30 pages longer than the previous edition, which covered version 1.1.1 of ProDOS.

You might be wondering, "What is a supplement, anyway?" The book "Beneath Apple ProDOS" ("BAP") contains much reference material needed to really take advantage of ProDOS capabilities. While other books now cover much of the same ground, "BAP" was the first one to put it all into print. The supplement, however, is unique: it contains a complete description of the internal details of both the ProDOS MLI kernel and BASIC.SYSTEM. If you are at all involved with the inner works of ProDOS, or are having trouble finding out the REAL scoop on some issues, you NEED the supplement. I have used my copies extensively, and depended heavily on it when writing the ProDOS version of S-C Macro Assembler.

The original supplement, for versions 1.0.1 and 1.0.2 cost $10. The second and third editions are $12.50 each. Incredibly low-priced! You must order these directly from Quality Software, at 21610 Lassen Street #7, Chatsworth, CA 91311. As I understand it, the supplement is only sold to owners of "BAP", and you have to use the coupon found on page 8-9 of "BAP" to do the ordering. ("BAP" itself is $19.95 retail, but we sell it for $18 here.)

A Note about Chessmaster 2000 | Bob Sander-Cederlof

Gina Montagano, of the Software Toolworks, recently sent me "Chessmaster 2000". If you have not heard of it, Chessmaster is a really good chess-playing program which will run on the Apple II family. It is also available for Mac, IBM, and others. I bought Microchess back in 1977, and Sargon in 1978; they did well, considering they ran in such small machines. Microchess would fit in a 4K Apple! We have come a long way since then. Chessmaster has many nice features, is the current reigning "champion", and is well worth the $49.95 price. (My 11-year-old stepson Matthew figured out a neat way to beat it. He let it tromp all over him, all the way to checkmate. Then he stepped back one move, swapped sides, and won!) Some features: display board in color or monochrome, 2-D or 3-D; 20 levels of competence, plus one that purposely makes occasional obviously bad moves; optional hints for your best move; huge opening book; library of famous games; and on and on. Negative: copy protected, although you may order a backup copy for $5. Chessmaster 2000 is available at most software stores.

Apple Assembly Line (ISSN 0889-4302) 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.)