Apple Assembly Line
Volume 8 -- Issue 5February 1988

In This Issue...

Another Way to Assemble into SYS Files

In the August 1987 issue of AAL I told how to patch the ProDOS version of S-C Macro Assembler so that target files (.TF directive) could be type SYS rather than BIN. Unfortunately the patches only work if the file is not already on the disk. In order to avoid an error message you can simply delete the target file before each assembly, and the easiest way to do this is by making an EXEC file. For example, I was working on a large program. SC.ACF is the file with a lot of include (.IN) directives. SCWP.SYSTEM is the target file. With the following three lines of text in an EXEC file named ASM, I can do an assembly by merely typing "-ASM":

   DELETE SCWP.SYSTEM
   LOAD SC.ACF
   ASM

Unless the assembler is patched, SCWP.SYSTEM will be a type BIN file after assembly. With the following three lines in an EXEC file named SYS, I can change the filetype of SCWP.SYSTEM from BIN to SYS by typing "-SYS":

   VERIFY SCWP.SYSTEM
   $800:A9 07 8D B4 BE A9 FF 8D B8 BE 20 00 BF C3 B4 BE 4C DA FD
   MGO$800

The VERIFY command reads the file info into a buffer at $BEB4. The code being poked into $800 changes the buffer and does an MLI call to SET FILE INFO. It then prints out the error code returned, which we hope is 00. If not 00, there was an error.


Peeking Inside AppleWorks 1.3, Part 3:
Keyboard Input Subroutines
Bob Sander-Cederlof

In this, the third of the series delving into the AppleWorks code, we will look into the low-level keyboard input code and a few associated routines. It is probably not extremely exciting, but it is fundamental, and just the right amount of code for this issue of AAL.

This time I decided to give an exact disassembly, rather than rewriting routines to my own liking as I worked through them. Nevertheless, I could not resist showing you my revisions of two subroutines, as you will see below. Until now, I have been working from a "raw" disassembly listing: that is, one produced with the Apple monitor "L" command, and my pencil. After figuring out what I wanted to reproduce, I sat down to the keyboard and typed in an equivalent program. This time I used the S-C DisAssembler to produce a set of source files, and then worked them over with the editing facilities of the S-C Macro Assembler. The result will re-assemble to an exact copy of APLWORKS.SYSTEM (version 1.3). Finally, I lifted out the subroutines I wanted to discuss this month and put them together in one source file. The result is on the following pages.

The S-C DisAssembler made my work a lot easier, but it did not do everything. It does work from a script which can detail which address ranges are code, and which are data. The version I was using also could differentiate between hex strings, ASCII strings, and address tables. Furthermore, SCDA lets me give label names in advance when I know what I want to call them. However, SCDA does not handle all the parameters following JSR XXXX lines which are part of the AppleWorks code. It does handle JSR $BF00, which is the ProDOS MLI call, but not the multitude of AppleWorks calls which I discussed in the December 1987 issue of AAL. SCDA also does not automatically generate local labels within subroutines. All labels not already given names are generated as "I.xxxx", where xxxx is the hexadecimal address. I have gone through the source code and replaced these with either meaningful names or local labels. I did leave one "I." label, because I did not know what to call it, in line 1490. SCDA also generates "X." labels for variables or subroutines outside the range being disassembled, and "Z." labels for page-zero references. I have left some of these in that form, pending more information about what they ought to be called.

If you look at the listing that follows, you may notice some strange comment lines that are filled with hexadecimal numbers. For example, see line 1640:

     1640 * (1D35) 1066 1192 137F 193F 19C5 1BFD

These lines were produced by the S-C DisAssembler, and are embedded cross-reference lines. The number in parentheses is the address of the label which follows on the next line, in this case "AW.KEYIN". The list of numbers after the parentheses are addresses of instructions which refer to AW.KEYIN. In this program, they just happen to all be JSR or JMP instructions. The disassembler produces lines like this for every label, and I left in the important ones.

Another thing you will notice in the listing is the heavy use of .PH and .EP directives. Each subroutine is surrounded by a pair of these. I wanted the listing here to show the same addresses as I found inside AppleWorks, and the .PH directive lets me do so. The object code that is stored in RAM during assembly of this source code will not be useful, because it will not be located in the addresses shown; rather, it will be all packed together starting at $0800, the default object code address. But I do not intend to use the code in this form, just display it. If you want to use one or more of these subroutines, include them in your own code WITHOUT the .PH and .EP directives.

In order to assemble these subroutines without the rest of the body of APLWORKS.SYSTEM, I had to define some subroutines not included here. Lines 1450-1470 show these names. DISPLAY.STRING and BASE.CALC.A were included in the January 1988 issue of AAL. I intend to include PRINTER.DRIVER in a future issue.

The main subroutine I want to talk about this issue begins at line 1510, and I have called it AW.KEYIN. As line 1640 shows, this is called from a lot of places; its main purpose is to get the next character from the keyboard. It also does a lot of other stuff, as we will see.

I broke the KEYIN subroutine into four parts. The first part is lines 1640-1720, and is the only entry point used by AppleWorks. The second part (lines 1730-2570) handles the actual character input, getting a character either from the type-ahead buffer or from the keyboard. The third part (lines 2580-3560) checks for certain special characters and acts on them. The last part (lines 3570-3630) stores the character in $84 and returns.

Looking at the first section first, notice lines 1660-1680. For reasons I have not yet determined, a flag is kept in pagezero location $A4 which can cause all keyboard input to be bypassed. You may remember, this same location also controls screen display (see the Jan 88 article). In both cases, if $A4 is non-zero, the operation is bypassed. You will get no display on the screen, and no keystrokes will be waited for. KEYIN will tell the world you typed an ESCAPE key, no matter what you may have really done. I don't know why all this is here yet.

Assuming $A4 is zero, Lines 1690-1720 will save the current cursor position. Apparently the cursor position may be changed in some circumstances within the KEYIN routine, and so we save them for later restoration.

The second part of KEYIN gets the next character. The "next character" may have already been typed a while ago, and tucked away in the type-ahead buffer. If so, KEYIN will get it from there. If not, it will wait until you type one.

You will recall that I talked about the POLL.KEYBOARD subroutine and the type-ahead buffer in the December 1987 issue. Lines 3970-4300 of this month's code show two more subroutines associated with the type-ahead buffer. CLEAR.KEYBUF will empty the type-ahead buffer, and CHECK.KEYBUF will test whether there are any characters in the buffer or not. For some reason there does not appear to be any subroutine to get a character out of the type-ahead buffer; instead, that code is included in-line wherever it is needed. This is not very efficient, but it "is what is". Lines 1760-1850 are one example of this. If it were up to me, I think I would have written one subroutine which returned Carry Clear if there were no characters in the buffer, or Carry Set if there was a least one character. In the latter case, I would also return the next character from the buffer in the A-register. Something like this:

  GET.CHAR.FROM.KEYBUF.IF.ANY
         LDA KEYBUF.OUT
         CMP KEYBUF.IN
         BEQ .2          ...buffer is empty
         TAX             Use the "out" index
         LDA KEYBUF,X    Get character from buffer
         INX             Advance buffer index
         CPX #10         Compare to buffer size
         BCC .1          ...not off the end yet
         LDX #0          Wrap-around to beginning
  .1     STX KEYBUF.OUT  New "out" index
         SEC             Indicate we got a character
         RTS
  .2     CLC             Indicate buffer was empty
         RTS

