Apple Assembly Line
Volume 3 -- Issue 5February 1983

In This Issue...

S-C Macro Assembler ///

The Apple /// version of the S-C Macro Assembler is coming right along! I am now selling a preliminary "as is" version for $100. That buys you the assembler, a few pages of documentation about the differences from the Apple ][ version, and free updates until the finished product appears. This is a working assembler for producing free-running programs; it assembles itself just fine. The biggest gap is the ability to produce relocatable modules for Pascal or BASIC. That will be added next. Call or write if you are interested in being among the first to have this new enhancement to the Apple ///.

Zero-Insertion-Force Game Socket Extender

One of the first things I did to my Apple back in 1977 was to plug a ZIF socket into the game connector. Not too easy, because it first has to be soldered to a header, but I did it.

Now I have discovered a source for a ready-made device that does the same thing, plus brings the socket outside the Apple (if you so desire). There's a picture of the device on page 14. For only $20 I'll send you one!


Really Adding ASCII StringsBob Sander-Cederlof

Last month I promised a "reasonably useful" program to add two numbers together from ASCII strings. I promised:

Okay! It took me three days, but I did it! Of course, the program has grown from 12 lines and 26 bytes of code to over 290 lines and over 450 bytes, too.

The program is now assembled to load at $9000, but you can choose other positions by changing line 1130. I set HIMEM:36864 before doing anything else in the Applesoft program, and then BRUN B.STRING ADDER.

When B.STRING ADDER is BRUN, only the setup code in lines 1160-1220 is executed. What this does is link in the ampersand (&) to the body of my program. Once the "&" is linked, my program responds to a call like "& +$,A$,B$,C$" by adding the numeric values represented in ASCII in A$ and B$ and storing the sum as a string in C$.

When an &-line occurs, Applesoft branches to my line 1520. Lines 1520-1600 check for the characters "+$," after the ampersand. If you don't like those characters, change them to something else. Anyway, if the characters do not match, you get SYNTAX ERROR. If they do match, it is time to collect the three strings variables.

Lines 1620-1690 collect the three string variables. The first two are the operands, the third is the result string. I save the address and length of the actual data of the operand strings. All I save at this point for the result string is the address of the variable descriptor. I call the subroutine PARSE.STRING.NAME to check for a leading comma, search for the variable name, and store the length and address of the referenced string data.

Lines 1730-1860 scan each operand string in turn to find the decimal point position. The routine SCAN divides a string at the decimal point (or where the decimal point would be if there was one), and returns in Y the number of characters to the left of the decimal point. SCAN returns in X the count of the number of characters on the right end, including the decimal point. I save the "digits.after" parts of both strings, and also the maxima of the two parts. The maxima describe the result string (almost).

Lines 1900-2000 finish the description of the result string, by lengthening the integral (left) side by two characters. These two characters allow for extension of the result by carry, and for representation of the sign of the result using ten's complement notation. At this point I also clear the necessary bytes of the result to zero, so the buffer can be used as an accumulator.

Now comes the EASY part. Lines 2040-2100 add each operand in turn to the buffer contents. EASY. Just call the subroutine ADD.TO.BUFFER, and it's done! Don't worry, I'll amplify later.

In ten's complement notation, if the first digit is 0-4 the number is positive; if the first digit is 5-9, the number is negative. For example, 1234 looks like 001234; -1234 becomes 998766. Ten's complement means in decimal the same thing two's complement means in binary. I can form the ten's complement by subtracting the number from a power of ten equal to the number of digits in the result. In that example, 1000000-1234=998766. Note that the ten's complement is equal to the nine's complement plus one. (Since 10=9+1.)

Lines 2140-2410 convert the buffer contents from the ten's complement numeric notation back to ASCII. Lines 2140-2180 set or clear the CARRY and TENS.FLAG sign bits according to the first digit in the buffer. A negative number, with a first digit of 5-9, causes both of these variables to get a value of the form 1xxxxxxx.

Lines 2190-2360 scan through the number from right to left, making the ten's complement if the number was negative, and converting each digit to ASCII. Lines 2370-2400 store a minus sign in the first digit position if the result is negative.

Line 2410 calls a subroutine to chop off leading zeros, and move the minus sign if there is one. You may justifiably ask, "Why did you call a subroutine rather than use in-line code?" Because when I wrote it in-line, the local labels stretched out too far from the major label STRADD and caused an assembly error. Also, sometimes I use subroutines for clarity, even when the subroutine is only called once.

The final step is to pack the resulting string up and ship it to the result string variable. Lines 2450-2590 do just that. AS.GETSPA makes room at the bottom of string pool space, and AS.MOVSTR copies the string data. C'est finis!

Lines 2640-3100 do the actual addition. On entry, X is either 0 or 4, selecting either the first or second operand. SETUP.OPERAND copies the string address into VARPNT, and retrieves the length of the string. Lines 2690-2760 set or clear the TENS.FLAG and CARRY variables according to the sign of the operand.

Lines 2780-2810 compute the position in the buffer at which the operand will be aligned properly. We saved the size of the integral (left) side of the buffer in MAX.DIGITS.BEFORE. That plus the lenght of the fractional side of the operand tells us where this operand aligns. Since we are using ten's complement for negative numbers, rather than nine's complement, we don't have to worry about extending the fractional parts to the same length. We can just start adding at the end of the current operand. (In ten's complement form fractional extensions are zeros; in nine's complement form, the extension digits would all be nines.)

Lines 2830-3100 do the addition. X points into the buffer, and Y points into the operand string. To start with, both X and Y point just past the end; therefore the loop BEGINS with a test-and-decrement sequence. I first t-a-d the buffer pointer; if it is zero, all is finished. If not, on to t-a-d the string pointer. If it is zero, there are still digits left in the buffer, so I use an assumed leading zero digit for the operand. We still may have carries to propagate across the rest of the sum.

Assuming neither pointer is zero, line 2900 gets the next digit from the operand string. If it is a decimal point, I just store the decimal point ASCII value into the buffer. If you want to be able to ignore leading blanks, insert the following two lines between line 2920 and 2930:

     2924       CMP #'          BLANK?
     2925       BEQ .3          YES, USE ZERO.

I left them out in my version, because I forgot I promised it to you.

If the character is not a decimal point (or blank), it may be a minus sign or digit. I did not put any error checking in my program for other extraneous characters; if you try them, you will get extraneous results! I treat a sign as a leading zero in the arithmetic loop.

