Programming wheels. (programs for common functions) Robert D. Hasting.
Anyone who writes computer programs will get the feeling sooner or later that he is reinventing the wheel when developing code to perform common functions.
The purpose of this article is to help you avoid the need to reinvent some common programming wheels. The wheels provided here are subroutines developed as part of an overall Medical Office system that I hve written.
They include a method of printing standardized error messages, an input checking routine, a number checker, a date checker, and a way to manipulate time (now doesn't that sound intriguing).
The individual subroutines may be used singly or nested together in a program.
The system used to develop these routines is an IBM Personal Computer with 64K memory, one 5 1/4 floppy disk drive, a color graphics display board, a parallel printer interface with a C. Itoh Model 1540 printer, and an El Cheapo 13 black and white TV set. The system runs with IBM DOS and Advanced Disk BASIC. Wherever I have used an unusual IBM Basic statement, an explanation of its function is included.
Let's start with the subroutine that is the most important part of any program that is operated by the uninitiated (that is anyone who doesn't eat, drink, and sleep with a computer as you and I do). This is the routine which prints a customized error message on the bottom of the display in response to incorrect operator input or a program failure condition. Big deal, you might say. So did I until I tried to create the routine and made 483 stupid mistakes. I did verify Programmer's Hypothesis #27 which states that programming errors increase exponentially with the latness of the hour.
Listing 1 is the error printing subroutine. The information that must be prepared prior to calling the subroutine is the actual error message stored in ERR MSG$ (clever variable name, huh?) and the position of the cursor prior to entering the subroutine stored in ROW and COL. In IBM Basic, this is acquired as follows:
100 ROW = CSRLIN : COL = POS(0)
The subroutine performs several functions. It sets an error flag to indicate that an error has been printed, beeps to wake you up, prints the error message in reverse video on the bottom of the screen, and, if called in response to an incorrect input by the operator, erases the incorrect input and returns the cursor to the original location.
IBM Basic has several interesting features which are used here. The normal video size is 24 X 40 or 24 X 80. This is woftware controlled by the WIDTH X command. The 25th line is normally used to display the mnemonics associated with the ten function keys located on the left side of the keyboard. However, the 25th line may be used by a program if the mnemonics are turned off. Line 970 does this.
If this line were located at the beginning of the main program, it would not have to be repeated here. A unique feature of this magic 25th line is both a blessing and a curse. This is the fact that the 25th line does not scroll upward with the rest of the screen when information is displayed --what a perfect place to anchor an error message.
Now for the secret curse. Even though the ikth line does not scroll, if the contents to be printed on the line exceed the display width, the remainder of the line is printed on the 24th line and does scroll upward. Stay tuned for an explanation of how this was cleverly deduced. But first, back to the listing.
Line 990 is not a gag. The BEEP statement toggles an 800 MHz. tone through the PC speaker for a quarter of a second. PRINT CHR$ (7) serves the same function. The LOCATE command in line 1000 moves the cursor to row 258 column 1 and turns off the cursor. Line 1010 invokes reverse video (black on white) and line 1020 prints the error message.
Now back to the curse. The STRING$ (X, Y) function returns a string of length X whose characters all have ASCII code Y. POS(0) returns the present cursor column position which, in this application, would appear right at the end of the error message.
What I wanted to do was to print white blanks to the end of the line so that the entire line would be in reverse video, regardless of the length of the message. That's what I wanted to do. However, on the 24th line which then merrily moves upward on the screen as you print other information.
If the previous dissertation made no sense at all, just remember to keep error messages shorter than the display width. To insure that this would not happen to me again, I defined the variable WIDE= 40 in the main program to be the value of the display width.
Then I changed line 1020 to truncate the error message to the length set by WIDE. The LEFT$ (X$, Y) function does this by creating a substring of X$ that starts at the leftmost character and extends for a length of Y characters.
The next two lines, 1030 and 1040, return the video to normal and the cursor to the position it occupied prior to printing the error message. But before we leave this fancy routine, let's do one more thing.
Good programming practice demands that the program print a prompt prior to each operator input and then check the input for correctness. If an error is detected, the program should print an error message and prompt the operator for another input. since the Medical System would use an 80-character monitor, I thought I would save myself a few keystrokes later on by extending the STRING$ function to the end of an 80-character line with STRING$ (80, 32).
What I had missed was a piece of information on page 4-186 of the IBM Basic manual which states that "If the printed line is longer than the defined WIDTH, basic goes to the next physical line and continues printing.' My El Cheapo TV displays a 40-character line.
As I mentioned before, when you try to print an 80-character string on the non-scrolling 25th line of a 40-character display, the remainder of the line is printed
Remember that ROW and COL contain the location of the cursor prior to the printing of the error. Line 1050 prints a string of blanks to erase the incorrect input and line 1060 returns the cursor to its original position.
All of this may seem to be a great deal of trouble just to print an error message, but there are important programming principles at work here. All error messages should be easy to read, easy to understand, and should attract the operator's attention immediately. This routine accomplishes two of the three objectives. Clear and simple error message text will accomplish the third.
A second subroutine (Listing 2) developed in the total Medical Office system provides a method of checking an operator input to see if it is a number. The operator input is placed in the variable, CHECK$. The subroutine first sets the error flag, MISTAKE, to zero and prints a line of blanks on the 25th line.
The variable BLANK$ has been defined in the main program as a 40-character string of blanks. Remember that the 25th line is where error messages are printed by the error printing routine.
The number checking subroutine then looks at each character individually with the MID$ (X$, Y, Z) function. X$ is the string to be divided, Y is the starting position, and Z is the number of characters in the substring.
Line 880 checks to see if the ASCII value of the substring lies between 48 and 57--the digits 0 through 9. If the entire input is numeric, the subroutine returns to the main program. If a non-numeric character is found, line 890 checks to see if it is a decimal point. If is not a decimal point, then ERRMSG$ is loaded with the proper error message and the error print-the error printing subroutine is called.
Since the error printing routine changes the value of MISTAKE from zero to one, it is an easy matter for the main program to detect an error by checking the value of MISTAKE.
Many business programs make use of multiple menus to guide operator input. They also allow the operator to return to the main menu or to the beginning of the program by pressing the ESC key. The next subroutine (Listing 3) provides this capability, as well as some input editing features.
After initializing the INPT$ variable, the routine turns on a medium sized cursor with the LOCATE, 1,6,7 command. The 1 means turn on, and the 6,7 specifies the cursor size and position.
Lines 100 and 110 wait for an operator keystroke. Each keystroke undergoes four tests. ASCII value 27 in line 120 is the value for the ESC key, which returns the subroutine to line number 1640 at the beginning of the main program. Most Basic languages do not include this feature. This line could be changed to set a flag which the main program could check. See Figure 1 for an example.
The second check is for ASCII value 13 which is the RETURN key, the normal exit from the subroutine. The IBM keyboard has two keys that can be used to erase characters from a line, the BACK-SPACE key and the DEL key. ASCII value 8, the BACKSPACE key, is checked in line 140. The DEL key returns a twocharacter string when pressed. The ASCII value of the second character is 83. Line 150 checks for this key. If either keystroke is detected, a short routine is called.
This routine shortens INPT$ by one character, backs up the cursor, prints a space, and then backs up the cursor again. This has the effect of erasing one character from the screen. If the four checks are passed, then the character is added to INPT$ and printed. Line 170 is needed because the INKEY$ function does not automatically display the keystroke on the screen.
Checking The Date
Often a program requires a knowledge of the present date. IBM Basic has a DATE$ function. However, if the operator makes an incorrect input while setting the date, Basic prints an error message and crashes.
I needed the ability to check operator input, to generate the appropriate error message (Where have I seen that before?), and to allow the operator to correct the mistake. I also needed the month, day, and year in separate variables. The subroutine in Listing 4 provides these features. It uses the other subroutines that we have already developed.
The format for the date that is checked by this subroutine is MM/DD/YYYY or MM-DD-YYYY. The date to be checked is contained in DTE$. The INSTR function in line 1130 searches for the location of the first occurrence of a slash character. The same function could be accomplished with the following loop:
1135 IF MID$ (DTE$, Y, 1)="/' THEN 1200
1140 NEXT Y
If no slash is found, the INSTR function returns a zero, and the first occurrence of a hyphen is checked. If no hyphen is found, the error printint routine is called.
If a slash is found, line 1200 extracts the substring containing the month. This substring is sent to the number checking routine and then is checked to see if it is a valid month.
Lines 1290 and 1310 look for the second occurrence of a slash or a hyphen. The substring containing the day is extracted and is run through the same checks. Finally, the same procedure is used to isolate and check the year.
If all checking is successful, the date is broken into the variables MO, DAY, and YEAR before the subroutine returns to the main program.
Now let's manipulate time. In several programs that I have written, the operator has been required to wait while a long sort or file transfer takes place. The time delay has been dependent upon the number of records being handled. The time to process a single record can be estimated.
The total transaction time can be calculated based on the number of records times the single record processing time. Then it is a simple matter to provide the operator with an indication of the time required to complete the entire proess. Listing 5 does this.
The only information needed by the subroutine is S, the total number of seconds for the entire transaction. The subroutine calculates the hours and minutes.
It converts these separate values into strings for concatenation (that means stick them together into one thing) into HH:MM:SS format.
Unfortunately, IBM Basic places a space in front of each number when converting it to a string. The routine in Lines 730-800 strips away the space and adds a zero to any value that is only one digit long.
Finally, Line 710 puts the whole mess together. That's concatenation! This can be printed in an appropriate message telling the operator to go get a cup of coffee.
Since IBM Basic has an internal clock accessible through the TIME$ function, I carried the subroutine one step farther and calculated the time at which the process would be finished.
This is done by separating TIME$ into hours, minutes, and seconds; adding the transaction time to these values; and reconverting the total to HH:MM:SS format. Lines 330-520 adjust the date if the total transaction time exceeds 24 hours. The subroutine uses the Date Check subroutine (Listing 4) to separate today's date, which is stored by the internal IBM clock in DATE$, into DAY, MO, and YEAR variables.
To show how these subroutines can interact with one another, I have written a simple program that asks for the date and time, prints out the separated date and then calculates a file transfer time. See Listing 6. Remember that error messages appear in reverse video at the bottom of the screen.
I hope that you can apply these routines and their underlying principles to the programs that you develop for your computer. Perhaps they will save you the trouble of revinventing a wheel or two while keeping your programming efforts rolling merrily along.
Table: Listing 1.
Table: Listing 2.
Table: Listing 3.
Table: Listing 4.
Table: Listing 5.
Table: Listing 6.
Photo: Figure 1.