If that subroutine existed, I could replace lines 1760-1850 with

       1760 JSR GET.CHAR.FROM.KEYBUF.IF.ANY
       1770 BCS .11    ...got a char

and eliminate the CHECK.KEYBUF subroutine. Cleaner code, in my estimation, and shorter too.

If there is no character waiting in the type-ahead buffer, lines 1860 and following will get one from the keyboard. This is not as simple as it sounds, because there are a lot of options. First, there are three possible choices for the kind of cursor display during keyboard input. As the comments in lines 1860-1940 say, you may choose a blinking underline cursor, a flashing block cursor, or no cursor at all. If you are an AppleWorks user, you will recognize the first two options as the insert and overwrite cursors. The third, no cursor at all, used when a message like "Press Any Key to Continue" is displayed. The options are selected by values in two variables I have named KEYIN.CURSOR.FLAG and KEYIN.CURSOR.TYPE.

Just in case we are going to display a cursor, lines 1960-2050 retreive the current screen character at the cursor position and save it. If the current cursor position is in AUX RAM, then screen memory is left switched in the AUX position. Line 2540 will later switch it back to MAIN RAM, after a keystroke has been read.

Lines 2070-2080 test whether we have chosen to display a cursor or not. If not, lines 2090-2140 will call on the READ.KEYBOARD subroutine to wait for you to type something. READ.KEYBOARD (lines 3670-3950) is a little smarter than the average keyboard reader. First, it does not wait forever. You call it with a timeout value in the Y-register. The loop which polls the keyboard counts down the timeout value. If it reaches zero, READ.KEYBOARD returns with a FALSE status; if you type a key before timeout, READ.KEYBOARD returns with the character in the A-register and a TRUE status. In addition, notice at line 3750 that READ.KEYBOARD stores whatever character was in the A-register on the screen. This character is determined by your choice of a cursor display. Finally, this subroutine reads the Open- and Solid-Apple keys. If either one of them is depressed, bit 7 of the character value is made 1; if neither is depressed, bit 7 is made 0.

If you do want a cursor display, lines 2150-2170 decide which type you want. Lines 2180-2310 handle the blinking underline, and lines 2320-2460 handle the flashing block. The blinking underline is in reality a repeating series of three characters: an underline, a blank, and the original screen character. The timeout values are chosen so that the underline and blank each are on the screen for about 17% of the time, and the original character the remaining 66%. If you like changing such things, this is the place to do it. You can change the overall speed of the blink, or change the ratio, or change the characters. You might choose one of the mousetext characters here, just for fun, instead of the underline. For example, $5C instead of $DF would display an under- and over-line character.

The flashing block cursor display involves change the character retrieved from the screen to the inverse equivalent character, and alternating between it and the original character. Lines 2330-2420 put up the inverse character for 79% of the time, and lines 2430-2460 put up the normal character for 21% of the time.

Regardless of which of the two displays you choose, when you type a key control will go to ".10" at line 2480. Here the original screen character will be restored.

And, regardless of which of three cursor-types, you eventually wind up at ".11", line 2540. Here we turn screen RAM back to MAIN, and test whether you have opted for further analysis of the input character or not. The variable at $FC4, which I did not give a meaningful name yet, controls that option. If $FC4 is non-zero, the game is all over; if zero, the KEYIN.ANALYSIS section gets to do some work.

I have detailed what KEYIN.ANALYSIS does in the comments in lines 2580-2790. Any normal printing character will simply be passed to you without further action. Any control character will clear the type-ahead buffer and then be passed to you. Any Apple-character will also clear the type-ahead buffer, and if it is not one of the special ones it will be passed to you.

The "special" Apple-characters are what KEYIN.ANALYSIS is here for. Lines 2990-3040 map lower-case letters to the upper-case range. Apple-E is used to change the cursor-type. If you have a blinking underline cursor on the screen, typing Apple-E makes it change to a flashing block cursor. And vice versa. The cursor-toggling code is in lines 3500-3560. Apple-H is used to print the screen contents on your printer. The code to do the screen dump is located right here, in lines 3170-3400. Apple-Q, -S, and -Y are also looked for. If you type and Apple-/, lines 2900-2980 change it to an Apple-? and pass the new code on to you.

By the way, that code for the Apple-/ is sure strange. It tests for the "/" character THREE TIMES! Why? I can think of two reasonable answers. First, maybe there used to be two other keys besides "/" which were changed to "?"; if so, maybe the author simply patched the code the way it is now rather than revising and re-assembling it. Naw.... More likely is that this is a "hook" for catching knockoffs of the code. I have found other hooks, such as deliberate use of a 3-byte reference to a pagezero variable when a 2-byte instruction could have been used. Anyone copying the code to create an illegal copy of AppleWorks for sale under their own name might miss these hooks, and find themselves waking up in court.

The Screen Print code is rather interesting. It calls on a subroutine I call PRINTER.DRIVER for printing each line, but that subroutine is not listed here. There are four different types of calls to PRINTER.DRIVER, controlled by the value in the A-register. If (A) is zero, the printer is initialized. On an Apple //e this means sending out a control-I code, and "80N", which turns off the screen echo many printer interfaces would otherwise perform. On an Apple //c it sends out the controls to set up the baud rate and LF after CR for a serial ImageWriter printer. More on this in a future issue.

Lines 3230-3290 copy a line from the screen to a buffer starting at $900, and then print it on the printer. At lines 3250-3270 PRINTER.DRIVER is called with (A)=79, the line length, which means to print a string whose address follows the JSR instruction. Then at lines 3280-3290 PRINTER.DRIVER is called with (A)=$FE, which means to print a carriage return. Finally, Lines 3340-3350 call PRINTER.DRIVER with (A)=$FF, meaning to "close" the driver.

After the screen print is completed, lines 3370-3390 make sure the cursor is back where it started. However, I did not notice anywhere it ever got moved, so this may be redundant code. The subroutine called here, MOVE.CURSOR.TO.XY, is shown in lines 4320-4480. It uses a round-about method, building a string for last month's DISPLAY.STRING subroutine. The setup for DISPLAY.STRING is also handled in a round-about way, using the code I called POINT.PSTR.AT.0A00, lines 4500-4600. I haven't yet found any reason why this code is not simpler. Why go through the back bedroom to get from the kitchen to the dining room? I think I would have written MOVE.CURSOR.TO.XY like this:

  SC.MOVE.CURSOR.TO.XY
         TXA
         CLC
         ADC AW.LEFT
         STA AW.CH
         TYA
         ADC AW.TOP
         STA AW.CV
         RTS