If the character is a digit, or an assumed leading zero, we can add it to the buffer's value. Lines 2960-3010 will complement the digit if the operand had a minus sign. Lines 3020-3070 add the current operand digit (or its complement) to the current buffer digit, plus any carry hung over from the preceding digit, and save the resulting carry in CARRY.

That's it! Now here is a short little Applesoft program to test the code.

     100  REM TEST&+$,A$,B$
     110  HIMEM: 36864: PRINT  CHR$ (4)"BLOAD B.STRING ADDER":
          CALL 36864
     120  INPUT A$: INPUT B$
     130  &  + $,A$,B$,C$
     140  PRINT C$: GOTO 120
 1000  *SAVE S.SUPER STRING ADDER
 1010  *--------------------------------
 1020  *      STRING ADDITION:  & +$,A$,B$,C$
 1030  *--------------------------------
 1040  BUFFER              .EQ $200 - $2FF
 1050  AMPERSAND.VECTOR    .EQ $3F5 - $3F7
 1060  AS.CHRGET           .EQ $00B1
 1070  AS.SYNERR           .EQ $DEC9
 1080  AS.PTRGET           .EQ $DFE3
 1090  AS.CHKCOM           .EQ $DEBE
 1100  AS.GETSPA           .EQ $E452
 1110  AS.MOVSTR           .EQ $E5E2
 1120  *--------------------------------
 1130         .OR $9000
 1140         .TF B.STRING ADDER
 1150  *--------------------------------
 1160  SETUP  LDA #$4C     JMP OPCODE
 1170         STA AMPERSAND.VECTOR
 1180         LDA #STRADD
 1190         STA AMPERSAND.VECTOR+1
 1200         LDA /STRADD
 1210         STA AMPERSAND.VECTOR+2
 1220         RTS
 1230  *--------------------------------
 1240  FRESPC              .EQ $71,72
 1250  VARPNT              .EQ $83,84
 1260  *--------------------------------
 1270  *      TWO SIMILAR BLOCKS, FOR A$ AND B$
 1280  *      REFERENCED WITH X=0 OR X=4
 1290  *--------------------------------
 1300  A.LENGTH            .BS 1
 1310  A.ADDR              .BS 2
 1320  A.DIGITS.AFTER      .BS 1
 1330  *
 1340  B.LENGTH            .BS 1
 1350  B.ADDR              .BS 2
 1360  B.DIGITS.AFTER      .BS 1
 1370  *--------------------------------
 1380  *      A THIRD BLOCK, NEARLY THE SAME AS ABOVE,
 1390  *      FOR C$:  REFERENCED WITH X=8
 1400  *--------------------------------
 1410  C.LENGTH            .BS 1
 1420  C.STRING            .BS 2
 1430  *--------------------------------
 1440  CARRY               .BS 1
 1450  TENS.FLAG           .BS 1
 1460  C.ADDR              .BS 2
 1470  MAX.DIGITS.BEFORE   .BS 1
 1480  MAX.DIGITS.AFTER    .BS 1
 1490  *--------------------------------
 1500  *      & BRANCHES HERE
 1510  *--------------------------------
 1520  STRADD CMP #$C8     CHECK FOR "+$,"
 1530         BNE .1
 1540         JSR AS.CHRGET
 1550         CMP #'$
 1560         BNE .1
 1570         JSR AS.CHRGET
 1580         CMP #',
 1590         BEQ .2
 1600  .1     JMP AS.SYNERR
 1610  *--------------------------------
 1620  .2     LDX #0       POINT AT A$ DATA
 1630         JSR PARSE.STRING.NAME   FIRST OPERAND
 1640         LDX #4       POINT AT B$ DATA
 1650         JSR PARSE.STRING.NAME   SECOND OPERAND
 1660         JSR AS.CHKCOM           RESULT STRING
 1670         JSR AS.PTRGET
 1680         STY C.STRING+1          ADDRESS OF VARIABLE
 1690         STA C.STRING
 1700  *--------------------------------
 1710  *      SCAN BOTH STRINGS TO DETERMINE BUFFER PARAMETERS
 1720  *--------------------------------
 1730         LDX #0       POINT AT A$ DATA
 1740         JSR SCAN     GET Y=LEFT LENGTH, X=RIGHT LENGTH
 1750         STX A.DIGITS.AFTER
 1760         STX MAX.DIGITS.AFTER
 1770         STY MAX.DIGITS.BEFORE
 1780         LDX #4       POINT AT B$ DATA
 1790         JSR SCAN     GET Y=LEFT LENGTH, X=RIGHT LENGTH
 1800         STX B.DIGITS.AFTER
 1810         CPX MAX.DIGITS.AFTER
 1820         BCC .3
 1830         STX MAX.DIGITS.AFTER
 1840  .3     CPY MAX.DIGITS.BEFORE
 1850         BCC .4
 1860         STY MAX.DIGITS.BEFORE
 1870  *--------------------------------
 1880  *      CLEAR THAT MUCH OF THE BUFFER
 1890  *--------------------------------
 1900  .4     INC MAX.DIGITS.BEFORE  TWO MORE CHARS FOR
 1910         INC MAX.DIGITS.BEFORE    SIGN AND CARRY
 1920         CLC
 1930         LDA MAX.DIGITS.BEFORE  TOTAL LENGTH OF RESULT
 1940         ADC MAX.DIGITS.AFTER
 1950         STA C.LENGTH
 1960         TAY
 1970         LDA #0       ZERO THE BUFFER FOR USE AS AN
 1980  .5     STA BUFFER-1,Y    ACCUMULATOR
 1990         DEY
 2000         BNE .5
 2010  *--------------------------------
 2020  *      ADD A$ TO BUFFER
 2030  *--------------------------------
 2040         LDX #0       POINT AT A$ DATA
 2050         JSR ADD.TO.BUFFER
 2060  *--------------------------------
 2070  *      ADD B$ TO BUFFER
 2080  *--------------------------------
 2090         LDX #4       POINT AT B$ DATA
 2100         JSR ADD.TO.BUFFER
 2110  *--------------------------------
 2120  *      CONVERT BUFFER TO ASCII AGAIN
 2130  *--------------------------------
 2140         LDA BUFFER   SEE IF NUMBER IS NEGATIVE
 2150         CMP #5       SET CARRY IF NEGATIVE, ELSE CLEAR
 2160         ROR          MAKE A=0XXXXXXX OR 1XXXXXXX
 2170         STA CARRY        TO SET OR CLEAR THESE FLAGS
 2180         STA TENS.FLAG    APPROPRIATELY
 2190         LDX C.LENGTH
 2200         BEQ .10      FINISHED
 2210  .6     LDA BUFFER-1,X
 2220         CMP #'.
 2230         BEQ .9
 2240         BIT TENS.FLAG
 2250         BPL .8
 2260         ASL CARRY
 2270         LDA #10
 2280         SBC BUFFER-1,X
 2290         CMP #10
 2300         BCC .7 
 2310         SBC #10
 2320  .7     ROR CARRY
 2330  .8     ORA #'0
 2340  .9     STA BUFFER-1,X
 2350         DEX
 2360         BNE .6
 2370  .10    BIT TENS.FLAG   SEE ABOUT FINAL SIGN
 2380         BPL .11         VALUE IS POSITIVE
 2390         LDA #'-         NEGATIVE, SO STUFF "-"
 2400         STA BUFFER        IN FRONT OF BUFFER
 2410  .11    JSR CHOP.OFF.LEADING.ZEROES
 2420  *--------------------------------
 2430  *      PUT (BUFFER) IN OUTPUT STRING
 2440  *--------------------------------
 2450         LDX #8       POINT AT C$ DATA
 2460         JSR SETUP.OPERAND
 2470         JSR AS.GETSPA
 2480         LDY #0
 2490         STA (VARPNT),Y
 2500         INY
 2510         LDA FRESPC
 2520         STA (VARPNT),Y
 2530         INY
 2540         LDA FRESPC+1
 2550         STA (VARPNT),Y
 2560         LDY C.ADDR+1
 2570         LDX C.ADDR
 2580         LDA C.LENGTH
 2590         JMP AS.MOVSTR
 2600  *--------------------------------
 2610  *      ADD STRING TO BUFFER
 2620  *      ENTER WITH X=0 FOR A$, X=4 FOR B$
 2630  *--------------------------------
 2640  ADD.TO.BUFFER
 2650         JSR SETUP.OPERAND
 2660         TAY          STRING LENGTH
 2670         LDA A.DIGITS.AFTER,X
 2680         PHA
 2690         LDX #0
 2700         LDA (VARPNT,X)  CHECK FOR MINUS SIGN
 2710         CMP #'-
 2720         BEQ .1       YES, CARRY SET
 2730         CLC          ELSE CLEAR CARRY
 2740  .1     ROR          MAKE A=0XXXXXXX OR 1XXXXXXX
 2750         STA TENS.FLAG  MAKE FLAGS<0 IF MINUS
 2760         STA CARRY
 2770  *--------------------------------
 2780         CLC          POINT INTO BUFFER WHERE OPERAND
 2790         PLA               ALIGNS
 2800         ADC MAX.DIGITS.BEFORE
 2810         TAX
 2820  *--------------------------------
 2830  .2     TXA          TEST X FOR BEGINNING OF BUFFER
 2840         BEQ .8       YES, FINISHED!
 2850         DEX          NO, BACK ANOTHER ONE
 2860         TYA          CHECK OPERAND POINTER
 2870         BEQ .3       END OF OPERAND, BUT WE
 2880  *                          STILL NEED TO FINISH CARRIES
 2890         DEY          BACK UP IN OPERAND
 2900         LDA (VARPNT),Y  NEXT CHAR FROM OPERAND
 2910         CMP #'.      DECIMAL POINT?
 2920         BEQ .7       YES, SKIP OVER IT
 2930         CMP #'-      MINUS SIGN?
 2940         BNE .4       NO, MUST BE DIGIT
 2950  .3     LDA #'0      ASCII ZERO THEN
 2960  .4     AND #$0F     CONVERT ASCII TO BINARY
 2970         BIT TENS.FLAG
 2980         BPL .5       NOT 9'S COMPLEMENTING
 2990         EOR #$FF
 3000         CLC
 3010         ADC #10      FORM 9'S COMPLEMENT
 3020  .5     ASL CARRY    GET PREVIOUS CARRY INTO C-BIT
 3030         ADC BUFFER,X
 3040         CMP #10      SEE IF CARRY
 3050         BCC .6       NO
 3060         SBC #10      YES, BACK THIS DIGIT DOWN
 3070  .6     ROR CARRY    SAVE CARRY FOR NEXT LOOP
 3080  .7     STA BUFFER,X
 3090         JMP .2
 3100  .8     RTS
 3110  *--------------------------------
 3120  *      SCAN STRING
 3130  *      ENTER WITH X=0 FOR A$, X=4 FOR B$
 3140  *      RETURN WITH X = # DIGITS AFTER DECIMAL POINT
 3150  *                        (COUNTING THE DECIMAL POINT)
 3160  *                  Y = # DIGITS BEFORE DECIMAL POINT
 3170  *                        (COUNTING SIGN IF ANY)
 3180  *--------------------------------
 3190  SCAN
 3200         JSR SETUP.OPERAND
 3210         LDY #0
 3220         TAX
 3230         BEQ .2       NULL STRING
 3240  .1     LDA (VARPNT),Y
 3250         CMP #'.      LOOKING FOR DECIMAL POINT
 3260         BEQ .2 
 3270         INY
 3280         DEX
 3290         BNE .1
 3300  .2     RTS
 3310  *--------------------------------
 3320  *      CHOP OFF LEADING ZEROES
 3330  *--------------------------------
 3340  CHOP.OFF.LEADING.ZEROES
 3350         LDY #1       FIND FIRST NON-ZERO POSITION
 3360  .1     LDA BUFFER,Y
 3370         CMP #'0
 3380         BNE .2 
 3390         INY
 3400         CPY MAX.DIGITS.BEFORE
 3410         BCC .1 
 3420         DEY
 3430  .2     LDA BUFFER   SIGN, MAYBE
 3440         CMP #'-
 3450         BNE .3 
 3460         DEY
 3470         STA BUFFER,Y
 3480  .3     CLC
 3490         TYA
 3500         ADC #BUFFER
 3510         STA C.ADDR
 3520         LDA #0
 3530         ADC /BUFFER
 3540         STA C.ADDR+1
 3550         SEC
 3560         TYA
 3570         EOR #$FF
 3580         ADC C.LENGTH
 3590         STA C.LENGTH
 3600         RTS
 3610  *--------------------------------
 3620  *      PARSE STRING NAME, SET UP POINTER
 3630  *--------------------------------
 3640  PARSE.STRING.NAME
 3650         TXA
 3660         PHA
 3670         JSR AS.CHKCOM
 3680         JSR AS.PTRGET     GET SECOND STRING PNTR
 3690         PLA
 3700         TAX
 3710         LDY #0
 3720         LDA (VARPNT),Y    GET LENGTH
 3730         STA A.LENGTH,X
 3740         INY
 3750         LDA (VARPNT),Y    GET ADDRESS OF DATA
 3760         STA A.ADDR,X
 3770         INY
 3780         LDA (VARPNT),Y
 3790         STA A.ADDR+1,X
 3800         RTS
 3810  *--------------------------------
 3820  *      LOAD ADDRESS INTO VARPNT
 3830  *      X=0 FOR A$, X=4 FOR B$
 3840  *--------------------------------
 3850  SETUP.OPERAND
 3860         LDA A.ADDR,X
 3870         STA VARPNT
 3880         LDA A.ADDR+1,X
 3890         STA VARPNT+1
 3900         LDA A.LENGTH,X
 3910         RTS
 3920  *--------------------------------

