The CBasic clinic; part six. John A. Libertine.
This will be the last in our series for Creative Computing. It is my hope that you will have reached the stage where CBasic is no longer intimidating and that you can continue on to bigger and better things on your own. With your documentation and with CBasic User Guide, you should be able to attack the more advanced features of this fine language. It does take patience and determination, but the results will be well worth the effort. For some, there may well be no reason ever to consider another language. Among the most regular users of CBasic are professional business programmers. For all but the compulsive acquirer of software, CBasic might well do all you require and more.
Our program this month will introduce a few new concepts and recapitulate much of what we have already learned. It is a very practical and usable program. I use it myself for "quick and dirty" small lists to be printed out with WordStar and MailMerge when it doesn't make sense to set-up dBase II or DataStar. Since it is a simple, functional program, don't look for sophistication or state of the art. IT works and works well. It can be an educational experience. In no way should you think this is the ultimate in CBasic programming! The Menu
Let's start by introducing the menu concept. You have seen it used in commercial programs many times. At the start, you are given several choices. You choose what you want to do from a menu displayed on the console. Our program this month starts off with such a menu. Take a look at the beginning of MAILLIST.BAS in Listing 1.
Before the menu display, we have a simple title screen. Then there are a couple of very important preliminary lines. One dimensions a string variable (A$) as an array with seven subscripts. (Review the last session for details on arrays.) This will be used for the seven strings (each a field) in a single record (name, salutation, four address lines, and a telephone number). The next line assigns a filename. Putting this assignment at the start of a program makes it easier if you decide to change the filename in the future. In my own version, I allow the user to name the file at the outset as part of the program. You might consider this possibility yourself. You should now have enough background to be able to do it several different ways.
Simply listing a menu is extremely easy, of course. The first decision you must make is how to implement the user's choice. You have such possibilities as using GOTO or GOSUB statements or the ON XX GOTO variation. You could call up subroutines or user-defined functions. These and other possibilities exist. As has been true all along, the solution used here is simple and straightforward. Let me repeat that noen of the programs we have discussed in the "Clinic" are very sophisticated or elaborate. The purpose is to get you started in CBasic, not to explore the ultimates. That will come only with time and practice.
The user is asked to enter 1, 2, 3, or 4. If a number higher than four or lower than one is entered, the program goes back and enters the menu choices again. This is a simple "error trap." As you progress, try to insert error traps frequently. You don't want a user to go off into a never-never world that eventually spews out garbage or crashes a program. Remember, it takes only a few minutes to write in an error trap which could save hours and hours of frustration or worse for the user.
You will notice that choices 3 and 4 will result in the program being diverted to lines 3000 and 4000. If either 1 or 2 is chosen, the program continues along the main path. As this program exists for my own use, 1 and 2 will also go to specific subroutines; however, I am trying to keep this a little simpler and shorter so the differences between 1 and 2 do not become apparent until later. In any case, choice 1 or 2 will result in the program continuing along the main road. Okay, let's follow along for choice 1 or 2.
The first line initializes the counting variable (Count%) to zero. Then the next two lines open a file. We are assuming (whenever a file is opened) that the file exists on disk. HEre we expect there will be a file named MAIL.FIL on the currently logged disk (probably A disk). If the file is on the B disk and that is not the logged disk, then change the filename to B:MAIL.FIL.
The IF END line just above the OPEN line is very important if the file is not on the disk. The statement will find the end of the file if there is no file in existence. It is handy to understand this concept. If a file exists, the IF END statement will execute only when and if the last field in the last record is read (i.e., when the program tries to read a field and there is nothing there).
However, if a file does not exist, there is nothing to read (including the filename itself) from the very start. This will cause the IF END statement to execute immediately. In this specific case, if a file does not exist, the IF END statement causes the program to divert to line 10.1. This is the direction we want for choice 1. (start new file).
Notice that if the file does exist, the program will go to line 10.2 which reads the file into memory. In between, you will see a line that catches the possibility that choice 1 results in opening an existing file. If that happens, the program goes to a subroutine which prints a warning that a file already exists. The user can then proceed to add names to that file or abort the program. This is another error trap of sorts. Without it, a user could be adding to a file instead of opening a new one. This could be an embarrassing if not serious blunder.
At any rate, one of two things will happen here. Either the program will open an existing file and read to the end, or a new file will be created. In either case, we are now ready to enter new data into the file. The Input Section
You should be able to follow this part of the program easily by now. If you experience difficulties, go back to the last couple of sessions and re-read the sections on file handling. We come next to the input section, which asks for names, addresses, etc. Note that there are four lines available for the address portion. In my program, there are three address lines plus a company line since 99% of my lists are business-oriented. You might consider using this format as well. (Just substitute "Enter Company Name" for "Enter Address line #1".)
There is a very simple but vital instruction given regarding these four lines. Note "If all four not needed, type return to skip." Your whole program could blow up if you do not allow for the fact that some inputs will have no data. Here a simple RETURN or ENTER will skip the input. This works here because we are using the LINE INPUT format. This puts a "null string" (a string that has no characters in it) into the data file. It usually shows up in the file as two quotation marks not separated by a space (""). In some sequential files which do not use the LINE INPUT statement, a null string would be represented by two commas not separated by a space (,,). There are other ways to accomplish the same result.
For example, you could ask for a special coded input such as a little-used character (a percent sign or caret perhaps) to indicate no input. This requires picking up that character in the output portion of the program and then changing it to a blank line or space or no space at all as may be necessary. This type of input/output is more likely to be used if the data are mostly alphanumeric.
For numerical inputs, you can use a specific number which you know will not be entered under normal circumstances (99999 or zero for example). The technique used here is easier but is only efficient if your inputs are all strings rather than numbers and the size of your file is relatively short.
Bear in mind that this type of input makes up a file in which each record consists of only one field. That is because each LINE INPUT is sent to the file as a complete line ending with a carriage return. That tells the computer you have reached the end of a record. In our program, that means each complete entry takes up seven records (seven separate lines) in the file.
For long files, that is not very efficient in the use of disk space. For up to about a couple of hundred names and addresses, you will not notice any slowdown in program execution or any great increase in the file size; but at some point it will begin to effect both of these factors.
As each entry is completed, a "mini menu" appears at the bottom of the screen asking the user to proofread the entry and indicate what action to take next. If the entry is incorrect, entering a caret will allow him to re-do it. If it is correct, the user chooses to continue with the next entry or end the input if all names have been entered. You will probably use this technique in many programs. Just be sure your program addresses each choice in a logical order (not necessarily the way it is listed in the choice list!).
For example, in this program we first want to address the problem of an entry which has an error. We certainly do not in this case want the wrong inputs read into a file. So the first action is to catch the caret response and send the program back for re-entry. If we get by this line, we then have a simple read to a file of the seven entries (using a FOR/NEXT loop).
Immediately under this is a line that adds to the count of names. And immediately under that is the line which catches an F entry indicating that the user is finished with the input. (Be sure your program adds to the count and reads to the file before the F entry sends the program to STOP). But if neither the caret nor F has been entered, the program continues to the next line, which sends us back to line 10 where we begin the next input. This continues until the user indicates he is finished (with an F entry). That gets us to line 1000 which closes the file.
At this point, the user is given a count of the names in the file and asked whether he wants to go back to the main menu or if he is through with the program. You can easily follow the logic here, I am sure. Just remember that the STOP line brings the program to an end. In some Basics, a STOP can be overridden--not in CBasic. STOP and END both unequivocally bring everything to a complete halt, end the program, close all files, and return the user to the operating system (CP/M). The Printout
However, let's go back to the main menu (or we could assume the user is bringing up the program from scratch). We still have two choices on the main menu to follow through. Let's take choice 3 (printout file on paper) first. You recall that that leads the program to line 3000, which brings up some instructions on the screen (be sure printer is ready, paper inserted, etc.) As you can see, before we can print a file, we must open it. We then proceed to read the first record.
We now have seven fields in memory. The LPRINTER statement says everything that follows goes to the printer instead of the terminal (console). So we simply tell the printer to print each field on a line by itself. The printout would look like this: John J. Jones Jack XYZ Company, Inc. 100 Main Street Anytown, MA 10000 (blank line here) (617) 123-4567
Note the blank line between the city/state and telephone number. Had we entered four address lines instead of three, the blank line would not appear. You can, by the way, program your output to eliminate blank lines like this. Essentially, you would say: IF A$(6) = "" THEN PRINT A$(7) INSTEAD. This is not the exact code you would have to use. See if you can figure it our for yourself.
You could program this to print out two columns, three columns, or whatever if you want to. If you want to try this, remember that you must read out as many records as there will be columns. Think about that. It also means that you will have to assign additional sets of variables. You will have to do something like read seven fields and assign each of the variables to another set like this: Column.one.name$=A$(1); Column.one.address1$=A$(2), etc. Then you read the second record and do the same (changing column.one to column.two) and so forth for the number of columns you want.
To simplify, let's say the new variables will be Cn (where C stands for column and n, for the column number) plus a number from one to seven for each variable. For a three-column printout, visualize the variables like this: C11$ C21$ C31$ C12$ C22$ C32$ C13$ C23$ C33$ and so forth up to: C17$ C27$ C37$ You could also do this with a two-dimensional array if you want to get fancy.
Once you have all this, you can print a line at a time. Using the TAB function or the PRINT USING format, you will print column.one.name$, then two, and so forth. The next line will print the salutation (or whatever you specify)--again column-by-column. Since you have seven variables, you will need seven times the number of columns as a total for the column variables. A three-column printout, then, would require 21 of these variables. This all sounds far more confusing than it is. Just think about it a whil (or better still try it), and it should come.
Note that the messages after the printout should appear on the screen rather than on the printer. To make this possible, we use the CONSOLE statement on a line by itself. This cancels the LPRINTER instruction and redirects readouts to the screen. Recall that LPRINTER and CONSOLE statements must occupy lines by themselves. Envelopes
After a printout, the user again opts to end the session or to go back to the main menu. Let's assume he goes back and chooses option 4 (print envelopes). Take a look at the routine beginning at line 4000. This is very similar to the printout we just went through. There are a couple of major differences, however.
To begin with, we will not print the salutation or the phone number on an envelope. Be careful, however. You must read out all the variables including these two. Just don't call for them in the actual printout. If you neglect to read all seven variables, the program will do crazy things. For example, if you read only five variables (regardless of what you call them) you will simply get the first five variables. When you go to read the next five, the first two will actually be variables six and seven of the previous record.
The only other difference is in the formatting of the output to fit on an envelope. Note that the spacing, etc. in the program may have to be adjusted to fit your printer and/or envelopes. Of course, you will also haveto make allowances to stop printing so you can insert a new envelope after each printout (unless you happen to have continuous form envelopes). Other than this, the sheet and envelope printouts are essentially the same.
Well, we have reached the end of "The CBasic Clinic." You obviously are not an expert programmer after these six sessions. You should, however, have gotten to the point where you can understand the fundamentals of working with CBasic. You should now be able to start with your documentation (or better still with CBasic User Guide) and get into the advanced functions and statements. Becoming a good programmer is not so much a matter of education as it is of determination to stick to it, think it through, and, above all, use your head.
If you have a problem, let me assure you that 99.9% of the time it is simply because you have made an error. Every programmer, neophyte or expert, has sat for hours trying to find the source of a stupid mistake. Eventually, it turns out to be something silly and obvious. Of course, it is obvious only after you find the error. If you approach CBasic with the idea that it is a challenge which can be fun, you will probably succeed. In fact, I can almost guarantee that you will succeed.