In this column, we continue the discussion of formatted screen techniques.
PUT And GET And The Text Screen
This is another one of those "Did you know?" tidbits. Did you know that when you use GRAPHICS 0 from Atari BASIC you have automatically opened the screen for GETting and PUTting via file number 6? It's true, and it is because Atari BASIC does not check the mode number for the GRAPHICS statement.
GRAPHICS 0 is thus exactly equivalent to:
OPEN #6, 12 + 16, 0, "S:"
So if you need to GET or PUT from or to the screen, you can do it directly to file #6 without any further ado.
Unfortunately, there are a few gotchas involved in using GET and PUT to the Atari Screen graphics driver ("S:"), some of which you may have seen before, so let's discuss them, as well as ways around them.
The first problem is that if you use PUT #6 combined with POSITION statements or PRINT statements, you will probably end up leaving some inverse video spaces (white boxes) around on the screen, as Program 1 illustrates. This is because the screen graphics driver works almost (but not quite) like the screen editor driver ("E:", the normal channel #0 device which PRINT and INPUT use). Unfortunately, "S." can't seem to handle its cursor properly, so it may be best to avoid using PUT #6.
Program 1: Problems With PUT #6
10 GRAPHICS 0 20 POSITION 30 * RND(0), 20 * RND(0) 30 PUT #6, 65 + 20 * RND (0) 40 GOTO 20
How can we avoid PUT #6 if we have something we need on the screen? Simple. Use PUT#0 (if you have BASIC XL or any other product which allows PUT to file #0) or PRINT. If you use PRINT, of course, you will have to use
in place of PUT #0,X. And why does outputting to file #0 work where using #6 does not? Because #0 is opened to "E:", and there are several subtle differences between "E:" and "S:" where cursor positioning and character I/O are concerned.
Unfortunately, while the problems with PUT #6 are fairly easy to get around, the problems with GET #6 must be dealt with directly. And why can't we simply use GET #0 in place of #6 here, as we did with PUT? Because, when you ask "E:" (channel #0) for a character, it waits until the user actually types in an entire line—terminated by a RETURN character—before returning anything at all to its caller (you are the caller via BASIC in this case).
The whole reason for using GET #6 is to allow ourselves to read individual characters from the screen. We simply can't use GET #0 or anything else which accesses "E:".
But this is putting the cart before the horse a little. Before "fixing" the problem, let's illustrate it with Program 2.
Program 2: Problems With GET #6
10 GRAPHICS 0 20 PRINT "ABCDEFGHIJKLMNOP" 30 FOR I = 2 TO 12 : POSITION 1, 0 40 GET #6, CHAR 50 POSITION 20, 20 : PRINT CHAR 60 FOR J = 1 TO 200 : NEXT J : REM jus t a delay loop 70 NEXT I
I hope you actually stopped while reading to try out that listing. Bizarre, isn't it? It seems that you can't GET data from the screen without destroying it. Now, most of the articles which I have seen which note this problem suggest that the only safe fix is the following:
- POSITION yourself on the character you want.
- GET the character to a variable.
- POSITION yourself again to the same location.
- PRINT the character back onto the screen.
That fix will indeed work, but I would propose that an alternate solution is to simply print a "left arrow" (backspace) and then the character, thus avoiding the extra POSITION statement. In Program 2, we could simply add this line to fix things up:
45 PRINT CHR$(30) ; CHR$(CHAR);
Now that you know how to properly PUT and GET to the screen, you probably have a fair idea of how I built my onscreen editor. It isn't too hard to do anything you want to the GRAPHICS 0 screen, once you get past the quirks in the Atari OS.
Fettering Your NEXT
Probably every BASIC book you have ever seen tells you to properly nest FOR/NEXT loops. Aside from the neatness of it, there are some good and practical reasons. Consider Program 3.
Program 3: Obviously Invalid Nesting
10 FOR I = 1 20 FOR J = 1 TO 3 30 NEXT I 40 NEXT J
Very few of you would deliberately write a BASIC program which looked like that. Even with the indentation I have given it, it should be obvious that something is wrong.
And, yet, it is fairly easy to write a program which will look proper and yet have the effect of that listing! Don't believe it? Try Program 4.
Program 4: A Subtle Problem
100 REM Program task: Print all numb ers from 1 to 9, in a nested loo p fashion. When the first sum o f 15 or 101 REM greater is found, cease the operation. When the sum is 10 o r more, don't print the result. 102 REM Repeat for the products of t he same numbers in the same fash ion. 110 print "I", "J", "SUM" 120 FOR I = 1 TO 9 130 FOR I = 1 TO 9 140 SUM = I + J 150 IF SUM > 14 THEN 200 160 IF SUM > 10 THEN 190 170 PRINT I, J, SUM 180 NEXT J 190 NEXT I 200 PRINT "I", "J", "PRODUCT" 210 FOR J = 1 TO 9 220 FOR I = 1 TO 9 230 PROD = I * J 240 IF PROD > 14 THEN 290 250 IF PROD > 10 THEN 280 260 PRINT J, I, PROD 270 NEXT I 280 NEXT J 290 END
Now this looks perfectly harmless, if somewhat pointless, right? It looks like it should work fine. Yet, if you will type it in and RUN it, you will find that line 280 will give you a NEXT WITHOUT MATCHING FOR error the first time it is reached. How? Surely line 210 is the FOR which matches the NEXT of line 280.
The Interpreter's Dilemma
If Atari BASIC were a compiler language, it would probably execute that program correctly. However, since it is an interpreter, it must work within the strictures of that mode. Interpreters, by their very nature, cannot easily keep a history of all NEXT usages. It is enough that they remember where the FOR statements are, so that when a NEXT is encountered they can go back to the FOR to execute the loop another time.
Consider, then, the dilemma of the poor interpreter in the above program. In line 160, we are asking it to bypass the end of the inner FOR loop (since we know we are done with the previous usage of it) and start the next iteration of the outer loop (NEXT I). But wait. There is still a FOR J on the runtime stack, yet we are executing a NEXT I. What can we do?
Atari BASIC does what most modern "smart" BASICs do. If it finds a loop variable NEXT which does not match the last FOR on the stack, it presumes that the user has jumped out of the inner loop (as indeed we have here) since that is a common occurrence. So BASIC looks backward in the stack for a matching FOR. Eureka! It finds the FOR I only one level down in the stack, without any intervening GOSUBs, so its supposition seems confirmed. All works well.
However, look at line 150, wherein we jump out of all the loops. What have we left on the run-time stack now? Obviously, both a FOR I and a FOR J. Well, no real problem. After all, we know we jumped all the way out of the loop, don't we? We don't. Why not? Because a BASIC interpreter must presume that the BASIC programmer knows what he or she is doing. It is, unfortunately, perfectly legal to jump in and out of a loop in Atari BASIC. It is, in fact, even legal to have more than one NEXT for any given FOR.
So what can BASIC think when it gets to line 210 but that it is starting the inner FOR loop over again? It leaves the FOR I in place (for all it knows, the next statement it encounters might be a NEXT I) and adds a new FOR J.
Disaster really strikes in line 220. Poor BASIC is trying its best. Knowing that it is not uncommon for BASIC programmers to jump out of loops or to jump to the beginning of a loop to start it again, BASIC almost has to presume that the FOR I of line 220 is the beginning of a new outer loop. Besides, it already has a FOR I on its runtime stack. How can it allow another?
Well, if this is the beginning of a new outer loop, better throw away the old outer loop and any of its inner loops. Say good-by to the old FOR I and FOR J; we're ready for another outer loop with a new FOR I. Right?
Wrong. But BASIC doesn't know about it while it stays in the FOR I loop, since it encounters no other FORs or NEXTs. In fact, the entire loop executes nicely with no problems, and the FOR is properly removed from the stack when the last value of I is reached. Did you notice that the stack is now empty?
Where did this NEXT J come from? FOR J was an inner loop and was thrown away when the outer loop was restarted.
The Fix In Atari BASIC
Actually, Atari BASIC is not a culprit here. Virtually every BASIC will have this same problem unless it makes a pre-pass through the user's program to detect possible inconsistencies (such as jumping out of nested loops). In point of fact, Atari BASIC is almost a good guy here. Recognizing that even with the best interpretation we could do, we could not prevent users from writing (or needing to write) structures such as I have shown you, we designed a "fix" into Atari BASIC.
The fix takes the form of the POP statement. POP simply removes the last level of the runtime stack. In Program 4, the easiest fix is
150 IF SUM > 9 THEN POP : POP : GOTO 200
(and a similar fix is needed in line 240, of course).
Notice I said that was the easiest fix. POP is usually not the best fix. Generally, you can write good and properly structured programs, with properly terminating FOR loops, without ever resorting to such extreme measures as the POP statement. Still, it is comforting to know that POP is around. Personally, I tend to use it whenever an error condition occurs and I want to get all the way back out to (for example) the menu level without leaving nasty GOSUBs or FORs on the runtime stack.
A curiosity: Did you notice that if the nesting in lines 200 through 290 is reversed (that is, if the FOR I occurs before the FOR J), the program will work correctly? Do you see why? Fundamentally, because you are now doing what BASIC expected you to do. Go try this example both ways on a Commodore or Radio Shack or whatever computer. Does either method work? I'd be interested in knowing.
If you ever get a NEXT WITHOUT MATCHING FOR error, look for this kind of structure in your program. If you find it, you can fix it with POP, but wouldn't it be nicer to write the program correctly?
A footnote to all of that: Can you begin to get an appreciation of what language designers must contend with? It is not enough that a language do what it is expected to do. A good language will come halfway toward helping its users over the rough spots.
Reading Object Code Files
Here's a loader for binary object files which will place them in memory at the location they were assembled for. The routine is written entirely in Atari BASIC, so it is slow. Next month, we'll present the same routine written in machine language, perhaps even in a version callable from a BASIC program (just to speed things up).
Atari object files have a fixed and reasonable format. The first two bytes of the file are always $FF and $FF (255 and 255, in decimal). They serve as a check that the file is indeed an object file. The next two bytes are the starting address in memory of the first (and perhaps only) "segment," while the following two bytes are the ending address of the segment. These header bytes are followed by enough object bytes to fill up the memory from the starting address through and including the ending address.
If a file has multiple segments, each segment may or may not (programmer's option) be preceded by the same $FF and $FF bytes. Each segment must always be headed by both a start and an end address. Without further ado, then, the loader program, Program 5.
Program 5: Load A Binary Object File
100 REM binary object file loader 110 DIM NAME$ (30) 120 PRINT "WHAT FILE TO LOAD "; 130 INPUT NAME$ 140 OPEN #1, 4, 0, NAME$ 200 REM get and check header 210 TRAP 400 220 GET #1, LOW : GET #1, HIGH 230 TRAP 40000 240 IF LOW = 255 AND HIGH = 255 THEN GET #1, LOW : GET #1, HIGH 250 START = LOW + 256 * HIGH 260 GET #1, LOW : GET #1, HIGH 270 QUIT = LOW + 256 * HIGH 300 REM read in a segment 310 FOR ADDR = START TO QUIT 320 GET #1, BYTE 330 POKE ADDR, BYTE 340 NEXT ADDR 350 GOTO 200 : REM try for another s egment 400 REM trapped to here, assume end- of-file 410 CLOSE #1
Since I'm running out of time and space this month, I will let the explanation of object file format, above, serve for now as an explanation of this program. I will warn you, however, that I cheated a bit in line 240 to make the multiple segment loading easier. The routine will try to load anything into memory, whether or not it is truly a binary object file. If your memory dies a violent death (fixable only by turning power off and back on), you tried to load something other than an object file with this. Naughty.
Next month some notes on destination strings in Atari BASIC. And maybe—just maybe—we'll play around with Atari screen I/O a little more.