More on the Macro-Videx ConnectionBill Linn

Don Taylor's original article in the August (1982) issue of AAL and Mike Laumer's follow-up the next month gave us the patches for running the S-C Macro Assembler in conjunction with the Videx 80-column board. I recently purchased a Videx card in order to implement the 80-column version of ES-CAPE, so I installed the patches.

I have really enjoyed using the Macro assembler in 80-column mode. Naturally, though, I couldn't resist adding a few enhancements to Don's and Mike's work.

Mike added the right arrow code, which copies characters off the Videx screen, but he stopped short of implementing the Escape-L LOAD sequence. To install the following code, you will need to change line 3080 in Don's article to point to my routine. Change it to "3080 .DA MY.ESC.L-1". Also, the STX instruction at line 4235 in Mike's article must be labelled GETCH.

     *--------------------------------------
     SCM.INSTALL .EQ SCM.BASE+$52A
     *
     MY.ESC.L 
            CPX #0          CURSOR AT BEGINNING?
            BEQ .1          YES, CONTINUE
            JMP SCM.ESC.L   NO, LET S-C HANDLE IT
     .1     LDA #0          CONNECT DOS
            STA $AA52       BY SETTING INTERCEPT STATE = 0
            LDA #$84        SEND A CTRL-D
            JSR MON.COUT
     .2     LDA LOADCMD,X
            JSR SCM.INSTALL
            JSR FAKE.COUT
            CPX #6
            BCC .2
     .3     STX $406        SAVE CHAR POS'N
            JSR GETCH       GET SCREEN CHAR
            LDX $406        RESTORE POS'N
            JSR SCM.INSTALL
            JSR FAKE.COUT
            CPX #40         40 CHARS SENT YET?
            BNE .3          NO, LOOP BACK
            JMP CLREOP      CLEAR TO END OF PAGE
     *                      AND EXIT
     *
     LOADCMD    .AS -/LOAD  /
     *--------------------------------------