The screen print code also calls on the subroutine I show in lines 4620-4940, COPY.SCRN.LINE.TO.0900. This subroutine picks up one line of characters, translating them from display codes to printer codes, and places the result in a buffer at $0900. Another subroutine, MAP.SCRN.CHARS.TO.INTERNAL, does the translation. That MAP... subroutine is never called from anywhere else, so it really should be considered part of the COPY... subroutine. Together they take 72 bytes. I rewrote COPY..., and my simpler, shorter version is shown in lines 5240-5600. Mine only takes 50 bytes. If someone had time to go over the whole program the same way, there might be room for a lot of new features.

  1000 *SAVE AW.SUBS.3
  1010 *--------------------------------
  1020 AW.CH   .EQ $14
  1030 AW.CV   .EQ $15
  1040 AW.BASE .EQ $16,17
  1050 PSTR    .EQ $80,81
  1060 Z.84    .EQ $84     Current Keyin Character
  1070 PNTR    .EQ $98,99
  1080 Z.A4    .EQ $A4
  1090 *--------------------------------
  1100 X.0900 .EQ $0900    Used during Screen Print (Apple-H)
  1110 X.0901 .EQ $0901
  1120 *--------------------------------
  1130 X.0A00 .EQ $0A00    Used for building little strings
  1140 X.0A01 .EQ $0A01
  1150 X.0A02 .EQ $0A02
  1160 *--------------------------------
  1170 X.0EA7 .EQ $0EA7
  1180 *--------------------------------
  1190 X.0F3B .EQ $0F3B
  1200 X.0F3D .EQ $0F3D
  1210 X.0FC4 .EQ $0FC4
  1220 *--------------------------------
  1230        .PH $1099
  1240 HANDLE.0A00 .DA X.0A00
  1250        .EP
  1260  
  1270 *--------------------------------
  1280        .PH $1176
  1290 KEYIN.CURSOR.TYPE   .HS 01   00=underline, 01=flashing
  1300 KEYIN.CURSOR.FLAG   .HS 01   00=no cursor, 01=cursor
  1310                     .BS 2    other variables
  1320 KEYBUF              .BS 10   type-ahead buffer
  1330 KEYBUF.IN           .HS 00
  1340 KEYBUF.OUT          .HS 00
  1350        .EP
  1360  
  1370 *--------------------------------
  1380 KEYBOARD     .EQ $C000
  1390 STROBE       .EQ $C010
  1400 SCRN.MAIN    .EQ $C054
  1410 SCRN.AUX     .EQ $C055
  1420 APPLE.OPEN   .EQ $C061
  1430 APPLE.SOLID  .EQ $C062
  1440 *--------------------------------
  1450 DISPLAY.STRING      .EQ $14D1   subroutine in AAL Jan 88
  1460 BASE.CALC.A         .EQ $1717   subroutine in AAL Jan 88
  1470 PRINTER.DRIVER      .EQ $1C21   subroutine in future AAL
  1480 *--------------------------------
  1490 I.1D0E     .EQ $1D0E
  1500 *--------------------------------
  1510        .PH $1D30
  1520 *--------------------------------
  1530 * (1D30) 1D71 1D7B 1D8C 1D9A 1DA6 1DBC 1DCA
  1540 KEYIN.CHAR.UNDER.CURSOR   .BS 1
  1550 * (1D31) 1D6C 1DC7 1F0F
  1560 KEYIN.COLUMN.INDEX        .BS 1
  1570 * (1D32) 1E27 1E2A 1E3C 1E3F
  1580 KEYIN.APPLE.H.LINE.NUMBER .BS 1
  1590 * (1D33) 1D3E 1E4E
  1600 KEYIN.CURSOR.CH           .BS 1
  1610 * (1D34) 1D43 1E51
  1620 KEYIN.CURSOR.CV           .BS 1
  1630 *--------------------------------
  1640 * (1D35) 1066 1192 137F 193F 19C5 1BFD
  1650 AW.KEYIN
  1660        LDA Z.A4     If non-zero, exit now as if
  1670        BEQ .1       ...you typed <ESC>
  1680        JMP KEYIN.EXIT.ESCAPE
  1690 .1     LDA AW.CH    Save current cursor position
  1700        STA KEYIN.CURSOR.CH
  1710        LDA AW.CV
  1720        STA KEYIN.CURSOR.CV
  1730 *--------------------------------
  1740 * (1D46) 1E1A 1E57 1E78
  1750 KEYIN.ANOTHER.CHAR
  1760        JSR CHECK.KEYBUF  Any characters in key-buffer?
  1770        BEQ .2            ...no
  1780        LDX KEYBUF.OUT    ...yes, get char from keybuf
  1790        LDA KEYBUF,X
  1800        INX               Bump keybuf out-index
  1810        CPX #10           At end of buffer?
  1820        BCC .1            ...no
  1830        LDX #$00          ...yes, wrap around to beginning
  1840 .1     STX KEYBUF.OUT    Save new keybuf out-index
  1850        JMP .11      Use the character
  1860 *--------------------------------
  1870 *   Key-buffer is empty, so we need to get a character
  1880 *   directly from the keyboard.  Therefore, we must:
  1890 *      1.  Save character now on screen under cursor
  1900 *      2.  Put up an appropriate cursor
  1910 *          a.  blinking underline
  1920 *          b.  flashing screen char
  1930 *          c.  no cursor at all
  1940 *      3.  Get a keystroke.
  1950 *--------------------------------
  1960 .2     LDA AW.CV    Point to the character at the cursor
  1970        JSR BASE.CALC.A
  1980        LDA AW.CH
  1990        LSR
  2000        BCS .3       ...Odd column, main RAM
  2010        STA SCRN.AUX ...Even column, aux RAM
  2020 .3     TAY
  2030        STY KEYIN.COLUMN.INDEX
  2040        LDA (AW.BASE),Y
  2050        STA KEYIN.CHAR.UNDER.CURSOR
  2060 *---Select type of cursor--------
  2070        LDX KEYIN.CURSOR.FLAG   
  2080        BNE .5       ...we do want a cursor display
  2090 *---No cursor at all-------------
  2100 .4     LDY #$FF     Long time-out count
  2110        LDA KEYIN.CHAR.UNDER.CURSOR
  2120        JSR READ.KEYBOARD
  2130        BEQ .4       ...No key yet
  2140        BNE .11      ...got a keystroke!
  2150 *---Some type of cursor----------
  2160 .5     LDX KEYIN.CURSOR.TYPE
  2170        BNE .8       ...Use flashing character
  2180 *---Use blinking underline-------
  2190 *   Repeat loop of underline, char, blank, char
  2200 .6     LDA #$DF     Underline character
  2210        CMP KEYIN.CHAR.UNDER.CURSOR
  2220        BNE .7       ...screen not now an underline
  2230        LDA #" "     ...now underline, change to blank
  2240 .7     LDY #28      Short time-out
  2250        JSR READ.KEYBOARD
  2260        BNE .10      ...got a key!
  2270        LDA KEYIN.CHAR.UNDER.CURSOR
  2280        LDY #108     Long time-out
  2290        JSR READ.KEYBOARD
  2300        BNE .10      ...got a key!
  2310        BEQ .6       ...always (no keystroke yet)
  2320 *---Use flashing character-------
  2330 .8     LDA KEYIN.CHAR.UNDER.CURSOR
  2340        AND #$7F     Change character to inverse
  2350        CMP #$40
  2360        BCC .9
  2370        CMP #$60
  2380        BCS .9
  2390        AND #$BF
  2400 .9     LDY #$6C
  2410        JSR READ.KEYBOARD
  2420        BNE .10      ...got a key!
  2430        LDA KEYIN.CHAR.UNDER.CURSOR
  2440        LDY #28      Short time-out
  2450        JSR READ.KEYBOARD
  2460        BEQ .8       ...no keystroke yet
  2470 *---Got a key, restore scrnchar--
  2480 .10    PHA
  2490        LDY KEYIN.COLUMN.INDEX
  2500        LDA KEYIN.CHAR.UNDER.CURSOR
  2510        STA (AW.BASE),Y
  2520        PLA
  2530 *--------------------------------
  2540 .11    STA SCRN.MAIN     Be sure in main RAM
  2550        LDX X.0FC4   Should we analyze the char?
  2560        BEQ KEYIN.ANALYSIS   ...yes
  2570        JMP KEYIN.EXIT   ...no, just store and return
  2580 *--------------------------------
  2590 *   Analyze the Character
  2600 *
  2610 *   1.  If character is $20-7F, just store it and return.
  2620 *   2.  Otherwise, start by clearing the key-buffer.
  2630 *   3.  If character is $00-1F, it will be fall through all
  2640 *           other tests and return after being stored.
  2650 *   4.  If character is $80-FF, an Apple key was down.
  2660 *   5.  If character is $E1-FA, it is lower-case and will
  2670 *           be changed to upper-case ($C1-DA).
  2680 *   6.  If character is not one of the following, just
  2690 *           store it and return.
  2700 *
  2710 *       Apple-/   Change to Apple-?, and return.
  2720 *       Apple-H   Print the screen, get another char.
  2730 *       Apple-Q   Clobber Z.A4, substitute <ESC>, return.
  2740 *       Apple-Y   Change to Control-Y and return.
  2750 *       Apple-S   If $EA7 non-zero, just store and return.
  2760 *                 Else, clobber Z.A4, substitute <ESC>,
  2770 *                     and return.
  2780 *       Apple-E   Toggle Cursor Mode, get another char.
  2790 *--------------------------------
  2800 KEYIN.ANALYSIS
  2810        CMP #$20
  2820        BCC .1       ...Control Character, analyze it.
  2830        CMP #$7F
  2840        BCS .1       ...Apple Character, analyze it.
  2850        JMP KEYIN.EXIT
  2860 *---Analyze the keychar----------
  2870 .1     PHA          Save character temporarily...
  2880        JSR CLEAR.KEYBUF
  2890        PLA          ...and get the character back.
  2900 *---Check for Apple-Slash--------
  2910        CMP #"/"
  2920        BEQ .2       ...Apple-Slash
  2930        CMP #"/"     <<<I don't know why they do it 3 times>>>
  2940        BEQ .2       <<<Maybe just for the fun of it.......>>>
  2950        CMP #"/"
  2960        BNE .3       ...not Apple-/
  2970 .2     LDA #"?"     ...Substitute Apple-?
  2980        JMP KEYIN.EXIT
  2990 *---Map Lower-Case to Upper------
  3000 .3     CMP #"a"
  3010        BCC .4       ...not lower-case letter
  3020        CMP #"z"+1
  3030        BCS .4       ...not lower-case letter
  3040        AND #$DF
  3050 *---If Apple-Y, make Ctrl-Y------
  3060 .4     CMP #"Y"     Check for Apple-Y
  3070        BNE .5       ...no
  3080        AND #$1F     Changes $D9 to $19, control-Y
  3090 *---Check for Apple-H------------
  3100 .5     CMP #"H"     Check for Apple-H
  3110        BNE .9       ...not Apple-H
  3120        LDA I.1D0E   ???
  3130        BNE .6       ...ignore the Apple-H
  3140        LDA X.0F3B
  3150        BNE .7       ...go ahead and print
  3160 .6     JMP KEYIN.ANOTHER.CHAR
  3170 *---Print the screen-------------
  3180 .7     STA X.0F3D
  3190        LDA #$00     "OPEN" Printer
  3200        JSR PRINTER.DRIVER
  3210        LDA #0       For LINE = 0 to 23
  3220        STA KEYIN.APPLE.H.LINE.NUMBER
  3230 .8     LDA KEYIN.APPLE.H.LINE.NUMBER
  3240        JSR COPY.SCRN.LINE.TO.0900
  3250        LDA #79      Print 79 characters from 0900
  3260        JSR PRINTER.DRIVER
  3270        .DA X.0900
  3280        LDA #$FE     Print CRLF
  3290        JSR PRINTER.DRIVER
  3300        INC KEYIN.APPLE.H.LINE.NUMBER  Next Line
  3310        LDA KEYIN.APPLE.H.LINE.NUMBER
  3320        CMP #24      Last line yet?
  3330        BCC .8       ...no, keep printing
  3340        LDA #$FF     "CLOSE" Printer
  3350        JSR PRINTER.DRIVER
  3360        JSR CLEAR.KEYBUF
  3370        LDX KEYIN.CURSOR.CH    Put cursor back...
  3380        LDY KEYIN.CURSOR.CV    ...but I don't know how
  3390        JSR MOVE.CURSOR.TO.XY     it could have moved.
  3400        JMP KEYIN.ANOTHER.CHAR    Get another character.
  3410 *--------------------------------
  3420 .9     CMP #"Q"     Check for Apple-Q
  3430        BEQ .10
  3440        CMP #"S"     Check for Apple-S
  3450        BNE .11
  3460        LDX X.0EA7
  3470        BNE .11
  3480 .10    STA Z.A4     Apple-Q or -S clobbers Z.A4
  3490        JMP KEYIN.EXIT.ESCAPE
  3500 *---If Apple-E, change cursor----
  3510 .11    CMP #"E"     Check for Apple-E
  3520        BNE KEYIN.EXIT
  3530        LDA KEYIN.CURSOR.TYPE
  3540        EOR #$01     Toggle btwn $00 and $01
  3550        STA KEYIN.CURSOR.TYPE
  3560        JMP KEYIN.ANOTHER.CHAR
  3570 *--------------------------------
  3580 * (1E7B) 1D39 1E69
  3590 KEYIN.EXIT.ESCAPE
  3600        LDA #$1B     Say you typed <ESC>
  3610 KEYIN.EXIT
  3620        STA Z.84     Store character here too
  3630        RTS
  3640 *--------------------------------
  3650        .EP
  3660  
  3670        .PH $1F0A
  3680 *--------------------------------
  3690 KEYBOARD.TIMEOUT .BS 2
  3700 *--------------------------------
  3710 * (1F0C) 1D7E 1D95 1D9F 1DB7 1DC1
  3720 READ.KEYBOARD
  3730        STY KEYBOARD.TIMEOUT+1
  3740        LDY KEYIN.COLUMN.INDEX
  3750        STA (AW.BASE),Y
  3760 .1     LDA KEYBOARD
  3770        BMI .2       ...got a keystroke
  3780        LDA KEYIN.CURSOR.FLAG
  3790        BEQ .1       If no cursor, then no time-out either
  3800        DEC KEYBOARD.TIMEOUT
  3810        BNE .1       ...more time left
  3820        DEC KEYBOARD.TIMEOUT+1
  3830        BNE .1       ...more time left
  3840        LDX #$00     timed out, return false
  3850        BEQ .4       ...always
  3860 .2     STA STROBE   Clear the strobe
  3870        LDX APPLE.OPEN
  3880        BMI .3       Apple, leave bit 7 = 1
  3890        LDX APPLE.SOLID
  3900        BMI .3       Apple, leave bit 7 = 1
  3910        AND #$7F     No Apple, make bit 7 = 0
  3920 .3     LDX #$01     Return TRUE
  3930 .4     RTS
  3940 *--------------------------------
  3950        .EP
  3960  
  3970        .PH $1FE0
  3980 *--------------------------------
  3990 * (1FE0) 1084 181D 1939 1BF1 1DE7 1E4B
  4000 *   Clear type-ahead buffer
  4010 *--------------------------------
  4020 CLEAR.KEYBUF
  4030        LDA #$00
  4040        STA KEYBUF.IN
  4050        STA KEYBUF.OUT
  4060        RTS
  4070 *--------------------------------
  4080        .EP
  4090  
  4100        .PH $13B2
  4110 *--------------------------------
  4120 * (13B2) 137A 1D46
  4130 *   Check whether any characters are queued up in the
  4140 *      keyboard buffer.  If so, return TRUE (status .NE.).
  4150 *      If not, return FALSE (status .EQ.).
  4160 *--------------------------------
  4170 CHECK.KEYBUF
  4180        LDA KEYBUF.IN     If pointers are same, the buffer
  4190        CMP KEYBUF.OUT        is empty.
  4200        BEQ .1            ...it is empty
  4210        LDA #$01          ...not empty, return TRUE (.NE.)
  4220        BNE .2
  4230 .1     LDA #$00
  4240 .2     RTS
  4250 *---Alternate code to do same----
  4260 ***    LDA KEYBUF.IN     If pointers are same, the buffer
  4270 ***    CMP KEYBUF.OUT        is empty.
  4280 ***    RTS          .NE. if not empty, .EQ. if empty
  4290 *--------------------------------
  4300        .EP
  4310  
  4320        .PH $1823
  4330 *--------------------------------
  4340 * (1823) 1024 13A2 1A58 1A89 1B1B 1E54 1E86 1E90 20B6 2B66 2B82
  4350 *   Move cursor to column (X), line (Y)
  4360 *      Works by building a string for STRING.DISPLAY
  4370 *--------------------------------
  4380 MOVE.CURSOR.TO.XY
  4390        STX X.0A01   Build string "05.XX.YY"
  4400        STY X.0A02
  4410        LDA #$05     "GoToXY" code
  4420        STA X.0A00
  4430        JSR POINT.PSTR.AT.0A00
  4440        LDA #3       String has 3 characters
  4450        JSR DISPLAY.STRING
  4460        RTS
  4470 *--------------------------------
  4480        .EP
  4490  
  4500        .PH $1EA9
  4510 *--------------------------------
  4520 * (1EA9) 182E 1F85 1FEC 208B 20CC
  4530 POINT.PSTR.AT.0A00
  4540        LDA HANDLE.0A00
  4550        STA PSTR
  4560        LDA HANDLE.0A00+1
  4570        STA PSTR+1
  4580        RTS
  4590 *--------------------------------
  4600        .EP
  4610  
  4620        .PH $187A
  4630 *--------------------------------
  4640 * (187A) 1036 1A81 1E2D
  4650 *   Used by Apple-H Screen Print function
  4660 *      (A) = line to be copied
  4670 *      Copies 80 characters to buffer at $0900
  4680 *--------------------------------
  4690 COPY.SCRN.LINE.TO.0900
  4700        JSR BASE.CALC.A
  4710        LDX #0
  4720        LDY #0
  4730 .1     LDA (AW.BASE),Y
  4740        BMI .2       80-FF
  4750        JSR MAP.SCRN.CHARS.TO.INTERNAL
  4760        JMP .3
  4770 .2     AND #$7F
  4780 .3     STA X.0901,X
  4790        STA SCRN.AUX
  4800        LDA (AW.BASE),Y
  4810        BMI .4
  4820        JSR MAP.SCRN.CHARS.TO.INTERNAL
  4830        JMP .5
  4840 .4     AND #$7F
  4850 .5     STA X.0900,X
  4860        STA SCRN.MAIN
  4870        INX
  4880        INX
  4890        INY
  4900        CPY #40
  4910        BCC .1       ...more on this line
  4920        RTS
  4930 *--------------------------------
  4940        .EP
  4950  
  4960        .PH $1E94
  4970 *--------------------------------
  4980 * (1E94) 1885 1897
  4990 *
  5000 *   Only called from subroutine which copies
  5010 *      a screen line to $0900, and only for
  5020 *      character values $00-7F.
  5030 *
  5040 *      00-1F  to  40-5F
  5050 *      20-3F  no change
  5060 *      40-5F  to  80-9F  (Mouse Graphics)
  5070 *      60-7F  no change
  5080 *--------------------------------
  5090 MAP.SCRN.CHARS.TO.INTERNAL
  5100        CMP #$20
  5110        BCS .1       not 00-1F
  5120        ORA #$40     Change 00-1F to 40-5F
  5130        BNE .2       ...always
  5140 .1     CMP #$40
  5150        BCC .2       ...20-3F
  5160        CMP #$60
  5170        BCS .2       ...60-7F
  5180        AND #$BF     Change 40-5F to 00-1F
  5190        ORA #$80         and then to 80-9F
  5200 .2     RTS
  5210 *--------------------------------
  5220        .EP
  5230  
  5240 *--------------------------------
  5250 *   A Shorter Version of COPY.SCRN.LINE.TO.0900
  5260 *--------------------------------
  5270 SC.COPY.SCRN.LINE.TO.0900
  5280        JSR BASE.CALC.A
  5290        LDX #0
  5300        LDY #0
  5310 .1     STA SCRN.AUX      Even char first
  5320        JSR GET.MAP.PUT.SCRN.CHAR
  5330        STA SCRN.MAIN     Then odd char
  5340        JSR GET.MAP.PUT.SCRN.CHAR
  5350        INY
  5360        CPY #40
  5370        BCC .1       ...more on this line
  5380        RTS
  5390 *--------------------------------
  5400 *      00-1F  to  40-5F
  5410 *      20-3F  no change
  5420 *      40-5F  to  80-9F  (Mouse Graphics)
  5430 *      60-7F  no change
  5440 *      80-FF  to  00-7F
  5450 *--------------------------------
  5460 GET.MAP.PUT.SCRN.CHAR
  5470        LDA (AW.BASE),Y
  5480        BMI .1       Change 80-FF to 00-7F
  5490        CMP #$60     now have 00-7F
  5500        BCS .2       ...60-7F no change
  5510        ADC #$40     Change 00-5F to 40-9F
  5520        BMI .2       ...40-5F became 80-9F
  5530        CMP #$60
  5540        BCC .2       ...00-1F became 40-5F
  5550        AND #$3F     Change 60-7F back to 20-3F
  5560 .1     AND #$7F     Change 80-FF to 00-7F
  5570 .2     INX
  5580        STA X.0900,X
  5590        RTS
  5600 *--------------------------------

