Named GOSUB With Variable Passing
Mike Smith Calgary, Canada
In COMPUTE! #12, I described a machine language program which would allow subroutines to be called by name rather than by number. This article is an extension of that idea. It describes a machine language program which allows parameters to be passed in and out of subroutines.
One of the nicer features of FORTRAN and PASCAL is their ability to pass variables into a subroutine. This feature is very useful when you wish to do the same operation on a large number of variables. Passing parameters into subroutines is convenient since the variable names used outside the subroutine don't have to be the same as used for the calculation within the subroutine. This makes programming and documentation easier. In addition, subroutines of this type can be used as a sort of multi-line function.
A Brief Example Of Parameter Passing
Suppose that you wish to perform a complicated operation upon variables A, B and C and have the answer returned in D. Then you wish to have the same operation performed upon the variables A1, B1 and C1 and have that answer returned in D1.
In FORTRAN that program would look like this:
CALL COMPL(A, B, C, D) (call subroutine with first variable set) CALL COMPL(A1, B1, C1, D1) (then with the second set) ......... (Use D and D1 in calculations) ......... SUBROUTINE COMPL(W, X, Y, Z) (use dummy variables with subroutine) (Complicated calculation using W, X and Y) ......... Z = ..... RETURN
In Applesoft BASIC things are a little more difficult. First, you must call the subroutine by a number rather than by a name. A second problem is that you can't pass the names of variables into the subroutine. Instead, you must move (reassign) the values into the variable names used in the subroutine. An equivalent Applesoft BASIC program would look something like:
10 W = A : X = B : Y = C (reassign first set of variables) 20 GOSUB 1000 30 D = Z 40 W = A1 : X = B1 : Y = C1 (reassign second set) 50 GOSUB 1000 60 D1 = Z 70 ...... (Use D and D1 in calculations) ......... 1000 (Complicated calculation using W, X and Y) ......... 1100 Z = ..... 1110 RETURN
Having to remember the subroutine number is no great problem if you are the person who did the programming, provided you only did the programming a week or so ago, and have not yet forgotten what subroutine number was needed for what. Having to reassign variables, as in statement 40, is no great problem either, provided you don't have a large number of different variables that need to be worked on. But why do something that the computer can make easier to understand and do?
The program described in this article uses the Applesoft BASIC ampersand command (&) to allow the naming of subroutines and the easy passing of numerical data. With the machine code routine installed in memory, the Applesoft program above becomes:
10 COMPL = 1000 (establish the subroutines name) 20 & GOSUB COMPL !COMPL(0), A, B, C, D! (pass the parameters) 30 & GOSUB COMPL !COMPL(0), A1, B1, C1, D1! 40 ......... (Use D and D1 in calculations) ......... 1000 & GET !COMPL(0), W, X, Y, Z! (identify the dummy variables) 1010 (Complicated calculation using W, X and Y) ......... 1100 Z = .... 1110 & RETURN !COMPL(0)!
In addition to passing parameters, Applesoft will now support GOTO and GOSUB statements that have names instead of numbers. For example
JUMP = 1000 : & GOTO JUMP or COMPL = 1000 : & GOSUB COMPL FIRST = 1000 : DEUX = 2000 : ON X GOSUB FIRST, DEUX
I decided to develop this parameter passing routine because I am repeatedly asked to translate FORTRAN program with subroutines into Applesoft. Most of those subroutines pass variables. Making sure that I didn't duplicate names and that I reassigned the right variable, was too much of a hassle. Hence this routine.
Loading The Program
The machine language program as described in this article is too long to put in a normally unused area of memory. The cassette buffer (at $300) will only accept around $CF locations before running into the DOS pointers at $3D0.
The program could be placed high in memory, just below the normal HIMEM. The HIMEM pointers must then be adjusted so that the program is not touched by Applesoft when strings are used. However, this means that people using 48K and 32K Apples, with or without the Program Line Editor at the top of memory, will all need different programs. The modifications are simple, if you know how. Therefore, I have adopted the technique of moving LOMEM up $200 bytes and storing the machine language code in the space created. Then everybody gets the same code.
Before entering the demonstration BASIC program, type:
POKE 104,10 : POKE 2560, 0 : NEW
These three instructions adjust LOMEM and the various Applesoft RUN, LOAD or SAVE programs. The pointers can be shifted down to their normal place by typing FP.
After the BASIC program has been run, the machine code can be saved by the command BSAVE VARIABLE.PASS, A$803,L$181. The program will stay active, below your BASIC program, until you power down or do an FP.
To reload the ML program the next time you power up, type BRUN VARIABLE.PASS either from the keyboard or as part of your HELLO program. The LAST line of the HELLO program should be PRINT CHR$(4); "BRUN VARIABLE.PASS".
The first couple of statements of the hex code are the machine language equivalent of POKE 104,10 : POKE 2560,0 : NEW. That means that you only have to adjust the memory the first time you enter in the code. If you forget to adjust the memory before running the demonstration BASIC program, you will receive the message SYNTAX ERROR in 34057, a non-existent line. Simply type NEW : POKE 104,10 : POKE 2560,0 : NEW, reload the program from disk and RUN again. If you didn't adjust LOMEM, then, when the BASIC program stored the machine language program, it did so all over itself, causing a gigantic mess.
There is a sneaky reason for starting the machine language program at $803 (2051) rather than at $800, the start of the empty memory area. Suppose that, for some reason or another, you need to enter FP to recover from your program doing something strange. Typing FP causes 0's to be written at locations $800-$802 to indicate that there is no longer a program in the memory. This misses the ML program since it starts at $803. Thus, a quick CALL 2051 and ABRACADABRA, the pointers shift and the program is back in business.
The details of the demonstration and machine language programs are given after the description of the new SYNTAX of the instructions and limitations of the new commands.
Syntax For The New Commands
& GOSUB NAME!NAME(0),A,B,.....!
The name of the subroutine must be predefined before the subroutine is called (e.g. NAME = 1000).
The first parameter after the exclamation mark must be an array; otherwise, a BAD SUBSCRIPT ERROR occurs. It is suggested that the name of this array be the same as the name of the subroutine; for ease of remembering rather than necessity. If more than ten parameters are to be passed by the routine, the array must be DIMensioned to the number of parameters. No check is performed to see if the array is large enough for all the parameters used.
The other parameters must be numerical, either real variables (A, B etc.) or elements of a real array (A(1), B(1) etc.). The arrays don't have to be predimensioned unless their length is greater than ten. Errors will occur on attempting to pass a string (TYPE MISMATCH) or an integer (SYNTAX). It should be noted that it is the value of the array element that is moved and not the array itself. This means that you can't pass over the whole array by passing over the first element of an array, (c.f. In FORTRAN, it is the address which is passed and not the value of the array element. So, the whole array can be accessed from FORTRAN subroutine if you know the first address. In Applesoft, memory is continually being repositioned. The address of any variable is therefore continually changing, making any address stored very quickly invalid.)
The parameters do not need to have been defined before calling the subroutine. The machine language program makes use of Applesoft routines which automatically allocate space in the memory for new arrays and variables.
& GET !NAME(0),P,Q,.....!
This should be the first statement of the subroutine. The subroutine can't be recursive (it can't call itself).
This command does not extend an existing Applesoft command as did the & GOSUB, & RETURN and & GOTO commands. Therefore I had to use a different command. I decided to use GET, Since to me, this new command goes and gets the parameter values. If you would prefer a different command, such as LOAD, then the modification to allow this is simple. To have a different command, POKE its token into location 2600 ($828) before BSAVEing the program. For example, POKE 2600, 167 will change this command to be & RECALL !......! rather than & GET !......!. (See page 121 of the Applesoft Manual for a list of the tokens).
The first parameter after the exclamation mark must be the same array used in the & GOSUB statement, otherwise unexpected values will be put into the parameters (P etc).
The other parameters must be real, otherwise a TYPE MISMATCH or SYNTAX ERROR will result. Either real variables (P) or elements of real arrays (P(1)) may be used. Again, the parameters don't have to be predefined before the subroutine call, unless they are arrays of length greater than ten. If the arrays need to be DIMensioned remember to do it outside the subroutine. Otherwise a REDIMENSIONED ARRAY ERROR will result on the second subroutine call.
The number of parameters in the & GET statement should be the same as the number of parameters in the & GOSUB statement. If this condition is not met, strange values could arrive in the parameters of the & GET statement.
& RETURN !NAME(0)!
The array used in the & RETURN statement should be the same array as used in the & GOSUB and & GET statements. As this array is used to temporarily store text pointers to the & GOSUB and & GET statements, strange results could result if the wrong array is used. However, it is probable that, instead of funny results, a SYNTAX ERROR will occur. The likelihood of the wrong array pointing to valid names in separate locations in memory is very small.
If the number of parameters in the & GET statement is not the same as the number of parameters in the & GOSUB statement, unpredictable values will be put into the parameters.
& GOTO NAME and & GOSUB NAME
The name of the subroutine must be established before it is called. If these commands are used, a normal RETURN is all that is needed. If & GET and & RETURN are used, a SYNTAX ERROR will occur.
& ON X GOSUB FNAME, SNAME and & ON X GOTO FNAME, SNAME
These ON X.... commands are supported, provided that no parameters are passed. That means that & ON X GOSUB FNAME, SNAME is permitted but & ON X GOSUB FNAME !FNAME(0),A,B,C,D!, SNAME !SNAME(0),A,B,C,D! is not. I felt that passing parameters in ON X... statements made the statements very unwieldy. The original idea behind introducing these new commands was to make the programs more readable rather than less. Multiline IF...THEN commands would do the same job, in a more readable fashion. For those people interested in implementing the unweildy ON... version, I have included the additional code needed (lines 176-189).
Warning on renumbering and crunching programs: Renumbering programs will not change the values of variables. Therefore, they will not change the pointers to the subroutines called by these new commands. This must be done by hand after the renumbering is complete. Utilities that crunch programs will not recognize the fact that the subroutines are being called and therefore will remove them as dead code. To overcome this removal problem, a dummy line that calls all subroutines, must be added to the program. After crunching, delete the dummy line. For example:
10 NAME = 1000 : FIRST = 2000 (define the subroutines) 20 IF X = 0 THEN GOSUB 1000: GOSUB 2000: GOTO 20 (dummy line to be removed after crunching)
Note that the dummy line is an IF..THEN statement that loops to itself. This means that a CRUNCHER, such as the one in DAKIN 5 PROGRAMMING AIDS 3.3, will leave that line alone, making it easy to remove.
BASIC Program Description
Line 180 – Establishes the machine language program.
Line 200 – Establishes the name of the subroutines to be called.
Line 220 – Demonstrates the command & GOSUB without passing any variables.
Line 250 – A loop is used to show that the stack is not corrupted by using these new commands. An OUT OF MEMORY ERROR will occur for 25 GOSUB calls without a proper return.
Line 260-280 – Establish random numbers for use in the variables.
Lines 290-320 – Demonstrates the & GOSUB command using both simple variables and arrays elements. The example subroutine adds together the first two numbers passed to it. The result is passed back in the third parameter.
Line 360 – Demonstrates that the subroutine call operated and that parameters were passed both ways.
Line 370 – Delay loop.
Line 1000 – Subroutine called without passing variables.
Line 2000 – New subroutine showing that variables were passed and used within the subroutine.
Line 5000-5070 – Machine language loading subroutine. It first checks that the DATA statements have been typed in correctly. Each DATA statement is the value of 16 locations plus the sum of the previous 16 locations used as a simple checksum. A typo error is indicated if the checksum is not the sum of the previous 16 locations.
Line 5080-5120 – Checks that POKEs have been performed.
Line 5130-5140 – POKEs the routine into memory.
Line 5150 – This establishes the AMPERSAND vector (&) pointers. This call is not necessary if the machine code is BRUN, but is necessary if the subroutine is BLOADed. Note that the CALL from BASIC is not the start of the ML program. If we did CALL the start of the program, an automatic NEW would occur, wiping out the demonstration program.
Machine Code Description
Briefly, the machine language program works as follows:
& GOSUB NAME!NAME(0), A, B...! The text pointers to the variable A are stored in the first two bytes of NAME(0). Then the value of A is moved into NAME(1), B into NAME(2) and so on.
& GET !NAME(0), W,X,...! The text pointers to the variable W are stored in the second two bytes of NAME(0). The value of NAME(1) is moved into W, NAME(2) into X and so on.
& RETURN !NAME(0)! The text pointer to W are recovered. The current values of W, X..are moved into NAME(1), NAME(2) etc. Then the text pointer to A is recovered. The values in NAME(1), NAME(2)...are moved into A, B....
The method of implementing the other commands is described in COMPUTE! #12.
Lines 15-31 – Zero page usage.
Lines 33-43 – Definition of tokens.
Lines 45-61 – Pointers to Applesoft routines. Internal Applesoft routines are used to cut down the amount of code required.
ADJMEM and AMPER. Lines 65-77 – Do the machine language equivalent of POKE 104, 0: POKE 2560, 0: NEW. Then set the AMPERSAND vector.
ENTRY. Lines 80-92 – Check on which of the new commands is required.
GOTO. Lines 94-99 – Front end of the normal Applesoft GOTO routine moved and modified to allow variables and numbers to be used in the GOTO statement.
GOSUB. Lines 101-134 – Handling of the & GOSUB command.
Line 101 – Front end of the normal Applesoft GOSUB routine moved and modified to allow variables and numbers in subroutine calls.
Line 121 — Is the first parameter an array?
This array is used for the storage of the text pointers and the parameters. The stack would get too full if it were used.
Line 130: — Store text pointers.
Line 132 — Move the other parameters into the array for storage.
GET. Lines 136—140 — Handling of the & GET command.
Line 136 — Locate the storage array.
Line 137 — Store the text pointers.
Line 139 — Move values stored in the array into the new parameters.
ARRay-GET. Lines 142—149 — Gets and stores the location of the storage array after checking the leading exclamation mark.
ON. Lines 151—189 — Handling of the ON X... command.
Line 152 — Get the value of X.
Line 154 — Determine if ON..GOTO or ON..GOSUB.
Line 163 — Decrement X until find the subroutine requested.
Line 167 — Step over the values not being used.
Line 172 — Return to BASIC if subroutine not found.
Line 176–189 — Adding these instructions will allow the passing of parameters in ON X... commands.
RETURN. Lines 191—213 — Handling of the & RETURN command.
Line 191 — Locate the storage array.
Line 198 — Store the current text pointers.
Line 199 — Recover the text pointers from the & GET statement.
Line 200 — Move the current values of the parameters in the & GET statement into storage.
Line 201 — Reset the storage array pointers.
Line 205 — Recover the text pointers from the & GOSUB statement.
Line 206 — Move the values in the storage array into the parameters used in the & GOSUB statement.
Line 207 — Recover the current text pointers and perform a normal RETURN.
CHecK-ARRay. Lines 215–228 — Checks and adjusts the pointers to the storage array if new variables have been introduced during the commands & GOSUB and & GET.
Modifications to the next two subroutines, PARSTO and STOPAR will allow the passing of INTEGER parameters.
PARameters-to-STOrage. Lines 230–243 — Moves the current values of the parameters in the & GOSUB and & GET commands into the storage array. Checks for integers and strings.
STOrage-to-PARameters. Lines 244-257 – Moves the values in the storage array into the parameters in the & GOSUB and & GET commands.
STOre-TeXT-pointers. Lines 259-264-Stores the current text pointers into the zeroth element (NAME(0)) of the storage array. The Y register is preset.
GET-TeXT-pointers. Lines 266-271 – Recovers the text pointers stored in the zeroth element of the storage array according to the value set in the Y register.
ADJust-PoinTers. Lines 273-276 – Adjust the pointers to the storage array if they have shifted because a new variable has been made. Note that the pointers don't have to be adjusted if a new array has been made. All new arrays will be placed above the storage array in memory as the storage array is defined first.
COMmand-END. Lines 278-283 – Looks for the final exclamation mark (!) of the command or other parameter. Pops the last subroutine address off of the stack allowing a quick return to BASIC if at the command's end.
"Applesoft Internal Entry Points" by Applesoft Computer Inc. in Apple Orchard March/April 1980, p. 12.
"Some Routines in Applesoft Basic" by J. Butterfield in COMPUTE!, September/October 1980, p. 68.
"Resolving Applesoft and Hires Graphics Memory Conflicts" by J. Schmoyer in COMPUTE!, April 1981, p. 76.
"Using Named GOSUB and GOTO Statements in Applesoft BASIC" by M. Smith in COMPUTE!, May 1981, p. 64.