Volume 4 -- Issue 11 | August 1984

In This Issue...

- 18-Digit Arithmetic, Part 4
- Enable/Disable IRQ from Applesoft
- Line Number Cross Reference
- Speaking of Slow Chips
- Modify DOS 3.3 for Big BSAVEs

New Cross Assemblers Available

We have recently added three new cross assemblers to the S-C Macro family.

         Intel Z-8...................$32.50
         General Instruments 1650....$50.00
         General Instruments 1670....$50.00

Unlike previous cross assemblers, which were based on Version 1.0 of the S-C Macro Assembler, these are based on Version 1.1. This means 80-column support for the Videx, STB-80, and Apple //e-//c 80-column, as well as standard 40-column. It also adds certain directives and fixes some problems which were in version 1.0.

We have also been hard at work generating Version 2.0 of the S-C Macro Assembler. It will be ready soon, complete with a brand new manual. It will support all the new opcodes and address modes of the 65C02, 65802, and 65816 processors. Owners of older versions of the S-C Assemblers will be able to upgrade for a very reasonable fee.

18-Digit Arithmetic, Part 4 | Bob Sander-Cederlof

This month we will look at two output conversion routines. The first one always prints in exponential form, while the second one allows setting a field width and number of fractional digits. The routines are written so that the output string may either be printed or fed to an Applesoft string variable.

Let's assume that the value to be printed has already been loaded into the DP18 accumulator, DAC. Lines 1230-1270 describe DAC as a 12-byte variable. The exponent is in the first byte, DAC.EXPONENT. It has a value from $00 to $7F:

           $00 means the whole number is zero
           $01 means the power of ten exponent is -63
           $3F means 10^-1
           $40 means 10^0
           $41 means 10^1
           $7F means 10^63

The 18 digits of the number, plus two extension digits, are in the next ten bytes in decimal format (each digit takes four bits). The extension is zeroed when you load a fresh value into DAC, but after some computations it holds two more digits to guard against roundoff and truncation problems.

The sign of the number is stored in DAC.SIGN: if the value in DAC.SIGN is from $00 to $7F, the number is positive; if from $80 to $FF, the number is negative.

If you have been following the DP18 series from the beginning, and typing in all the code (or getting it from the Quarterly Disks), then you will realize that to integrate each installment takes some work. In order to print the sections separately, and have them separately readable, I must repeat some variable declarations. The listing this month refers to two previously printed subroutines, DADD and MOVE.YA.ARG. These are simply equated to $FFFF in lines 1030 and 1040, so that the code will assemble. If you really want it to work, you have to remove those two lines and include the code for the subroutines. The fact that three installments have already been printed also somewhat restricts me, because even if I see possible improvements I must be careful not to contradict the code you already have.

Quick Standard Format Conversion

The subroutine QUICK.FOUT which begins on line 1600 converts the contents of DAC to a string in FOUT.BUF in the format

           sd.dddddddddddddddddEsxx

The first s is the sign, which is included only if negative. The d's are a series of up to 18 significant digits (trailing zeroes will not be included). The letter E is always included, to signify the power-of-ten exponent field. The letter s after the E is the sign of the exponent: it is always included, and will be either + or -. The xx is a two-digit exponent, and both digits will always be included. The decimal point will be included only if there are non-zero digits after it. If the number is exactly zero, the string in FOUT.BUF will be simply "0". Here are some more examples:

           value           string
           1000            "1E+03"
           .001            "1E-03"
           -262564478.5    "-2.625644785E+08"

Two processes are involved in converting from DAC to FOUT.BUF. One is the analysis of the DAC contents; the other is the process of storing sequential characters into FOUT.BUF. The latter process is handled in most cases by the subroutine at lines 3720-3820. Entry at STORE.CHAR stores the contents of the A-register in the next position in FOUT.BUF, and increments the position pointer (INDEX). Entry at STORE.DIGIT first converts the value in the A-register to an ASCII digit by setting the high nybble to "3". (The digits 0-9 are $30-$39 in ASCII.)

QUICK.FOUT begins by setting INDEX, the FOUT.BUF position pointer, to 0. At lines 1630-1700 the special case of the value in DAC being exactly zero is tested and handled. If the value in DAC is zero, then DAC.EXPONENT will be zero. (This is a convention throughout DP18, to simplify making values of zero and testing for them.) If the value is zero, ASCII zero is stored in FOUT.BUF, followed by a terminating $00 byte.

If the value is not zero, the next job is to check the sign of the value. Lines 1710-1740 insert a minus sign in FOUT.BUF if the value is negative.

Lines 1760-1910 pull out the 18 digits of the mantissa from DAC.HI through DAC.HI+8. The extension digits are ignored. The code here looks an awfully lot like a routine to convert from hex to ASCII, ignoring the possible hex digits A-F. That is because the digits are four bits each, and ARE like hex digits. Lines 1830-1860 insert the decimal point after the first digit.

Lines 1930-2020 look at the formatted number in FOUT.BUF and trim off the trailing zeroes. If all digits after the decimal point are zero, the decimal point is trimmed off too. If you would rather that QUICK.FOUT always printed exactly 18 digits, trailing zeroes and all, you could cut out these lines.

Lines 2040-2290 format the exponent field. First the letter E is installed in FOUT.BUF. Then lines 2060-2120 install the exponent sign. There is a little adjustment here due to the fact that the value in DAC is in the form ".DDDD" times a power of ten, and we are converting to "D.DDD" times a power. That means the exponent in DAC.EXPONENT is one larger than we will print. The DEY at line 2080 adjusts for this offset.

Lines 2130-2180 get the absolute value of the exponent by removing the $40 bias and taking the 2's complement if the result is negative. Lines 2190-2290 convert the binary value of the exponent to two decimal digits, and insert them into FOUT.BUF. Lines 2300-2310 terminate the FOUT.BUF string with $00.

Once the value has been converted to a string in FOUT.BUF, we can either print it or put it into an Applesoft string variable. The subroutine QUICK.PRINT which begins at line 1370 calls QUICK.FOUT and then prints the characters from FOUT.BUF.

Fancier Formatted Conversion

The second conversion routine, which begins at line 2350, allows you to specify the number of digits to display after the decimal point, and the number of characters in the output field. The value will be formatted into the field against the right end, with leading blanks as necessary to fill the field. The value will be rounded to the number of digits that will be converted. If you are familiar with the FORTRAN language, you will recognize this as the "Fw.d" format. W is the width of the field, and D is the number of fractional digits. Here are some examples:

         W  D   value   string
         12 5   1234.56 "  1234.56000"
         12 1   1234.56 "      1234.6"

As before, the output string will be stored in FOUT.BUF in ASCII code, terminated by a $00 byte. If the value will not fit into the W.D field you specify, the entire field will be filled with "*" characters.

