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