Building Blocks of Command Files
Even if you aren't yet aware of it, just by creating a command file, you have, in fact, become a programmer. As you become more familiar with the Shark language, you'll be writing longer and more complex command files, and you'll find the methods described in this section essential.
What Are Modules?
Modules are not just a set of commands, but an approach to programming in general. Basically, a module is any command sequence that performs a well-defined task. Modular programming is a technique that is used in Shark (among other computer languages) to create large and powerful command files by breaking the job into smaller components.
These component modules can then be linked so as to be executed in a "chain," one after another. Or they may be called into execution by commands in a "main" module: for example, you might have the individual modules linked to a single command file that creates a "main menu" to let the user decide which task (module) is to be performed.
There are several advantages to this approach:
- ■ First, short command sequences are obviously easier to debug than long ones. The component modules can be tested individually and errors spotted and corrected more quickly;
- ■ Second, by taking standard tasks (those to be performed a number of times in the course of a command file) and assigning them to modules wherever possible, you won't have to write the same command sequence over and over in the command file;
- ■ Third, a Shark command file can't be more than 15 to 20 thousand characters. It must be even smaller if it uses a REPORT command or data files with many fields;
- ■ Finally, and perhaps most importantly, the command file as a whole is easier to read because the details of each task are relegated to its respective module; the calling commands themselves indicate the action to be performed.
Keep in mind that these modular techniques will serve you best if you consider their use at the planning stage—before you actually start writing your command file.
Besides making the command files themselves more efficient, these techniques will make you a more efficient programmer. By breaking a long and complex job into smaller, more easily understood modules, you can even collect a library of command-file tools that you can use in many applications.
Chains, Procedures, and Subroutines:
There are three kinds of modules in Shark programming:
- ■ Chained files, which are simply a matter of one command file calling another into execution with the CHAIN command.
- ■ Procedures are modules within a command file. They are defined and named with the PROCEDURE structure, and called for execution by name with the PERFORM command.
- ■ Subroutines are modules stored as separate command files. A subroutine is called into execution by the DO command. When the subroutine has been performed, execution returns to the calling command file. In this section, we'll examine the differences among these three methods—which to use and when. In order to understand the differences, you should know something about the way Shark manages its memory space—its environment.
Environment:
The term environment refers to the current arrangement of data files, variables and other settings in Shark internal memory—its "workspace." This arrangement includes all of the data files opened and the memory variables being used and the order in which they are defined.
The environment changes when any of the following actions is performed:
- ■ A data file is opened (with the USE command) or closed (with CLOSE or CLEAR or any commands such as APPEND FROM, that close an open data file).
- ■ A file number is assigned to a data file (by SELECT and/or USE).
- ■ A data file's structure is changed (MODIFY); if a data file is modified, any compiled command files that use it must be re-compiled.
- ■ A memory variable is created or released; you needn't be concerned with the effect of memory variables on the environment unless one or more variables are released in your command file or its subroutines.
- ■ A new command file is executed (by DO or CHAIN in conversational mode or by CHAIN in a command file). Ideally, the environment for running a command file is established at the very beginning of the file: all data files to be used are opened and all memory variables declared, and all SET switches and command settings are established. This ideal practice is not always feasible in every application, however. Fortunately, Shark's language is flexible and varied enough so that there is rarely only one way of doing things.
Modular Programming Commands:
Each of the three modular techniques we've mentioned - chaining, procedures, and subroutines - affects the environment (and is affected by it) in different ways. Each has its own advantages and restrictions, and you should be aware of these differences in choosing which kind of module to use.
Chaining Command Files:
CHAIN
Leaves the current command file and starts running another. CHAIN clears the environment and replaces the command file in memory (the calling file) with the command file named (the chained file). CHAIN does not, by itself, return to the original command file (compare DO, below) but you can subsequently chain any number of other command files, including the original, calling file. A command file can even be chained to itself in order to start over from the beginning.
Environment:
In starting a new command file, CHAIN clears the environment - that is, it closes all currently open data files and releases all memory variables. If you want to continue to use the same data files, you'll have to reopen them in the new, chained command file.
To "carry over" a set of memory variables from the calling command file to the chained file, use the GLOBAL command at the beginning of both command files. (More information is available under "GLOBAL" in the SharkBase "Commands" Section)
The maximum size for a single command file that Shark can run is about 15 to 20 thousand characters (in its text form). Chaining a series of command files as modules is a good strategy for overcoming this restriction because each command file called into a chain runs independently, in its own environment. To run a command file that is larger than 15 to 20K, break it into smaller files and use CHAIN to run the smaller files one at a time.
Example:
Here's an example of a "main-menu" command file (named, appropriately enough, "mainmenu.prg") that uses chained files as modules:
ERASE
TEXT
What do you want to do?
0. Quit this program
1. General Ledger
2. Accounts Payable
3. Accounts Receivable
4. Report
ENDTEXT
ACCEPT "Enter choice (0 - 4)" TO answer
DO CASE
CASE answer = "0"
CLS
? "Program ended. Returning to Conversational Shark"
CANCEL
CASE answer = "l"
CHAIN ledger
CASE answer = "2"
CHAIN acctpay
CASE answer = "3"
CHAIN acctrec
CASE answer = "4"
CHAIN acctrpt
OTHERWISE
? "Incorrect entry. Press any key to try again."
ok = INKEY()
CHAIN mainmenu
ENDCASE
CHAIN mainmenu
Note that EACH of the four separate chained command files (ledger, acctpay, acctrec, and acctrpt) is a separate module for our purposes.
By typing a number from 1 to 4, the user activates the corresponding CHAIN command to call into execution the appropriate module. Each command file clears the existing environment and establishes its own. An error in one command file doesn't affect any of the others. The individual command files needn't be present at compile-time. When the main-menu command file is executed, however, a CPL or PRG version of each chained filed must be present where Shark expects to find it (usually with a \PRG or \CPL directory/folder) if its corresponding menu option is chosen. At the end of each of the module files is the command CHAIN mainmenu; thus, every module, including the main-menu module itself, eventually returns the user to this menu.
Procedures (Modules within the Command File):
A procedure is a module that is stored in the same file as the command that calls for it. A procedure is defined by putting a command sequence in a PROCEDURE structure at the end of the command file. Once defined, it can be called for execution anywhere in the file by the PERFORM command. Whenever a particular command sequence is called for a number of times at different places in a command file, you should consider defining the sequence as a procedure. This helps to keep the command file as a whole smaller and thereby easier to read and debug. To define a procedure:
PROCEDURE <procedure name>
Introduces the command sequence to be used as a named procedure. The procedure name can be up to 10 characters long: it may include letters, numerals, underlines or colons, but it must begin with a letter.
ENDPROCEDURE
This is the end-command of a PROCEDURE structure. Ends the PROCEDURE command sequence; execution resumes with the line below the calling PERFORM command.
To call a procedure:
PERFORM <procedure name>
The procedure call within the main command file: executes the named procedure. A procedure can contain a PERFORM command to call yet another procedure in the same command file. No procedure can call itself, either directly or though another procedure.
Environment for a Procedure:
Unlike a chained-file module, a procedure doesn't clear the current environment and create its own. Instead, it uses the environment that's in effect when it's first called.
When Shark compiles a command file, each procedure within the command file is compiled only once—in the environment of the first PERFORM command that calls it.
This means that if a given procedure is "sensitive" to the environment—i.e., if it refers to any data files, fields or memory variables—then every time it is called into execution, it should find the same environment - i.e. the same data files and variables in use - as when it was first called for by PERFORM.
Be especially careful if the environment of the command file changes between calling PERFORM commands— for example, if data files are closed and new ones opened or if the order of the memory variable table is changed (by releasing some variables and creating new ones). In such a case, the called procedure should be "insensitive" to the changed environment it must not refer to any data files or variables.
Example:
Here's a procedure that pops a help screen in a window over the existing screen as needed throughout a command file. The procedure is called for by the PERFORM command—here, for instance, in a DO CASE menu:
DO CASE CASE answer = "B" PERFORM showhelp <command file continues>
At the end of the command file, the procedure is defined by a PROCEDURE structure:
PROCEDURE showhelp SCREEN 1,2 | Store the current screen WINDOW 1,1,9,75 DOUBLE | Draw a 10-line window box TEXT | Display the help screen This could be a demo help screen . . . Pressto continue ENDTEXT p=inkey() WINDOW SCREEN 2,1 | Restore previous screen contents ENDPROCEDURE | Continue main program
Note that this procedure is insensitive to the environment: it will work with whatever data file happens to be selected when the PERFORM showhelp command is executed, regardless of which data files are opened, closed or selected between PERFORM commands.
In some cases, where there are changes to the environment in the course of a command file, you can make a procedure environmentally insensitive. Do this by providing the procedure with its own set of memory variables. That way, the values to be processed by the procedure need only be stored to these variables before the PERFORM command is executed.
VARIABLE templ,temp2,tempsubttl,temptax,temptotaltemp1 = price temp2 = quantity PERFORM calc REPLACE calc amount WITH tempsubttl REPLACE sales:tax WITH temptax REPLACE total WITH temptotal
At the end of the command file:
PROCEDURE calc tempsubttl = tempi * temp2 temptax = tempsubttl * .065 temptotal = tempsubttl + temptax ENDPROCEDURE
If you do use this method, be sure to define the variables to be used by the procedure at the start of the command file (we used the VARIABLE command for this) and to exempt them from any RELEASE commands.
SUBROUTINES - Modules in Separate Files:
DO:
The DO command — familiar to dBASE users — calls a module that's stored in a separate command file. In Shark, this kind of module is called a subroutine. Upon completing execution of the subroutine (or upon encountering a RETURN command), Shark returns execution to the next line after the DO command in the original, calling command file.
To create a subroutine:
The subroutine is a command file on disk, one with the extension PRG.
When the main command file is compiled, the subroutine is compiled within the calling command file.
To call a subroutine:
DO <command-file name>
Executes the named command file as a subroutine and, upon its completion, returns execution to the next command line in the current command file.
RETURN:
Used in the subroutine file to return command execution to the command line immediately following the calling DO command in the original command file. RETURN can be used to "break out" of the subroutine (e.g., in a branch structure) and resume the main command file.
If no RETURN command is provided in the subroutine, execution returns automatically to the calling command file upon completion of the subroutine.
Example:
In the main command file:
DO progl | Execute the instructions in the subroutine file <any command sequence> | Do this, then... RETURN | return to the main command file
DO commands can be nested: one subroutine file can call yet another; but nesting is limited to 6 levels.
Environment
Like chained command files, a subroutine is a command sequence that is stored in a separate command file on disk. Like procedures, a subroutine assumes the environment that's in effect when it is called, rather than creating its own.
There, however, the resemblance between subroutines and other modules ends. Here are the differences:
- ■ First, a chained command file always clears the environment before beginning execution. A subroutine, by contrast, "inherits" the existing environment.
- ■ Second, a subroutine always returns to the calling compiling file — i.e., to the command line immediately following the DO command that called it. Chained files can't return to the calling command file (except to start it over from the beginning).
- ■ Third, when you compile a command file that uses subroutines, the accompanying subroutines are also compiled. They are stored within the calling command file as overlays. No separate CPL file is created for the subroutine.
- ■ Fourth, unlike procedures, a subroutine is compiled each time it is called for. That means if the main command file calls for a single subroutine ten times, then the subroutine (as well as the current environment) is compiled ten times into the main command file.
This last distinction highlights the major advantage and the major disadvantage of using subroutines. Because it is compiled every time it's called, a single subroutine can be called into many different environments in fact, it can be called by more than one command file.
Compiling subroutines adds to the overall size of your compiled command file, however, and may make the command file as a whole too large for a floppy disk. There is a simple remedy for this: where the subroutine is to be executed in the same environment, put the calling DO command within a procedure. For example, define the procedure like this:
PROCEDURE subl DO process ENDPROCEDURE
and then call the subroutine with the command PERFORM subl. Where the same subroutine is called for in a second environment, put it in another procedure -
PROCEDURE sub2 DO process ENDPROCEDURE
- and call it with PERFORM sub2.
You may want to "turn off" the automatic compiling of subroutines in order to debug them separately. To do this, use the SET DO switch. See the Alphabetical Command Reference for details.
Summary - When to Use PERFORM, DO, and CHAIN:
All three command file-module structures—procedures, subroutines, and chained command files—can be applied in more or less the same way. All three redirect the execution of a main command file in order to run a stored command sequence, either from another file (CHAIN, DO) or from elsewhere in the same file (PERFORM).
Keep in mind that each type of module is compiled differently. For more information about the environment and Shark's use of memory, see Appendix A.
Guidelines:
- ■ Within a command file, use procedures wherever possible for repeated tasks. Think of the command file itself as a set of short procedures linked by a main, calling module.
- ■ As a good practice, where feasible, open all data files and define all memory variables at the beginning of the main command file module.
- ■ Where possible, avoid closing data files or releasing memory variables between PERFORM calls to a given procedure.
- ■ If your command file becomes too large for Shark to compile as a single file (more than 15 -20,000 characters), you may need to break the command file into two or more files. To do this, use CHAIN where possible instead of DO/RETURN. Use the GLOBAL command to pass variables between chained files
- ■ Use DO and RETURN when using CHAIN would require a lengthy or complex repeat definition of files and variables (e.g., a matrix).