Percentage PrinterBob Sander-Cederlof

The following program will print the ratio M/N as a percentage, assuming M and N are 24-bit integers. The percentage will be rounded to the nearest integer value, and print as one, two, or three digits followed by the "%" symbol. If M is more than 999% of N, the value printed will be garbled.

The straightforward way to compute this percentage would be to compute P = 100M/N, convert the quotient to decimal, and print it. To accomplish rounding, you could check the remainder after the division: if twice the remainder is greater than or equal to N, increment the quotient.

After a little head-scratching, I discovered another method. I accomplish the division and the conversion to decimal at the same time, and never multiply by 100. For rounding I cheated a little, and added N/256 to M before dividing.

N/256 is a breeze to compute, because it mearely means offseting the addition loop by one byte. However, the CORRECT rounding term would be N/200. The difference between N/256 and N/200 is 56N/51200, or about .0011N. This is "negligible", at least to me. It in effect means that I am rounding up percentages with fractional parts above .4989, instead of just those with fractions .5 or higher. Who will ever notice? Since my main use for this routine is to print what fraction of my hard disk is in use, it will be plenty close enough. (In fact, maybe I don't even need to round at all!)

The division/conversion loop works by using partial division. Each time I call the division loop, I divide M by N; however, I only loop enough times to get the next decimal digit of the quotient. Then I multiply the remainder by ten, so that the next division will generate the next digit. Trust me, it really works!

I added the digit printout to the tail end of the same subroutine which generates the next digit, and put in some logic to suppress leading zeroes. It looks funny if 10% prints out as 010%, so I ignore that leading zero. On the other hand, the logic makes sure 0% does not print as just "%".

To use the subroutine, you first have to store the 24-bit value for N. I wrote a subroutine for this, but you don't necessarily have to use it. If you do use it, load the most significant byte in the A-register, the middle byte in X, and the low byte in Y. I call this loading a 24-bit value into AXY. Then do a JSR STORE.N. In contrast to the usual way you see multi-byte values stored in most 6502 code, I store M and N in High-to-Low order. This made the various loops inside the GET.DIGIT subroutine shorter. Lines 1100-1160 are the STORE.N subroutine. Nothing fancy here!

After storing the N-value, load up the M-value in AXY and do a JSR PERCENT.CALC. The percentage that M is of N will be printed, and the subroutine will return. I wrote a demonstration program, shown in lines 2000-2260. This program sets N=255 and then prints out all percentages for M = 0 to 255. You can vary the value of N and see different effects.

When you call PERCENT.CALC, lines 1180-1200 store your M-value. Lines 1210-1230 initialize the leading zero flag so that those zeroes will be suppressed. Lines 1240-1350 accomplish the pseudo-rounding I described above. I append another byte to the M-value, which is in effect after the radix point. [Radix point? What's a radix point? In decimal numbers, we call it a decimal point. In binary numbers, we could call it a binary point. In general, it is the demarcation between the integral and fractional parts of a number.] Finally, lines 1370-1410 generate and print three digits of the answer followed by the %-sign.

GET.DIGIT, lines 1430-1800, does all the real work. Lines 1440-1530 subtract N from M until M goes negative. The Y-register counts how many times this takes, less one. Unless M was greater than or equal to 10N, the number in Y will be a value from 0-9, and will be the first or next digit of the percentage. But before printing that digit, I need to modify the remainder.

Lines 1540-1610 add N back one time, so that the remainder is positive. We subtracted once too often, so this fixes things. Then lines 1620-1710 multiply the remainder by 10. Next time GET.DIGIT is called, we will generate the next digit of the percentage.

Lines 1720-1790 print the digit, unless it is a leading zero. If the digit is not zero, it is obviously not a leading zero, so it is printed. Any time a digit gets printed, I store a negative value in my leading zero flag, indicating that any future zeroes cannot be called "leading" (see line 1780). The leading zero flag started out at 2, and each time I test it gets decremented in line 1750. If the first two digits are both zero, it will go negative forcing the third digit to print even it is also zero.

If you are interested, it is very simple to modify this program so that is prints out a rounded percentage in the format xxx.x%, to the nearest tenth of a percent. All we have to do is change the rounding term from N/256 to N/2048, which affects the code in lines 1240-1350, and then add the follow lines:

       1392    LDA #"."        Print a decimal point
       1394    JSR MON.COUT
       1396    JSR GET.DIGIT   Print the tenths digit

Other simple modifications could change the variable size from 24-bits to 16-bits, 32-bits, or whatever you need.

  1000 *SAVE S.PERCENT.CALC
  1010 *--------------------------------
  1020 *   Subroutine for printing M/N as a percentage
  1030 *   where M and N are 24-bit integers.
  1040 *      1.  With (AXY)=N, do JSR STORE.N
  1050 *      2.  With (AXY)=M, do JSR PERCENT.CALC
  1060 *--------------------------------
  1070 MON.CROUT  .EQ $FD8E
  1080 MON.PRBYTE .EQ $FDDA
  1090 MON.COUT   .EQ $FDED
  1100 *--------------------------------
  1110 STORE.N
  1120        STA N        MOST SIGNIFICANT
  1130        STX N+1
  1140        STY N+2      LEAST   "
  1150        RTS
  1160 *--------------------------------
  1170 PERCENT.CALC
  1180        STA M        MOST SIGNIFICANT
  1190        STX M+1
  1200        STY M+2      LEAST   "
  1210 *---Init Leading Zero Flag-------
  1220        LDA #2       Start with LZ-flag = 2
  1230        STA Z
  1240 *---Add N/256 to Round Result----
  1250        LDA N+2      Accuracy would demand adding
  1260        STA M+3      N/200, but N/256 is close enough.
  1270        CLC             N     N      56N
  1280        LDX #1         --- = --- + -------
  1290 .1     LDA M+1,X      200   256   200*256
  1300        ADC N,X
  1310        STA M+1,X      And 56/51200 = .0010937 (very small)
  1320        DEX
  1330        BPL .1
  1340        BCC .2
  1350        INC M
  1360 *---Compute & Print Digits-------
  1370 .2     JSR GET.DIGIT     Hundreds digit
  1380        JSR GET.DIGIT     Tens digit
  1390        JSR GET.DIGIT     Units digit
  1400        LDA #"%"
  1410        JMP MON.COUT
  1420 *--------------------------------
  1430 GET.DIGIT
  1440        LDY #-1      Y will be the quotient
  1450        SEC
  1460 .1     INY          Increment Quotient
  1470        LDX #2
  1480 .2     LDA M,X      Subtract Denominator
  1490        SBC N,X
  1500        STA M,X
  1510        DEX
  1520        BPL .2
  1530        BCS .1       This goes around once to often...
  1540 *---Add N back in once-----------
  1550        LDX #2
  1560 .3     LDA M,X      So we need to add it back once.
  1570        ADC N,X
  1580        STA M,X
  1590        STA T,X      Save copy of M in T, to make it
  1600        DEX               easier to multiply by 10.
  1610        BPL .3
  1620 *---Multiply M by 10-------------
  1630        JSR M.TIMES.2     M = 2 (4M + T )
  1640        JSR M.TIMES.2     ...now we have 4M
  1650        LDX #3            ...add T (copy of original M)
  1660 .4     LDA M,X
  1670        ADC T,X
  1680        STA M,X
  1690        DEX
  1700        BPL .4
  1710        JSR M.TIMES.2     5M times 2 is 10M
  1720 *---Print digit if not leading zero---
  1730        TYA          digit is quotient from above
  1740        BNE .5       ...digit not zero, so print it
  1750        DEC Z        Is it a leading zero?
  1760        BPL .6       ...yes, don't print it.
  1770 .5     ORA #"0"     Make it ASCII
  1780        STA Z        Kill LZ-flag by setting bit 7 = 1
  1790        JSR MON.COUT Print the digit
  1800 .6     RTS
  1810 *--------------------------------
  1820 M.TIMES.2
  1830        LDX #3       Double 4-byte value in M
  1840        CLC
  1850 .1     ROL M,X
  1860        DEX
  1870        BPL .1
  1880        RTS
  1890 *--------------------------------
  1900 M      .BS 4
  1910 N      .BS 3
  1920 T      .BS 4
  1930 Z      .BS 1        LEADING ZERO FLAG
  1940 *--------------------------------
  1950 *   Test Routine for PERCENT.CALC
  1960 *      For M = 0 to 255
  1970 *      Print M,PERCENT(M/255)
  1980 *      Next M
  1990 *--------------------------------
  2000 TT
  2010        LDA #0
  2020        STA TM       Start TM=0
  2030        TAX
  2040        LDY #255     Set N = 255
  2050        JSR STORE.N
  2060 .1     LDY TM       Set M to current TM
  2070        TYA
  2080        AND #7       If TM Mod 8 = 0, start new line
  2090        BNE .2       ...same line
  2100        JSR MON.CROUT
  2110 .2     TYA          Get M again
  2120        JSR MON.PRBYTE   Print M-value too
  2130        LDA #" "          followed by one blank
  2140        JSR MON.COUT
  2150        LDA #0
  2160        TAX          Leading bytes of M = 00 00
  2170        JSR PERCENT.CALC  print M/N percent
  2180        LDA #" "
  2190        JSR MON.COUT      followed by two blanks
  2200        JSR MON.COUT
  2210        INC TM            Next TM
  2220        BNE .1            ...until wraps around to 00
  2230        RTS               Finished
  2240 *--------------------------------
  2250 TM     .BS 1
  2260 *--------------------------------
  2270        .LIF

Another Quick Two-Digit Decimal PrinterBob Sander-Cederlof

I have written a number of articles in the past about converting values from binary to decimal and printing or displaying them. Usually such routines need to handle large numbers, but sometimes they are limited to single-byte values. And once in a while, you know in advance the value will be between 0 and 99 decimal.

For example, when you are printing the date you know in advance that the day, month, and year numbers are only two digits each. The following program is actually used in one such date printer, to print the day number and year number. For simplicity, it always prints two digits, even if the first digit is a zero. This is the way I want it to be when printing the year, but the day would probably look better without a leading zero.

Lines 1000-1140 in the listing which follows are a test routine which call on my PD subroutine to print every possible value from 00 to 99. Lines 1150 to the end are the PD subroutine. Lines 1240-1290 are not actually assembled, because the variable "blank.fill" is zero. If you change line 1010 to make "blank.fill" equal to 1, lines 1240-1290 will be assembled. Then values less than 10 will print with a leading blank rather than a leading zero.

  1000 *SAVE Q2D.DECIMAL
  1001        .list con
  1010 blank.fill .eq 0
  1020 *--------------------------------
  1030 COUT   .EQ $FDED
  1040 T
  1050        LDY #0       For Y = 0 to 99
  1060 .1     TYA          A = Y
  1070        JSR PD       Print two digits decimal
  1080        LDA #" "     Print two spaces
  1090        JSR COUT
  1100        JSR COUT
  1110        INY          Next Y
  1120        CPY #100
  1130        BCC .1
  1140        RTS          Finished!
  1150 *--------------------------------
  1160 PD     LDX #"0"-1   Start with ASCII zero-1
  1170        SEC          Set up subtraction
  1180 .1     INX          Increment ten's digit
  1190        SBC #10      Take out ten
  1200        BCS .1       Still more tens
  1210        ADC #"0"+10  Add back one ten, and make ASCII
  1220        PHA          Save unit's digit
  1230        TXA          Get ten's digit
  1240    .do blank.fill
  1250    cmp #"0"         If these are assembled, print
  1260    bne .2           00-09 as " 0" through " 9"
  1270    lda #" "
  1280 .2
  1290    .fin
  1300        JSR COUT     Print ten's digit
  1310        PLA          Get unit's digit
  1320        JMP COUT     and print it
  1330 *--------------------------------

New Versions of S-C Word ProcessorBob Sander-Cederlof

A long time ago I wished there were some sort of word processor for the Apple II. Paul Lutus wrote AppleWriter, and I bought a copy. It was limited to a 40-column display, and only showed upper-case on the screen, due to limitations in the old Apple II and II Plus machines. It cost $50, and that seemed like a pretty good price.

After a while I found out about the Paymar Lower-Case Adapter, and added lower-case display to the screen. Some patches inside AppleWriter made it work with the adapter. Then the shift-key mod was invented, and I installed that also. I started hoping for even more....

So, I wrote my own word processor, based on AppleWriter's features, and using some of the internal techniques Paul Lutus developed which made AppleWriter faster than any other word processors available. I simplified the editing commands, speeded up and enhanced a lot of the features, and significantly shortened the code. I gave it the ability to use standard text files, with blazingly fast disk load and save. It gradually grew into a product, which we sold with all the source code for $50. More than a pretty good price.

However, the S-C Word Processor was still limited to a 40-column display. Bob Deen, a high school student at the time, was doing some programming work for us. He did a lot of work on our Cross Assemblers, for example. He was also using the S-C Word Processor a lot, so I asked him to make an 80-column version for the Apple //e. He succeeded, and also added "widow" and "orphan" protection. (See the article in AAL, July, 1984.) By the way, Bob Deen is now a computer scientist at Jet Propulsion Laboratories in Pasadena, California, doing image enhancement software.

Meanwhile, Apple made a widow out of DOS 3.3 by bringing out the ProDOS system. The S-C Word Processor was still tied to DOS, partly because of my fancy disk I/O and the catalog-menu system. Then last December Bob Gardner sent us a ProDOS version! (Bob, who lives in Washington state, has been a loyal customer and friend since at least 1983.) He did a terrific job, making the ProDOS version even better than the DOS one. The only drawback was that it only worked in 40-columns. Well, in March he sent an 80-column version.

Now, still for only $50, you get both DOS and ProDOS versions which work in both 40- and 80-columns. To use the 80-column versions you have to have an Apple //e, //c, or IIgs. The 40-column versions will work on those or older Apples, but the older Apples do need lower-case display and shift-key mods.

I wouldn't want you to think the S-C Word Processor has all the features of the Word Perfect, or other such major products. No, it is not that sophisticated. But it does have all the basic features we need for everyday work, it is very fast, and you get all the source code so you can personalize it.

Some of you have done some extensive personalizing already. Horst Schneider, a retired businessman in Denver, modified it to become a part of his business management package, adding mail merge and other features along the way. Larry Skutchan, who works at American Printing House for the Blind, made a talking version which works with Street Electronics' "Echo" speech synthesizers. If you are interested in either of these, I could put you in touch with Horst or Larry.

A curious bit of history: all three of the programmers who have made the major contributions to SCWP are named Bob! I guess we could give it the nickname of the Three-Bob Word Processor! No, it is inexpensive, but we do charge more than three shillings.

If you already have an earlier version of the S-C Word Processor and would like to get an update to the latest, send $7.50 (or only $5 if you only want the ProDOS disk).


Printing the ProDOS Date and TimeBob Sander-Cederlof

ProDOS-8 stores the date and time information from in four bytes in the Global Page starting at $BF90:

     $BF90:  MMMDDDDD   Low-order bits of Month, Day (1-31)
     $BF91:  YYYYYYYM   Year (0-99), high bit of Month
     $BF92:  00mmmmmm   Minute (0-59)
     $BF93:  00hhhhhh   Hour (0-23)

The following subroutine, lines 1000-1590, will print out the date in the form DD-MMM-YY. Lines 1600-1800 are an alternative method for printing out the 3-letter month name abbreviation. Lines 1810-1910 print the time in the form hh:mm.

The value stored in the Global Page may not be current. It is automatically updated every time you close or flush a file, or you can force it to be updated by using the MLI call shown in lines 1920-end. If you have looked into the Global Page description in the manuals, you may have noticed that $BF06 is a vector to the date/time update code. Don't try to use it directly unless you are sure the Language Card is properly switched before and after the call. The best way is to use the MLI call, as I did.

  1000 *SAVE PRINT.DATE
  1010        .LIST MOFF
  1020 *--------------------------------
  1030 *   Subroutine to print date from ProDOS Global Page
  1040 *      in form DD-MMM-YY.
  1050 *   Two different methods for printing the 3-letter month
  1060 *      name are shown, with month-name table in normal and
  1070 *      transposed order.
  1080 *--------------------------------
  1090 DATE   .EQ $BF90,BF91   Date in form: MMMDDDDD, YYYYYYYM
  1100 *                       Time in form: 00mmmmmm, 000hhhhh
  1110 COUT   .EQ $FDED
  1120 *--------------------------------
  1130 PRINT.DATE
  1140        LDA DATE     Get MMMDDDDD
  1150        AND #$1F     Isolate Day of Month
  1160        JSR PD       Print the day number
  1170        LDA #"-      Print a dash
  1180        JSR COUT    
  1190 *----PRINT MONTH FROM TABLE------
  1200        LDA DATE+1   Get YYYYYYYM
  1210        LSR          High bit of Month-number into Carry
  1220        PHA          Save 0YYYYYYY on stack
  1230        LDA DATE     Get MMMDDDDD
  1240        ROR          MMMMDDDD
  1250        LSR          0MMMMDDD
  1260        LSR          00MMMMDD
  1270        LSR          000MMMMD
  1280        LSR          0000MMMM   Month number (1-12)
  1290        TAX
  1300        LDA MONTH.TBL.1-1,X  1st letter
  1310        JSR COUT
  1320        LDA MONTH.TBL.2-1,X  2nd letter
  1330        JSR COUT
  1340        LDA MONTH.TBL.3-1,X  3rd letter
  1350        JSR COUT    
  1360        LDA #"-      Print dash
  1370        JSR COUT    
  1380 *----PRINT YEAR------------------
  1390        PLA          GET 0YYYYYYY FROM STACK
  1400 *---Fall into PD subroutine------
  1410 PD     LDX #"0"-1   Start with ASCII zero-1
  1420        SEC          Set up subtraction
  1430 .1     INX          Increment ten's digit
  1440        SBC #10      Take out ten
  1450        BCS .1       Still more tens
  1460        ADC #"0"+10  Add back one ten, and make ASCII
  1470        PHA          Save unit's digit
  1480        TXA          Get ten's digit
  1490        JSR COUT     Print ten's digit
  1500        PLA          Get unit's digit
  1510        JMP COUT     and print it
  1520 *--------------------------------
  1530        .MA AS
  1540        .AS -/]1/
  1550        .EM
  1560 *--------------------------------
  1570 MONTH.TBL.1 >AS "JFMAMJJASOND"
  1580 MONTH.TBL.2 >AS "AEAPAUUUECOE"
  1590 MONTH.TBL.3 >AS "NBRRYNLGPTVC"
  1600 *--------------------------------
  1610 ALTERNATIVE.MONTH.PRINTER
  1620        LDA DATE+1   GET YYYYYYYM
  1630        LSR          M INTO CARRY
  1640        LDA DATE     GET MMMDDDDD
  1650        ROR          MMMMDDDD
  1660        LSR          0MMMMDDD
  1670        LSR          00MMMMDD
  1680        LSR          000MMMMD
  1690        LSR          0000MMMM
  1700        STA TEMP     Multiply month number by 3
  1710        ASL
  1720        ADC TEMP
  1730        TAX          Index is 3,6,9,...
  1740        LDY #3       Print 3 consectutive letters
  1750 .1     LDA MONTH.TABLE-3,X
  1760        JSR COUT
  1770        INX          Next letter
  1780        DEY
  1790        BNE .1
  1800        RTS          Finished
  1810 *--------------------------------
  1820 TEMP   .BS 1
  1830 MONTH.TABLE >AS "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC"
  1840 *--------------------------------
  1850 PRINT.TIME
  1860        LDA DATE+3   Get 00hhhhhh
  1870        JSR PD
  1880        LDA #":"
  1890        JSR COUT
  1900        LDA DATE+2   Get 00mmmmmm
  1910        JMP PD
  1920 *--------------------------------
  1930 UPDATE.DATE.AND.TIME
  1940        JSR $BF00    MLI ENTRY POINT
  1950        .DA #$82,0000     GET DATE/TIME, NO PARMS
  1960        RTS
  1970 *--------------------------------
  1980        .LIST OFF

Apple Assembly Line (ISSN 0889-4302) is published monthly by S-C SOFTWARE CORPORATION, P. O. Box 280300, Dallas, TX 75228 Phone (214) 324-2050. Subscription rate is $24 per year in the USA, Canada, and Mexico, or $36 in other countries. Back issues are $1.80 each for Volumes 1-7, $2.40 each for Volume 8 (plus postage). A subscription tothe newsletter with a Monthly Disk containing all program source code and article text is $64 per year in the USA, Canada and Mexico, and $90 to other countries.

All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)