Jonathan E. Sisk's
Pick/BASIC: A Programmer's Guide
Chapter 4 - The Concept of Loops
In the first programming example, an important aspect of Pick/BASIC was covered: data has to be checked to be sure that it adheres to some some format or condition. This is called data validation. Even though the Pick System has no concept of data types, as programmers and users we must ensure that data entered via the terminal is checked. We cannot effectively use a system if the contents of its items are not reliable.
Program Example 2 continues in this exploration of editing techniques and introduces the principles of reiteration ("looping"), string manipulation, and some expansions on the IF-THEN construct. Statements and functions covered include: MATCHES, GOTO, COUNT, TRIM, IF-THEN-ELSE, LOOP-UNTIL-REPEAT.
The first seven lines of the example reiterate principles, concepts, and instructions covered in Example 1. From this point on in the book, only new topics and new ideas on previous topics are examined.
Enter Example 2, shown in Fig. 4-1.
Fig. 4-1. Program Example 2.
TOP .I 001 * EX.002 002 * DATA VALIDATION, BRANCHING AND LOOP STRUCTURES 003 * mm/dd/yy: date last modified 004 * JES: author's initials 005 * 006 PROMPT ":" 007 * 008 10 * GET VALID SOCIAL SECURITY NUMBER 009 * 010 PRINT 011 PRINT "ENTER SOCIAL SECURITY NUMBER (nnn-nn-nnnn) ": 012 INPUT SOCIAL.SECURITY.NUMBER 013 IF SOCIAL.SECURITY.NUMBER = "QUIT" THEN STOP 014 * 015 * CHECK RESPONSE FOR ACCEPTABLE PATTERN 016 * 017 IF SOCIAL.SECURITY.NUMBER MATCHES "3N'-'2N'-'4N" THEN 018 PRINT "THAT'S A GOOD NUMBER" ; * MUST BE GOOD. 019 END ELSE 020 PRINT "SORRY, NOT A GOOD NUMBER. TRY AGAIN" 021 GOTO 10 ; * MUST NOT BE GOOD. 022 END 023 * 024 20 * GET VALID DATE 025 * 026 PRINT 027 PRINT "ENTER A DATE (mm-dd-yy) " : 028 INPUT TEST.DATE 029 IF TEST.DATE = "QUIT" THEN STOP 030 * 031 * CHECK "FIXED" PATTERN MATCH FIRST 032 * 033 IF TEST.DATE MATCHES "2N'-'2N'-'2N" THEN 034 PRINT 035 PRINT "DATE PASSED FIXED PATTERN MATCH" ; * YUP. IT PASSED. 036 END ELSE 037 PRINT 038 PRINT "DATE FAILED FIXED PATTERN MATCH" ; * NOPE. NO GOOD. 039 END 040 * 041 * NOW, CHECK VARIABLE PATTERN MATCH 042 * 043 IF TEST.DATE MATCHES "1N0N1X1N0N1X2N0N" THEN 044 PRINT 045 PRINT "DATE PASSED VARIABLE PATTERN MATCH" 046 END ELSE 047 PRINT 048 PRINT "DATE FAILED VARIABLE PATTERN MATCH" 049 END 050 * 051 * GET STRING FOR ALPHA AND MATCHES TEST 052 * 053 LOOP 054 PRINT 055 PRINT "ENTER A WORD FOR THE ALPHABETIC TEST" · 056 INPUT ALPHA.STRING 057 UNTIL ALPHA(ALPHA.STRING) DO 058 PRINT 059 PRINT "SORRY, THAT FAILED THE ALPHA TEST. TRY AGAIN" 060 REPEAT 061 IF ALPHA.STRING = "QUIT" THEN STOP 062 * 063 * PASSED ALPHA, NOW TRY IT WITH "MATCHES" 064 * 065 IF ALPHA.STRING MATCHES "0A" THEN ;* THAT'S A ZERO! 066 PRINT 067 PRINT "THAT ALSO PASSED THE MATCHES TEST" 068 END 069 * 070 * GET SENTENCE FOR "COUNT" TEST 071 * 072 PRINT 073 PRINT "ENTER SEVERAL WORDS EACH SEPARATED BY A BUNCH OF SPACES" 074 INPUT WORD.STRING 075 IF WORD.STRING = "QUIT" THEN STOP 076 * 077 * DETERMINE THE NUMBER OF SPACES 078 * 079 NUMBER.OF.SPACES = COUNT(WORD.STRING," ") 080 * 081 * TELL OBSERVER HOW MANY SPACES THERE ARE 082 * 083 PRINT 084 PRINT "YOUR RESPONSE CONTAINED" : NUMBER.OF.SPACES : "SPACES" 085 * 086 * STRIP EXTRA SPACES 087 * 088 WORD.STRING = TRIM(WORD.STRING) 089 * 090 * DETERMINE NUMBER OF SPACES AFTER STRIPPING WITH TRIM 091 * 092 NUMBER.OF.SPACES = COUNT(WORD.STRING," ") 093 * 094 * TELL OBSERVER THE NEW NUMBER 095 * 096 PRINT 097 PRINT "AFTER TRIMMING, THERE ARE" : NUMBER.OF.SPACES : "SPACES" 098 * 099 * SHOW OBSERVER THE STRING AFTER THE TRIM 100 * 101 PRINT 102 PRINT "HERE'S WHAT THE STRING LOOKS LIKE AFTER THE TRIM: " 103 PRINT 104 PRINT WORD.STRING 105 END
A Note about Program Execution. A PICK/BASIC program begins executing at the first executable (non-remark) statement in the program and then "walks down" through the program, one statement at a time, until it runs out of statements and the program stops. Various statements are used to overcome this default sequence of events. The IF statements are used to control which statements are executed, depending on the outcome of tests conducted in their conditional logic. The GOTO statement makes the program resume execution at a location other than the next physical source line, by transferring program execution to a statement label. Various other statements provided in PICK/BASIC allow the program to overcome the default sequential execution of a program.
USING STATEMENT LABELS
There are occasions in a program where execution may have to transfer to a point other than the next immediate line. Most often this is because a section of code must be repeated, or to bypass certain instructions. This phenomenon is known as looping and involves the use of statement labels. (Note that there are other, even better methods available to perform loops.)
On most Pick systems, a statement label is a number at the beginning of a source line. In our example:
008 10 * GET VALID SOCIAL SECURITY NUMBER
Note that the attribute (or line) numbers ("008" in this example) have nothing to do with statement labels ("010" in this example). Line numbers are placed on the left side of the screen by the Editor.
Statement labels are optional in PICK/BASIC. They are only used to indicate the destination of a GOTO or GOSUB statement elsewhere in the program. It is a good idea, however, to insert them only when they have a purpose. Unnecessary statement labels might be misleading during debugging and maintenance.
Spaces are generally not important to the syntax of a program. Some compilers are sensitive to the placement of spaces between variables and keywords (PICK/BASIC instructions), so it is advisable to put spaces between each keyword/variable in a statement. When a statement label is placed on a source line in a program, it must be the first non-blank character on the line and must be followed by a valid PICK/BASIC statement. One popular convention is to have only remark statements on source lines that contain statement labels, as in the previous example.
Some implementations of Pick allow statement labels to contain or consist of alphabetic characters. For example, on systems which support alphabetic labels, as illustrated in Fig. 4-2.
033 GOSUB PRINT.DETAIL.LINE * * 127 PRINT.DETAIL.LINE: ;* ROUTINE TO PRINT DETAIL LINE ON CHECK
Fig. 4-2. Using alphabetic statement labels.
THE MATCHES RELATIONAL OPERATOR
On line 17, a test is made to determine whether the input matches a predetermined pattern, using the MATCHES relational operator:
017 IF SOCIAL.SECURITY.NUMBER MATCHES "3N'-'2N'-'4N" THEN
If it does match, then the message "THAT'S A GOOD NUMBER" appears on the screen, and program execution continues after the END statement on line 22. If it does not match the pattern, then the message "SORRY, NOT A GOOD NUMBER. TRY AGAIN" appears on the screen and program execution transfers, via the GOTO statement, back to line 7, where the statement label "10" appears. Using the GOTO statement in this form illustrates one means of setting up a repetitive loop function.
The MATCHES (or MATCH) relational operator checks data against a pattern. Three pattern-match operators are available: "N" for numeric, "A" for alphabetic and "X" for wildcards (any character). These pattern-match operators must be preceded by an integer number (or zero), which indicates the exact length of the number of characters that will be accepted. In addition to the three pattern-match operators, Literals may be specified by enclosing literal strings in quotes. Parts A through D of Fig. 4-3 illustrate various pattern matches.
The pattern-match string in line 17 of the program indicates that the only string that will be accepted is one in which the first three characters are numbers, followed by a hyphen (-), followed by two numbers, another hyphen, and four more numbers. (When a pattern match operator is preceded by a zero, any number of the character type are accepted.)
Part A of Fig. 4-3 shows a form similar to line 17, here applied to dates. This form is technically correct but is not very flexible; it forces the operator to precede the one-character months (like May) and days of the month (like the fifth) with a 0 (zero) during entry. Further, it only allows the "-" as a delimiter.
Match Acceptable Unacceptable expression input input "2N'-'2N'-'2N" 12-01-97 12/01/97 12-1-97 "1N0N1X1N0N1X2N0N" 12-12-97 1DEC97 12/12/1997 12-12 2-2-97 1 - - 1 "1A2N1A3N" A22T003 A2T03 "1A0A', '1A0A" PALMER, ARNIE ARNIE PALMER
Fig. 4-3. Examples Of pattern matching expressions for A) date input, B) generalized date input, C) combined alphabetic and numeric entries, and D) common data entry conventions.
The Pick System does not require leading zeros or any specific delimiter between months, days, and the year. The only rule is that the delimiter must be non-numeric, and consistent. The next pattern match accepts virtually any valid "external" date format.
This expression literally reads, "Look for one number (1N) followed by any length of numerals (0N), which also includes zero numerals, followed by any character (1X) as a delimiter, and so on. This very "generalized" date pattern match handles just about any valid date. Using this technique prevents you from having to code individually all the possible pattern matches in a complex IF-THEN statement using multiple OR clauses.
The third example of Fig. 4-3 illustrates a pattern composed of letters and numbers. It specifically requires one alphabetic character, followed by two numbers, followed by another single alphabetic character, followed by three numbers. Part D illustrates a pattern of alphabetic characters, separated by the comma. This shows how the MATCHES operator may be used to enforce data entry conventions, which assist in standardizing the methods by which data is entered.
THE IF-THEN-ELSE CONSTRUCT
At the end of Example 1 there was an explanation of the single-line IF- THEN and the multiline IF-THEN constructs. The other extension to the IF statement is the ELSE construct, which in this example appears in lines 17- 22:
017 IF SOCIAL.SECURITY.NUMBER MATCHES "3N'-'2N'-'4N" THEN 018 PRINT "THAT'S A GOOD NUMBER" ; * MUST BE GOOD. LET'S CONTINUE 019 END ELSE 020 PRINT "SORRY, NOT A GOOD NUMBER. TRY AGAIN" 021 GOTO 10; * MUST NOT BE GOOD. 022 END
The THEN initiator precedes the statement, or statements, and executes when the conditional expression evaluates true (numeric nonzero). The ELSE construct precedes the statements and executes on a false condition (i.e., zero or null).
The Single-Line Form
The first, and simplest, general form of the single-line IF-THEN-ELSE construct is:
IF conditional.expression THEN statement ELSE statement
This reads, "If the conditional expression evaluates true, then the single statement after the THEN initiator is executed and program execution continues on the next line of the program; if the conditional expression evaluates false, then the single statement after the ELSE initiator is executed and program execution continues on the next line of the program."
Since statements may be delimited by semicolons the next form of this construct occurs as:
IF conditional.expression THEN statement; statement ELSE statement; statement...
(Note that this is still the single-line form!) If the conditional expression evaluates true, then the statements after the THEN initiator are executed in turn. If it evaluates false, then all of the statements after the ELSE initiator are executed. Once again, there is no limit to the number of statements that may follow the THEN or ELSE clause, but when there's more than one, it makes the program much more maintainable to have each statement on a separate line.
The Multiline Form
As discussed earlier in the explanation of the single-line form, it is generally accepted that when there is more than one statement to perform after the THEN or ELSE initiator, then the multiline form is used. This version has the general form:
IF conditional.expression THEN statement statement statement END ELSE statement statement statement END
This form also relies upon the initiator/terminator relationship introduced earlier. When the THEN appears as the last word on a line, it is considered an initiator, which means that it must be terminated on a subsequent source line with an END statement.
The same situation holds true with the ELSE clause. When the ELSE appears as the last word on a line, it too must be terminated later with an END statement.
When the conditional expression evaluates true, all of the statements up to the next END ELSE statement are executed. Because of the ELSE clause, after these statements execute, execution transfers to the first executable statement after the next END statement. When the conditional expression evaluates false, execution transfers to the statements following the END ELSE statement, and after execution, program execution continues with the next executable statement after the END statement.
THE GOTO STATEMENT
The GOTO statement makes the program resume execution at the first statement following the label number that follows the GOTO statement. (Remember that the source line numbers along the left side of the screen are placed there by the Editor, and have nothing to do with statement labels.) If this line is executed, program execution transfers to statement label "10," which happens to occur on source line 8.
021 GOTO 10; * MUST NOT BE GOOD. MAKE THEM TRY AGAIN.
Here is another example of the GOTO statement:
001 COUNTER = 0 002 10 COUNTER = COUNTER + 1 003 PRINT COUNTER 004 IF COUNTER = 5 THEN STOP 005 GOTO 10
This illustrates the logic behind using the GOTO statement to perform loops. In this case, the variable COUNTER is assigned an initial value of zero on line 1. On line 2, COUNTER is incremented by taking its current value and adding 1 (one) to it. The result of the calculation is assigned to the variable COUNTER so that its value is now 1 (one). Line 3 prints the current value of COUNTER, which outputs a 1 (one) on the next line on the screen. Line 4 is where the test takes place. The logic of line 4 indicates that if the current value of COUNTER is 5, then the program stops. Since there is no ELSE clause in the IF-THEN statement, the logic "falls through" to the next line (5) each time through the loop until the value in COUNTER reaches 5. The GOTO statement on line 5 unconditionally transfers program execution to line 2, where it finds the statement label "10."
While some programmers use GOTO statements as a standard practice, many programmers never use them. The reason that GOTOs are forbidden in some programming shops is that when they are overused and/or used incorrectly, they make program logic much harder to analyze--leading to what is often called "spaghetti code."
Forbidding GOTO statements is a technique that is frequently associated with the concept of structured programming, which is discussed in Chapter 13, following the discussion of subroutines and loops.
FIXED AND VARIABLE PATTERN MATCHES
This block of code illustrates the principles mentioned earlier in the explanation of pattern matches:
033 IF TEST.DATE MATCHES "2N'-'2N'-'2N" THEN 034 PRINT 035 PRINT "DATE PASSED FIXED PATTERN MATCH" ; * YUP, IT PASSED. 036 END ELSE 037 PRINT 038 PRINT "DATE FAILED FIXED PATTERN MATCH"; * NOPE, NO GOOD. 039 END
Specifically, the program is waiting for the input of a date. After receiving the input, on line 33 it is tested for a "fixed" pattern of 2 numbers followed by a "-" delimiter, two more numbers, another dash, and two more numbers. If the input adheres to this format, the message from line 35 displays; otherwise the message from line 38 displays.
The pattern match on line 43 is much more flexible about accepting input. Although it looks much more complicated, the benefit achieved in using it outweighs the complexity of coding it.
043 IF TEST.DATE MATCHES "1NON1X1NON1X2NON" THEN 044 PRINT 045 PRINT "DATE PASSED VARIABLE PATTERN MATCH" 046 END ELSE 047 PRINT 048 PRINT "DATE FAILED VARIABLE PATTERN MATCH" 049 END
Specifically, this pattern match looks for at least one number, followed by any character, at least one number, another character, and then at least two numbers.
THE LOOP CONSTRUCT
The concept of loops was introduced earlier in this example. There are actually several methods available to perform loops. Coincidentally, this one is called "LOOP":
053 LOOP 054 PRINT 055 PRINT "ENTER A WORD FOR THE ALPHABETIC TEST" : 056 INPUT ALPHA.STRING 057 UNTIL ALPHA(ALPHA.STRING) DO 058 PRINT 059 PRINT "SORRY, THAT FAILED THE ALPHA TEST. TRY AGAIN" 060 REPEAT
The LOOP construct has nearly as many possible forms as the IF- THEN statement; for the most part, however, there are several relatively "standard" methods of use. One such form is illustrated in Fig. 4-4.
001 COUNTER = 0 ;* Assign initial value 002 LOOP ;* Start/Return point 003 COUNTER = COUNTER + 1 ;* Increment Counter 004 PRINT COUNTER ;* Display Counter 005 UNTIL COUNTER = 5 DO ;* Check to see if done 006 REPEAT ;* Not done, return to LOOP 007 STOP ;* We're outta here...
Fig. 4-4. Standard form of the LOOP construct.
This standard form effectively does the same thing as the loop described earlier, which used the GOTO statement. Note that the LOOP form does not require statement labels or GOTO statements.
On line 1 of Fig. 4-3, the variable COUNTER is initialized to zero. On line 2 the LOOP statement appears, which indicates the beginning of a LOOP process. This is the point at which program execution returns when the next REPEAT statement is executed. The statements on lines 2 and 3 execute unconditionally, incrementing the value of COUNTER by 1 (one) and printing its value.
At line 5, the conditional expression test takes place to determine when to exit the loop. Line 5 reads, "If the current value of COUNTER is NOT 5, then REPEAT the process." Otherwise, when COUNTER does reach 5, execution transfers to the next statement after the REPEAT statement. In this case, that's line 7.
THE UNTIL CLAUSE AND DO INITIATOR
Each time the LOOP statement is initiated, there must be an UNTIL (or WHILE) clause:
057 UNTIL ALPHA(ALPHA.STRING) DO
The UNTIL/WHILE is always followed by a conditional expression and the initiator, DO, in the form:
UNTIL conditional.expression DO
WHILE conditional.expression DO
On line 57, the conditional expression tests the contents of the variable ALPHA.STRING to determine if it is entirely composed of alphabetic characters. The logic of this line reads, "If ALPHA. STRING contains only alphabetic characters, meaning that the ALPHA function evaluates true, then exit the loop." This means that program execution continues at line 61. Otherwise, the message "SORRY, THAT FAILED THE ALPHA TEST. TRY AGAIN" prints, and the REPEAT statement forces the loop to start over from line 53, where the LOOP statement occurs.
The basic difference between the UNTIL and the WHILE clause is in its logic. The UNTIL form works until a true (or positive) result occurs as a result of its conditional expression. The inverse of this is the WHILE form, which works while its conditional expression evaluates as true (numeric non- zero), or until it evaluates as false (zero or null). Fortunately, since they are so close in meaning, you may use one form for nearly every loop.
Note: On many systems, the UNTIL and WHILE clauses are optional in the LOOP construct. The EXIT statement may be used to terminate the loop.
A COMPARISON OF MATCHES AND ALPHA
Line 65 of the example, using the MATCHES relational operator, checks the input to determine if it is composed entirely of alphabetic characters, just like the ALPHA function did earlier:
065 IF ALPHA.STRING MATCHES "0A" THEN 066 PRINT 067 PRINT "THAT ALSO PASSED THE MATCHES TEST" 068 END
This illustrates an important principle in the Pick System. There is virtually always more than one way to do something1. These two separate functions only appear to be identical. The ALPHA function is, in fact, more efficient in terms of the amount of CPU "horsepower" required to perform the function, This happens to hold true when the NUM function is compared with the MATCHES "ON" as well, but the NUM function is more efficient than its "MATCHES" counterpart. This means that:
ALPHA(string) is better than MATCHES "0A"
NUM(numeric.expression) is better than MATCHES "0N"
The moral of this story is: Use the MATCHES statement only for "composite" or complex pattern matches, like dates or general ledger account numbers. Use the intrinsic functions, NUM and ALPHA, on purely numeric or alphabetic data, respectively.
Note that some characters, notably the hyphen and period will "pass" as acceptable numeric characters in the NUM function but are not accepted with the MATCHES "ON" statement.
THE COUNT FUNCTION
The COUNT function is used to determine the number of occurrences of a character, or a string of characters, within another string of characters. This example simply determines the number of spaces in the string of characters that you entered, and reports it on line 84:
079 NUMBER.OF.SPACES = COUNT(WORD.STRING," ")
For another example of the COUNT function, consider this example:
STRING = "ABC*DEF*GHI*JKL" SEARCH.STRING = "*" NUMBER.OF.STARS = COUNT(STRING,SEARCH.STRING)
Upon execution, the variable NUMBER.OF.STARS contains the number 3, since there are three occurrences of the "*" (asterisk) in the variable STRING. This statement and its counterpart, the DCOUNT statement, are particularly useful in PICK/BASIC, especially in situations when you need to determine how many values appear in an attribute. The DCOUNT function is covered in Example 4.
Note that there is a potential problem when using COUNT or DCOUNT with overlapping strings. For example:
produces "8" as the result, while
produces "9" as the result. This means that you must carefully make sure that you use DCOUNT only with a single character as the delimiter, or make sure that the delimiter is not repeated if null fields are possible within the string being counted.
THE TRIM FUNCTION
The TRIM function statement is very useful for removing extraneous blanks from a string of characters. When a string is "trimmed," all leading and trailing blanks are removed, and any occurrences of two or more spaces within the string are replaced by a single blank. On line 88, the string that you entered is trimmed, and the result is placed back into the same variable:
088 WORD.STRING = TRIM(WORD.STRING)
Consequently, on line 92, where the number of spaces within the string is counted, the number is dramatically reduced. Line 97 reports the number of spaces now present in the line, and line 102 displays the result of the TRIM function.
REVIEW QUIZ 2
1 ) The INPUT statement prints a character before waiting for input. How is this character assigned?
2) How could you make the ">" (right angle bracket) the prompt character?
3) What is a statement label?
4) What are two of the six methods to transfer program execution to another location in a program? (Use the techniques discussed in this chapter)
5) What relational operator checks input to make sure that it adheres to a particular format?
6) What pattern matches are required to validate the following formats?
A) 02-4000-01 B) A1000/101 C) CLAUS,SANTA D) 12/1/89 E) 1000.15
7) What two purposes does the END statement serve?
8) Where are spaces significant in a source program?
9) Where else are spaces used, and when?
10) What statement is required to print the number of occurrences of the letter "i" in "Mississippi?"