Apple Assembly Line
Volume 7 -- Issue 7 April 1987

In This Issue...

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 "", 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
  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
  1230        .EM
  1240 *---------------------------------
  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 *--------------------------------
  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 *---------------------------------
  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
  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   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 *--------------------------------
  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 *--------------------------------
  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 *--------------------------------
  3300 MY.VTAB
  3310        CMP MON.BOTTOM    Stay inside current window
  3320        BCC .1   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 *--------------------------------
  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
  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 16
3094        LSR 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 ...

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:

       XCE             ENTER NATIVE MODE
       REP #$20        M=0, 16-BIT DATA
       PLA             PULL OFF 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.

  1010 *--------------------------------
  1030 *--------------------------------
  1040        .OP 65816
  1050 *--------------------------------
  1060 NUM    .EQ $06
  1070 PTR    .EQ $07,08
  1080 *--------------------------------
  1090 HOME   .EQ $FC58
  1110 COUT   .EQ $FDED
  1120 *--------------------------------
  1140        PER :1+1     When Subroutine is Nearby
  1150 :1     BRA ]1
  1160        .EM
  1170 *--------------------------------
  1190        PER :1+2     When Subroutine is Far Away
  1200 :1     BRL ]1
  1210        .EM
  1220 *--------------------------------
  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---------------
  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-------------------
  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 *--------------------------------
  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.

  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
  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
  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:  /
  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?

  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, 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
  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
  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:


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.

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