I've been a bit remiss about my column recently. The editorial staff at COMPUTE! has covered nicely for me, splitting some of my larger articles into two parts and cutting and pasting. I shall try to make life easier for them for the next few months, since I have finally accumulated a mental backlog of material which I feel is suitable for this column.
Mind you, I can still use some input from you readers on what you would like to see, so don't stop writing. As I have stated often in the past, it doesn't seem ethical for me to review software; but that shouldn't keep me from commenting on books, hardware, and who knows what else.
And, in that vein; this isn't truly a "review," since I have not had a chance to actually try it yet, but the most interesting new product for the "serious" Atari owner that I have seen lately is the new 64K byte memory card from Mosaic Electronics. With it you can make your 800 behave just like a 1200 so far as the bank selecting of RAM versus ROM goes. Mosaic rightly points out that there is zero software currently available to take advantage of the RAM which must lie where the OS ROMs are, so perhaps the other configuration of their RAM board makes more sense. How about up to 192K bytes of RAM in an Atari 800, with all but the first 48K being bank selected in 4K hunks that reside at $C000 through $CFFF. That gives you 36 little 4K byte banks, so just imagine the graphics switching you might do (in modes 7 and below only, though)! It's not cheap, but it certainly seems like a solution looking for a problem.
I was right on two counts! First, I said the 1200 was overpriced. But look at the prices now. I am seriously considering buying one. Or I was. Because I just heard that Atari is dropping the 1200! Welcome, welcome, Atari 600,1400, and 1450, which were introduced at the Summer Consumer Electronics Show. All will have expansion capability like nothing Atari has built before. So watch out world: here come the add-ons. [For more on the new Atari products at CES, see Tom Halfhill's article "The Fall Computer Collection At The Summer Consumer Electronics Show" elsewhere in this issue.]
Since, by the time you read this, the announcements will have been made, you will be able to see how good my rumor sources and crystal ball gazers are. Me? I'm sitting on the edge of my chair for another week or two.
One more thing before we get to the meat of this month's column. It would appear that I fooled more than a few people with my April column. If you were fooled, I apologize. But not much. After all, April Fool articles in computer magazines are a tradition that goes back to the first days of Datamation (a magazine sent free to anyone who owns a computer worth more than a quarter million dollars, heavily loaded with IBM mainframe articles, but it wasn't always so). Be assured that if you were fooled you were in good company: I showed the article to a COBOL programmer with ten years experience, and she didn't get it either. (To be fair to me, though, didn't you notice the title of the column that month, "Outasight: Atari"?)
Well, enough chitchat. Shall we tackle BAIT one more time? I am not sorry to see this series end, but looking at the finished product I can honestly say that those who understand it (and know at least a smattering of machine language) should be able to tackle COMPUTE!'s Atari BASIC Sourcebook, wherein we detail the workings of a real interpreter.
BAIT, Part 4
This month we present the listing of BAIT in its entirety. It is not a small listing, and there is no room in a single column to recap all the details of its creation and function. So, you really need Parts 1 through 3 (which appeared in March, May, and June) if you want the full design principles.
As a very brief summary, though, let's mention the following:
- BAIT is a very simple pseudo-BASIC interpreter which has been written in Atari BASIC.
- BAIT accepts only single-letter statement names (as shown in the table) and single-letter j variable names (A through Z).
- BAIT allows BASIC-style screen editing, line numbering, etc., with the restriction that line numbers must be from 1 to 99.
- There is no precedence of operators, parentheses, functions, or any other amenities. This is a primitive language.
Does it work? Yes. Is it useful? Only as a learning tool. Could it be made useful? If we wrote a compiler for the same language, maybe.
This month, I have finally implemented the rest of the statements listed in the table. In particular, we now have Accept, Call, Fetch, New, Return, and Store available to us.
New and Return function exactly like their BASIC counterparts of the same names. Accept, Call, and Store are simply different names for BASIC'S INPUT, GOSUB, and POKE, respectively. They had to be named as they are to implement the single-letter statement names.
Fetch, then, is the only strange statement. It owes its existence to the fact that BAIT doesn't allow functions. Generally, Fetch is equivalent to PEEK, but its format is that of POKE (and, naturally, Store). It does, however, require a variable to store its Fetched value in (much like GET in Atari BASIC).
The statements are fairly straightforward, and we shall see more of them a little later on. For now, though, let's analyze the additions and changes made to BAIT this month on a line-by-line basis. The lines discussed below are those which have changed or been added since the June column. If you have typed in BAIT as we have proceeded through parts 1, 2, and 3, you may enter just those lines.
• Line 1130. This is the stack we will use for "remembering" where Calls (GOSUBs) were made from. The size is arbitrary, but I cheated and used a fixed number, so don't change it unless you also change line 10910.
• 1720. This makes screen editing of BAIT programs very, very much easier. See line 2300.
• 2200. We always reset the Call stack pointer because program editing could invalidate any or all pending Return locations.
• 2300. See line 1720. This is how we eliminate the "?" prompt from the screen when using the INPUT statement. A clever trick: use it in all your programs. It comes to you courtesy of Howard Fishman. Thanks, Howard.
• 2360. Notice that this line (which used to strip off the question mark) is now gone. You won't miss it.
• 1540, 5520, and 5530. The TRAP to BAD-VALUE was added just in case your BAIT program generated an overflow.
• 8310 and 8410. Cosmetic changes only.
• 8500 and 8510. A new error message. It's used for all BAIT numeric data problems.
• 10210. A minor change to allow Print (without a following expression) to be followed by a colon statement separator.
• 10530-10550. A fix. Without it, the Goto doesn't occur until the end of the line. Thus 'G 10: P "oops" ' would indeed print the "oops" until now. But this fixes it.
• 10810-10860. Finally, some new code! Actually, Accept is fairly simple and closely follows the format of Let. Instead of requiring an expression after an equals sign, though, Accept wants the user to INPUT something from the keyboard. Thanks to the TRAP, only numeric data will be allowed.
• 10910-10960. We process the Call statement. Line 10910 seems unnecessary: who would want to go 50 levels deep in a BAIT program? But it works. Notice that all three vital pointers must be saved on the stack. Could it have been done more compactly? Yes, but this way is much simpler. Finally, we allow Goto to do the real work of transferring control to a new line number.
• 11110-11150. Fetch also follows the form of Let, but in reverse. First we get an address (line 11110), then a comma (line 11120), and finally a variable to put the Fetched value into (line 11130). The TRAP of line 11140 insures that the address given was a legal one.
• 11310-11370. Return is the opposite of Call. Again, line 11310 is for safety only; good programmers can't make mistakes like this, right? Lines 11320 to 11350 restore the information saved by Call in lines 10920 to 10950. Finally, since we saved CURLOC before we joined the Goto processing, we must skip over the line number expression to find out if there is a colon (":") waiting for us.
• 11410-11450. Store is almost identical to Fetch. The exception: the item after the comma can be any expression at all; it does not need to be a simple variable. Again, the TRAP in line 11440 insures against illegal addresses and/or data.
Sampling The BAIT
Well, we can presume that you typed all of BAIT in properly, yes? So let's quickly try some BAIT programs, to see what you can do in the language.
Caution: The lowercase letters shown in these listings are there for clarity only! BAIT accepts only single-letter commands, so just leave out all lowercase letters. Do not convert them to uppercase. For example, the first line of Program 1 should actually be typed in as '1 S 20,0' (and even the spaces may be left out if desired).
Program 1: Tick-Tock
1 Store 20,0 2 Print "SHOWING HOW SLOW BAIT IS" 3 Fetch 20,T 4 Print "THAT TOOK";: Print T;: Print "CLOCK TICKS" 5 End D B
Program 2: Recursion
1 Print "GIVE ME AN INTEGER NUMBER";: Accept N 2 Let A = 1 : Call 10 3 Print "THE FACTORIAL OF YOUR NUMBER IS";: Print A 4 Print : Print : Goto 1 10 If N < 2 : Return 11 Let A = A*N : Let N = N-l 12 Call 10 13 Return D B
Challenge: Can you modify BAIT so that it will, indeed, ignore the lowercase letters? If so, your BAIT programs could be more readable.
And there you have it. BAIT in all its glory. Or is that gory? Some carpers may claim that the only thing it proves is that people will try to write anything in BASIC. I like to think it may have provided a way for some of you to understand the mechanics of an interpreter. If it helps turn even one or two people into systems-level programmers, it will have done its job.
But if BAIT didn't interest you, don't worry. There are even a few out there that don't like to program games. (I certainly like to play them. I'm hooked on – oops, can't review software here, sorry.)
Self-relocatable Machine Language, Part 2
Last time we were on this subject, I promised to give a reason why we would want to write self-relocatable machine language. And sometimes I even keep my promises.
The primary advantage of self-relocatable code is, obviously, that you can load it and run it anywhere in memory. But why would you want to do that? Why not just decide where the code will go and leave it at that? Well, let's try to answer those questions.
First of all, none of what I am about to say pertains to programs which "take over" the system. After all, if you know that your code will run in such and such a way because, for example, you only give it out on a heavily protected game disk, then you can obviously place various hunks of machine language exactly where you want them. And they'll stay put.
But a large proportion of my readers are, I believe, attempting to either write machine language programs which interface to BASIC or are attempting to add on to the operating system in some way. In both these instances, self-relocatable code is invaluable.
Why? Because there simply isn't very much room in the Atari memory map that isn't used for something or other. In ppint of fact, the only clear portion of memory seems to be the infamous "Page Six." But, remember, even Atari BASIC can clobber the lower half of that page. And BASIC A +, Microsoft BASIC, Atari PASCAL, and several other products use portions or all of Page Six. What to do?
Well, if you have been following my articles, you will know that I advocate placing your program at LOMEM, moving LOMEM up to cover your program, and hooking into the system reset chain so that you can preserve your program if the user hits the reset key.
All well and good, but suppose LOMEM moves? And it will and it does. Depending on the number of disk drives and/or files you need to support, LOMEM can be anywhere from $A20 (with OSS PicoDOS) to $1DOO (standard Atari DOS) to $2C00 (OS/A+ version 4.1). And, if the RS-232 drivers are to be loaded (for the 850 interface), you can count on LOMEM being even higher still.
What's a poor old machine language programmer supposed to do? Follow my directions, natch. Put your program at LOMEM, no matter where it is. And that's easy to do if your program is self-relocatable.
And, before we get into discussing how to write this magic kind of program, I would like to point out one other significant instance where self-relocatable programs are handy. Putting programs at LOMEM and moving LOMEM up is all very well and good if you can do that before BASIC gets control. But once the language is entered, it has already noted the contents of LOMEM and used them for its own initialization purposes. Changing LOMEM will not necessarily force BASIC to move its own internal LOMEM, and you may wind up with a conflict of usage.
But there is a hunk of memory which is properly handled by BASIC as far as we are concerned: strings. Any data, including a machine language program, placed in a dimensioned string is guaranteed to be moved around intact (for example, when a new program line is entered or when a new variable is introduced).
Indeed, there have been many articles published which put a machine language routine or two in a string and then call the routine via USR( ADR( strings$ ),…). In fact, I have even seen a few adventuresome souls who have used ADR("some graphics and other characters here"). That is, it is perfectly O.K. to take the address of a literal string, also.
For the rest of this series, I will presume that we are writing programs which are designed to reside in Atari BASIC strings. I think that is sufficient, since there is little, if any, difference in concept between placing programs in strings and placing them at a potentially movable LOMEM.
From Why To How
Let's begin by listing the things you don't have to worry about when writing self-relocatable programs. Some of these things were discussed briefly last month; others are new but should be fairly obvious. The following, then, are intrinsically "safe" types of machine language:
- All instructions which involve only one or more registers (e.g., TAX, PHA, INY, etc.).
- All load immediate instructions which do not involve the address of a location as the immediate value (e.g., LDA #5, but not LDY #LOCATION/256).
- All branch instructions (BNE, BCC, etc.).
- All instructions involving fixed operating system or language specific locations, either in ROM or RAM (e.g., STA LEFTMARGIN, JSRCIO).
- Several miscellaneous instructions which do not reference memory addresses, such as SED, SO; CLC, NOP, RTS, etc.
What about the intrinsically unsafe instructions? Here is one of them:
Any instruction which references an absolute memory location within your own code (or another block of relocatable code) or which references a fixed RAM location which is not dedicated to the purpose intended.
Now, that's not so bad. There are a lot more safe conditions than unsafe ones, aren't there? And, yet, it takes only one unsafe instruction to clobber you, so let's concentrate on some techniques for avoiding the unsafe conditions.
Safe Relocatable Techniques
1. Change JMPs to branches. Usually, you can do a CLC followed by a BCC to substitute for a JMP. Sometimes, the target of the jump is too far away, though. In that case, add an intermediate branch point, so that the first BCC branches to a second BCC, etc.
2. Save register values on the stack (via TAX, PHA, etc.) rather than in fixed RAM locations. If you need to save a value in between calls from a higher level routine (e.g., the BASIC program), though, you will have to find some safe place to put it. Watch out! There are only four safe locations in zero page and only a handful in other parts of memory. More about such safe locations in the next part in this series.
3. If you need to reference bytes in a table, string, or other portion of memory, why not let BASIC handle the addressing for you? For example, consider this BASIC line:
TEST = USR( ADR(CODE$), ADR(TABLE$) )
Presuming that your machine language routine is in CODE$, it can then reference TABLES as follows:
PLA ; parameter count PLA STA ZTEMP + 1 ; high byte of address PLA STA ZTEMP ; low byte of address LDY #0 LDA (ZTEMP),Y ; get first byte of the table ...
That program fragment is certainly intrinsically relocatable (except for the location of ZTEMP, but it needn't be preserved in between calls to the fragment). And BASIC will certainly move TABLES around as it needs, giving you the address when you need it.
4. If you absolutely have to use a hunk of nonrelocatable programming, and you don't have space to keep it on a permanent basis, why not temporarily move it from a relocatable location (e.g., TABLES in our example above) to a fixed location (e.g., BASIC'S input buffer at $580 or some such). Then you can use it safely there, without worrying about relocatability. Of course, each time you are called from BASIC you would have to move the routine. But, as slow as BASIC is, you might never notice the extra overhead.
Next time we will continue right here. We will try to develop some even more useful techniques, including one which can only be used with USR calls from BASIC. Stay tuned.
A Accept < variable > (INPUT) B Begin (RUN) C Call <line-number> (GOSUB) D Display (LIST) E End F Fetch <address> , <variable> (pseudo-PEEK) G Goto <line-number> I If <expression>, <statement> L Let < variable > = < expression > N New P Print < string-literal > Print < variable > Print R Return S Store <address>, <expression> (POKE)