Secondly, I wanted a longer "*---" line on my screen, so I changed it to 68 characters instead of 38. This uses more of the 80-column screen, without wrapping around during assembly. To make this modification insert the following two lines after the label "INSTALL.PATCHES" in Don's original listing:

     LDA #68
     STA SCM.BASE+$494

Finally, I changed the dimensions of the Videx cursor so that it looks like a blinking underline instead of a blinking block. (Users of my ES-CAPE are already familiar with my love for the blinking underline!) Insert the following lines immediately after the "INSTALL.VECTORS" label:

     LDA #$0A       VIDEX REGISTER 10
     STA V.DEV0
     LDA #$68
     STA V.DEV0+1
     LDA #$0B       VIDEX REGISTER 11
     STA V.DEV0
     LDA #$08
     STA V.DEV0+1

Speaking of ES-CAPE, I am making progress on Version 2 and have included suggestions from many of you. If you have others, please drop me a line soon at 3199 Hammock Creek, Lithonia, GA 30058, or call evenings at (404) 483-7637.


On CATALOG ARRANGER and RAM Card DOSChuck Welman

Chuck Welman just called to report some errors in the January piece on using CATALOG ARRANGER with a relocated DOS. He says that the sentence about where to put the BIT MONREAD statements had problems. Here's his corrected version:

"Then add BIT MONREAD at these positions: Lines 1675, 3775, 3895, 3955, 4015 (".5" moved to this line), 4205 (".3" moved to this line, 4315, 4425, 4455 (".7" moved to this line), and 4895."

Chuck also passed along instructions for using FILENAME EDITOR with a RAM Card DOS. Here are his additions:

     2635 .3     BIT MONREAD
     2640        JSR MON.BELL
     2642        BIT DOSREAD
     2644        BIT DOSREAD
     2646        RTS

Thanks to all of you for showing your appreciation for these programs.


Quickie No. 6Bob Sander-Cederlof

Here is a little run-anywhere program sure to wake up the neighborhood dogs. Put it in your program as a last resort to get attention, because the only escape is by RESET or power-off.

     1000 ALARM  INY       INCREMENT DELAY TIME
     1010        TYA
     1020        TAX       DELAY COUNT TO X
     1030        LDA $C030 TOGGLE SPEAKER
     1040 .1     DEX       DELAY LOOP
     1050        BNE .1
     1060        BEQ ALARM ....FOREVER....

That's it, only eleven bytes! For a slightly different effect, change the "DEX" in line 1030 to "INX".


Patch to Fix .TI ProblemMike Laumer

You may have noticed the annoying problem with the .TI directive, in which there is sometimes a blank line after the title line and sometimes not. The blank line is there when the page break is forced with a .PG directive, but not when it is caused by merely filling a page.

The following little patch will fix it. I haven't put a definite address on the patch, because I don't know what other patches you may already have appended to the assembler. Just find an empty place and plop it in!

Motherboard version: :$21F0:4C xx yy (was 20 CF 2C) :$yyxx:20 CF 2C 4C E3 21

RAM Card version: :$E33C:4C xx yy (was 20 1B EE) :$yyxx:20 1B EE 4C 2F E3

