Jonathan E. Sisk's
Pick/BASIC: A Programmer's Guide
Chapter 9 - The CASE Statement and Controlling Switches
In program example 7, a very practical alternative to the IF-THEN statement is introduced: the CASE statement. Additionally, you will examine the effect of some of the "switches" available. These include the BREAK key, the ECHO flag, and the PRINTER output flag.
From Fig. 9-1, enter Program Example 7.
THE SUBSTRING (TEXT EXTRACTION) FUNCTION
The square brackets are used to extract a fixed number of characters from a string. This fixed number of characters is typically referred to as a substring:
017 IF OPTION[1,1] = "Q" THEN STOP ; * SHORTCUT BAILOUT
If the variable called NAME contained the string "WASHINGTON," for example, executing the instruction:
would produce "WASHING". The first numeric argument within the square brackets indicates the starting position within the string and the second numeric argument refers to the number of characters to be retrieved or extracted.
Fig. 9-1. Program Example 7.
EX.007 001 * EX.007 002 * CASE, BREAK KEY ON/OFF, PRINTER ON/OFF, ECHO ON/OFF 003 * mm/dd/yy: date last modified 004 * JES: author's initials
005 * 006 PROMPT ":" 007 * 008 10 * MAIN STARTING POINT AND RETURN POINT 009 * 010 PRINT @(-1) : @(20,0) : "EXAMPLE 7": ; * CLEAR SCREEN 011 PRINT @(58,0) : TIMEDATE() : ; * PRINT TIME AND DATE 012 PRINT @(3,3) : "A. BREAK KEY TEST": ; * DISPLAY MENU 013 PRINT @(3,5): "B. PRINTER TEST": 014 PRINT @(3,7): "C. ECHO TEST": 015 PRINT @(3,10) : "ENTER OPTION LETTER OR 'QUIT' TO STOP" : 016 INPUT OPTION ; * GET RESPONSE 017 IF OPTION[1,1] = "Q" THEN STOP ; * SHORTCUT BAILOUT 018 * 019 * MAKE DECISION BASED ON WHAT WAS ENTERED 020 * 021 BEGIN CASE 022 CASE OPTION = "A" ; * TEST BREAK ON AND OFF 023 BREAK OFF ; * DISABLE BREAK KEY 024 PRINT @(-1): @(10,10): ; * POSITION FOR MESSAGE 025 PRINT "YOUR BREAK KEY IS DISABLED..." ; * TAUNT OPERATOR 026 PRINT @(10,12) : "PRESS <CR> WHEN READY ": 027 INPUT ANYTHING ; * AWAIT RESPONSE 028 BREAK ON ; * ENABLE BREAK KEY 029 PRINT @(-1) : @(10,10) : "BREAK KEY IS WORKING AGAIN": 030 PRINT @(10,12) "PRESS <CR> WHEN READY " · 031 INPUT ANYTHING 032 CASE OPTION = "B" ; * TEST PRINTER ON, CLOSE, OFF 033 PRINTER ON ; * ENABLE PRINTER OUTPUT 034 CRT @(-1) : @(10,10) : "THE PRINTER FLAG IS NOW ON." : 035 PRINT CHAR(12) ; PRINT; PRINT; * SHOULD GO TO SPOOLER 036 PRINT "PRINTER TEST IN PROGRESS" ; * DITTO 037 PRINTER CLOSE ; * CLOSE SPOOLER ENTRY 038 PRINTER OFF ; * DISABLE PRINTER OUTPUT 039 PRINT @(10,18): "THE PRINTER FLAG IS NOW OFF" · 040 PRINT 8(10,20) : "PRESS <CR> WHEN READY" : 041 INPUT ANYTHING 042 CASE OPTION = "C" ; * TEST ECHO ON AND OFF 043 ECHO OFF ; * DISABLE CHARACTER ECHO 044 PRINT @(-1) : @(10,10) : "ECHO IS OFF. ENTER YOUR NAME": 045 INPUT YOUR.NAME ; * SHOULD NOT APPEAR ON SCREEN 046 ECHO ON ; * ENABLE CHARACTER ECHO 047 PRINT @(10,12) : "ECHO IS ON AGAIN. " : 048 PRINT "HI THERE, " : YOUR.NAME ; * PROVE IT'S BACK ON 049 PRINT @(10,14): "ENTER ANYTHING" : 050 INPUT ANYTHING 051 CASE 1; * MUST NOT BE A VALID ANSWER ANNOY OPERATOR NOW. 052 PRINT @(-1) : @(10,10) : "SORRY. NOT WHAT I WANTED" : 053 PRINT @(10,12) : "PRESS <CR> TO TRY AGAIN " : 054 INPUT ANYTHING 055 END CASE 056 GOTO 10 ; * START WHOLE THING OVER 057 END
On line 17 of the example appeared the statement:
IF OPTION[1,1] = "Q" THEN STOP
This tells the program to extract only the first character of the variable called OPTION. If it is the letter "Q," then the program stops. Otherwise, the program continues at the next line.
This approach simplifies the number of possible responses that you may need to test for when asking questions of operators. For example, if the program contained these statements:
PRINT "DO YOU WANT THIS REPORT PRINTED ? (Y/N) " : INPUT ANSWER IF ANSWER[1,1] = "Y" THEN PRINTER ON
It would prevent having to check for all the possible derivatives of the response, "YES." For example:
IF ANSWER = "Y" OR ANSWER = "YES" OR ANSWER = "YUP" THEN PRINTER ON
Note that on Ultimate systems, the second argument in the substring function defaults to one if omitted. For instance:
produces the same result as
THE CASE CONSTRUCT
You have seen the IF-THEN and IF-THEN-ELSE construct in most of the previous examples. The CASE construct in the example program (Fig. 9- 2) is similar to a series of IF statements.
021 BEGIN CASE 022 CASE OPTION = "A" ; * TEST BREAK ON AND OFF 023 BREAK OFF ; * DISABLE BREAK KEY 024 PRINT @(-1): @(10,10): ; * POSITION FOR MESSAGE 025 PRINT "YOUR BREAK KEY IS DISABLED..." ; * TAUNT OPERATOR 026 PRINT @(10,12) : "PRESS <CR> WHEN READY ": 027 INPUT ANYTHING ; * AWAIT RESPONSE 028 BREAK ON ; * ENABLE BREAK KEY 029 PRINT @(-1) : @(10,10) : "BREAK KEY IS WORKING AGAIN": 030 PRINT @(10,12) "PRESS <CR> WHEN READY " · 031 INPUT ANYTHING
Fig. 9-2. The first CASE condition in Program Example 7.
The CASE construct always begins with the statement BEGIN CASE. This statement generally appears on a line all by itself. It indicates to the system that a series of CASE statements will follow. The END CASE statement terminates the CASE construct.
The next executable statement after BEGIN CASE must be a CASE statement. The CASE statement has a structure somewhat similar to the IF statement, in that it is always followed by a conditional expression. When the conditional expression following a CASE statement evaluates true, all statements up to the next CASE or END CASE statement are executed.
In line 22 of this example, if the operator enters the letter "A," then all of the statements up to and including line 31 are executed. Execution then resumes at line 56, which is the first executable statement following the END CASE statement.
THE BREAK ON AND BREAK OFF STATEMENTS
The terminal break key may be enabled or disabled under program control:
023 BREAK OFF ; * DISABLE BREAK KEY
028 BREAK ON ; * ENABLE BREAK KEY
This is sometimes very important. There are many occasions when programs update multiple files. Disabling the break key prevents the operator from interrupting the program before all of the files have been updated, which would leave some of them updated and others not updated.
To prevent the operator from interrupting program execution by using the break key, use the statement:
This turns off the break key, rendering it useless until it is reenabled with the statement:
At line 23, the break key is disabled, and the operator is then encouraged to go ahead and "give it a try." The operator may hammer the break key as long (or as hard) as he or she wants to, but it won't work. Even the "last resort" of turning the terminal off and then on again proves useless on most versions of Pick. After they have exhausted their patience and hit a carriage return to satisfy the INPUT statement on line 27, the break key is enabled again on line 28 and once again the program suggests trying it.
This time it works. The side effect of "breaking" a program is that you are left in the PICK/BASIC debugger, at the "*" prompt character. (On some implementations of Pick, the program "breaks" into the debugger when the break key is enabled.) For example, when the break key is pressed, this program displays:
(break) *I28 *
At the * prompt character, enter the letter "G" and press Return. This instructs the program to "Go ahead" and resume execution exactly where it left off.
An important note about the debugger: There are two ways to get into it. One is voluntarily, as you just discovered. Under this circumstance, it is OK for you to enter a "G" and have the program continue from where it left off. The other case of entering the debugger is, of course, involuntarily. This occurs when you are running programs that encounter a "fatal" error condition, like trying to write to a diskette that is still in its jacket. A whole section is devoted to using the debugger in Appendix D. For now, you are left with your own intuitive skills for dealing with "fatal" program errors. Two other responses at the debugger prompt are END (to return control to TCL), and OFF.
GROUP LOCKS AND THE "DEADLY EMBRACE"
Note from the Author, July 5th, 1995: As I converted the original version of this chapter to its' Web version, I realized that this section is seriously out of date, and will I pick it up on the rewrite pass. The important thing to remember from what you read here is that all known current versions of Pick now support item locking, rather than the group-locking scheme you are about to hear. This makes your life much better.
Everything that has a potential benefit seems to come with some strings attached. This is true when dealing with the break key. The obvious benefit achieved by disabling the break key is in the protection that it offers to interrupting multiple file updates. The disadvantage occurs in the potential phenomenon known as "deadly embrace". This situation occurs, albeit rarely, when two processes contend for information from the same group in a file.
The Pick System scatters data items "across" the file storage area in a method referred to as hashing. Each file is created from a contiguous block of frames. The number of contiguous frames is a function of the modulo that was chosen for the file by the person who created it. The modulo thus specifies the number of groups that are available to the file. As extra storage space is required for a group, frames are automatically linked to the end of each group. This technique provides for automatic file expansion, which is completely transparent to the user. Unfortunately, it has some potentially serious side effects.
The problem stems from the theory of how items are updated in a file. When a new item is placed into a file, it is always placed at the "end" of the group. When an item is updated in a group, the item is physically "pulled out" of its old location. All the remaining items in the group "shift left" to fill in the gap created by the departure of the updated item. (It's like stepping out of a line for a movie: your space is immediately filled.) The updated item then is put at the end of the group.
While the group is "in motion," that is, "shifting left," there is a danger of another process attempting to update the group. If a second process does attempt to update the group, it may result in what commonly has been called a soft Group Format Error. This "transient" GFE is usually self-correcting. It often displays the terror-inducing message GROUP FORMAT ERROR! and then goes away. What happened is that once the update by the first process completed, the group returns to a stable condition, where the second process can now update the group. Realizing this, the second process effectively says "Just kidding!" and then completes the update. In theory, no data is lost.
To prevent this potential problem, PICK/BASIC has a provision for avoiding "contention." It is called group locks. Group locks are set with any derivative of a READ statement. These are the instructions that activate group locks:
Regular (non-locking) form: Group-lock form: READ READU READV READVU MATREAD MATREADU
Once a group lock has been "set" on a group, no other process may access that group until the lock is "released.", (access, in this definition, means that no other process may retrieve any item from the group through PICK/BASIC. Non-PICK/BASIC tasks, such as the SAVE process and the ACCESS retrieval language, are granted access to the data without even noticing the group locks.) Group locks are released when the item is written with the "normal" form of the appropriate WRITE statement, or when the RELEASE statement is executed. Here are more statements which affect the locks:
Regular (unlocking) form: Group-lock (non-unlocking) form: WRITE WRITEU READV WRITEVU MATREAD MATWRITEU
The potential scenario for disaster goes like this: Suppose there are two terminals running programs which update the same file. The first process reads an item from Group A and sets a group lock. Next, the second process reads an item from Group B, also setting a group lock. Now, without unlocking the group lock on Group A, the first process attempts to read an item from Group B, and runs into the group lock. The terminal running the first process "locks up" and starts beeping. Meanwhile, the second process- not even aware that the first process is "locked out"--attempts to read an item from Group A.
The second process terminal also locks up and starts beeping. Neither process may continue until the other has released the group locks, but they have locked each other out--hence the term "deadly embrace."
If the break key happens to be disabled at this point, both processes are in deep trouble, since they cannot be interrupted and "ended" through the PICK/BASIC debugger. Some implementations of Pick provide a TCL verb called CLEAR-GROUP-LOCKS, which unconditionally resets all of the group locks. Without having this verb, there are still a few other resorts, one of which is trying to log the locked terminals off from a third terminal using the LOGOFF verb. This doesn't always work, especially when the terminals' break keys are disabled. The last resort is a cold start; before doing so, make sure that all the other users have completed what they are doing and have logged off. This helps to ensure that all the write-required frames in real memory have had a chance to be written to disk, thus helping to avoid the possibility of a hard GFE.
OPTIONS FOR OUTPUT CONTROL
Pick/BASIC offers a number of statements that allow you to specify, under program control, the destination of program output.
The PRINTER ON Statement
In the second CASE statement, the program checks to see if the letter "B" is requested as the option. If so, then program execution transfers to line 33, where the PRINTER ON statement is executed:
033 PRINTER ON ; * ENABLE PRINTER OUTPUT
The PRINTER ON statement directs all output from subsequent PRINT statements to the printer. Actually, the output first goes to a part of the operating system called the Spooler. Perhaps you've had some prior experience with the Pick Spooler. If not, issue the command SP-ASSIGN at TCL before running this example. That sets your output assignment status to "normal." As long as you have a printer and it's ready, this works. "Ready" means that it is plugged in, turned on, and the "on-line" light is lit.
Incidentally, there is another way of directing output to the PRINTER. In Chapter 2 there was a brief discussion about the options that are available with the TCL commands used to compile and execute programs. The RUN command allows a (P) option. This has the same effect as issuing a PRINTER ON statement at the start of the program. From then on, all of the output from PRINT statements is directed to the Spooler. For example:
>RUN BP EXAMPLE (P)<cr>
Or, if the program is cataloged, simply enter
>EXAMPLE (P) <cr>
The CRT Statement
The CRT statement functions exactly like the PRINT statement but it always directs its output to the screen, regardless of the PRINTER ON/OFF status. Line 34 clears the screen, then positions the cursor to position 10 on line 10 and outputs the message "THE PRINTER FLAG IS NOW ON."
034 CRT @(-1): @(10,10): "THE PRINTER FLAG IS NOW ON.":
Line 35 issues a CHAR(12). On most printers, this causes a form feed. The first statement in a program that directs output to the printer also displays a message on the screen indicating the Spooler entry (job) number. This number is assigned automatically by the Spooler. In the example, after sending out the form feed, three rapid-fire PRINT statements are executed, which output several blank lines at the top of the report. These are followed by the message "PRINTER TEST IN PROGRESS," and that completes the print job.
Note for Ultimate users: The CRT statement may not compile in your program. If not, change the CRT statement(s) to DISPLAY, which may work.
The PRINTER CLOSE Statement
Once a program starts directing output to the Spooler, the report doesn't actually start printing until the print job is "closed":
037 PRINTER CLOSE ; * CLOSE SPOOLER ENTRY
Although the program has printed everything that it was told to, and even though a PRINTER OFF statement is about to be issued, the printer is not considered closed. It "closes" when one of two things happens: either a PRINTER CLOSE statement is executed, or the program stops.
The PRINTER OFF Statement
The PRINTER OFF statement resets the status of printer output. This means that the output from subsequent PRINT statements in the program are directed to the screen, rather than to the Spooler:
038 PRINTER OFF ; * DISABLE PRINTER OUTPUT
The message "THE PRINTER FLAG IS NOW OFF," is displayed and the program pauses to await input. Upon receipt of input, program execution transfers to line 56, the first executable statement after the END CASE statement.
The ECHO ON and ECHO OFF Statements
Normally, every character that is typed on the keyboard is first sent to the computer to be recognized, and then is "echoed" back to the screen. The ECHO OFF statement turns off the echo function. Although the program accepts all the characters that are entered, they are not displayed on the screen.
043 ECHO OFF ; * DISABLE CHARACTER ECHO 044 PRINT @(-1): @(10,10) ; "ECHO IS OFF. ENTER YOUR NAME" : 045 INPUT YOUR.NAME ; * SHOULD NOT APPEAR ON SCREEN 046 ECHO ON ; * ENABLE CHARACTER ECHO
Typically this feature is used when requesting passwords.
At line 44, terminal echo is disabled with the ECHO OFF statement, and you are then asked to enter your name. You will not be able to see the characters that you type as they are entered. On line 47, the terminal echo is reenabled with the ECHO ON statement and you are asked to enter something else. The characters that you type will appear as they are entered.
THE CASE 1 STATEMENT
At this point in the logic of the program, it has been determined that the response received is not the letter "A," nor "B," nor "C." If it had been one of these letters, then the series of statements following the appropriate CASE statement would have been executed and program execution would have then resumed at the first executable statement after the END CASE statement. Since the first executable statement after the END CASE is the statement GOTO 10, it causes program execution to go back to the top of the program, where the menu is displayed.
The CASE 1 statement is the catch-all case. It is generally used as the last CASE statement in a BEGIN CASE statement. This statement is executed if none of the other conditional expressions in the other CASE statements evaluate to true. If this statement is executed, a message is displayed indicating that there is a faulty operator at the keyboard, who should please try again.
THE CASE CONSTRUCT VS. IF-THEN
You may be wondering when to use a series of CASE statements rather than a series of IF-THEN statements. Good question. Some people feel that the CASE construct is more visually appealing than the IF-THEN construct-- but then, some people like Hawaiian music in elevators and some don't. Generally, CASE statements are used for "n-way" branches. There is at least one provable efficiency in the CASE statement over the IF-THEN statement.
Consider the examples shown in Fig. 9-3. The first example (A) illustrates "fall-through" IF-THEN logic, while the second shows the CASE construct.
These two examples effectively do the same thing. They assign the variable NAME based on the single letter entered into INITIAL. This kind of logic appears frequently in programs. The CASE form is much more efficient than the IF-THEN example, because once any of the conditional expressions evaluate true, then program execution transfers immediately to the next executable statement after the END CASE statement. In the first example, even after any one of the conditional expressions evaluates true, all of the other IF statements are still evaluated, even though they cannot possibly be true.
PRINT "ENTER CHARACTER'S INITIAL" : INPUT
IF INITIAL = "F" THEN NAME = "FRED FLINTSTONE"
IF INITIAL = "W" THEN NAME = "WILMA FLINTSTONE"
IF INITIAL = "P" THEN NAME = "PEBBLES FLINTSTONE"
IF INITIAL = "D" THEN NAME = "DINO FLINTSTONE"
IF INITIAL # "F" AND INITIAL # "W" AND INITIAL # "P" AND INITIAL # "D" THEN NAME = "UNKNOWN"
PRINT "ENTER CHARACTER'S INITIAL" :
CASE INITIAL = "F"
NAME = "FRED FLINTSTONE"
CASE INITIAL = "W"
NAME = "WILMA FLINTSTONE"
CASE INITIAL = "P"
NAME = "PEBBLES FLINTSTONE"
CASE INITIAL = "D"
NAME = "DINO FLINTSTONE"
NAME = "UNKNOWN"
Fig. 9-3. "Fall-through" IF-THEN logic vs. the CASE statement.
Additionally, the catch-all logic on line 7 of the first example is clumsy, where the CASE 1 portion of the second example is a much more elegant way of handling the "otherwise" situation.
REVIEW QUIZ 7
1) What is the significance of the "[" and "]" characters? Give an example of how they are used:
2) What does the BEGIN CASE statement do?
3) What is the general form of the CASE statement?
4) What do BREAK OFF and BREAK ON do?
5) What does PRINTER OFF do?
6) What impact does the PRINTER ON statement have on PRINT statements? On CRT statements?
7) What other method, besides PRINTER ON, is available for activating printer output?
8) What does PRINTER CLOSE do? When is it used?
9) What do ECHO OFF and ECHO ON do?