Chaining Programs In Applesoft BASIC
Richard J Kaufman
By appending a few lines to Applesoft BASIC programs, you can cause them to call each other and still keep their variables intact.
When a BASIC program gets so large that it exhausts all available memory, you have very few options. If you try to make the program more memory-efficient, the first step is usually to delete the REMarks; but this can prove a disaster when you come back to the program six months later. Even the best programmers forget the details of old programs, and can benefit from a few reminders. You can also split the program in two and save all the shared variables in a disk file, which the second program can read and use. This is slow at best. The old DOS manual shows a CHAIN command, which is just what you need in these situations. Unfortunately, CHAIN works only with Integer BASIC, which is no longer supported by Apple. Most high-level languages provide some means to call another program that does not remain memory resident, and include a means of passing variables. Applesoft BASIC, alas, does not. It's too basic.
There is a way, however, to chain (transfer control between) Applesoft programs, leaving the first program's variables and arrays intact. It requires an understanding of how Applesoft handles memory, and a little experimentation, but it will greatly increase the amount of BASIC code you can run at one session. Even if memory size is not a problem, you may find several small, modular programs easier to deal with than one huge one.
Table 1: Applesoft Pointer Locations
|$67||103||TXTTAB||Start of program (usually $0801)|
|$69||105||VARTAB||Start of simple variable space.|
|$6B||107||ARYTAB||Start of array space.|
|$6D||109||STREND||End of variable space.|
|$6F||111||FRETOP||Start of string space.|
Inside Applesoft BASIC
Table 1 summarizes the pairs of memory locations that tell BASIC where to find the program, its variables, arrays, and strings.
VARTAB, the start of simple variable space, usually begins right after the program text itself. However, it can be set to any desired value using the LOMEM command. This is the key to our chaining technique. Imagine that a program—which weíll call FIRST—has issued a command to run a second program named SECOND:
PRINT CHR$(4);"RUN SECOND"
When SECOND loads at TXTTAB (location $0801), it overwrites the previous contents of BASIC program memory, including at least part of FIRST. Assume that the program text for SECOND is shorter than the text of FIRST. In this case, the variable space is not yet destroyed, and it will not be until SECOND starts to use its variables. The figure helps you to visualize what is happening.
If SECOND sets VARTAB, ARYTAB, STREND, and FRETOP to the values in effect when FIRST was exited, all of FIRST's variables will be available to SECOND. The only exceptions are strings that were assigned values by literal statements in FIRST—for example, A$ = "THIS IS A STRING"—rather than by an INPUT statement, a disk read, or another string operation. Such strings must be reassigned. This is because of the way Applesoft handles string variables. The entry for a string variable in the variable table doesn't actually contain the text of the string. Instead, it contains a pointer holding the address of the string's text. The text of strings assigned by INPUT or created by some string operation will be stored in the tables above FRETOP. However, for strings with literal assignments, the variable table entry will point to the literal statement in the program text. When another program is chained, the program text containing the literal definition will be overwritten, but the variable table entry will still point into the program text area. Thus, the string assignment is no longer valid.
Applesoft Memory Organization
There is a way to avoid this problem. Literal strings involved in string operations such as concatenation are given definitions in the string pool above FRETOP, even if the string isn't actually changed. Thus, if you replace the literal string definition A $ = "THIS IS A STRING" with A$ = "THIS IS A STRING" + "", the string definition will be preserved after chaining. Adding (concatenating) an empty (null) string to the literal assignment will cause a copy of the string definition to be created in the string pool above FRETOP.
Implementing An Applesoft Chain
The need to chain BASIC programs often is brought about by the use of high-resolution graphics. Program space starts at $0801, and hi-res graphics page 1 starts right in the middle of the program space, at $2000. Translated to decimal, this leaves a mere 6144 bytes for the program. You can use hi-res page 2 for graphics (it starts at $4000), but suppose you need both high-resolution screens for some special effects? Program 1 shows how to cause an Applesoft program to load at any desired location in memory. (Starting the program above hi-res page 2 will give you more room, unless you use many large arrays or lots of strings.)
Line 5 of Program 1 is the key to this technique. This line can be appended at the beginning of any program you wish. Note that the variable LOC is set to the one location beyond the last byte of hi-res screen 2. This is fine if you want the program to load at that location. Otherwise, set LOC to the address of the location where you want your program to start. Of course, if you're not doing graphics, you can leave the program loading address alone.
Line 6 sets LOMEM, the bottom of variable storage, to location 34817. This gives you 10,241 bytes of program text space above hi-res screen 2. The optimum value for LOMEM will be determined by the longest program to be chained. You can estimate this by examining a catalog of the programs to be chained.
To calculate the approximate length of a program, multiply the number of sectors shown in the catalog entry for the program by 256 for DOS 3.3 or by 512 for ProDOS. For instance, a program that occupies four sectors when saved under DOS 3.3 is about 1024 (4 * 256) bytes long. A four-sector program saved under ProDOS will be about 2048 (4 * 512) bytes long. A more precise method is to check the difference between the values of VARTAB and TXTTAB after loading the longest program in the chain and use that value. The following line shows how to do this:
PRINT (PEEK(105) + 256 * PEEK(106))-(PEEK(103) + 256 * PEEK(104))
Once you've determined the program length, add that value to the program load address to get the minimum value for LOMEM.
In practice, it's usually easiest to start with 35000 for LOMEM and adjust the value if you find that you have memory problems. If you are not using both graphics screens, you can use a much lower value for LOMEM, but this is not likely to be necessary unless you are using a lot of array space.
Lines 10–30 of Program 1 assign a literal value to a string, use that string in a concatenation operation, print it, accept a numeric value for the variable X, and assign the value 9999 to Y.
We now are ready to save the program pointers, a task which is done in lines 900–910. The top of hi-res page 1 is 16383. We work down from there, putting the pointer bytes into the top of the screen. They do not show up on the screen, since they are nondisplayed "slack bytes." If you are not doing graphics, and you need to chain, you may have to find some other safe place to store your pointers. Perhaps you can use space in one of the DOS buffers or else find some unused space in low memory. If waiting for the extra time required doesn't bother you, the pointers can even be saved to a file. Line 920 runs Program 2.
Lines 5 and 6 of Program 2 retrieve the program pointers stashed by Program 1. Lines 10–40 then print the strings and variables assigned in Program 1. As you can see from looking at the output, the literal string assigned in line 10 of Program 1 didn't make it. The string table points to the location where the string resided in Program 1's text, but that area of memory now contains Program 2's text. It's necessary to reassign a value to the string. However, line 15 of Program 2 shows that the involving the literal string in a string operation—as in line 15 of Program 1—will create a string that can be successfully chained.
Remember, strings entered from the keyboard or from disk files will not need resetting, while those initialized with data statements will need to be reinitialized.
You can continue chaining from program to program. Just keep saving and retrieving the pointers.
For instructions on entering these programs, please refer to "COMPUTE!'s Guide to Typing In Programs" elsewhere In this issue.
Program 1: First
7E 5 LOC = 24576 + 1 : IF PEEK (103) + PEEK (104) $ 256 < > LOC THEN POKE LOC - 1, 0 : POKE 103, LOC - INT (LOC / 256) * 256 : POKE 104, INT (LOC / 256) : PRINT CHR$ (4) "RUN PROGRAM 1" 47 6 LOMEM : 34817 92 10 E1ND$ = "THIS IS PROGRAM 1" 9B 15 E2ND$ = E1ND$ + "" BE 20 PRINT "STRING = "; E1ND$ AA 30 PRINT "ENTER A NUMBER, X " : INPUT X : Y = 9999 9B 99 REM STORE POINTERS AT END OF HIGH-RES SCREEN 1 FB 900 POKE 16383, PEEK (105) : POKE 16382, PEEK (106) : POKE 16381, PEEK (107) : POKE 16380, PEEK (108) : POKE 16379, PEEK (109) : POKE 16378, PEEK (110) DB 910 POKE 16377, PEEK(111) : POKE 16376, PEEK (112) BE 920 PRINT CHR$ (4);"RUN PROGRAM 2" 97 930 END
Program 2: Second
57 5 POKE 105, PEEK (16383) : POKE 106, PEEK (16382) : POKE 107, PEEK (16381) : POKE 108, PEEK (16380) 2E 6 POKE 109, PEEK (16379) : POKE 110, PEEK (16378) : POKE 111, PEEK (16377) : POKE 112, PEEK (16376) 78 10 PRINT "STRING =[";E1ND$;"] 8A 15 PRINT "STRING =[";E2ND$;"] D3 20 E1ND$ = "THIS IS PROGRAM 2" 04 30 PRINT "X = "; X; " Y = ";Y FE 40 PRINT "STRING. = ";E1ND$;