Another .TI problem of which I am aware is that the line count is messed up on the first page of the symbol table listing. This is caused by the fact that the extra carriage returns in the "SYMBOL TABLE" message are not counted. You can clean up the appearance by making the last line of your source program be ".PG"; this forces the symbol table to start on a fresh page.


Apple //e NotesBob Sander-Cederlof

We don't have one yet, but we did play with one for about an hour last week. All our software works fine, as long as you stay in the 40-column caps-lock mode. We will be making new versions available in the near future which take full advantage of the extended memory, lower-case, and 80-column display.

The best write-up I have seen yet on the //e is in the February 1983 Apple Orchard (published by the International Apple Core, 908 George St., Santa Clara, CA 95050).

Here are some of the things that caught my attention:

* Real shift key, and a caps-lock key.

* Open-Apple and Closed-Apple keys, which duplicate the first two paddle buttons.

* Recessed RESET key. CTRL-RESET required (no longer a switchable option). CTRL-Closed-Apple-RESET starts a memory test program.

* Two 8K ROMs, instead of six 2K ROMs. The extra 2K of ROM space is used by the modified Monitor program. Fancy soft-switches map the extra 2K into the $C000-C7FF space. These sockets are supposedly compatible with 2764 EPROMs.

* Apparently the Monitor now uses (clobbers) zero-page locations $08 and $1F.

* Up- and down-arrows on the keyboard. Down is CTRL-J, or linefeed. Up is CTRL-K.

* The keyboard includes all the ASCII set, even $7F (DELETE, or RUBOUT).

* 64K RAM on the motherboard. This simulates an Apple II Plus with a 16K RAM card in slot 0.

* New slot instead of slot 0, with 60-pin connector (other slots still have 50-pin connectors). Apple's 80-column card plugs in here. The extra pins carry other signals not normally available at the slots. Look for some amazing new combined function cards from the peripheral-card makers for this slot! I wouldn't be surprised to find ads real soon for 256K RAM cards including 80-column support, clock- calendar, serial/parallel interfaces, and all on one card.

* 80-column card with or without extra 64K RAM. But this 64K RAM is soft-switched in a totally different manner. It maps over the same space as the motherboard 64K, with switches to map portions such as page-zero, text screen, hi-res screen, and so on.

* Now you can READ the state of most of the soft-switches. Bit 7 (high bit) tells the state, as follows:

       $C013 -- RAMREAD
       $C014 -- RAMWRT
       $C015 -- SLOTCXROM/CX00ROM
       $C016 -- ALTZP/MAIN
       $C017 -- SLOTC3ROM/SLOTROM
       $C018 -- 80 COL STORE
       $C019 -- VERTICAL BLANKING
       $C01A -- TEXT
       $C01B -- MIXED MODE
       $C01C -- PAGE2
       $C01D -- HIRES
       $C01E -- ALTCHAR
       $C01F -- 80 COL DISP

* Yes, you saw right...the vertical blanking signal is now readable! So lovers of Lancaster's Enhancements can continue to tinker!

* Inverse lower-case display is selectable, at the expense of the flashing mode.

* The cursor display is different. A small checkerboard alternates with the character under the cursor in 40-column mode. In 80-column mode an inverse blank is the normal cursor, and an inverse "+" is used when in escape-mode.

Whether we view the changes as improvements or not, the //e will very soon be the standard we all have to deal with. The same situation arose when Apple switched from II to II Plus. A year from now, when 300,000 have been sold, we will wonder how we ever lived without it!


Macro Assembler PatchBob Sander-Cederlof

Peter Bartlett, of Chicago, has reported an unpublished limit on the number of Target Files that can be generated by one assembly. Right now there can only be 31; above that number the load address and length bytes go astray. If you need more than 31 files from one assembly, you can make the following patches:

Regular version

          :$29EA:3F

Language Card version

          :$C083 C083 EB36:3F N C080

These patches will allow you to have up to 63 target files. That should be plenty!


TRAPPER: An Applesoft Input TunerAllen Marsalis

How would you like a radio which played every available station at one time? Well that's how I sometimes feel about using Applesoft's INPUT statement. I want to be able to "tune in" on the character(s) of the input stream, in much the same way as a radio tunes into a station. Applesoft's INPUT statement, however, accepts all characters typed into the keyboard and allows up to 255 of them. This means that I have to do a lot of checking and monitoring of string lengths and characters to avoid input errors.

For example, when answering a Y or N question, what happens when the user inputs "WXYZ"? Provisions are needed within the program to guard against such errors. This can be very inconvenient and space-consuming, yet it is essential for good programming.

A better example occurs when you are creating a disk file. Field lengths and data types are often restricted, such as in a name, address, or social security number. A SSN, for instance, has a fixed length and must be constructed of numbers only. Checking a field such as this can be very time consuming and lengthy. In fact, it seems that a quarter of the contents of my Applesoft programs does nothing but check on field lengths, option boundaries, and other input checks.

So, I set out to create an input routine which would allow Applesoft to "tune" into the characters specified and also monitor the field length. I've seen several input routines such as this on larger systems, but all had one disadvantage: Only a fixed number of options were available, such as alpha only, numeric only, and (Y or N) input. More options available meant more parameters were necessary, making the systems more cumbersome to work with. After much thought I decided on a totally new approach which would allow almost limitless control of input. I christened this routine TRAPPER for "Tuning and Regulating APPlesoft Entries by Restriction."

