This chapter describes the benefits of using subprograms, and shows many of the details of using them.
The following program contains two subprograms and one user-defined function:
10 OPTION BASE 1
20 DIM Numbers(20)
30 CALL Build_array(Numbers(*),20) ! Subprogram call.
40 CALL Sort_array(Numbers(*),20) ! Subprogram call.
50 PRINT FNSum_array(Numbers(*),20) ! User function call.
60 END
65 !
70 SUB Build_array(X(*),N) ! Subprogram "Build_array".
80 ! X(*) is the array to be defined
90 ! N tells how many elements are in the array
100 ! (1 is assumed to be the lower index)
110 FOR I=1 TO N
120 DISP "ELEMENT #";I;
130 INPUT "?",X(I)
140 NEXT I
150 SUBEND
155 !
160 SUB Sort_array(A(*),N) ! Subprogram "Sort_array".
170 ! A(*) is array to be sorted
180 ! N tells how many elements are in the array (1 is assumed
190 ! to be the lower bound)
200 ! Sort the array (elements 1-N) in increasing order
210 ! Algorithm used: Shell sort or Diminishing increment sort
220 ! Ref: Knuth, Donald E., The Art of Computer Programming,
230 ! Vol. 3 (Sorting and Searching), (Addison-Wesley 1973)
240 ! pp. 84-85
250 INTEGER T,S,H,I,J
260 REAL Temp
270 T=INT(LOG(N)/LOG(2)) ! # of diminishing increments
280 FOR S=T TO 1 STEP -1
290 H=2^(S-1) ! ...16,8,4,2,1
300 FOR J=H+1 TO N
310 I=J-H
320 Temp=A(J)
330 Decide: IF Temp>=A(I) THEN Insert
340 Switch: A(I+H)=A(I)
350 I=I-H
360 IF I>=1 THEN Decide
370 Insert: A(I+H)=Temp
380 NEXT J
390 NEXT S
400 SUBEND
405 !
410 DEF FNSum_array(A(*),N) ! User-defined function "Sum_array".
420 ! Add A(1)...A(N)
430 INTEGER I
440 REAL Array_total
450 FOR I=1 TO N
460 Array_total=Array_total+A(I)
470 NEXT I
480 RETURN Array_total
490 FNEND
Lines 10 through 60 are the main program. As you can see, it does nothing but call subprograms, which in turn do all the work. Line 70 is the header for the subprogram which asks the user to enter the values stored in his array. Notice that the main program has declared the array's name to be Numbers(*), but the subprogram uses the name X(*) to deal with the same array. The subprogram can name its variables whatever it wants without interfering with variables used outside the subprogram's context. The only variables that can be affected outside the subprogram's context are those passed through the parameter list (as shown here) or through COM (discussed later). In both cases, the matching between the subprogram and the outside world is done through the position of the variable(s) in the parameter list or COM block, not the actual name of the variable(s).
Starting at line 160 is the next subprogram which sorts the array into ascending order. The comments at the front of the subprogram serve to discuss the definition of the parameters used, and what effect the subprogram has on them. Also, the algorithm used is given, along with the proper reference material. It is an excellent idea to give a list of such pertinent details at the front of all subprograms. This makes debugging, modifying, optimizing, and re-using the subprogram much easier.
Starting at line 410 we see an example of a function subprogram. Functions are similar to SUB subprograms in concept. This particular example just adds the elements of the array together and returns the final value to the main program, which prints it.
The preceding examples only showed some of the general features of subprograms. This section shows a few of the details of using subprograms.
We have seen in the above examples how the two types of subprograms are called--SUBs are invoked explicitly using the CALL statement, while functions are invoked implicitly just by using the name in an expression, an output list, etc. A nuance of SUB subprograms is that the CALL keyword is optional when invoking a SUB subprogram. Thus our example of the main program which causes an array of numbers to be sorted could look like this:
10 OPTION BASE 1
20 DIM Numbers(20)
30 Build_array(Numbers(*),20)
40 Sort_array(Numbers(*),20)
50 PRINT FNSum_array(Numbers(*),20)
60 END
There are, however, three instances which require the use of CALL when invoking a subprogram.
CALL is required:
A subroutine and a subprogram are very different in HP BASIC.
If you are a newcomer to HP BASIC, be careful to distinguish between these two terms. They have been used differently in some other programming languages.
A subprogram is located after the body of the main program, following the main program's END statement. (The END statement must be the last statement in the main program except for comments.) Subprograms may not be nested within other subprograms, but are physically delimited from each other with their heading statements (SUB or DEF) and ending statements (SUBEND or FNEND).
A subprogram has a name which may be up to 15 characters long. Here are some legal subprogram names:
Initialize
Read_dvm
Sort_2_d_array
Plot_data
A SUB subprogram (as opposed to a function subprogram) is invoked explicitly using the CALL statement. A function subprogram is called implicitly by using the function name in an expression. It can be used in a numeric or string expression the same way a constant would be used, or it can be invoked from the keyboard. A function's purpose is to return a single value (either a REAL number, a COMPLEX number or a string).
There are several functions that are built into the BASIC language which can be used to return values, such as SIN, SQR, EXP, etc.
Y=SIN(X)+Phase
Root1=(-B+SQR(B*B-4*A*C))/(2*A)
Using the capability of defining your own function subprograms, you can essentially extend the language if you need a feature not provided in BASIC.
X=FNFactorial(N)
Angle=FNAtn2(Y,X)
PRINT FNAscii_to_hex$(A$)
In general, if you want to analyze data and generate a single value, then you probably want to implement the subprogram as a function. If you want to actually change the data itself, generate more than one value as a result of the subprogram, or perform any sort of I/O, it is better to use a SUB subprogram.
As mentioned earlier, there are two ways for a subprogram to communicate with the main program or with other subprograms:
There are two places where parameter lists occur:
30 CALL Build_array(Numbers(*),20) ! Subprogram call.
50 PRINT FNSum_array(Numbers(*),20) ! User-defined function call.
It is known as the pass parameter list because it specifies what information is to be passed to the subprogram.
70 SUB Build_array(X(*),N) ! Subprogram "Build_array".
410 DEF FNSum_array(A(*),N) ! User-defined function "Sum_array".
This is known as the formal parameter list because it specifies the form of the information that can be passed to the subprogram.
The formal parameter list is part of the subprogram's definition, just like the subprogram's name. The formal parameter list defines:
The subprogram has the power to demand that the calling context match the types declared in the formal parameter list--otherwise, an error results.
The calling context provides a pass parameter list which corresponds with the formal parameter list provided by the subprogram. The pass parameter list provides:
It is perfectly legal for both the formal and pass parameter lists to be null (non-existent).
There are two ways for the calling context to pass values to a subprogram:
The distinction between these two methods is that a subprogram cannot alter the value of data in the calling context if the data is passed by value, while the subprogram can alter the value of data in the calling context if the data is passed by reference.
The subprogram has no control over whether its parameters are passed by value or passed by reference. That is determined by the calling context's pass parameter list. For instance, in the example below, the array Numbers(*) is passed by reference, while the quantity 20 is passed by value.
30 CALL Build_array(Numbers(*),20) ! Subprogram call.
The general rules for passing parameters are as follows:
Note that enclosing a variable in parentheses is sufficient to create an expression and that literals are expressions. Using pass by value, it is possible to pass an INTEGER expression to a REAL formal parameter (the INTEGER is converted to its REAL representation) without causing a type mismatch error. Likewise, it is possible to pass a REAL expression to an INTEGER formal parameter (the value of the expression is rounded to the nearest INTEGER) without causing a type mismatch error (an integer overflow error is generated if the expression is out of range for an INTEGER).
Here is a sample formal parameter list showing which types each parameter
demands: SUB Read_dvm(@Dvm,A(*),INTEGER
Lower,Upper,Status$,Errflag)
@Dvm |
This is an I/O path name which may refer to either an I/O device or a
mass storage file. Its name here implies that it is a voltmeter, but it is
perfectly legal to redirect I/O to a file just by using a different ASSIGN
with @Dvm . |
A(*) |
This is a REAL array. Its size is declared by the calling context. Without
MAT, there is no way to find the size of the array except through information
supplied explicitly by the calling context; hence the parameters
Lower and Upper . |
Lower Upper |
These are declared here to be INTEGERs. Thus, when the calling program invokes this subprogram, it must supply either INTEGER variables or INTEGER expressions, or an error will occur. |
Status$ |
This is a simple string that returns the status of the voltmeter to the main program. The length of the string is defined by the calling context. |
Errflag |
This is a REAL number. The declaration of the string
Status$ has limited the scope of the INTEGER keyword
which caused Lower and Upper to require
INTEGER pass parameters. |
Let's look at our previous example from the calling side (which shows the pass parameter list):
CALL Read_dvm(@Voltmeter,Readings(*),1,400,Status$,Errflag)
@Voltmeter |
This is the pass parameter which matches the formal parameter
@Dvm in the subprogram. I/O path names are always passed
by reference, which means the subprogram can close the I/O path or assign
it to a different file or device. |
Readings(*) |
This matches the array A(*) in the subprogram's formal
parameter list. Arrays are always passed by reference. |
1, 400 |
These are the values passed to the formal parameters
Lower and Upper . Since constants
are classified as expressions rather than variables, these parameters have
been passed by value. Thus, if the subprogram used either
Lower or Upper on the left-hand side
of an assignment operator, no change would take place in the calling context's
value area. |
Status$ |
This is passed by reference. If it were enclosed in parentheses, it would be passed by value. Notice that if it were passed by value, it would be totally useless as a method for returning the status of the voltmeter to the calling context. |
Errflag |
This is passed by reference. |
Another important feature of formal parameter lists is the OPTIONAL keyword. Any formal parameter list (the one defining the subprogram) may contain the keyword OPTIONAL somewhere. The OPTIONAL keyword indicates that any parameters that follow it are not required in the pass parameter list of a calling context--they are optional. On the other hand, all parameters preceding the OPTIONAL keyword are required. If no OPTIONAL appears in the subprogram's parameter list, then all the parameters must be specified, or an error will be generated. The rules requiring matching of parameter types apply to OPTIONAL parameters as well as to ordinary parameters. There is a standard function called NPAR which can be used inside the subprogram to find out how many pass parameters the calling context actually did use. (NPAR will return 0 if used inside the main program, or if no parameters were passed to a subprogram.)
The OPTIONAL/NPAR combination is very effectively used in situations requiring external instrument setups. Most instruments have several different ranges, modes, settings, etc., which can be used depending upon the requirements of the user. Often, the user doesn't require the entire flexibility the instrument has to offer, and would rather use some reasonable defaults.
Consider the HP 3437A Digital Voltmeter. Among other things, this device has two data formats (packed and ASCII), three trigger modes (internal, external, and hold/manual), three voltage ranges (0.1V, 1V, and 10V), and also has programmable values for delay between readings and number of readings taken. Naturally, the values used for the various settings will depend entirely upon the application for which the voltmeter is being used, but let's make some assumptions:
A reasonable setup routine for the voltmeter might look like this:
2010 SUB Setup_dvm(@Dvm,INTEGER Readings,REAL Delay, OPTIONAL INTEGER
Prange, Ptrigger,Pformat)
2020 SELECT NPAR
2030 CASE 3
2040 Format=1 ! Default ASCII format
2050 Trigger=1 ! Default internal trigger
2060 Range=2 ! Default 1 volt range
2070 CASE 4
2080 Format=1
2090 Trigger=1
2100 Range=Prange
2110 CASE 5
2120 Format=1
2130 Trigger=Ptrigger
2140 Range=Prange
2150 CASE 6
2160 Format=Pformat
2170 Trigger=Ptrigger
2180 Range=Prange
2190 END SELECT
2200 OUTPUT @Dvm;"N"& VAL$(Readings)& "SD"& VAL$(Delay)
& "SR"& VAL$(Range)& "T"& VAL$(Trigger)& "F"& VAL$(Format)
2210 SUBEND
Legal invocations of the Setup_dvm subprogram are:
570 Setup_dvm(@Dvm,100,.001) ! Default Range,Trigger,Format
630 Setup_dvm(@Dvm,500,.05,3) ! Default Trigger,Format
850 Setup_dvm(@Dvm,50,.005,1,2) ! Default Format
1010 Setup_dvm(@Dvm,70,.075,2,1,2) ! Explicitly declare all values
Notice in the example above that local variables are used instead of the formal parameters. This is because it is illegal to use an OPTIONAL parameter variable if that variable was not passed from the calling context.
Since we've discussed parameter lists in detail, let's turn now to the other method a subprogram has of communicating with the main program or with other subprograms, the COM block.
There are two types of COM (or common) blocks: blank and labeled. Blank COM is simply a special case of labeled COM (it is the COM whose name is nothing) with the exception that blank COM must be declared in the main program, while labeled COM blocks don't have to be declared in the main program. Both types of COM blocks simply declare blocks of data which are accessible to any context having matching COM declarations.
A blank COM block might look like this:
10 OPTION BASE 1
20 COM Conditions(15),INTEGER,Cmin,Cmax,@Nuclear_pile,Pile_status$[20],
Tolerance
A labeled COM might look like this:
30 COM /Valve/ Main(10),Subvalves(10,15),@Valve_ctrl
A COM block's name, if it has one, will immediately follow the COM keyword, and will be set off with slashes, as shown above. The same rules used for naming variables and subprograms are used for naming COM blocks.
Any context need only declare those COM blocks which it needs to access. If there are 150 variables declared in 10 COM blocks, it isn't necessary for every context to declare the entire set--only those blocks that are necessary to each context need to be declared. COM blocks with matching names must have matching definitions. As in parameter lists, matching COM blocks is done by position and type, not by name.
There are several characteristics of COM blocks which distinguish them from parameter lists as a means of communications between contexts:
Any COM blocks needed by your program must be resident in memory at prerun time (prerun is caused by pressing [RUN], executing a RUN command, executing LOAD or GET from the program, or executing a LOAD or GET from the keyboard and specifying a run line.) Thus if you want to create libraries of subprograms which share their own labeled COM blocks, it is wise to collect all the COM declarations together in one subprogram to make it easy to append them to the rest of the program for inclusion at prerun time. (The subprogram need not contain anything but the COM declarations.)
COM can be used to communicate between programs which overlay each other using LOAD or GET statements, if you remember a few rules:
The first occurrence in memory of a COM block is used to define or set up
the block. Subsequent occurrences of the COM block must match the defining
block, both in the number of items, and the types of the items. In the case
of strings and arrays, the actual sizes need be specified only in the defining
COM blocks. Subsequent occurrences of the COM blocks may either explicitly
match the size specifications by re-declaring the same size, or they may
implicitly match the size specifications. In the case of strings, this is
done by not declaring any size, just declaring the string name. In the case
of arrays, this is done by using the (*)
specifier for
the dimensions of the array instead of explicitly re-declaring the dimensions.
Consider the following COM block definition:
10 COM /Dvm_state/ INTEGER Range,Format,N,REAL Delay,Lastdata(1:40),Status$[20]
The following occurrence of the same COM block within a subprogram matches the COM block explicitly and is legal:
2000 COM /Dvm_state/ INTEGER Range,Format,N,REAL Delay,Lastdata(1:40),Status$[20]
The following block within a different subprogram uses implicit matching and is also legal:
4010 COM /Dvm_state/ INTEGER Range,Format,N,REAL Delay,Lastdata(*),Status$
The following declaration is illegal, since it uses explicit size specifications for the array and string which do not match the original definition from line 10.
5020 COM /Dvm_state/ INTEGER Range,Format,N,REAL Delay,Lastdata(1:30),Status$[15]
The following declaration is also illegal, since it violates the types set forth by the defining block.
6010 COM /Dvm_state/ Range,Format,N,REAL Delay,Lastdata(*),Status$
In general, the implicit size matching on arrays and strings is preferable
to the explicit matching because it makes programs easier to modify. If it
becomes necessary to change the size of an array or string in a COM block,
it only needs to be changed in one statement, the one which defines the COM
block. If all other occurrences of the COM block use the
(*)
specifier for arrays, and omit the length field in
strings, none of those statements will have to be changed as a result of
changing an array or string size.
In all the previous discussion, subprogram calls were made explicitly by
specifying the subprogram name. For example, here are the ways you might
call Mysub
explicitly:
CALL Mysub(Distance)
Mysub(Distance)
This method is adequate in the vast majority of cases. As an advanced feature,
BASIC also allows you to call a subprogram using a string name. For example,
the following code segment is equivalent to previous call to
My_sub
:
100 Name$="Mysub"
110 CALL Name$ WITH (Distance)
For details about CALL, refer the HP BASIC Language Reference.
As mentioned in the introduction to this chapter, a subprogram has its own context or state which is distinct from a main program and all other subprograms. In between the time that a CALL statement is executed (or an FN name is used) and the time that the first statement in the subprogram gets executed, the computer performs a "prerun" on the subprogram. This "entry" phase is what defines the context of the subprogram. The actions performed at subprogram entry are similar, but not identical, to the actual prerun performed at the beginning of a program. Here is a summary:
Space for all arrays and variables declared is set aside, whether they are declared explicitly with DIM, REAL, INTEGER, or COMPLEX, or implicitly just by using the variable. The entire value area is initialized as part of the subprogram's prerun. All numeric values are set to zero, all strings are set to the null string, and all I/O path names are set to undefined.
ON KEYs are a special case of the event-initiated conditions that are part of context switching. They are special because they are the only event conditions which give visible evidence of their existence to the user through the softkey labels at the bottom of the CRT. These key labels are saved away just as the event conditions are, and the labels get restored to their original state when the subprogram is exited, regardless of any changes the subprogram made in the softkey definitions. This means the programmer doesn't have to make any special allowances for re-enabling his keys and their associated labels after calling a subprogram which changes them--the language system handles this automatically.
It is important to remember that the called subprogram inherits the softkey labels. All the keys are still active in some sense; ON KEY...CALL/RECOVER will cause their original program branches to take place immediately if the proper key is pressed, and ON KEY...GOTO/GOSUB will log the fact that a key is pressed until the subprogram is exited, at which time the proper branch will occur. This latter case may cause some consternation on the part of the user if he presses a softkey expecting immediate action and nothing happens since the key was temporarily disabled due to a called subprogram. If the called subprogram is expected to take a noticeably long time to execute, it might be a good idea to explicitly remove the labels from the disabled softkeys using the OFF KEY statement. Thus, the user won't expect anything to happen as a result of pressing a softkey. This technique is also useful for guaranteeing that a given subprogram is not interrupted prematurely. (The DISABLE statement is useful for preventing program branches as a result of an event-initiated happening, although it will not turn off the softkey labels.)
The event-initiated RECOVER statement allows the programmer to cause the program to resume execution at any given place in the context defining the ON...RECOVER as a result of a specified event occurring, regardless of subprogram nesting.
Thus, if a main program executes an ON...RECOVER statement (for example a softkey or an external interrupt from the SRQ line on an HP-IB), and then calls a subprogram, which calls a subprogram, which calls a subprogram, etc., program execution can be caused to immediately resume within the main program as a result of the specified event happening.
By way of illustration, consider the following example. Suppose you are
performing an exhaustive component test on a circuit board. The program may
be designed like so:
When lunch break comes around, you may want to halt the current test so you can use the computer to play chess, or your boss might wander by and want to see the results of the rest of the tests performed this week. In either case, if the test program is nested three or four levels deep in subprograms, it might take a while for the test to complete. By defining a softkey to RECOVER to the main program, you can instantly terminate the test at any time, and make the computer available for something else. The RECOVER will discard anything being done in any of the subprograms between the context declaring the event-initiated RECOVER, and the subprogram being executed when the specified event occurs.
Again, the DISABLE statement can be used within any subprograms in which it is critical not to allow interruptions.
Functions and subprograms can be called by using CALL and FN at the keyboard. There are some restrictions:
This section shows some of the tasks involved in using and managing subprogram libraries.
As mentioned earlier, subprograms are also convenient for use in creating and distributing libraries. They are also handy when you have a large program, along with sizable data arrays, which could potentially require more memory than is currently installed in your computer. You can break the program up into subprograms, each of which may be programmatically loaded, called, and then deleted in order to conserve memory. This section outlines some of the operations you will need to perform in creating, using, and maintaining subprogram libraries.
You can determine which subprograms are in a PROG-type (PROG for Series 300 and 700, PROG2 for Series 700) file by performing a CAT on the file:
CAT "ProgFile"
The system returns a list of the subprograms and user-defined functions in the file, along with other information, such as the amount of memory required for each subprogram. (See the HP BASIC Language Reference description of CAT for details.)
If you already have subprograms stored in PROG-type file(s), there are several options to choose from in loading them into memory:
LOADSUB Sub_name FROM "File"
LOADSUB ALL FROM "File"
LOADSUB FROM "File"
(Note that this statement is not programmable; that is, it cannot appear in a program line.)
You can also use INMEM to determine if a subprogram is already loaded. For example:
IF NOT INMEM ("Mysub")
THEN LOADSUB ALL FROM "MYSUBS"
Suppose your program has several options to select from, and each one needs many subprograms and much data. All the options, however, are mutually exclusive; that is, whichever option you choose, it does not need anything that the other options use. This means that you can clean up everything you've used when you are finished with that option.
If all of your subprograms can be put into one file, you can selectively retrieve them as needed with this sort of statement:
LOADSUB Subprog_1 FROM "SUBFILE"
LOADSUB Subprog_2 FROM "SUBFILE"
LOADSUB FNNumeric_fn FROM "SUBFILE"
LOADSUB FNString_function$ FROM "SUBFILE"
Note that only one subprogram per line can be loaded with this form of LOADSUB. If, for any program option, you need so many subprograms that this method would be cumbersome, you could use the following form of the command.
For this method, you store all the subprograms needed for each option in its own file. Then, when the program's user selects Program Option 1, you could have this line of code execute:
LOADSUB ALL FROM "OPT1SUBFL"
and if the user selects Option 2,
LOADSUB ALL FROM "OP2SUBFL"
and so forth.
There is one other form of LOADSUB, but it cannot be used programmatically. This is covered next.
In the LOADSUB FROM form, for which you need PDEV, neither ALL nor a subprogram name is specified in the command. This is used prior to program execution. It looks through the program in memory, notes which subprograms are needed (referenced) but not loaded, goes to the specified file and attempts to load all such subprograms. If the subprograms are found in the file, they are loaded into memory; if they are not, an error message is displayed and a list of the subprograms still needed but not found in the file is printed.
This can be handy in two ways. The first and obvious way is that subprograms can be loaded quickly. The other way is this: Type a LOADSUB FROM command where the file name is a file in which you know there are none of the subprograms you need (perhaps a null PROG-type file). Of course, no subprograms will be loaded, but a list of those yet undefined will be printed.
Any COM blocks declared in subprograms brought into memory with a LOADSUB by a running program must already have been declared. LOADSUB does not allow new COM blocks to be added to the ones already in memory. Furthermore, any COM blocks in the subprograms brought in must match a COM block in memory in both the number and type of the variables. Otherwise, an error occurs.
NOTE |
---|
If a main program is in a file referenced by a LOADSUB, it will not be loaded; only subprograms can be loaded with LOADSUB. Main programs are loaded with the LOAD command. |
With all this talk of loading subprograms from files, one question arises: How do you get the subprograms in the file? Easily: type in the subprograms you want to be in one file, and then STORE them with the desired file name. You must use STORE and not SAVE, because the LOADSUB looks for a PROG-type file. If you can't type in your subprograms error-free the first time (and who can?), you can type in your program with all the subprograms it needs and debug them. After storing everything in a file for safekeeping, delete what you do not want in the file, and STORE everything else in the subprogram file from which you will later do a LOADSUB. In this way, you know the subprograms will work when you load them.
The utility of the LOADSUB commands would be greatly reduced if you could
not delete subprograms from memory. There is a way to delete subprograms
during execution of a program: DELSUB. If you want to delete only selected
ones, you could type something like this (you can also execute these statements
in programs): DELSUB Sort_data,Print_report,FNPoly_solve
If you are sure of the positioning of the subprograms in memory, here is
a method of deleting whole groups of subprograms: DELSUB Print_report
TO END
You can combine these methods: DELSUB Sort_data,Print_report,FNGet_name$
TO END
The subprograms to be deleted do not have to be contiguous in memory, nor does the order in which you specify the subprograms in a DELSUB statement have to be the order in which they occur in memory. The computer deletes each subprogram before moving on to the next name.
If there are any comments after an FNEND or SUBEND, but before the next SUB or DEF FN, these will be deleted as well as the rest of the subprogram body.
If the computer attempts to delete a non-existent subprogram, an error occurs, and the DELSUB statement is terminated. This means that subprograms whose names are listed after the error-causing name will not be deleted.
You can avoid getting this error by first determining if the statement is already in memory by executing the INMEM command. For example:
IF INMEM ("mysub")
THEN DELSUB "mysub"
A subprogram can be deleted only if it is not currently active and if it is not referenced by a currently active ON RECOVER/CALL statement. This means:
Between the time that a subprogram is entered and the time it is exited, the computer keeps track of an activation record for that subprogram. Thus, if a subprogram calls a subprogram that calls a subprogram, etc., none of the subsequently-called subprograms can delete the original one or any of the ones in between because the system knows from the activation record that control will eventually need to return to the original calling context. A similar situation exists with active event-initiated CALL/RECOVER statements. As long as the possibility of the specified event occurring exists, the system will not let the subprogram be deleted. In essence, the system will not let you execute two mutually-exclusive, contradictory commands simultaneously.
There are some rules to remember when inserting SUB and DEF FN statement in the middle of the program. All DEF FN and SUB statements must be appended to the end of the program. If you want to insert a subprogram in the middle of your program because your prefer to see it listed in a given order, you must perform the following sequence:
With the PDEV binary, the job is much easier:
In either case there is an optional final step. It is not required that you do a REN to renumber the program at this point, but often it is desirable to close up the void left in the program line numbering which resulted from the block of subprograms being moved to the end of memory.
It is not possible to delete either DEF FN or SUB statements with the [DEL LN] or [Delete line] key unless you first delete all the other lines in the subprogram. This includes any comments after the SUBEND or FNEND. Another way to delete DEF FN and SUB statements is to delete the entire subprogram, up to, but not including, the next SUB or DEF FN line (if any). This can be done either with the DEL command, or with the DELSUB command.
If you want to merge two subprograms together, first examine the two subprograms carefully to insure that you don't introduce conflicts with variable usage and logic flow. If you've convinced yourself that merging the two subprograms is really necessary, here's how you go about it:
Once again, with PDEV, your job is greatly simplified: Execute a MOVELINES command in which you move everything from one subprogram--excluding the SUB/DEF FN and SUBEND/FNEND statements--into the desired position in the other subprogram. If there are any declarative statements in the moved code, you will probably want to move those up next to the declarative statements in the receiving code. Don't forget to go back to the place where the code came from and delete the SUB/DEF FN statement and the SUBEND/FNEND statements.
The SUBEND and FNEND statements must be the last statements in a SUB or function subprogram, respectively. These statements don't ever have to be executed; SUBEXIT and RETURN are sufficient for exiting the subprogram. (If SUBEND is executed, it will behave like a SUBEXIT. If FNEND is executed, it will cause an error.) Rather, SUBEND and FNEND are delimiter statements that indicate to the language system the boundaries between subprograms. The only exception to this rule is the comment statements (either REM or !), which are allowed after SUBEND and FNEND.
Both function subprograms and SUB subprograms are allowed to call themselves. This is known as recursion. Recursion is a useful technique in several applications.
The simplest example of recursion is the computation of the factorial function. The factorial of a number N is denoted by N! and is defined to be N × (N-1)! where 0!=1 by definition. Thus N! is simply the product of all the whole numbers from 1 through N inclusive. A recursive function which computes N factorial is:
100 DEF FNFactorial(INTEGER N)
110 IF N=0 THEN RETURN 1
120 RETURN N*FNFactorial(N-1)
130 FNEND
We'll consider a more useful application of recursion in the following section on Top-Down Design.
A major problem that every programmer faces is designing programs that can be easily implemented and tested. A method of program design that has become widely recommended is Top-Down Design, also known as Stepwise Refinement.
The general approach is to consider a problem at its highest level, and break it down into a small number of identifiable subtasks. Each subtask is in turn considered as a large problem which is to be broken down into smaller problems, and so on until the "smaller problems" which have to be solved turn out to be lines of code, which the computer knows how to solve! At the higher levels of this process, the various subtasks are implemented as subprogram calls. It is best to define exactly what each subprogram is supposed to do long before the subprogram is actually written. Furthermore, this should be done at each level of refinement. By considering what each subprogram requires as input and what it returns as output from the topmost levels, the most serious problems of programming (namely defining your data structures and the communications paths between subprograms) are attacked at the beginning of the problem solving process, rather than at the end when all the small pieces are trying to jumble together. It is best to tackle these questions at the beginning because then you have the most flexibility--no code has been written and it's not necessary to try and save any investment in programming time.