As listed here, I have set FOUT.BUF as a 41-byte variable. This means the maximum W is 40, leaving room for the terminating $00 byte. If you want longer conversions, simply change line 1060.

FOUT expects the W and D parameters to be in the A- and Y-registers, respectively. Lines 2380-2460 check W and D for legality. If W is larger than FOUT.BUF.SIZE-1, then it is set to that value. We don't want to store converted characters beyond the end of FOUT.BUF! Then if D is larger than W-1, it is pruned back.

Lines 2480-2540 initialize various variables used during the following conversion. Once again, INDEX will point to the position in FOUT.BUF. I could probably have economized some in the use of variables by re-using the same variables for different purposes, but I wanted to keep them separate to make it easier to code and debug.

Line 2560 calls ROUND.DAC.D to round the value in DAC to D digits after the decimal point. This boils down to adding .5 times 10 to the D power to the value in DAC. ROUND.DAC.D, at lines 3860-4000, does just that. First the rounding number is built in ARG, then DADD adds ARG to FAC.

Lines 2570-2610 store a minus sign into SIGN.CHAR if the value in DAC is negative. SIGN.CHAR was initialized to $00 above. If the sign is negative, line 2590 will increment SIGN.SIZE. SIGN.SIZE will either be 0 or 1, and will be used later in determining how many leading blanks are needed. We cannot store the sign character into FOUT.BUF until the leading blanks have been stored.

Lines 2630 to 2710 compute how many digits will be printed before the decimal point (NO.LEADING.DIGITS), and how many zeroes before the first significant digit after the decimal point (NO.LEADING.ZEROES). If the power-of-ten exponent was negative, there will be no leading digits and some leading zeroes; if positive, there will be some leading digits and no leading zeroes. For example,

        .2345E-5     .000002345    5 leading zeroes
        .2345E+3     234.5         3 leading digits

What if the exponent is more than 18? This would mean more digits might be extracted from DAC than exist, so lines 2730-2790 limit NO.LEADING.DIGITS to 18. NO.INTEGRAL.ZEROES takes up the slack, to print any necessary zeroes between the last significant digit before the decimal point, and the decimal point. For example, if W=25 and D=2, and the value is .1234E+20, we will get NO.LEADING.DIGITS=18 and NO.INTEGRAL.ZEROES=2:

           "  12340000000000000000.00"