TRAPPER employs a coded restriction string (not unlike Applesoft's IF expression) to tune out the characters I don't want to accept. TRAPPER is then, in essence, a tiny interactive interpreter that provides a short, convenient method of filtering out any unwanted characters in the input. Here's how it works.

TRAPPER uses three parameters as follows:

Syntax: & INPUT (A, B$, C$)

     A:  Input field length (real expression)
    B$:  Coded restriction string (string expression)
         includes:  > < = ' AND OR NOT <sp> <single char>
    C$:  Input string (string variable)
         variable to receive input

As I have said, the restriction string is a simple relational expression as is used by Applesoft's IF statement. It is constructed of the following special characters and rules:

  1. < > = are its relational operators
  2. AND OR NOT are its logical operators
  3. Blanks are allowed anywhere within the expression, but lengthy expressions increase the delay between keystrokes.
  4. One and only one character is allowed within single quotes.
  5. <cr> and <-- have special functions and cannot be trapped.
  6. Parentheses are not yet implemented.

EXAMPLES:

     YN$ = " ='Y' OR ='N' "            :REM (Y OR N) ONLY
     NOSP$ = " NOT =' ' "              :REM NO SPACES ALLOWED
     MENU$ = " NOT <'1' AND NOT >'4' " :REM ALLOWS 1 THRU 4
     WAITCR$ = ""                      :REM WAIT FOR A <CR>

After using Trapper awhile, I noticed a significant reduction in the size of my Applesoft programs, with even better error trapping than ever before possible. And it doesn't print that leading question mark which I never did like (not all input prompts are questions.)

For a 48K Apple, DOS sets HIMEM at $9600. Trapper resides just below this at $9300 and moves HIMEM down to that point.

 1000  *SAVE S.TRAPPER
 1010  *--------------------------------
 1020  *      TRAPPER, BY ALLEN MARSALIS
 1030  *--------------------------------
 1040         .OR $9300
 1050         .TF B.TRAPPER
 1060  *--------------------------------
 1070  RLEN   .EQ $1A      RESTRICTION STRING
 1080  RSTR   .EQ $1B      DESCRIPTOR
 1090  TEMPPT .EQ $52
 1100  LASTPT .EQ $53
 1110  FRESPC .EQ $71,72
 1120  HIMEM  .EQ $73,74
 1130  VARPNT .EQ $83,84
 1140  FACMO  .EQ $A0
 1150  *--------------------------------
 1160  BUF    .EQ $200     INPUT BUFFER
 1170  AMPVEC .EQ $3F5     AMPERSAND VECTOR
 1180  STROBE .EQ $C010    KEYBOARD STROBE
 1190  *--------------------------------
 1200  AS.FRMNUM  .EQ $DD67     EVALUATE NUMERIC FORMULA
 1210  AS.CHKSTR  .EQ $DD6C     REQUIRE STRING
 1220  AS.FRMEVL  .EQ $DD7B     EVALUATE GENERAL FORMULA
 1230  AS.CHKCLS  .EQ $DEB8     REQUIRE ")"
 1240  AS.CHKCOM  .EQ $DEBE     REQUIRE ","
 1250  AS.CHKOPN  .EQ $DEBB     REQUIRE "("
 1260  AS.SYNCHR  .EQ $DEC0     REQUIRE (A-REG)
 1270  AS.SYNERR  .EQ $DEC9     SYNTAX ERROR
 1280  AS.PTRGET  .EQ $DFE3     GET VARIABLE PNTR
 1290  AS.GETSPA  .EQ $E452     GET SPACE IN STRING AREA
 1300  AS.MOVSTR  .EQ $E5E2     COPY STRING DATA
 1310  AS.FRETMP  .EQ $E604     FREE TEMPORARY STRING
 1320  AS.CONINT  .EQ $E6FB     CONVERT FAC TO 8-BITS
 1330  *--------------------------------
 1340  MON.CLREOL .EQ $FC9C     CLEAR TO END-OF-LINE
 1350  MON.RDKEY  .EQ $FD0C     READ A KEY
 1360  MON.COUT   .EQ $FDED     DISPLAY A CHARACTER
 1370  *--------------------------------
 1380  SETUP  LDA #$4C     "JMP" OPCODE
 1390         STA AMPVEC
 1400         LDA #TRAPPER
 1410         STA AMPVEC+1
 1420         LDA /TRAPPER
 1430         STA AMPVEC+2
 1440         LDA #SETUP   SET HIMEM UNDER TRAPPER
 1450         STA HIMEM
 1460         LDA /SETUP
 1470         STA HIMEM+1
 1480         RTS
 1490  *--------------------------------
 1500  *      AMPERSAND COMES HERE
 1510  *--------------------------------
 1520  TRAPPER
 1530         LDA #$84          "INPUT" TOKEN
 1540         JSR AS.SYNCHR
 1550         JSR AS.CHKOPN     "& INPUT ("
 1560         JSR AS.FRMNUM     READ FIELD LENGTH PARAMETER
 1570         JSR AS.CONINT     CONVERT TO 8-BIT VALUE
 1580         STX FL            SAVE FIELD LENGTH
 1590         JSR AS.CHKCOM     ","
 1600         JSR AS.FRMEVL     GET RESTRICTION STRING
 1610         JSR AS.CHKSTR
 1620         JSR AS.CHKCOM     ANOTHER ","
 1630         LDY #2            SAVE DESCRIPTOR
 1640  .1     LDA (FACMO),Y
 1650         STA RLEN,Y
 1660         DEY
 1670         BPL .1
 1680         LDA TEMPPT        DID FRMEVL MAKE A TEMP STRING?
 1690         CMP #$56
 1700         BCC .2            NO
 1710         LDA LASTPT        YES, SO FREE THE TEMP
 1720         LDY #0
 1730         JSR AS.FRETMP
 1740  .2     LDA #0            INIT BUFFER INDEX
 1750         STA BINDEX
 1760  *---UNDERSCORE INPUT FIELD-------
 1770         LDA #$DF          UNDERLINE CHAR
 1780         JSR PRINT.FIELD
 1790         LDA #$88          BACKSPACE  TO BEGINNING AGAIN
 1800         JSR PRINT.FIELD
 1810  *---READ A KEY-------------------
 1820         BIT STROBE        DON'T ALLOW TYPE AHEAD
 1830  .3     JSR MON.RDKEY     READ NEXT KEY
 1840         AND #$7F          INTERNAL FORM
 1850         STA KEY           SAVE IT
 1860  *---BACKSPACE--------------------
 1870         CMP #$08          BACKSPACE?
 1880         BNE .22           NO
 1890         LDA BINDEX        IGNORE AT BEGINNING OF LINE
 1900         BEQ .21
 1910         LDA #$88          YES, ECHO IT
 1920         JSR MON.COUT
 1930         LDA #$DF          REPLACE UNDERLINE
 1940         JSR MON.COUT
 1950         LDA #$88          BACKSPACE AGAIN
 1960         JSR MON.COUT
 1970         DEC BINDEX        BACK UP BUFFER TOO
 1980  .21    JMP .3
 1990  *---CARRIAGE RETURN--------------
 2000  .22    CMP #$0D          RETURN?
 2010         BNE .23           NO
 2020         JSR MON.CLREOL
 2030         JSR AS.PTRGET     GET DESTINATION STRING
 2040         JSR AS.CHKCLS     MUST HAVE ")" AT END
 2050         LDA BINDEX        LENGTH OF INPUT LINE
 2060         JSR AS.GETSPA     FIND ROOM FOR IT
 2070         LDY #0            MOVE IN DESCRIPTOR
 2080         STA (VARPNT),Y
 2090         INY
 2100         LDA FRESPC
 2110         STA (VARPNT),Y
 2120         INY
 2130         LDA FRESPC+1
 2140         STA (VARPNT),Y
 2150         LDY /BUF          COPY DATA INTO STRING
 2160         LDX #BUF
 2170         LDA BINDEX
 2180         JMP AS.MOVSTR     ...AND RETURN
 2190  *---CHECK IF VALID KEY-----------
 2200  .23    JSR CHECK.RESTRICTIONS
 2210  *---CHECK VALIDITY AND ECHO------
 2220         LDA KEY           GET KEY AGAIN
 2230         LDA BINDEX
 2240         CMP FL
 2250         BCS .27           TOO FAR, ABORT KEY
 2260         LDA NEW           IF NEW = FAIL, ABORT KEY
 2270         BEQ .27           YES, ABORT KEY
 2280         LDA KEY
 2290         LDY BINDEX
 2300         STA BUF,Y         PUT KEY INTO BUFFER
 2310         INC BINDEX
 2320         CMP #$20          IF KEY WAS CONTROL-KEY,
 2330         BCS .26              THEN PRINT SPACE
 2340         LDA #$20
 2350  .26    ORA #$80
 2360         JSR MON.COUT      ECHO
 2370         JMP .3            NEXT KEY
 2380  .27    LDA #$07          RING BELL
 2390         BNE .26
 2400  *--------------------------------
 2410  CHECK.RESTRICTIONS
 2420         LDA #0
 2430         STA RINDEX        RINDEX = 0
 2440         STA NEW           NEW = FAIL
 2450         STA ANDOR         ANDOR = OR
 2460         STA NOT           NOT = FALSE
 2470  *---FETCH OPERATOR---------------
 2480  .4     LDY RINDEX        IF RINDEX >= RLEN,
 2490         CPY RLEN              THEN QUIT SCAN
 2500         BCC .5            NOT YET
 2510         RTS
 2520  .5     LDA (RSTR),Y      FETCH OPERATOR
 2530         INC RINDEX
 2540  *---DETERMINE OPERATION----------
 2550         CMP #'            IGNORE BLANKS
 2560         BEQ .4
 2570         CMP #'<           < = >, THEN FETCH OPERAND
 2580         BEQ .10
 2590         CMP #'>
 2600         BEQ .10
 2610         CMP #'=
 2620         BEQ .10
 2630         CMP #'A           "AND"
 2640         BEQ .7
 2650         CMP #'O
 2660         BEQ .8
 2670         CMP #'N           "NOT"
 2680         BEQ .9
 2690         JMP AS.SYNERR
 2700  *---AND OPERATOR-----------------
 2710  .7     LDA #'N
 2720         JSR SYNSTR
 2730         LDA #'D
 2740         JSR SYNSTR
 2750         LDA #1            SET AND OPERATOR
 2760         STA ANDOR
 2770         BNE .4            ...ALWAYS
 2780  *---OR OPERATOR------------------
 2790  .8     LDA #'R
 2800         JSR SYNSTR
 2810         LDA #0            SET OR OPERATOR
 2820         STA ANDOR
 2830         BEQ .4            ...ALWAYS
 2840  *---NOT OPERATOR-----------------
 2850  .9     LDA #'O
 2860         JSR SYNSTR
 2870         LDA #'T
 2880         JSR SYNSTR
 2890         LDA #1            SET NOT OPERATOR "TRUE"
 2900         STA NOT
 2910         BNE .4            ...ALWAYS
 2920  *---FETCH OPERAND----------------
 2930  .10    STA ROPR
 2940         LDA #$27          CHECK FOR APOSTROPHE
 2950         JSR SYNSTR
 2960         LDY RINDEX
 2970         LDA (RSTR),Y      GET OPERAND
 2980         STA ROPD
 2990         INC RINDEX
 3000         LDA #$27          ANOTHER APOSTROPHE
 3010         JSR SYNSTR
 3020  *---EVALUATE RELATIONAL OPERATION
 3030         LDA NEW
 3040         STA LAST          LAST = NEW
 3050         LDA #0            NEW = FAIL
 3060         STA NEW
 3070         LDY ROPR          OPERATOR
 3080         LDA KEY           LATEST KEY
 3090         CMP ROPD          COMPARE TO OPERAND
 3100         BEQ .11           THEY ARE EQUAL
 3110         BCC .12           KEY < OPERAND
 3120         CPY #'>           KEY > OPERAND
 3130         BEQ .13           SUCCESS!
 3140         BNE .14           FAIL.
 3150  .11    CPY #'=
 3160         BEQ .13           SUCCESS
 3170         BNE .14           FAIL
 3180  .12    CPY #'<
 3190         BNE .14           FAIL
 3200  .13    LDA #1            FLAG SUCCESS
 3210         STA NEW
 3220  *---PERFORM NOT OPERATION--------
 3230  .14    LDA NOT           IF NOT, TOGGLE NEW
 3240         BEQ .17           NOT NOT
 3250         LDA NEW
 3260         EOR #1
 3270         STA NEW
 3280         LDA #0            CLEAR NOT
 3290         STA NOT
 3300  *---PERFORM AND/OR OPERATION-----
 3310  .17    LDA LAST
 3320         LDY ANDOR
 3330         BEQ .18           OR
 3340         AND NEW           AND
 3350         STA NEW
 3360         JMP .4
 3370  .18    ORA NEW
 3380         STA NEW
 3390         JMP .4
 3400  *--------------------------------
 3410  SYNSTR STA HOLD     SAVE CHAR
 3420  .1     LDY RINDEX
 3430         LDA (RSTR),Y
 3440         INC RINDEX
 3450         CMP #'            IGNORE BLANKS
 3460         BEQ .1
 3470         CMP HOLD
 3480         BEQ .2
 3490         JMP AS.SYNERR
 3500  .2     RTS
 3510  *--------------------------------
 3520  PRINT.FIELD
 3530         LDY FL
 3540  .1     JSR MON.COUT
 3550         DEY
 3560         BNE .1
 3570         RTS
 3580  *--------------------------------
 3590  HOLD   .BS 1
 3600  NOT    .BS 1
 3610  ANDOR  .BS 1
 3620  FL     .BS 1
 3630  NEW    .BS 1
 3640  LAST   .BS 1
 3650  KEY    .BS 1
 3660  BINDEX .BS 1
 3670  RINDEX .BS 1
 3680  ROPR   .BS 1
 3690  ROPD   .BS 1
 3700  *--------------------------------

Star-tling StuntsBill Morgan & Mike Laumer

In most assemblers, including the S-C Macro Assembler, you can use the character "*" in the operand of an instruction to mean the current value of the location counter. (The location counter is a variable used by the assembler to keep track of where the next byte of object code goes.) Here are a couple of simple examples of using the *, from page 6-2 of the Macro Assembler manual:

          0800- 03       1000 QT     .DA #QTSZ
          0801- 41 42 43 1010        .AS /ABC/
          0003-          1020 QTSZ   .EQ *-QT-1
                         1030  
          0804- 00 00    1040 VAR    .DA *-*
                         1050  
          0806-          1060 FILLER .BS $900-*
          0900-          1070 END    .EQ *

The QT, QTSZ example uses the * to help calculate the length of a string of characters. The VAR line uses "*-*" to define a variable as having a value of zero.

The expression labelled FILLER causes the assembler to skip ahead to $900. This has much the same effect as .OR $900, but it won't cause the assembler to close a target file, the way .OR would.

One thing Bill wanted was an expression to have the assembly skip up to the beginning of the next page, no matter what that page might be. Here's what we came up with:

          0800- 34 12    1000 START  .DA $1234
          0802-          1010 FILL   .BS *+255/256*256-*
          0900- 45 23    1020 END    .DA $2345

If you change the origin to $C00, END will move to $D00. With this coding, END will always be $100 above START. Note that there is no precedence when the assembler is evaluating an expression. Terms are taken strictly left-to-right. But notice how smart the expression cracker in the assembler is! It knows that a "*" between numbers or labels means "multiply", and a "*" between arithmetic operators means "location counter".

In the American Heart Association CPR project Mike uses lots of overlays, and has to make sure that modules don't grow above a certain address. He does it by putting lines like these at the end of a module:

          1000        .DO *>LIMIT
          1010   !!! PROGRAM TOO BIG !!!
          1020        .FIN

Here's an example, to keep a program below the Hi-res pages:

          1000        .OR $1FFE
          1010        .DA $4321
          1020        .DO *>$2000
          1030   !!! PROGRAM TOO BIG !!!
          1040        .FIN

That will assemble just fine:

                         1000        .OR $1FFE
          1FFE- 21 43    1010        .DA $4321
                         1020        .DO *>$2000
                         1040        .FIN

          0000 ERRORS IN ASSEMBLY

But, try inserting another line:

          1015        .DA $1234

Here's what happens:

          *** BAD OPCODE ERROR
           1030   !!! PROGRAM TOO BIG !!!

          0001 ERRORS IN ASSEMBLY

The key to this technique is putting a couple of blanks at the beginning of line 1030. That way, the assembler tries to parse "!!!" as an opcode, and reports an error during pass one, before any code has been generated.

You should be very careful about using "*", and experiment on a test disk when trying something new. For example, take another look at line 1060 in the first listing. If you put "*-$900" for the operand, that would be negative. The result would be $FF07, which would try to write 65,287 zero bytes onto your target file. The next thing you see is probably DISK FULL!

That's about all the tricky things we have room for right now. We hope these hints will help you to navigate "by the stars" in your programming. Just remember to experiment carefully with the * operand before using it in vital programs. There are also many pitfalls on this road!


Promising New BookBob Sander-Cederlof

I just received an advance copy of a forthcoming book by Jules Gilder (a long-time AAL subscriber), titled "Now That You Know Apple Assembly Language, What Can You Do With It?" As the title implies, this will be an intermediate level look at really using assembly language in your Apple. It looks good. As soon as I have details about price and publication date, I'll let you know.


A Sometimes Useful PatchBob Sander-Cederlof

Sometimes you would like to see all the hex bytes a macro produces, but not the expanded lines of source code. The >LIST MOFF directive turns off both, but with the following three byte patch you can see the hex bytes for each macro call.

Motherboard version:   :$218B:0   (was 03)
                       :$21B3:0   (was 05)
                       :$21E2:0   (was 10)

RAM Card version:      :$C083 C083   (enable writing)
                       :$E2D7:0   (was 03)
                       :$E2FF:0   (was 05)
                       :$E32E:0   (was 10)

Don't make these into permanent patches, because there will be times when you want to use the .LIST directives normally. If you feel like making the changes often, you might make two separate versions of the assembler, or make some EXEC files to do the patching on demand.


Source Code for a Word ProcessorBob Sander-Cederlof

I finally have had to face it. I am never going to have time to finish the S-C word processor. It is certainly usable, because we have been using it here for months now. And we use it a lot, writing the newsletter, manuals, letters, etc. My father-in-law uses it, and so does my best friend, Fred. Fred's 11-year-old daughter is also using it, and loves it. She is currently typing a research paper using it.

I know it is easy to use, because I didn't even give Fred a list of commands, let alone a reference manual. Of course, I did sit down with them for a few hours at the first, because they had never even seen a word processor before.

In power, it is somewhere between Applewriter 1.1 and Applewriter II. It is similar in operation to Applewriter 1.1, and works in 40-column mode only. It requires a lower-case display and shift-key mod.

It can read Applewriter 1.1 files, and instantly convert them to standard ASCII form. Normally it uses standard Apple text files (type T in the catalog). Of course, with Bobby Deen's help, I built in FAST read and write of those text files. Faster than binary files, actually. Something like 100 sectors in 7 seconds, if I remember correctly.

I want to make a deal with you. I'll send you the complete commented source code on disk, together with a few sample text files. The text files will describe the command repertoire. If you are already familiar with Applewriter 1.1, you won't have any trouble at all. The assembled word processor will also be there, in case you don't have the S-C Macro Assembler.

But if you do have my assembler, you can proceed to modify, improve, augment, enhance, and so on, to your heart's content.

I'll send you the disk, if you'll send me $50. Or your charge card numbers, of course. I also want your commitment to keep this in the family. You know, don't go out and write a manual and wrap it in a fancy cover and call it YOUR product!

If you do enhance it, send in your additions and we'll make this a joint effort. With all of us working on it, we may soon have the world's best word machine!


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

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