Volume 8 -- Issue 5 | February 1988

In This Issue...

- Peeking Inside AppleWorks 1.3, Part 3: Keyboard Input Subroutines
- Percentage Printer
- Another Quick Two-Digit Decimal Printer
- New Versions of S-C Word Processor
- Printing the ProDOS Date and Time

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 Printer | Bob 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 Printer | Bob 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 Processor | Bob 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 Time | Bob 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.)