Lines 2810-2870 now calculate the total number of non-blank characters which will be required: one for sign if the sign is negative, all the leading digits and integral zeroes before the decimal point, one for the decimal point itself, and D fractional digits. (Just now I noticed that I could have saved two bytes and two cycles by changing line 2810 from CLC to SEC, and eliminating the ADC #1 at line 2860.)

Lines 2890-2920 compute how many significant digits of fraction will be needed. You specified D digits of fraction, but only DD of them will come from the value in DAC. This will be less than D if there are any leading zeroes.

Lines 2940-2970 check whether the converted number can fit in a W-wide field. If not, Lines 3370-3400 fill the field with stars and exit.

Lines 2980-3030 compute how many leading blanks will be needed to right justify the number in the W-field. There is some hopscotch here because we are going to put "0." in front of numbers that have no integral digits.

At long last, we are ready to begin string characters in FOUT.BUF. Lines 3050-3070 store the leading blanks. A subroutine STORE.N.CHARS does the dirty work. STORE.N.CHARS (lines 3670-3710) expects the character to be stored in the A-register, and the count in the Y-register. It also expects that the Z-status is set according to the count in Y. Thus, if the count is zero, the subroutines returns immediately without storing any characters.

STORE.N.DIGITS, at lines 3440-3620, is quite similar to STORE.N.CHARS. Once again, the count must be in the Y-register and the Z-status should reflect the value in Y. Digits are picked out of the value in DAC using an index DIGIT.PICKER, and stored into FOUT.BUF using STORE.DIGIT.

Lines 3090-3110 store the sign if it is negative. Lines 3120-3210 print whatever digits are needed before the decimal point. This will include leading digits (if any) and integral zeroes (if any), or simply one zero (if neither of the other).

Lines 3230-3320 store the fractional part. This includes the decimal point, leading fractional zeroes (if any), and fractional digits (if any).

Finally, lines 3340-3350 store a terminating $00 at the end of the string in FOUT.BUF.

A subroutine called FORMAT.PRINT at line 1450 calls FOUT and then prints the contents of FOUT.BUF. You could now write a higher level routine, if you wish, which would examine the exponent to determine whether the number would fit in a 20-character field. If not, you could use QUICK.PRINT. If so, use FOUT with W=40 and D=18, and then truncate leading spaces and trailing zeroes. This would give you a complete print routine for any numbers, printing them in simple form when they fit and exponential form when they don't. Indeed, just such a routine already exists in DP18, but will have to wait for a future installment. FOUT can also be used as the base for a complete PRINT USING facility, and that is also already in DP18 waiting for future installments.

Meanwhile, enjoy these two conversions, and experiment with your own.

      1000 *SAVE S.DP18 FOUT
      1010 *--------------------------------
      1020 AS.COUT       .EQ $DB5C
      1030 DADD          .EQ $FFFF
      1040 MOVE.YA.ARG   .EQ $FFFF
      1050 *--------------------------------
      1060 FOUT.BUF      .BS 41
      1070 FOUT.BUF.SIZE .EQ *-FOUT.BUF
      1080 *--------------------------------
      1090 W          .BS 1
      1100 D          .BS 1
      1110 INDEX      .BS 1
      1120 SIGN.SIZE  .BS 1
      1130 SIGN.CHAR  .BS 1
      1140 ZERO.CHAR  .BS 1
      1150 WW         .BS 1
      1160 DD         .BS 1
      1170 DIGIT.PICKER        .BS 1
      1180 NO.LEADING.ZEROES   .BS 1
      1190 NO.LEADING.DIGITS   .BS 1
      1200 NO.INTEGRAL.ZEROES  .BS 1
      1210 NO.LEADING.BLANKS   .BS 1
      1220 *--------------------------------
      1230 DAC           .BS 12
      1240 DAC.EXPONENT  .EQ DAC
      1250 DAC.HI        .EQ DAC+1
      1260 DAC.EXTENSION .EQ DAC+10
      1270 DAC.SIGN      .EQ DAC+11
      1280 *--------------------------------
      1290 ARG           .BS 12
      1300 ARG.EXPONENT  .EQ ARG
      1310 ARG.HI        .EQ ARG+1
      1320 ARG.EXTENSION .EQ ARG+10
      1330 ARG.SIGN      .EQ ARG+11
      1340 *--------------------------------
      1350 *   QUICK PRINT
      1360 *--------------------------------
      1370 QUICK.PRINT
      1380        JSR QUICK.FOUT
      1390        JMP FOR.PRINT.1
      1400 *--------------------------------
      1410 *   FORMATTED PRINT
      1420 *      (A)=WIDTH OF FIELD
      1430 *      (Y)=# OF FRACTIONAL DIGITS
      1440 *--------------------------------
      1450 FORMAT.PRINT
      1460        LDX #'0      USE ZEROES BEFORE FRACTION
      1470        STX ZERO.CHAR
      1480        JSR FOUT
      1490 *--------------------------------
      1500 FOR.PRINT.1
      1510        LDY #0
      1520 .1     LDA FOUT.BUF,Y
      1530        BEQ .2
      1540        JSR AS.COUT
      1550        INY
      1560        BNE .1       ...ALWAYS
      1570 .2     RTS
      1580 *--------------------------------
      1590 *      QUICK CONVERSION
      1600 *--------------------------------
      1610 QUICK.FOUT
      1620        LDY #0
      1630        STY INDEX
      1640        LDA DAC.EXPONENT
      1650        BNE .0       NUMBER IS NOT ZERO
      1660        INC INDEX
      1670        STY FOUT.BUF+1
      1680        LDA #'0
      1690        STA FOUT.BUF  MAKE IT '0'
      1700        RTS
      1710 .0     LDA DAC.SIGN
      1720        BPL .1
      1730        LDA #'-      NEGATIVE
      1740        JSR STORE.CHAR
      1750 *--------------------------------
      1760 .1     LDA DAC.HI,Y NEXT BYTE OF #
      1770        PHA
      1780        LSR
      1790        LSR
      1800        LSR
      1810        LSR
      1820        JSR STORE.DIGIT
      1830        CPY #0
      1840        BNE .2
      1850        LDA #'.      PUT DECIMAL POINT
      1860        JSR STORE.CHAR
      1870 .2     PLA          DO 2ND DIGIT
      1880        JSR STORE.DIGIT
      1890        INY
      1900        CPY #9       8 MORE BYTES
      1910        BCC .1
      1920 *--------------------------------
      1930        LDY INDEX    TRUNCATE TRAILING ZEROS
      1940 .3     DEY
      1950        LDA FOUT.BUF,Y
      1960        CMP #'0
      1970        BEQ .3       DONE
      1980        CMP #'.      TRAILING DECIMAL PT?
      1990        BNE .4       NO
      2000        DEY          YES, DELETE IT
      2010 .4     INY
      2020        STY INDEX    SAVE NEW END OF NUMBER
      2030 *--------------------------------
      2040        LDA #'E
      2050        JSR STORE.CHAR E FOR EXPONENT
      2060        LDA #'+
      2070        LDY DAC.EXPONENT
      2080        DEY
      2090        CPY #$40
      2100        BCS .5
      2110        LDA #'-
      2120 .5     JSR STORE.CHAR
      2130        TYA          EXPONENT
      2140        SEC
      2150        SBC #$40     REMOVE OFFSET
      2160        BPL .6
      2170        EOR #$FF
      2180        ADC #1
      2190 .6     LDX #'0-1
      2200        SEC
      2210 .8     INX
      2220        SBC #10
      2230        BCS .8
      2240        ADC #'9+1
      2250        PHA
      2260        TXA
      2270        JSR STORE.CHAR
      2280        PLA
      2290        JSR STORE.CHAR
      2300        LDA #0
      2310        JMP STORE.CHAR
      2320 *--------------------------------
      2330 *   FORMATTED CONVERSION
      2340 *      (A)=WIDTH OF FIELD
      2350 *      (Y)=# OF FRACTIONAL DIGITS
      2360 *--------------------------------
      2370 FOUT
      2380        CMP #FOUT.BUF.SIZE    LIMIT WIDTH
      2390        BCC .1
      2400        LDA #FOUT.BUF.SIZE-1
      2410 .1     STA W
      2420        CPY W        FORCE D<W
      2430        BCC .2
      2440        TAY
      2450        DEY
      2460 .2     STY D
      2470 *--------------------------------
      2480        LDA #0
      2490        STA INDEX
      2500        STA SIGN.SIZE
      2510        STA SIGN.CHAR
      2520        STA NO.INTEGRAL.ZEROES
      2530        STA NO.LEADING.ZEROES
      2540        STA DIGIT.PICKER
      2550 *--------------------------------
      2560        JSR ROUND.DAC.D   ROUND TO D DIGITS
      2570        LDA DAC.SIGN
      2580        BPL .3
      2590        INC SIGN.SIZE
      2600        LDA #'-      MINUS SIGN
      2610        STA SIGN.CHAR
      2620 *--------------------------------
      2630 .3     SEC
      2640        LDA DAC.EXPONENT
      2650        SBC #$40     REMOVE OFFSET
      2660        BPL .4
      2670        EOR #$FF
      2680        STA NO.LEADING.ZEROES
      2690        INC NO.LEADING.ZEROES
      2700        LDA #0
      2710 .4     STA NO.LEADING.DIGITS
      2720 *--------------------------------
      2730        SEC
      2740        LDA NO.LEADING.DIGITS
      2750        SBC #18
      2760        BMI .5
      2770        STA NO.INTEGRAL.ZEROES
      2780        LDA #18      18 SIGNIF DIGITS MAX
      2790        STA NO.LEADING.DIGITS
      2800 *--------------------------------
      2810 .5     CLC          CALCULATE TOTAL # OF DIGITS
      2820        LDA SIGN.SIZE
      2830        ADC NO.LEADING.DIGITS
      2840        ADC NO.INTEGRAL.ZEROES
      2850        ADC D
      2860        ADC #1
      2870        STA WW
      2880 *--------------------------------
      2890        SEC
      2900        LDA D
      2910        SBC NO.LEADING.ZEROES
      2920        STA DD
      2930 *--------------------------------
      2940        SEC
      2950        LDA W
      2960        SBC WW
      2970        BMI .14      ...OVERFLOW
      2980        STA NO.LEADING.BLANKS
      2990        LDA NO.LEADING.DIGITS
      3000        BNE .6
      3010        DEC NO.LEADING.BLANKS
      3020        BPL .6
      3030        INC NO.LEADING.BLANKS IT WENT -, MAKE 0
      3040 *---STORE LEADING BLANKS---------
      3050 .6     LDA #' '     BLANK
      3060        LDY NO.LEADING.BLANKS
      3070        JSR STORE.N.CHARS
      3080 *---STORE SIGN-------------------
      3090        LDA SIGN.CHAR
      3100        BEQ .8
      3110        JSR STORE.CHAR
      3120 *---STORE INTEGRAL DIGITS--------
      3130 .8     LDY NO.LEADING.DIGITS
      3140        BEQ .10
      3150        JSR STORE.N.DIGITS
      3160        BEQ .11      ...ALWAYS
      3170 .10    LDA ZERO.CHAR  NO INTEGER PART,SO PRINT 0
      3180        JSR STORE.CHAR
      3190 .11    LDA #'0
      3200        LDY NO.INTEGRAL.ZEROES
      3210        JSR STORE.N.CHARS
      3220 *---STORE FRACTION---------------
      3230        LDA #'.
      3240        JSR STORE.CHAR
      3250        LDA DD
      3260        ORA NO.LEADING.ZEROES
      3270        BEQ .13
      3280        LDA ZERO.CHAR
      3290        LDY NO.LEADING.ZEROES
      3300        JSR STORE.N.CHARS
      3310        LDY DD
      3320        JSR STORE.N.DIGITS
      3330 *---TERMINATE STRING-------------
      3340 .13    LDA #0
      3350        JMP STORE.CHAR
      3360 *--------------------------------
      3370 .14    LDA #'*'     FILL FIELD WITH STARS
      3380        LDY W
      3390        JSR STORE.N.CHARS
      3400        JMP .13
      3410 *--------------------------------
      3420 *   STORE NEXT (Y) DIGITS
      3430 *--------------------------------
      3440 SND..1 LDA DIGIT.PICKER
      3450        CMP #20
      3460        BCC .1
      3470        LDA #0
      3480        BEQ .2       ...ALWAYS
      3490 .1     LSR          LEFT/RIGHT --> C
      3500        TAX          INDEX --> X
      3510        INC DIGIT.PICKER
      3520        LDA DAC.HI,X
      3530        BCS .2
      3540        LSR
      3550        LSR
      3560        LSR
      3570        LSR
      3580 .2     JSR STORE.DIGIT
      3590        DEY
      3600 STORE.N.DIGITS
      3610        BNE SND..1
      3620        RTS
      3630 *--------------------------------
      3640 *   STORE (Y) OF THE CHARACTER IN (A)
      3650 *      (Z-STATUS IF COUNT IS 0)
      3660 *--------------------------------
      3670 SNC..1 JSR STORE.CHAR
      3680        DEY
      3690 STORE.N.CHARS
      3700        BNE SNC..1
      3710        RTS
      3720 *--------------------------------
      3730 *      STORE A CHAR IN THE BUFFER
      3740 *--------------------------------
      3750 STORE.DIGIT
      3760        AND #$0F
      3770        ORA #'0'
      3780 STORE.CHAR
      3790        LDX INDEX
      3800        STA FOUT.BUF,X
      3810        INC INDEX
      3820        RTS
      3830 *--------------------------------
      3840 *      ROUND DAC TO (D) DECIMAL PLACES
      3850 *--------------------------------
      3860 ROUND.DAC.D
      3870        LDA DAC.SIGN  GET THE SIGN
      3880        PHA          SAVE IT
      3890        LDA #CON.1HALF
      3900        LDY /CON.1HALF
      3910        JSR MOVE.YA.ARG   MOVE .5*10^-D INTO ARG
      3920        PLA          GET SIGN
      3930        STA ARG.SIGN
      3940        LDA D        GET # OF PLACES
      3950        EOR #$FF     MAKE IT NEGATIVE BY 2S COMPLEMENT
      3960        SEC          ADD 1 DURING NEXT ADD
      3970        ADC #$40     ADD OFFSET
      3980        STA ARG.EXPONENT
      3990        JMP DADD     ADD .5*10^-D;FOUT WILL TRUNCATE IT
      4000 *--------------------------------
      4010 CON.1HALF .HS 4050000000000000000000

Enable/Disable IRQ from Applesoft | Bob Sander-Cederlof

If you have applied the patches to DOS 3.3 that we published in the January 1984 issue (pages 10,11), and if you now are using interrupts from such sources as the Timemaster II or a handy pushbutton, you have probably run into the need to enable and disable IRQ from within an Applesoft program. (That sentence is the kind you have to read without interruption, so I really should have begun the paragraph with SEI and ended it with CLI.)

What is need is four bytes of assembly language, at a location that you can CALL. For example:

           300- 58    CLI
           301- 60    RTS
           302- 78    SEI
           303- 60    RTS

If those four bytes are in memory as shown, you can CALL 768 to enable IRQ interrupts, and CALL 771 to disable them. You can install the four bytes like this:

           100 POKE 768,88: POKE 769,96
           110 POKE 770,120:POKE 771,96

Now there are often times when poking into page 3 is not possible. Are there other tricky ways to get those bytes installed, without using page 3?

I found a half dozen or so. First, realize that the four bytes only need to be there when you call them. The rest of the time the same locations could be used for other purposes. For example, we could poke them into the input buffer at $200, as long as we do it every time we CALL it:

           100 POKE 512,88:POKE 513,96:CALL 512
                 to enable interrupts, or

           500 POKE 512,120:POKE 513,96:CALL 512
                 to disable them.

The result of a multiplication or division is left, sometimes normalized and sometimes not, in $62...$66. If we find two numbers whose product leaves the bytes $58 and $60 at $62 and $63, we could CALL 98:

           100 X = 1*707 : CALL 98 : REM ENABLE IRQ
           200 X = 1*963 : CALL 98 : REM DISABLE IRQ

On the next page is a table showing the various methods I found:

          Enable (CLI..RTS)      Disable (SEI..RTS)
           -------------------------------------------------------
           100 POKE 38,88            100 POKE 38,120
           110 POKE 39,96            110 POKE 39,96
           120 CALL 38               120 CALL 38
           -------------------------------------------------------
           100 CALL 8411232-8411065  100 CALL 8419424-8419257
           -------------------------------------------------------
           100 GOSUB 24664           100 GOSUB 24696
           ...                       ...
           24664 CALL 117:RETURN     24696 CALL 117:RETURN
           -------------------------------------------------------
           100 X = 1*707 : CALL 98   100 X = 1*963 : CALL 98
           -------------------------------------------------------
           100 X = RND(-8411323.5)   100 X = RND(-8419424.5)
           110 CALL 203              110 CALL 203
           -------------------------------------------------------
           100 HOME:FLASH:PRINT"X "  100 HOME:FLASH:PRINT"8 "
           110 NORMAL:CALL1024       110 NORMAL:CALL1024
           -------------------------------------------------------

Can you figure out how all these work? They are pretty tricky! Can you think of some more?

Line Number Cross Reference | Bill Morgan

Have you ever had to modify a BASIC program written by someone who didn't seem to know what he was doing? Deciphering several hundred undocumented lines of split FOR/NEXTs and tangled GOTOs can lead to a severe headache. We recently had a consulting job that involved just such a project: one program to be altered was about a hundred sectors of spaghetti-plate Applesoft. A couple of the biggest problems were figuring out which lines used a particular variable, and what lines called others, or were called from where.

Back in November of 1980, AAL published a Variable Cross Reference program which neatly took care of the first problem by producing a listing in alphabetical order of all the variables used and all the lines using them. At the end of that article, Bob S-C pointed out that the program could, with some effort, be modified into just the sort of Line Number Cross Reference we now needed. Well, I drew the job of making that modification, and here's what I came up with.

The Basis

These Cross Reference programs use a hash-chain data structure to store the called and calling line numbers. Each called line has its own list of lines which refer to it. We locate these lists by using the upper six bits of the line number for an index into a table located at $280. This table contains the address of the beginning of each of the 64 possible chains. Each chain is made up of the data for a range of 1024 possible called line numbers. The first one has called lines 0-1023, the second has 1024-2047, and so on.

The entry for each called line is made up of a pointer to the next called line in that chain, this called line number, a pointer to the next calling line, and the number of this calling line. Each subsequent calling line entry has only the last four bytes. A pointer with a value of zero marks the end of each chain and each list.

VCR used three characters for each variable: the first two letters of the variable name and a type designator of "$", "%" or " ". The first character was the hash index and the last two characters were stored at the beginning of each variable's chain. LCR uses the high-order 6 bits of the called line number for the hash index and stores both bytes of the number in the chain. This is slightly redundant, so if you want to store more information about the called line, you can use the upper six bits of the chain entry.

VCR stored the calling line numbers with the high byte first, backwards from usual 6502 practice. This was done so the same search-compare code could handle both variable names and line numbers. To simplify the conversion I kept the same structure, even though it's no longer strictly necessary.

The Program

LCR, the overall control level, is identical to VCR and just calls the other routines.

INITIALIZATION prepares a couple of pointers and zeroes the hash table. The only difference here is the size of the hash table.

PROCESS.LINE is also the same as in VCR. This routine steps through the lines of the Applesoft program, moving the calling line number into our data area and JSRing to SCAN.FOR.CALLS to work on each line.

SCAN.FOR.CALLS is the first really new section of code. We start by setting a flag used to mark ON ... GO statements. Then we step through the bytes of the line, looking for tokens that call another line. GOTO and GOSUB are processed immediately. For a THEN token we check to see if the next character is a number. If it is, we deal with it; if not, we go on. If we find an ON token, we set the flag and keep looking. After a GOTO or GOSUB we check ONFLAG. If there was an ON, we look for a comma to mark another called line number.

PROCESS.CALL first converts the ASCII line number of the called line into a two-byte binary number and then searches the data structure for that line. If it is there, we simply add this calling line to the list. If we don't find the called line we create a new entry for it.

CONVERT.LINE.NUMBER is lifted straight from Applesoft's LINGET, at $DA0C.

NEXT.CHAR is a utility routine to get the next byte from the program and advance the pointer.

SEARCH.CALL.TABLE starts the search pointer on the appropriate chain.

CHAIN.SEARCH uses the pointer in an entry to step to the next entry. If the pointer is zero, then there is no next entry and the search has failed. We then compare the line number in the entry to the one we're looking for. If the entry is less than the search key, we go on. If it is equal, we update the pointer and report success. If we hit an entry greater than the key, the search fails and we return.

SEARCH.LINE.CHAIN is called after SEARCH.CALL.TABLE has found a match. Here we move the pointer to the calling line field of the matching entry and use the current calling line for a search key.

ADD.NEW.ENTRY first updates the pointers in the previous entry and this new entry, and the end-of-table pointer. We then make sure there is room for the new entry and move the data up into the new space.

Now we are done with the routines devoted to building the Cross Reference tables. Interestingly, SEARCH.CALL.TABLE, CHAIN.SEARCH, SEARCH.LINE.CHAIN, and ADD.NEW.ENTRY are the real heart of this program, and the only change I had to make in these routines from VCR to LCR was to alter the method of figuring the hash index in SEARCH.CALL.TABLE. Next we come to getting the data back out of the tables and onto a display.

PRINT.REPORT first sets a pointer we'll be using later on and then steps through the hash table, calling PRINT.CHAIN for each entry found.

PRINT.CHAIN starts out by checking for a pause or abort signal from the keyboard. It then moves the current called line number into LINNUM, checks to see if it really exists, and prints it, followed by an asterisk if it is undefined. Now we move a pointer up to the start of the calling line list and call PRINT.LINNUM.CHAIN to display all the entries. The last step is to move the pointer up to the next called line in this chain, if any, and go back to do that one.

CHECK.DEFINITION keeps its own pointer into the program and steps along checking each called line to see if it actually exists. It provides a space or an asterisk to be printed after the line number.

PRINT.LINNUM.CHAIN displays the calling lines stored for each called line. We first tab to the next column (or line if necessary), then get the line number out of the list and print it. Lastly, we move the pointer up to the next entry, if any, and loop back.

TAB.NEXT.COLUMN prints enough blanks to move over to the next output position. If a new line is necessary, it checks the line number to see if the new line should go to the screen only, or also to a printer. This is Louis Pitz's addition, designed to automatically handle either 40- or 80-column output.

PRINT.LINE.NUMBER and CHECK.FOR.PAUSE are pretty standard routines to convert a two-byte binary number into five decimal characters, and to provide for pause/abort during display.

Well, now we have a Line Number Cross Reference to go along with the Variable Cross Reference. Now all that remains is to integrate the two programs into one master Applesoft Cross Reference Utility. Maybe you could call it with "&V" for VCR, or "&L" for LCR, and simply "&" to get both listings. Any takers out there?

PS: Bob suggested that I add a diagram of the hash chain structure, and a summary of the search process. OK, here they are...

    One of
    64 hash                  Calling Line Lists
    pointers

      ----      ---------      ----
     |    |--->|    |    |--->|    |--->0
      ----      ---------      ----

                ---------      ----      ----      ----
               |    |    |--->|    |--->|    |--->|    |--->0
                ---------      ----      ----      ----
    Called
    Line
    Chain       ---------
               |    |    |--->0
                ---------

                ---------      ----
               |    |    |--->|    |--->0
                ---------      ----

    Found a Call

        * Use high 6 bits of called line number to index Hash Table
        * Get pointer from Hash Table to find start of chain
             * If no pointer in Hash Table, make new entry
        * Search chain for same line number
             * If not found, make new link in chain
             * If found, search calling line list
        * Enter new calling line in list

      1000 *SAVE S.LCR
      1010 *--------------------------------
      1020 *    LINE NUMBER CROSS REFERENCE
      1030 *       FOR APPLESOFT PROGRAMS
      1040 *
      1050 *       Based on Variable Cross Reference
      1060 *       Original by Bob S-C 11/80
      1070 *       Modified by Louis Pitz 8/83
      1080 *       Adapted by Bill Morgan 8/84
      1090 *--------------------------------
      1100        .OR $6000
      1110 *      .TF B.LCR
      1120 *--------------------------------
      1130        LDA #$4C     set & vector
      1140        STA $3F5
      1150        LDA #LCR
      1160        STA $3F6
      1170        LDA /LCR
      1180        STA $3F7
      1190        RTS
      1200 *--------------------------------
      1210 TEMP    .EQ $15
      1220 COUNTER .EQ $16
      1230 ONFLAG  .EQ $17           ON ... GO flag
      1240 DEFFLAG .EQ $17
      1250 PNTR    .EQ $18,19        pointer into program
      1260 LZFLAG  .EQ $1A           leading zero flag
      1270 DATA    .EQ $1A thru $1D
      1280 NEXTLN  .EQ $1A,1B        address of next line
      1290 LINNUM  .EQ $1C,1D        current line number
      1300 STPNTR  .EQ $1E,1F        pointer into call table
      1310 TPTR    .EQ $9B,9C        temp pointer
      1320 ENTRY   .EQ $9D thru $A4  8 bytes
      1330 CALL    .EQ ENTRY+2
      1340 SIZE    .EQ $A5,A6
      1350 HSHTBL  .EQ $280
      1360 *--------------------------------
      1370 PRGBOT  .EQ $67,68        beginning of program
      1380 LOMEM   .EQ $69,6A        beginning of variable space
      1390 EOT     .EQ $6B,6C        end of variable table
      1400 *--------------------------------
      1410 COMMA      .EQ ',
      1420 CR         .EQ $8D
      1430 TKN.GOTO   .EQ $AB
      1440 TKN.GOSUB  .EQ $B0
      1450 TKN.ON     .EQ $B4
      1460 TKN.THEN   .EQ $C4
      1470 *--------------------------------
      1480 MON.CH     .EQ $24
      1490 KEYBOARD   .EQ $C000
      1500 STROBE     .EQ $C010
      1510 AS.MEMFULL .EQ $D410
      1520 MON.PRBL2  .EQ $F94A
      1530 MON.CROUT  .EQ $FD8E
      1540 MON.COUT   .EQ $FDED
      1550 MON.COUT1  .EQ $FDF0
      1560 *--------------------------------
      1570 LCR    JSR INITIALIZATION
      1580 .1     JSR PROCESS.LINE
      1590        BNE .1            until end of program
      1600        JSR PRINT.REPORT
      1610        JSR INITIALIZATION  erase call table
      1620        LDA #0            clear $A4 so Applesoft
      1630        STA $A4           will work correctly
      1640        RTS
      1650 *--------------------------------
      1660 INITIALIZATION
      1670        LDA LOMEM         start call table
      1680        STA EOT           after program
      1690        LDA LOMEM+1
      1700        STA EOT+1
      1710        LDX #$80          # of bytes for hash pointers
      1720        LDA #0
      1730 .1     STA HSHTBL-1,X
      1740        DEX
      1750        BNE .1
      1760        LDA PRGBOT        start pointer at
      1770        STA PNTR          beginning of program
      1780        LDA PRGBOT+1
      1790        STA PNTR+1
      1800        RTS
      1810 *--------------------------------
      1820 PROCESS.LINE
      1830        LDY #3            capture pointer and line #
      1840 .1     LDA (PNTR),Y
      1850        STA DATA,Y
      1860        DEY
      1870        BPL .1
      1880        LDA DATA+1        check if end
      1890        BEQ .3            yes, return .EQ.
      1900        CLC
      1910        LDA PNTR          adjust pointer to
      1920        ADC #4            skip over data
      1930        STA PNTR
      1940        BCC .2
      1950        INC PNTR+1
      1960 .2     JSR SCAN.FOR.CALLS
      1970        LDA DATA          point to next line
      1980        STA PNTR
      1990        LDA DATA+1        and return .NE.
      2000        STA PNTR+1
      2010 .3     RTS
      2020 *--------------------------------
      2030 SCAN.FOR.CALLS
      2040        LDA #$FF
      2050        STA ONFLAG
      2060 .1     JSR NEXT.CHAR
      2070        BEQ .4
      2080        CMP #TKN.THEN     scan for call token
      2090        BEQ .2
      2100        CMP #TKN.GOTO
      2110        BEQ .3
      2120        CMP #TKN.GOSUB
      2130        BEQ .3
      2140        CMP #TKN.ON
      2150        BNE .1            no match, keep going
      2160        LSR ONFLAG        set flag for ON token
      2170        BPL .1         ...always
      2180
      2190 .2     LDY #0            after THEN, check
      2200        LDA (PNTR),Y      for line number
      2210        CMP #'0
      2220        BCC .1            <0 isn't
      2230        CMP #'9+1
      2240        BCS .1            neither is >9
      2250
      2260 .3     JSR PROCESS.CALL  handle this call
      2270        LDA ONFLAG        are we in ON?
      2280        BMI SCAN.FOR.CALLS no, go on
      2290        JSR NEXT.CHAR     yes, look for comma
      2300        BEQ .4            EOL
      2310        CMP #COMMA
      2320        BEQ .3            comma, get another call
      2330        BNE SCAN.FOR.CALLS ...always
      2340
      2350 .4     RTS
      2360 *--------------------------------
      2370 PROCESS.CALL
      2380        JSR CONVERT.LINE.NUMBER
      2390        JSR SEARCH.CALL.TABLE
      2400        BCC .2            found same call
      2410        LDA #0
      2420        STA ENTRY+4       start of line number chain
      2430        STA ENTRY+5
      2440        LDA LINNUM+1      MSB first
      2450        STA ENTRY+6
      2460        LDA LINNUM
      2470        STA ENTRY+7
      2480        LDA #8            add 8 byte entry
      2490 .1     JMP ADD.NEW.ENTRY
      2500
      2510 .2     JSR SEARCH.LINE.CHAIN
      2520        BCC .3            found same line number
      2530        LDA #4            add 4 byte entry
      2540        BNE .1         ...always
      2550
      2560 .3     RTS
      2570 *--------------------------------
      2580 CONVERT.LINE.NUMBER
      2590        LDA #0
      2600        STA CALL+1
      2610        STA CALL
      2620 .1     JSR NEXT.CHAR
      2630        BEQ .2            EOL
      2640        SEC
      2650        SBC #'0           make value
      2660        BCC .2            <0 isn't number
      2670        CMP #9+1
      2680        BCS .2            >9 isn't number
      2690        PHA               save value
      2700        LDA CALL
      2710        STA TEMP
      2720        LDA CALL+1        multiply CALL * 10
      2730        ASL
      2740        ROL TEMP
      2750        ASL
      2760        ROL TEMP
      2770        ADC CALL+1
      2780        STA CALL+1
      2790        LDA TEMP
      2800        ADC CALL
      2810        STA CALL
      2820        ASL CALL+1
      2830        ROL CALL
      2840        PLA               get value this digit
      2850        ADC CALL+1        and add it in
      2860        STA CALL+1
      2870        BCC .1
      2880        INC CALL
      2890        BCS .1         ...always
      2900
      2910 .2     LDA PNTR          back up PNTR
      2920        BNE .3
      2930        DEC PNTR+1
      2940 .3     DEC PNTR
      2950        RTS
      2960 *--------------------------------
      2970 NEXT.CHAR
      2980        LDY #0
      2990        LDA (PNTR),Y
      3000        BEQ .1            EOL
      3010        INC PNTR          bump pointer
      3020        BNE .1
      3030        INC PNTR+1
      3040 .1     RTS
      3050 *--------------------------------
      3060 SEARCH.CALL.TABLE
      3070        LDA CALL          hi-byte of called line
      3080        AND #$FC          hi 6 bits
      3090        LSR               make 0-126
      3100        ADC #HSHTBL       carry is clear
      3110        STA STPNTR
      3120        LDA /HSHTBL
      3130        ADC #0
      3140        STA STPNTR+1
      3150 *--- fall into CHAIN.SEARCH routine
      3160 *--------------------------------
      3170 CHAIN.SEARCH
      3180 .1     LDY #0            point at pointer in entry
      3190        LDA (STPNTR),Y
      3200        STA TPTR
      3210        INY
      3220        LDA (STPNTR),Y
      3230        BEQ .4            end of chain, not in table
      3240        STA TPTR+1
      3250        LDX #2            2 bytes in number
      3260        LDY #2            point at line number in entry
      3270 .2     LDA (TPTR),Y      compare numbers
      3280        CMP ENTRY,Y
      3290        BCC .3            not this one, but keep looking
      3300        BNE .4            not in this chain
      3310        DEX
      3320        BEQ .5            same number
      3330        INY               next byte pair
      3340        BNE .2         ...always
      3350
      3360 .3     JSR .5            update pointer, clear carry
      3370        BCC .1         ...always
      3380
      3390 .4     SEC               did not find
      3400        RTS
      3410
      3420 .5     LDA TPTR          point to matching entry
      3430        STA STPNTR
      3440        LDA TPTR+1
      3450        STA STPNTR+1
      3460        CLC
      3470        RTS
      3480 *--------------------------------
      3490 SEARCH.LINE.CHAIN
      3500        CLC               adjust pointer to start
      3510        LDA STPNTR        of line # chain
      3520        ADC #4
      3530        STA ENTRY
      3540        LDA STPNTR+1
      3550        ADC #0
      3560        STA ENTRY+1
      3570        LDA #ENTRY
      3580        STA STPNTR
      3590        LDA /ENTRY
      3600        STA STPNTR+1
      3610        LDA LINNUM        put line number into symbol
      3620        STA ENTRY+3
      3630        LDA LINNUM+1
      3640        STA ENTRY+2
      3650        JMP CHAIN.SEARCH
      3660 *--------------------------------
      3670 ADD.NEW.ENTRY
      3680        STA SIZE
      3690        CLC               see if room
      3700        LDX #1
      3710        LDY #0
      3720        STY SIZE+1
      3730 .1     LDA (STPNTR),Y    get current pointer
      3740        STA ENTRY,Y       into new entry
      3750        LDA EOT,Y         point old entry
      3760        STA (STPNTR),Y    to this one
      3770        STA TPTR,Y
      3780        ADC SIZE,Y        and adjust end-of-table
      3790        STA EOT,Y
      3800        INY
      3810        DEX
      3820        BPL .1            now do low-bytes
      3830 *--- see if there's going to be enough room
      3840        LDA EOT
      3850        CMP #LCR
      3860        LDA EOT+1
      3870        SBC /LCR
      3880        BCS .3            MEM FULL error
      3890 *--- move entry into call table
      3900        LDY SIZE
      3910        DEY
      3920 .2     LDA ENTRY,Y
      3930        STA (TPTR),Y
      3940        DEY
      3950        BPL .2
      3960        LDA TPTR
      3970        STA STPNTR
      3980        LDA TPTR+1
      3990        STA STPNTR+1
      4000        RTS
      4010
      4020 .3     JMP AS.MEMFULL    abort with error message
      4030 *--------------------------------
      4040 PRINT.REPORT
      4050        LDA PRGBOT
      4060        STA PNTR          start defined line search
      4070        LDA PRGBOT+1      at beginning of program
      4080        STA PNTR+1
      4090        LDA #0            start at chain 0
      4100 .1     STA TEMP
      4110        ASL
      4120        TAY
      4130        LDA HSHTBL+1,Y
      4140        BEQ .2            no entries for this chain
      4150        STA STPNTR+1
      4160        LDA HSHTBL,Y
      4170        STA STPNTR
      4180        JSR PRINT.CHAIN
      4190 .2     INC TEMP
      4200        LDA TEMP
      4210        CMP #$40
      4220        BCC .1            still more chains
      4230        RTS               finished
      4240 *--------------------------------
      4250 PRINT.CHAIN
      4260        JSR CHECK.FOR.PAUSE
      4270        BEQ .1            <CR> abort
      4280        LDY #2
      4290        LDA (STPNTR),Y
      4300        STA LINNUM+1
      4310        INY
      4320        LDA (STPNTR),Y
      4330        STA LINNUM
      4340        JSR CHECK.DEFINITION
      4350        JSR PRINT.LINE.NUMBER
      4360        LDA DEFFLAG       "*" or " "
      4370        JSR MON.COUT
      4380        CLC
      4390        LDA STPNTR
      4400        ADC #4            point at line # chain
      4410        STA TPTR
      4420        LDA STPNTR+1
      4430        ADC #0
      4440        STA TPTR+1
      4450        JSR PRINT.LINNUM.CHAIN
      4460        JSR MON.CROUT
      4470        LDY #1
      4480        LDA (STPNTR),Y    pointer to next call
      4490        BEQ .2            no more
      4500        PHA
      4510        DEY
      4520        LDA (STPNTR),Y
      4530        STA STPNTR
      4540        PLA
      4550        STA STPNTR+1
      4560        BNE PRINT.CHAIN  ...always
      4570
      4580 .1     PLA               return to top level
      4590        PLA               if <CR> abort
      4600 .2     RTS
      4610 *--------------------------------
      4620 CHECK.DEFINITION
      4630        LDY #3
      4640        LDX #1
      4650 .1     LDA (PNTR),Y      look at next line in program
      4660        CMP LINNUM,X
      4670        BCC .4            < our number, get new line
      4680        BNE .2            >  "    "   , not defined
      4690        DEY               =  "    "   , go on
      4700        DEX               now do low order bytes
      4710        BPL .1
      4720        LDA #" "          found it!
      4730        BNE .3         ...always
      4740
      4750 .2     LDA #"*"          flag undefined line
      4760 .3     STA DEFFLAG
      4770        RTS
      4780
      4790 .4     LDY #1
      4800        LDA (PNTR),Y      hi-byte of next line address
      4805        BEQ .2
      4810        PHA
      4820        DEY
      4830        LDA (PNTR),Y      and lo-byte
      4840        STA PNTR
      4850        PLA
      4860        STA PNTR+1
      4870        JMP CHECK.DEFINITION
      4880 *--------------------------------
      4890 PRINT.LINNUM.CHAIN
      4900        LDA #0            reset counter to 0
      4910        STA COUNTER       for each call
      4920 .1     JSR TAB.NEXT.COLUMN
      4930        LDY #2            point at line #
      4940        LDA (TPTR),Y
      4950        STA LINNUM+1
      4960        INY
      4970        LDA (TPTR),Y
      4980        STA LINNUM
      4990        JSR PRINT.LINE.NUMBER
      5000        LDY #1            set up next pointer
      5010        LDA (TPTR),Y
      5020        BEQ .2            end of chain
      5030        PHA
      5040        DEY
      5050        LDA (TPTR),Y
      5060        STA TPTR
      5070        PLA
      5080        STA TPTR+1
      5090        BNE .1         ...always
      5100
      5110 .2     RTS
      5120 *--------------------------------
      5130 TAB.NEW.LINE
      5140        JSR MON.CROUT
      5150
      5160 TAB.NEXT.COLUMN
      5170 .1     LDA #7            first tab stop
      5180 .2     CMP MON.CH        cursor position
      5190        BCS .3            perform tab
      5200        ADC #6            next tab stop
      5210        CMP #33           end of line?
      5220        BCC .2
      5230        INC COUNTER       count the screen line
      5240        LDA COUNTER
      5250        AND #1            look at odd-even bit
      5260        BEQ TAB.NEW.LINE  both scrn and printer
      5270        LDA #CR
      5280        JSR MON.COUT1     <CR> to screen only
      5290        JMP .1         ...always
      5300
      5310 .3     BEQ .4            already there
      5320        SBC MON.CH        calculate # of blanks
      5330        TAX
      5340        JSR MON.PRBL2
      5350 .4     RTS
      5360 *--------------------------------
      5370 PRINT.LINE.NUMBER
      5380        LDX #4            print 5 digits
      5390        STX LZFLAG        turn on leading zero flag
      5400 .1     LDA #'0           digit=0
      5410 .2     PHA
      5420        SEC
      5430        LDA LINNUM
      5440        SBC PLNTBL,X
      5450        PHA
      5460        LDA LINNUM+1
      5470        SBC PLNTBH,X
      5480        BCC .3            less than divisor
      5490        STA LINNUM+1
      5500        PLA
      5510        STA LINNUM
      5520        PLA
      5530        ADC #0            increment digit
      5540        BNE .2         ...always
      5550
      5560 .3     PLA
      5570        PLA
      5580        CMP #'0
      5590        BEQ .5            zero, might be leading
      5600        SEC               turn off LZFLAG
      5610        ROR LZFLAG
      5620 .4     ORA #$80
      5630        JSR MON.COUT
      5640        DEX
      5650        BPL .1
      5660        RTS
      5670 .5     BIT LZFLAG        leading zero flag
      5680        BMI .4            no
      5690        CPX #0            if all zeroes, print one
      5700        BEQ .4
      5710        LDA #'            blank
      5720        BNE .4         ...always
      5730
      5740 PLNTBL .DA #1
      5750        .DA #10
      5760        .DA #100
      5770        .DA #1000
      5780        .DA #10000
      5790 PLNTBH .DA /1
      5800        .DA /10
      5810        .DA /100
      5820        .DA /1000
      5830        .DA /10000
      5840 *--------------------------------
      5850 CHECK.FOR.PAUSE
      5860        LDA KEYBOARD      keypress?
      5870        BPL .2            no, go on
      5880        STA STROBE
      5890        CMP #CR           RETURN?
      5900        BEQ .2            yes
      5910 .1     LDA KEYBOARD      no, wait for
      5920        BPL .1            another stroke
      5930        STA STROBE
      5940        CMP #CR           return .EQ. if RETURN
      5950 .2     RTS
      5960 *--------------------------------

Speaking of Slow Chips | Robert H. Bernard

William O'Ryan's article (AAL June 1984) about making the 65C02 work in II+s reminds me of some other slow chip problems I have had in the past with Apples.

Years ago, I had a problem with an SSM AIO card in an Apple that traced to a slow 74LS138 at position H2. The symptom was that every few hours the program would fly off into the weeds. I traced it to the device select for the slot, which caused the data on the bus to be late for ROM program fetches from the card. I was able to fix the problem in that case by swapping H2 with another '138 from a different (less critical) position.

Some time later I was able to fix a problem in another machine by swapping the ROM SELECT chip at position F12 (another 74LS138) with another '138. There are apparently many marginal timing situations in II+s, and they are not necessarily in the oldest ones.

All this slow circuit stuff has some interesting side effects. I personally had a number of conversations with SSM about this problem before I found the real cause, and all they could suggest was a capacitor on the clock line. Even after I found the problem, the SSM people I talked to seemed uninterested in the fix, perhaps because they couldn't apply it directly to their product.

The unfortunate end result was that a number of organizations that previously sold or recommended AIO cards stopped doing so. A domino effect was that our local retailer stopped pushing Anadex printers (which required the DTR signal, at that time only available on the AIO) rather than find another serial card to replace the AIO. I always wondered if the Anadex people noticed the effect on their sales....

Modify DOS 3.3 for Big BSAVEs | Bob Sander-Cederlof

Jim Sather (author of "Understanding the Apple II" and designer of the QuikLoader card) called today, and one topic of discussion was DOS 3.3's limit of 32767 for the maximum size of a binary file. Jim has been blowing 27256 EPROMs, which are 32768 bytes long. To write a whole EPROMs worth of code on disk it takes two files, because the EPROM holds one more byte than the maximum size file.

The limit doesn't apply if you write the file with the .TF directive in the S-C Macro Assembler, but it is checked when you type in a BSAVE command. The "L" parameter must be less than 32768.

I remembered that somewhere very recently I had read of a quick patch to DOS to remove the restriction. Where? Hardcore Computing? Call APPLE? Washington Apple Pi?

The answer was "yes" to both Call APPLE and W.A.P., because Bruce Field's excellent Apple Doctor column is printed in both magazines. The July 1984 Call APPLE, on page 55, has the answer:

> "Sure, change memory location $A964 in DOS from $7F to $FF. From Applesoft this can be done with POKE 43364,255. This changes the range attribute table in DOS to allow binary files as large as 65535 bytes."

By the way, please do not try to BSAVE 65535 bytes on one file. You will not succeed, because doing so will involve saving bytes from the $C000-C0FF range. This is where all the I/O soft switches are, any you will drive your Apple and peripherals wild. And you will not be able to BLOAD it, because it will load on top of the DOS buffers. In general, do not BSAVE any area of RAM which includes $C000-C0FF. Do not BLOAD into the DOS buffers or DOS variables.

If you want to test Bruce's patch, make the patch and then BSAVE filename,A$800,L$8E00. This will save from $800 through $95FF.

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 $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $12 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).

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